HEX
Server: Apache
System: Linux www3.pit.tblive.com 5.14.0-687.38.1.el9_8.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Aug 12 17:19:12 EDT 2026 x86_64
User: awaldron (1020)
PHP: 8.1.34
Disabled: exec,passthru,shell_exec,system
Upload Files
File: /home/awaldron/_phpassport/api.pennhillspassport.com/v1/_globals.php
<?php

//**********************************************************************************************************//
// TRIB NEWS HELPERS **************************************************************************************//

function getFirstCarouselImage($url)
{
    // Decide carousel ID based on URL
    $id = (strpos($url, 'tribhssn.triblive.com') !== false)
        ? 'postCarousel'
        : 'storyCarousel';

    $html = @file_get_contents($url);
    if ($html === false) {
        return null;
    }

    libxml_use_internal_errors(true);

    $dom = new DOMDocument();
    $dom->loadHTML($html);
    $xpath = new DOMXPath($dom);

    // First <img> inside the chosen carousel
    $img = $xpath->query(
        '//*[@id="'.$id.'"]//img'
    )->item(0);

    if ($img && $img->hasAttribute('src')) {
        return trim($img->getAttribute('src'));
    }

    return null;
}



function getFirstCarouselThumb($url)
{
    // Decide carousel ID based on URL
    $id = (strpos($url, 'tribhssn.triblive.com') !== false)
        ? 'postCarousel'
        : 'storyCarousel';

    $html = @file_get_contents($url);
    if ($html === false) {
        return null;
    }

    libxml_use_internal_errors(true);

    $dom = new DOMDocument();
    $dom->loadHTML($html);
    $xpath = new DOMXPath($dom);

    // First thumb-wrap inside storyCarousel
    $node = $xpath->query(
        '//*[@id="'.$id.'"]//*[contains(@class,"thumb-wrap")]'
    )->item(0);

    if ($node && $node->hasAttribute('style')) {
        if (preg_match("/url\\(['\"]?(.*?)['\"]?\\)/", $node->getAttribute('style'), $m)) {
            return $m[1];
        }
    }

    return null;
}

//**********************************************************************************************************//
// DATABASE FUNCTIONS **************************************************************************************//

function dbconnect()
{
    $db = null;
    // LOCALHOST
    if ($_SERVER['HTTP_HOST'] == 'localhost:8888') {
        $db = new mysqli('localhost', 'root', 'root', 'phcdc_members');
    } // PRODUCTION
    else if ($_SERVER['HTTP_HOST'] == 'api.pennhillspassport.com') {
        $db = new mysqli('localhost', 'awaldron_phpassport_user', '35&LtmeL6C*5', 'awaldron_phpassport');
    }

    if ($db && $db->connect_errno) {
        response(500, ['success' => false, 'errors' => ['Error connecting to the database.']]);
    }

    $db->set_charset("utf8mb4");

    return $db;
}


//**********************************************************************************************************//
// CORS FUNCTIONS ******************************************************************************************//

function cors()
{

    // Allow from any origin
    if (isset($_SERVER['HTTP_ORIGIN'])) {
        // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
        // you want to allow, and if so:
        header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Max-Age: 86400');    // cache for 1 day

    }

    // Access-Control headers are received during OPTIONS requests
    if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
            // may also be using PUT, PATCH, HEAD etc
            header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");

        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
            header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");

        exit(0);
    }
}


//**********************************************************************************************************//
// API FUNCTIONS *******************************************************************************************//

function validate_body($args, $rules)
{

    $valid = true;
    $errors = [];

    if ($args !== false && $rules !== false) {
        foreach ($rules as $param => $rule) {
            // CHECK REQUIRED
            if (strstr($rule, 'req')) {
                if (!isset($args[$param])) {
                    $valid = false;
                    $errors[] = $param . ' is required.';
                }
            }
            // CHECK EMAIL
            if (strstr($rule, 'email')) {
                if (!filter_var($args[$param], FILTER_VALIDATE_EMAIL)) {
                    $valid = false;
                    $errors[] = $param . ' is not a valid email address.';
                }
            }
        }
    }

    if (!$valid) {
        response(400, ['success' => false, 'errors' => $errors, 'payload' => $args]);
    }
    return true;
}

function validate_token($uid)
{
    // IF uid==0 THEN THROW ERROR INVALID TOKEN
    if ($uid !== false && $uid === 0) {
        response(401, ['success' => false, 'errors' => ['Security token invalid.']]);
    }
}

function validate_admin($is_admin)
{
    // IF is_admin==0 THEN THROW ERROR NOT AUTHORIZED
    if (!$is_admin) {
        response(401, ['success' => false, 'errors' => ['Unauthorized. User must be an admin.']]);
    }
}

function utf8ize($d)
{
    if (is_array($d)) {
        foreach ($d as $k => $v) {
            $d[$k] = utf8ize($v);
        }
    } else if (is_string($d)) {
        return utf8_encode($d);
    }
    return $d;
}


function response($responseCode = 200, $responseBody = [])
{
    http_response_code($responseCode);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($responseBody);
    exit;
}


function new_token($db, $uid)
{
    $token = bin2hex(random_bytes(64));
    $created = time();
    $expires = time() + 60 * 60 * 12;
    $ins = "INSERT INTO tokens 
            (user_id, token, created, expires) VALUES 
            (" . $uid . ",'" . $token . "'," . $created . "," . $expires . ")";
    if ($result = $db->query($ins)) {
        return [
            'token' => $token,
            'created' => $created,
            'expires' => $expires
        ];
    } else {
        response(400, ['success' => false, 'errors' => ['Error creating token.']]);
    }
}

function check_token($db, $token)
{
    $sql = "SELECT token_id, user_id FROM tokens WHERE token='" . $token . "' AND expires > " . time();
    $res = qRow($db, $sql);
    if (!empty($res['user_id'])) {
        // UPDATE TOKEN EXPIRATION
        $upd = "UPDATE tokens SET expires=" . (time() + 60 * 60 * 12) . " WHERE token_id=" . $res['token_id'];
        $db->query($upd);
        return $res['user_id'];
    } else {
        return 0;
    }
}


function randomString($n)
{
    $characters = '01234567890123456789ABCDEFGHIJKLMNPQRSTUVWXYZABCDEFGHIJKLMNPQRSTUVWXYZ';
    $randomString = '';

    for ($i = 0; $i < $n; $i++) {
        $index = rand(0, strlen($characters) - 1);
        $randomString .= $characters[$index];
    }

    return $randomString;
}

function randomPassword($n)
{
    $uppers = "ABCDEFGHIJKLMNPQRSTUVWXYZABCDEFGHIJKLMNPQRSTUVWXYZ";
    $downers = "abcdefghijklmnpqrstuvwxyzabcdefghijklmnpqrstuvwxyz";
    $numbers = "01234567890123456789";
    $symbols = "!@#$%&*!@#$%&*";

    $randomString = '';

    // 1 Uppercase
    for ($i = 0; $i < 1; $i++) {
        $index = rand(0, strlen($uppers) - 1);
        $randomString .= $uppers[$index];
    }

    // 3 Lowercase
    for ($i = 0; $i < 3; $i++) {
        $index = rand(0, strlen($downers) - 1);
        $randomString .= $downers[$index];
    }

    // 4 Numbers
    for ($i = 0; $i < 4; $i++) {
        $index = rand(0, strlen($numbers) - 1);
        $randomString .= $numbers[$index];
    }

    // 1 symbols
    for ($i = 0; $i < 1; $i++) {
        $index = rand(0, strlen($symbols) - 1);
        $randomString .= $symbols[$index];
    }


    return $randomString;
}


//**********************************************************************************************************//
// DB HELPER FUNCTIONS *************************************************************************************//


function qVal($db, $sql)
{
    $res = $db->query($sql);
    if ($res->num_rows > 0) {
        $row = $res->fetch_assoc();
        foreach ($row as $k => $v) {
            return $v;
        }
    } else {
        return false;
    }
}

function qRow($db, $sql)
{
    $res = $db->query($sql);
    if ($res->num_rows > 0) {
        $row = $res->fetch_assoc();
        return $row;
    } else {
        return false;
    }
}

function qSet($db, $sql)
{
    $out = [];
    $res = $db->query($sql);
    while ($row = $res->fetch_assoc()) {
        $out[] = $row;
    }
    return $out;
}

function qCol($db, $sql)
{
    $out = [];
    $res = $db->query($sql);
    while ($row = $res->fetch_assoc()) {
        foreach ($row as $key => $val) {
            $out[] = $val;
        }
    }
    return $out;
}

function qsql($q)
{
    if ($q->num_rows() == 0) return false;
    else {
        $r = $q->result_array();
        $r = $r[0];
        foreach ($r as $k => $v) return $v;
    }
}

function qin($a)
{
    $in = '(';
    for ($i = 0; $i < count($a); $i++) {
        if ($i > 0) $in .= ',';
        $in .= "'" . $a[$i] . "'";
    }
    $in .= ')';
    return $in;
}

function shut()
{
    $error = error_get_last();

    if ($error && ($error['type'] & E_FATAL)) {
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }

}

function handler($errno, $errstr, $errfile, $errline)
{

    switch ($errno) {

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR';
            break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING';
            break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE';
            break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE';
            break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR';
            break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING';
            break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR';
            break;
        case E_COMPILE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING';
            break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR';
            break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING';
            break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE';
            break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT';
            break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR';
            break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED';
            break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED';
            break;

    }

    $message = '<b>' . $typestr . ': </b>' . $errstr . ' in <b>' . $errfile . '</b> on line <b>' . $errline . '</b><br/>';

    if (($errno & E_FATAL) && ENV === 'production') {

        header('Location: 500.html');
        header('Status: 500 Internal Server Error');

    }

    if (!($errno & ERROR_REPORTING))
        return;

    if (DISPLAY_ERRORS) {
        response(400, ['success' => false, 'error' => $message]);
        //printf('%s', $message);
    }
    //Logging error on php file error log...
    if (LOG_ERRORS)
        error_log(strip_tags($message), 0);

}


function get_email_template($body, $m, $ecid)
{
    $email = <<<END
<html>
<head>
<body>
    <div style="width:100%; background-color:#F6F6F6; padding:20px 0">
        <div style="background-color:#FFFFFF; max-width:600px;margin:0 auto;font-size:16px;line-height:1.6em">
            <div style="background-color:#33612a; padding:20px; border-bottom:solid 4px #efa72d">
                <a href="https://pennhillscdc.org" target="_blank" style="display:block;text-align:center;">
                    <img src="https://pennhillscdc.org/e/email-header.png" width="200" height="148" />
                </a>
            </div>
            <div style="padding:20px;">
                <p style="font-size:24px;color:#efa72d;font-weight:bold">[SUBJECT]</p>
                <p>Hi [NAME],</p>
                [BODY]
                <p>&nbsp;<br/><b>Have a great day!</b>
                    <br/>Your friends at the Penn Hills CDC
                </p>
            </div>
            <div style="background-color:#33612a; padding:20px; border-top:solid 4px #efa72d">
                <a href="https://pennhillscdc.org" target="_blank" style="display:block;text-align:center;">
                    <img src="https://api.pennhillscdc.org/v1/mail/read/[MAILCODE]/[ECID]" width="180" height="44" />
                </a>
                <p style="color:#FFFFFF; margin-top:12px; font-size:13px; text-align:center; line-height: 1.3em">
                    <b style="color:#efa72d">Penn Hills Community Development Corporation</b>
                    <br/>PO Box 17730
                    <br/>Penn Hills, PA 15235
                    <br/>
                    <br/><a href="https://api.pennhillscdc.org/v1/mail/unsubscribe/[MAILCODE]/[ECID]" style="color:#FFFFFF"><u>unsubscribe</u></a>
                </p>
            </div>
        </div>
    </div>
</body>
</html>
END;

    $email = str_replace('[NAME]', $m['fname'], $email);
    $email = str_replace('[MAILCODE]', $m['mailcode'], $email);
    $email = str_replace('[SUBJECT]', $body['subject'], $email);
    $email = str_replace('[ECID]', $ecid, $email);
    $email = str_replace('[BODY]', $body['body'], $email);
    $email = str_replace('<img ', '<img style="max-width:100%" ', $email);

    return $email;
}


function generatePassword($length = 10)
{
    // Define the character sets to be used in the password
    $uppercase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
    $lowercase = 'abcdefghjkmnpqrstuvwxyz';
    $numbers = '123456789';
    $symbols = '!@#$%&_+?';

    // Combine all character sets
    $allCharacters = $uppercase . $lowercase . $numbers . $symbols;

    // Initialize the password variable
    $password = '';

    // Generate the random password
    for ($i = 0; $i < $length; $i++) {
        $randomIndex = mt_rand(0, strlen($allCharacters) - 1);
        $password .= $allCharacters[$randomIndex];
    }

    return $password;
}

function generateHash($length = 10)
{
    // Define the character sets to be used in the password
    $uppercase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
    $lowercase = 'abcdefghjkmnpqrstuvwxyz';
    $numbers = '123456789';
    $symbols = '!@#$%&_+?';

    // Combine all character sets
    $allCharacters = $uppercase . $lowercase . $numbers;

    // Initialize the password variable
    $password = '';

    // Generate the random password
    for ($i = 0; $i < $length; $i++) {
        $randomIndex = mt_rand(0, strlen($allCharacters) - 1);
        $password .= $allCharacters[$randomIndex];
    }
    return $password;
}


function iso_date()
{
    $milli = floor(microtime(true) * 1000) % 1000;
    if ($milli < 10) {
        $milli = '00' . $milli;
    } else if ($milli < 100) {
        $milli = '0' . $milli;
    }
    $now = date('c');
    $now = str_replace('+00:00', '.' . $milli . 'Z', $now);
    return $now;
}


function format_library_events_xml($xml)
{
    $i = 0;
    $events = [];

    foreach ($xml as $event) {
        foreach ($event as $k => $v) {
            if ($k === 'title') {
                $title = (string)$v;
                $p1 = explode('--', $title);
                $p2 = explode(' at ', $p1[1]);
                $title = trim($p2[0]);
                $events[$i]['title'] = $title;
            } elseif ($k === 'ekdate') {
                $events[$i]['ekdate'] = (string)$v;
                $events[$i]['start_time'] = strtotime($events[$i]['ekdate']);
                $events[$i]['start'] = str_ireplace('+00:00', '.000Z', date('c', $events[$i]['start_time']));
            } elseif ($k === 'description') {
                $desc = (string)$v;
                $desc = mb_convert_encoding($desc, "HTML-ENTITIES", 'UTF-8');
                //$desc = htmlallentities($desc);

                $desc = str_replace('<p> </p>', '', $desc);
                $desc = str_replace('&Acirc;', '', $desc);
                $desc = str_replace('&acirc;&#128;&#153;', '\'', $desc);
                $desc = str_replace('&acirc;&#128;&#148;', '&mdash;', $desc);
                $desc = str_replace("\n", '&mdash;', $desc);

                $desc = strip_tags($desc, '<p><a><br><u><i>');
                $desc = preg_replace('/(<[^>]+) style=".*?"/i', '$1', $desc);
                $desc = preg_replace('/<span[^>]+>/i', '$1', $desc);
                $desc = preg_replace('/<\/span>/i', '$1', $desc);
                //$desc = preg_replace('/<a[^>]+>/i', '$1', $events[$i]['description']);
                //$desc = preg_replace('/<\/a>/i', '$1', $events[$i]['description']);
                $desc = str_replace('Location:', '<b>LOCATION:</b>', $desc);
                //$desc = str_replace('<a ', '<a target="_blank" ', $desc);
                $events[$i]['description'] = $desc;

                $events[$i]['private'] = (stristr($desc, 'private') ? 1 : 0);
            } else if ($k === 'link') {
                $events[$i]['ekid'] = substr($v, strpos($v, "#") + 1);
                $events[$i][$k] = (string)$v;
            } else if ($k === 'author') {
                $author = (string)$v;
                $email = preg_replace('/\(([^)]+)\)/i', '', $author);
                $author = substr($v, strpos($v, "(") + 1, -1);

                $events[$i]['description'] .= '<p><b>CONTACT:</b> ' . $author . ' - <a href="mailto:' . $email . '">' . $email . '</a></p>';
            } else {
                $events[$i][$k] = (string)$v;
            }
        }
        $i++;
    }
    return $events;
}


function slugify($text, string $divider = '-')
{
    // replace non letter or digits by divider
    $text = preg_replace('~[^\pL\d]+~u', $divider, $text);

    // transliterate
    $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);

    // remove unwanted characters
    $text = preg_replace('~[^-\w]+~', '', $text);

    // trim
    $text = trim($text, $divider);

    // remove duplicate divider
    $text = preg_replace('~-+~', $divider, $text);

    // lowercase
    $text = strtolower($text);

    if (empty($text)) {
        return 'n-a';
    }

    return $text;
}


function resize_crop_image($image_path, $output_path, $width, $height)
{
    // Get image dimensions
    list($original_width, $original_height) = getimagesize($image_path);

    // Calculate aspect ratios
    $original_aspect_ratio = $original_width / $original_height;
    $desired_aspect_ratio = $width / $height;

    // Determine cropping coordinates
    if ($original_aspect_ratio > $desired_aspect_ratio) {
        $new_width = $original_height * $desired_aspect_ratio;
        $new_height = $original_height;
        $x_offset = round(($original_width - $new_width) / 2);
        $y_offset = 0;
    } else {
        $new_width = $original_width;
        $new_height = $original_width / $desired_aspect_ratio;
        $x_offset = 0;
        $y_offset = round(($original_height - $new_height) / 2);
    }

    // Create image resources
    $source_image = imagecreatefromstring(file_get_contents($image_path));
    $destination_image = imagecreatetruecolor($width, $height);

    // Resize and crop image
    imagecopyresampled($destination_image, $source_image, 0, 0, round($x_offset), round($y_offset), round($width), round($height), round($new_width), round($new_height));

    // Save image
    imagejpeg($destination_image, $output_path);

    // Clean up resources
    imagedestroy($source_image);
    imagedestroy($destination_image);
}


function formatPhoneNumber($number)
{
    // Remove any non-numeric characters from the input number
    $number = preg_replace('/\D/', '', $number);

    // Check if the number is exactly 10 digits
    if (strlen($number) == 10) {
        // Format the number as xxx-xxx-xxxx
        $formattedNumber = substr($number, 0, 3) . '-' . substr($number, 3, 3) . '-' . substr($number, 6, 4);
        return $formattedNumber;
    } else {
        // If the number doesn't have 10 digits, return it as is
        return $number;
    }
}

function pull_facebook_images($items) {
    foreach ($items as &$item) {
        $images = [];

        // Extract the media:content if it exists and add it to the images array
        if (isset($item['media:content']) && !empty($item['media:content'])) {
            $images[] = $item['media:content'];
        }

        // Capture all image URLs from the description
        if (isset($item['description'])) {
            $pattern = '/<img[^>]+src="([^"]+)"/i';

            // Use preg_match_all to get all image src attributes
            preg_match_all($pattern, $item['description'], $matches);

            // Add the found image URLs to the images array
            if (!empty($matches[1])) {
                $images = array_merge($images, str_replace("&amp;","&",$matches[1]));
            }

            // Remove the <img> tags from the description
            //$item['description'] = preg_replace($pattern, '', $item['description']);
            $item['description'] = trim(strip_tags($item['description']));
        }

        // Remove duplicate images
        $images = array_unique($images);

        // Add the images array to the item if there are any images
        if (!empty($images)) {
            $item['images'] = array_values($images); // Reset keys to be sequential
        }
    }

    return $items;
}

function fetchXml($url)
{
    // Fetch the XML content from the URL
    $xmlContent = file_get_contents($url);

    // Replace the invalid namespace with a valid one
    $xmlContent = trim($xmlContent);
    $xmlContent = str_replace(' xmlns="thumbnail"', '', $xmlContent);
    $xmlContent = str_replace('<media', '<imgurl', $xmlContent);
    $xmlContent = str_replace('270/170', '1620/1020', $xmlContent);

    // Load the modified XML content
    return simplexml_load_string($xmlContent, "SimpleXMLElement", LIBXML_NOCDATA);
}

function xmlToArray($xml)
{
    $articles = [];
    foreach ($xml->channel->item as $item) {
        $article = [];

        // Extract data for each article
        $article['title'] = (string)$item->title;

        // Extract image URL from <description> CDATA
        $description = htmlspecialchars_decode((string)$item->description);
        preg_match('/<img[^>]+src="([^"]+)"/', $description, $imageMatches);
        $article['image_url'] = strlen((string)$item->imgurl) > 0 ? (string)$item->imgurl : (isset($imageMatches[1]) ? $imageMatches[1] : '');

        // Extract text content from <description> CDATA
        $descriptionText = strip_tags($description);
        $article['description_text'] = $descriptionText;

        // Extract text content from <dc:creator> CDATA
        $article['creator'] = strlen((string)$item->children('dc', true)->creator) > 0 ? (string)$item->children('dc', true)->creator : (string)$item->author;

        // Extract and format <pubDate> in ISO 8601 format
        $pubDate = date_create_from_format('D, d M Y H:i:s O', (string)$item->pubDate);
        $article['pubDate'] = $pubDate ? $pubDate->format('Y-m-d\TH:i:s.v\Z') : '';

        // Extract <link>
        $article['link'] = (string)$item->link;

        // Extract <img>
        //$article['imgurl'] = (string)$item->imgurl;

        // Add article to the articles array
        $articles[] = $article;
    }
    return $articles;
}


function extractImageUrl($content) {
    // Use regex to extract the src attribute of the img tag
    $matches = [];
    preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $matches);

    // Return the first match or null if no match is found
    return $matches[1] ?? null;
}

function fetchAndParseRSS($url) {

    $config['useragent'] = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0';

    $handle = curl_init();
    curl_setopt($handle, CURLOPT_URL, $url);
    curl_setopt($handle, CURLOPT_FRESH_CONNECT, TRUE);
    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($handle, CURLOPT_USERAGENT, $config['useragent']);
    curl_setopt($handle, CURLOPT_REFERER, 'https://www.phsd.org');

    $response = curl_exec($handle);
    curl_close($handle);

    $xml = simplexml_load_string($response);
    $json = json_encode($xml);

    // Decode the JSON string to an associative array
    $rssArray = json_decode($json, true);

    // Extract image URLs from the 'content' field in each 'entry'
    if (isset($rssArray['entry'])) {
        // Handle the case where there is only one entry
        if (isset($rssArray['entry'][0])) {
            foreach ($rssArray['entry'] as &$entry) {
                if (isset($entry['content'])) {
                    $entry['image_url'] = extractImageUrl($entry['content']);
                }
            }
        } else {
            // Handle the case where there is only one entry as an associative array
            if (isset($rssArray['entry']['content'])) {
                $rssArray['entry']['image_url'] = extractImageUrl($rssArray['entry']['content']);
            }
        }
    }

    return $rssArray;
}


function removeQueryParams($url) {
    $pos = strpos($url, '?');
    if ($pos !== false) {
        return substr($url, 0, $pos);
    }
    return $url;
}

function formatHtml( $html, $strip_tags = false ) {
    $allowed_tags = !is_bool($strip_tags) ? $strip_tags : null;

    $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8');
    $html = str_replace(["’", "“", "”"], ["'", '"', '"'], $html);
    if ($strip_tags) {
        $html = strip_tags($html, $allowed_tags);
    }
    $html = addslashes(trim($html));
    return $html;
}

function numeric($string) {
    // Use preg_replace to remove all non-numeric characters
    return preg_replace('/\D/', '', $string);
}

function convertToISO($dateString) {
    // Try to create a DateTime object from the input date string
    try {
        $date = new DateTime($dateString, new DateTimeZone('UTC'));
    } catch (Exception $e) {
        // If the date string is invalid, return an error message
        return 'Invalid date string';
    }

    // Format the date to the required ISO 8601 format with milliseconds and 'Z' for UTC
    return $date->format('Y-m-d\TH:i:s.000\Z');
}

function convertUnixToISO($timestamp) {
    // Create a DateTime object from the Unix timestamp
    $date = new DateTime("@$timestamp");

    // Set the timezone to UTC
    $date->setTimezone(new DateTimeZone('UTC'));

    // Format the date in ISO 8601 with milliseconds and Z suffix
    return $date->format('Y-m-d\TH:i:s.v\Z');
}

function convertJoeDate($dateString) {
    // Create a DateTime object from the input string
    $date = new DateTime($dateString);

    // Convert the date to the desired format and return it
    return $date->format('Y-m-d\TH:i:s.000\Z');
}

function timestamp_to_gmt($date_in, $convert_local = true) {

    if ($convert_local) {
        // Create a DateTime object with the given date string in Eastern Time
        $dateTime = new DateTime($date_in, new DateTimeZone('America/New_York'));
        // Set the timezone to GMT
        $dateTime->setTimezone(new DateTimeZone('GMT'));
    }
    else {
        $dateTime = new DateTime($date_in);
    }

    // Format the date in the desired format (Y-m-d\TH:i:s.v\Z)
    return $dateTime->format('Y-m-d\TH:i:s.v\Z');
}

function convert_timestamp_format($ical_timestamp) {
    // Create a DateTime object from the iCal timestamp
    $date = DateTime::createFromFormat('Ymd\THis\Z', $ical_timestamp, new DateTimeZone('UTC'));

    // If the DateTime object was successfully created, format it to the desired format
    if ($date) {
        return $date->format('Y-m-d\TH:i:s.v\Z');
    }

    // Return null or an error message if the input format is incorrect
    return null;
}


function removeAttributes($html) {
    // Regular expression to match attributes in HTML tags
    $pattern = '/(<[a-z][a-z0-9]*)(?:\s+[a-z\-]+\s*=\s*(?:"[^"]*"|\'[^\']*\'))+/i';

    // Replace the matched attributes with just the tag name
    $cleanedHtml = preg_replace($pattern, '$1', $html);

    return $cleanedHtml;
}


function parse_ical($ical_string) {
    $lines = explode("\n", $ical_string);
    $events = [];
    $current_event = null;

    foreach ($lines as $line) {
        $line = trim($line);

        if ($line === "BEGIN:VEVENT") {
            $current_event = [];
        } elseif ($line === "END:VEVENT") {
            if ($current_event) {
                $events[] = $current_event;
            }
            $current_event = null;
        } elseif ($current_event !== null) {
            list($key, $value) = explode(":", $line, 2);

            // Handle cases where value might continue on the next line (folding)
            while (isset($lines[key($lines) + 1]) && strpos($lines[key($lines) + 1], ' ') === 0) {
                $value .= "\n" . trim(next($lines));
            }

            $current_event[$key] = trim(stripcslashes($value));
        }
    }

    return $events;
}


function getSchoolEvents( $id ) {
    $local_file_path = '/home/awaldron/_phpassport/ical.dat';
    $handle = curl_init();
    $fp = fopen($local_file_path, 'w+'); // Open a file to write to

    $url = 'https://www.phsd.org/calendar/calendar_'.$id.'_gmt.ics';
    $useragent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0';

    $handle = curl_init();
    curl_setopt($handle, CURLOPT_URL, $url);
    curl_setopt($handle, CURLOPT_FRESH_CONNECT, TRUE);
    curl_setopt($handle, CURLOPT_USERAGENT, $useragent);
    curl_setopt($handle, CURLOPT_REFERER, 'https://www.phsd.org');
    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
    curl_setopt($handle, CURLOPT_FILE, $fp); // Write directly to file
    curl_setopt($handle, CURLOPT_TIMEOUT, 60); // Set timeout

    $success = curl_exec($handle);
    curl_close($handle);
    fclose($fp);

    // Check if file has content
    if (filesize($local_file_path) > 0) {
        $response = file_get_contents($local_file_path);
        $events = parse_ical( $response );
        return $events;
    } else {
        return null;
    }
}

function steelGoatDateRange() {
    // Get the first day of the current month
    $firstDay = date('Y-m-01'); // 'Y-m-01' gives the first day of the current month

    // Get the last day of the third month from now
    $lastDay = date('Y-m-t', strtotime('+3 months', strtotime($firstDay))); // 'Y-m-t' gives the last day of the month

    // Return the two dates
    return $firstDay.','.$lastDay;
}


/*
	
function xmlToArray($xml, $namespaces = [])
{
    $array = [];

    // Handle the attributes of the element
    foreach ($xml->attributes() as $attrName => $attrValue) {
        $array['@attributes'][$attrName] = (string)$attrValue;
    }

    // Handle the children of the element
    foreach ($xml->children() as $childName => $child) {
        $childArray = xmlToArray($child, $namespaces);

        // If there are multiple children with the same name, group them in an array
        if (isset($array[$childName])) {
            if (!is_array($array[$childName]) || !isset($array[$childName][0])) {
                $array[$childName] = [$array[$childName]];
            }
            $array[$childName][] = $childArray;
        } else {
            $array[$childName] = $childArray;
        }
    }

    // Handle namespaced elements
    foreach ($namespaces as $prefix => $namespace) {
        foreach ($xml->children($namespace) as $childName => $child) {
            $childArray = xmlToArray($child, $namespaces);
            $array["{$prefix}:{$childName}"] = $childArray;
        }
    }

    // Handle the value of the element (including CDATA)
    $text = trim((string)$xml);
    if (!empty($text) || empty($array)) {
        $array['_value'] = $text;
    }

    // Handle CDATA
    if ($xml->xpath('normalize-space()') && $xml->children()->count() == 0) {
        $array['_cdata'] = (string)$xml;
    }

    // Separate description data
    if (isset($array['_value']) || isset($array['_cdata'])) {
        $htmlContent = isset($array['_cdata']) ? $array['_cdata'] : $array['_value'];
        $array['description_text'] = strip_tags($htmlContent);

        // Extract image URLs
        preg_match_all('/<img[^>]+src="([^">]+)"/i', $htmlContent, $matches);
        $array['image_urls'] = $matches[1];
    }

    return $array;
}
*/


function parseAgenda($html) {
    $eventsList = [];  // Flat associative array for all events
    $currentYear = date('Y'); // Default year

    // Match all month and day blocks
    preg_match_all('/<div class="agenda-month">(.*?)<\/div>(.*?)((<div class="agenda-month">)|$)/s', $html, $monthBlocks);

    // Loop through each month block
    foreach ($monthBlocks[1] as $index => $monthName) {
        $month = trim($monthName);
        $daysHtml = $monthBlocks[2][$index];

        // Extract all agenda-day blocks for this month
        preg_match_all('/<div class="agenda-day">(.*?)<\/div>/s', $daysHtml, $dayMatches);

        foreach ($dayMatches[1] as $dayBlock) {
            // Extract the day (e.g. "Mon, 21")
            preg_match('/<i>(.*?)<\/i>/', $dayBlock, $dateMatch);
            $dayText = isset($dateMatch[1]) ? trim($dateMatch[1]) : null;

            // Parse day and month to get the full date in ISO format
            if ($dayText && preg_match('/\w+, (\d+)/', $dayText, $dayParts)) {
                $day = $dayParts[1];
                $monthNum = date('m', strtotime($month)); // Convert month name to number
                $dateISO = sprintf('%s-%02d-%02dT00:00:00.000Z', $currentYear, $monthNum, $day);
            } else {
                continue; // Skip if we can't parse the date
            }

            // Extract all events within that day
            preg_match_all('/<div class="agenda-event">(.*?)<\/div>/s', $dayBlock, $eventMatches);

            foreach ($eventMatches[1] as $eventBlock) {
                // Extract the event time from the <b> tag (append time to dateISO if needed)
                preg_match('/<b>(.*?)<\/b>/s', $eventBlock, $timeMatch);
                $time = isset($timeMatch[1]) ? trim($timeMatch[1]) : null;

                // Append time to ISO date if available
                if ($time) {
                    $time24hr = date('H:i:s', strtotime($time)); // Convert to 24-hour format
                    $dateISO = sprintf('%s-%02d-%02dT%s.000Z', $currentYear, $monthNum, $day, $time24hr);
                }

                // Extract the event title from the <span> tag
                preg_match('/<span>(.*?)<\/span>/s', $eventBlock, $titleMatch);
                $title = isset($titleMatch[1]) ? trim($titleMatch[1]) : null;

                // Extract the event URL from the <a> tag
                preg_match('/<a href="(.*?)"/s', $eventBlock, $urlMatch);
                $url = isset($urlMatch[1]) ? trim($urlMatch[1]) : null;

                // Add the event to the flat list
                if ($title && $url) {
                    $eventsList[] = [
                        'date'  => $dateISO,
                        'title' => $title,
                        'url'   => $url,
                    ];
                }
            }
        }
    }

    return $eventsList;
}

function savvy_description( $url ) {
    $handle = curl_init();
    curl_setopt($handle, CURLOPT_URL, $url);
    curl_setopt($handle, CURLOPT_FRESH_CONNECT, TRUE);
    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($handle);
    curl_close($handle);

    $linesArray = preg_split('/\r\n|\r|\n/', $response);
    $found_description = false;

    foreach ($linesArray as $line) {
        if ($found_description) {
            return trim($line);
        } else if (strstr($line, '<b>Details</b>')) {
            $found_description = true;
        }
    }
}

//function savvy_format( $url ) {
//    $handle = curl_init();
//    curl_setopt($handle, CURLOPT_URL, $url);
//    curl_setopt($handle, CURLOPT_FRESH_CONNECT, TRUE);
//    curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
//    $response = curl_exec($handle);
//    curl_close($handle);
//
//    $linesArray = preg_split('/\r\n|\r|\n/', $response);
//
//    $currMonth = '';
//    $currYear = '';
//    $currDate = '';
//    $currTime = '';
//    $currUrl = '';
//    $currTitle = '';
//
//    $events = [];
//    $in_event = false;
//    $debug = false;
//
//    foreach ($linesArray as $line) {
//        if (strstr($line, 'agenda-month')) {
//            $month_year = explode(' ',strip_tags(trim($line)));
//            $currMonth = $month_year[0];
//            $currYear = $month_year[1];
//            if ($debug) echo "\n".$currMonth. ' '. $currYear;
//        }
//        if (strstr($line, '</i>')) {
//            $text = trim(strip_tags($line));
//            if (preg_match('/^([A-Za-z]{3}),\s*(\d{1,2})$/', $text, $m)) {
//                $currDay  = $m[1];
//                $currDate = $m[2];
//                if ($debug) echo "\n".$currDay.' '.$currDate;
//            }
//        }
//        if ($in_event) {
//            if (strstr($line, '</b>')) {
//                $currTime = trim(strip_tags($line));
//                if ($debug) echo "\n".$currTime;
//            }
//            if (strstr($line, '<a href=')) {
//                preg_match('/<a\s+href=["\']([^"\']+)["\']/', $line, $matches);
//                $currUrl = $matches[1] ?? null;
//                if ($debug) echo "\n".$currUrl;
//            }
//            if (strstr($line, '<span>')) {
//                $currTitle = str_replace(' Agenda', '', trim(strip_tags(trim($line))));
//                if ($debug) echo "\n".$currTitle;
//                switch ($currTitle) {
//                    case 'Council Meeting': $currTitle = 'Penn Hills Council Meeting'; break;
//                    case 'Zoning Hearing Board Meeting': $currTitle = 'Penn Hills Zoning Board Meeting'; break;
//                    case 'Planning Commission Meeting': $currTitle = 'Penn Hills Planning Commission Meeting'; break;
//                    case 'Non-Voting Meeting': $currTitle = 'Penn Hills Council Non-Voting Meeting'; break;
//                }
//            }
//            if (strstr($line, '</div>')) {
//                if (!strstr($currTitle, 'Library') && !strstr($currTitle, 'Agenda')) {
//                    $events[] = array (
//                        'month' => $currMonth,
//                        'date' => $currDate,
//                        'year' => $currYear,
//                        'time' => $currTime,
//                        'url' => $currUrl,
//                        'title' => $currTitle,
//                    );
//                    if ($debug) echo "\n".'-> Event Added!';
//                } else {
//                    if ($debug) echo "\n".'-> Event IGNORED';
//                }
//                if ($debug) echo "\n".'------------------';
//                $in_event = false;
//            }
//        }
//        if (strstr($line, 'agenda-event')) {
//            $in_event = true;
//        }
//    }
//    return $events;
//}

function savvy_format($url) {
    libxml_use_internal_errors(true);

    // Fetch HTML
    $html = file_get_contents($url);
    if ($html === false) {
        return [];
    }

    // Load DOM
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    $xpath = new DOMXPath($dom);

    $events = [];

    $currMonth = '';
    $currYear  = '';
    $currDate  = '';

    // Get all agenda blocks in document order
    $nodes = $xpath->query(
        '//div[contains(@class,"agenda-month")] |
         //div[contains(@class,"agenda-day")]'
    );

    foreach ($nodes as $node) {

        /* ===========================
           Month header
           =========================== */
        if (strpos($node->getAttribute('class'), 'agenda-month') !== false) {
            $text = trim($node->textContent);
            [$currMonth, $currYear] = explode(' ', $text, 2);
        }

        /* ===========================
           Day block
           =========================== */
        if (strpos($node->getAttribute('class'), 'agenda-day') !== false) {

            // Extract date
            $dateNode = $xpath->query('.//i', $node)->item(0);
            if (!$dateNode) continue;

            // "Wed, 28"
            if (!preg_match('/^[A-Za-z]{3},\s*(\d{1,2})$/', trim($dateNode->textContent), $m)) {
                continue;
            }

            $currDate = $m[1];

            // Loop events for this day
            $eventNodes = $xpath->query('.//div[contains(@class,"agenda-event")]', $node);

            foreach ($eventNodes as $eventNode) {

                $titleNode = $xpath->query('.//span', $eventNode)->item(0);
                $timeNode  = $xpath->query('.//b', $eventNode)->item(0);
                $linkNode  = $xpath->query('.//a', $eventNode)->item(0);

                if (!$titleNode || !$timeNode || !$linkNode) continue;

                $title = trim(str_replace(' Agenda', '', $titleNode->textContent));
                $time  = trim($timeNode->textContent);
                $url   = $linkNode->getAttribute('href');

                // Normalize titles (your original logic)
                switch ($title) {
                    case 'Council Meeting':
                        $title = 'Penn Hills Council Meeting';
                        break;
                    case 'Zoning Hearing Board Meeting':
                        $title = 'Penn Hills Zoning Board Meeting';
                        break;
                    case 'Planning Commission Meeting':
                        $title = 'Penn Hills Planning Commission Meeting';
                        break;
                    case 'Non-Voting Meeting':
                        $title = 'Penn Hills Council Non-Voting Meeting';
                        break;
                }

                // Ignore library events
                if (str_contains($title, 'Library')) {
                    continue;
                }

                $events[] = [
                    'month' => $currMonth,
                    'date'  => $currDate,
                    'year'  => $currYear,
                    'time'  => $time,
                    'url'   => $url,
                    'title' => $title,
                ];
            }
        }
    }

    return $events;
}


function savvy_dates($eventData) {
    if (empty($eventData['time'])) {
        $eventData['time'] = '12am';
    }

    // Check if all necessary date components are available
    if (!isset($eventData['month'], $eventData['date'], $eventData['year'], $eventData['time'])) {
        return "Incomplete date information!";
    }

    // Create a string combining the date and time parts
    $dateString = $eventData['month'] . ' ' . $eventData['date'] . ', ' . $eventData['year'] . ' ' . $eventData['time'];

    // Create a DateTime object from the string in the Eastern Time Zone
    $startDate = DateTime::createFromFormat('F j, Y ga', trim($dateString), new DateTimeZone('America/New_York'));

    if (!$startDate) {
        return "Invalid date or time format! - " . $dateString;
    }

    // Convert the start DateTime object to UTC (Zulu time)
    $startDate->setTimezone(new DateTimeZone('UTC'));

    // Clone the start date to create the end date
    $endDate = clone $startDate;

    // Add 1.5 hours to the end date
    $endDate->modify('+1 hour 30 minutes');

    // Return both start and end times in ISO 8601 Zulu format
    return [
        'start' => $startDate->format('Y-m-d\TH:i:s.000\Z'),
        'end' => $endDate->format('Y-m-d\TH:i:s.000\Z'),
        'allday' => $eventData['time'] === '12am' ? 1 : 0
    ];
}


function getDayStartAndEnd($timezone = 'America/New_York') {
    // Create DateTime objects for start and end of the day
    $date = new DateTime('now', new DateTimeZone($timezone));

    // Start of the day
    $startOfDay = clone $date;
    $startOfDay->setTime(0, 0, 0);
    $startOfDayUtc = clone $startOfDay;
    $startOfDayUtc->setTimezone(new DateTimeZone('UTC'));
    $startFormatted = $startOfDayUtc->format('Y-m-d\TH:i:s.v\Z');

    // End of the day
    $endOfDay = clone $date;
    $endOfDay->setTime(23, 59, 59);
    $endOfDayUtc = clone $endOfDay;
    $endOfDayUtc->setTimezone(new DateTimeZone('UTC'));
    $endFormatted = $endOfDayUtc->format('Y-m-d\TH:i:s.v\Z');

    return [
        'start' => $startFormatted,
        'end' => $endFormatted
    ];
}


function chatgpt_post_title($text) {
    $api_key = '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'; // Replace with your OpenAI API Key
    $api_url = 'https://api.openai.com/v1/chat/completions';

    $data = [
        'model' => 'gpt-4', // Use gpt-4 or gpt-3.5-turbo for cheaper rates
        'messages' => [
            ['role' => 'system', 'content' => 'Summarize the input into a concise, engaging title under 100 characters. Ignore mentioning number of fire stations responding to the incident.'],
            ['role' => 'user', 'content' => $text]
        ],
        'max_tokens' => 50, // Limits response length
        'temperature' => 0.9 // Adjust for creativity
    ];

    $headers = [
        "Content-Type: application/json",
        "Authorization: Bearer $api_key"
    ];

    $ch = curl_init($api_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

    $response = curl_exec($ch);
    curl_close($ch);

    $result = json_decode($response, true);

    return $result['choices'][0]['message']['content'] ?? 'Error generating title';
}


function save_facebook_image($originalUrl) {
    $path = parse_url($originalUrl, PHP_URL_PATH);
    $filename = basename($path);

    $savePath = "/home/awaldron/_phpassport/pennhillspassport.com/fb/" . $filename;
    $publicUrl = "https://pennhillspassport.com/fb/" . $filename;

    // Check if the image already exists
    if (file_exists($savePath)) {
        return $publicUrl;
    }

    // Use the original Facebook URL to download the image
    $ch = curl_init($originalUrl);
    $fp = fopen($savePath, 'wb');

    curl_setopt($ch, CURLOPT_FILE, $fp);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_FAILONERROR, true);

    $success = curl_exec($ch);
    curl_close($ch);
    fclose($fp);

    if ($success) {
        return $publicUrl;
    } else {
        return false;
    }
}