403Webshell
Server IP : 121.121.20.254  /  Your IP : 216.73.216.70
Web Server : Microsoft-IIS/10.0
System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64
User : IUSR ( 0)
PHP Version : 8.3.28
Disable Function : NONE
MySQL : ON  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/Program Files/MariaDB 10.6/data/vegebasketdeliveryorder/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/Program Files/MariaDB 10.6/data/vegebasketdeliveryorder/wpstgbak_snippets.ibd
���������"<EE
@
��������������������������&&�������������������������"<�-K
��������Q�EQ�"0i����������"<E���������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i�	
�����������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i���������������������������������������������������������������������������������������������������������������������������������������������������������i������������������������������������������������������������������������������������������������������������������������������������������������������i���������������������������������������������������������������������������������������������������������������������������������"<'�������������E�E���
(E�E2infimumsupremum��!�		)���
pc��	�4���������"NE�E��x
���)ErE�Zinfimumsupremum+global�	��front-end� )global�(��content�0global�8global�@global�H.global�P�Nsite-head-js�	Xglobal�
`global�hglobal�p��global�
p�c�"N�bNQ�����������E�E<��
�w*E�E2|infimumsupremum���偀 ��(���0�� 8@*��
H�P���	X��`��h�t��
p����x��p��c����[���������QSX
E2�����/**
 * AJAX for Delivery Order / Cash Sale lookup
 * Uses existing project connection: get_mssql()
 *
 * Changes:
 * - Debtor search: only IsActive = 'T'
 * - Item search: only IsActive = 'T'
 * - Item search returns ItemUOM.Price for create forms that need default pricing
 * - Added SQL query timeout to prevent long hanging searches
 * - Basket proof image lookup is optional and never blocks receipt display
 */

if (!defined('ABSPATH')) {
    exit;
}

if (!function_exists('ac_ajax_get_conn')) {
    function ac_ajax_get_conn() {
        // get_mssql() is supplied by the existing AutoCount/WordPress bridge.
        // Keep this helper small so every AJAX endpoint fails consistently if
        // the bridge connection is missing or unavailable.
        if (!function_exists('get_mssql')) {
            return new WP_Error('missing_get_mssql', 'Function get_mssql() not found.');
        }

        $conn = get_mssql();

        if (!$conn) {
            return new WP_Error('mssql_connection_failed', 'get_mssql() returned empty connection.');
        }

        return $conn;
    }
}

if (!function_exists('ac_ajax_sql_options')) {
    function ac_ajax_sql_options() {
        // These endpoints run while staff are typing in picker modals. A long
        // SQL Server wait makes the UI feel frozen, so every lookup uses the
        // same short timeout.
        return array(
            'QueryTimeout' => 8,
        );
    }
}

if (!function_exists('ac_ajax_current_user_can_staff_lookup')) {
    function ac_ajax_current_user_can_staff_lookup() {
        if (!is_user_logged_in()) {
            return false;
        }

        $user = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return current_user_can('manage_options') || in_array('editor', $roles, true);
    }
}

if (!function_exists('ac_ajax_current_user_can_debtor_lookup')) {
    function ac_ajax_current_user_can_debtor_lookup() {
        if (ac_ajax_current_user_can_staff_lookup()) {
            return true;
        }

        if (!is_user_logged_in()) {
            return false;
        }

        $user = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return in_array('driver', $roles, true);
    }
}

if (!function_exists('ac_ajax_clean_ymd')) {
    function ac_ajax_clean_ymd($value) {
        $value = trim((string) $value);

        if (preg_match('/^\d{4}-\d{2}-\d{2}/', $value, $m)) {
            return $m[0];
        }

        return '';
    }
}

add_action('wp_ajax_ac_cs_debtor_search', 'ac_cs_debtor_search_ajax');

function ac_cs_debtor_search_ajax() {
    // WordPress AJAX nonce name must match wp_create_nonce('ac_cs_debtor_search')
    // in DOcreate-staff-workflow.php.
    if (!ac_ajax_current_user_can_debtor_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_cs_debtor_search', 'nonce');

    $q = trim(wp_unslash($_POST['q'] ?? $_GET['q'] ?? ''));

    if ($q === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();

    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    // Search supports both "contains" matching and "starts with" ordering.
    // AccNo starts-with results appear first because staff often know the code.
    $like   = '%' . $q . '%';
    $starts = $q . '%';

    $sql = "
        SELECT TOP 20
            AccNo,
            CompanyName,
            SalesAgent
        FROM Debtor
        WHERE
            -- Never show inactive debtors in the page picker; selecting one
            -- would fail later when creating the delivery order.
            IsActive = 'T'
            AND (
                AccNo LIKE ?
                OR CompanyName LIKE ?
            )
        ORDER BY
            CASE WHEN AccNo LIKE ? THEN 0 ELSE 1 END,
            CompanyName ASC,
            AccNo ASC
    ";

    $params = array($like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params, ac_ajax_sql_options());

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Debtor search query failed.';

        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }

        error_log('[ac_cs_debtor_search] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $code = trim((string)($row['AccNo'] ?? ''));
        $name = trim((string)($row['CompanyName'] ?? ''));

        if ($code === '' && $name === '') {
            continue;
        }

        $items[] = array(
            'code'       => $code,
            'name'       => $name,
            'salesAgent' => trim((string)($row['SalesAgent'] ?? '')),
        );
    }

    sqlsrv_free_stmt($stmt);

    wp_send_json_success(array(
        'items' => $items,
    ));
}

add_action('wp_ajax_ac_itemcode_suggest', 'ac_itemcode_suggest_ajax');

function ac_itemcode_suggest_ajax() {
    // WordPress AJAX nonce name must match wp_create_nonce('ac_itemcode_suggest')
    // in DOcreate-staff-workflow.php.
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_itemcode_suggest', 'nonce');

    $term = trim(wp_unslash($_POST['term'] ?? $_GET['term'] ?? ''));

    if ($term === '') {
        wp_send_json_success(array('items' => array()));
    }

    $conn = ac_ajax_get_conn();

    if (is_wp_error($conn)) {
        wp_send_json_error(array('error' => $conn->get_error_message()), 500);
    }

    // Search by ItemCode, Description, and Desc2. ItemCode starts-with results
    // are ranked first because short produce/item codes are common in staff use.
    $like   = '%' . $term . '%';
    $starts = $term . '%';

    $sql = "
        SELECT TOP 20
            i.ItemCode,
            i.Description,
            ISNULL(i.Desc2, '') AS Description2,
            i.BaseUOM,
            -- Price is read from ItemUOM as a default for create forms. The
            -- Delivery Order edit page does not expose client-side price edits.
            ISNULL(iu.Price, 0) AS Price
        FROM Item i
        -- Match ItemUOM by the item's BaseUOM so the picker gets the normal
        -- default price for the same UOM shown on the item.
        LEFT JOIN ItemUOM iu
            ON iu.ItemCode = i.ItemCode
            AND iu.UOM = i.BaseUOM
        WHERE
            -- Keep inactive items out of the picker; the bridge rejects them
            -- later and staff would only see a confusing save failure.
            i.IsActive = 'T'
            AND (
                i.ItemCode LIKE ?
                OR i.Description LIKE ?
                OR ISNULL(i.Desc2, '') LIKE ?
            )
        ORDER BY
            CASE WHEN i.ItemCode LIKE ? THEN 0 ELSE 1 END,
            i.Description ASC,
            i.ItemCode ASC
    ";

    $params = array($like, $like, $like, $starts);
    $stmt = sqlsrv_query($conn, $sql, $params, ac_ajax_sql_options());

    if ($stmt === false) {
        $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
        $msg = 'Item search query failed.';

        if (!empty($errors[0]['message'])) {
            $msg .= ' ' . $errors[0]['message'];
        }

        error_log('[ac_itemcode_suggest] ' . $msg);
        wp_send_json_error(array('error' => $msg), 500);
    }

    $items = array();

    while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
        $code = trim((string)($row['ItemCode'] ?? ''));
        $desc = trim((string)($row['Description'] ?? ''));

        if ($code === '' && $desc === '') {
            continue;
        }

        // Keep the JSON keys stable for JavaScript pickers.
        $items[] = array(
            'code'  => $code,
            'desc'  => $desc,
            'desc2' => trim((string)($row['Description2'] ?? '')),
            'uom'   => trim((string)($row['BaseUOM'] ?? '')),
            'price' => (float)($row['Price'] ?? 0),
        );
    }

    sqlsrv_free_stmt($stmt);

    wp_send_json_success(array(
        'items' => $items,
    ));
}

add_action('wp_ajax_ac_bs_get_basket_proof', 'ac_bs_get_basket_return_proof_ajax');

function ac_bs_get_basket_return_proof_ajax() {
    // This endpoint is intentionally "best effort." Receipt display should not
    // fail just because an optional basket return photo cannot be found.
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_bs_basket_proof', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        // Return success with found=false so the front-end can continue without
        // treating missing proof storage as a hard receipt error.
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'WordPress database connection not available.',
        ));
    }

    $ledgerId   = isset($_POST['ledgerId']) ? intval($_POST['ledgerId']) : 0;
    $sourceRef  = isset($_POST['sourceRef']) ? trim(sanitize_text_field(wp_unslash($_POST['sourceRef']))) : '';
    $debtorCode = isset($_POST['debtorCode']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorCode']))) : '';
    $debtorName = isset($_POST['debtorName']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorName']))) : '';
    $txnDate    = isset($_POST['txnDate']) ? ac_ajax_clean_ymd(wp_unslash($_POST['txnDate'])) : '';

    $table = $wpdb->prefix . 'ac_basket_return_proof_images';

    $table_exists = $wpdb->get_var(
        $wpdb->prepare('SHOW TABLES LIKE %s', $table)
    );

    if ($table_exists !== $table) {
        // Some deployments may not have proof-image capture enabled yet.
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'Basket return proof table not found.',
        ));
    }

    $where = array();
    $args  = array();

    // Build several lookup paths because older records may have only ledger ID,
    // source ref, debtor/date, or debtor name/date depending on when the proof
    // was captured.
    if ($ledgerId > 0) {
        $where[] = 'ledger_id = %d';
        $args[]  = $ledgerId;
    }

    if ($sourceRef !== '') {
        $where[] = 'source_ref = %s';
        $args[]  = $sourceRef;
    }

    if ($debtorCode !== '' && $txnDate !== '') {
        $where[] = '(debtor_code = %s AND DATE(captured_at) = %s)';
        $args[]  = $debtorCode;
        $args[]  = $txnDate;
    }

    if ($debtorName !== '' && $txnDate !== '') {
        $where[] = '(debtor_name = %s AND DATE(captured_at) = %s)';
        $args[]  = $debtorName;
        $args[]  = $txnDate;
    }

    if (empty($where)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No lookup value received.',
        ));
    }

    $sql = "
        SELECT
            image_url,
            image_path,
            attachment_id
        FROM `{$table}`
        WHERE (" . implode(' OR ', $where) . ")
          AND deleted_at IS NULL
        ORDER BY id DESC
        LIMIT 1
    ";

    $prepared_sql = $wpdb->prepare($sql, $args);
    $row = $wpdb->get_row($prepared_sql, ARRAY_A);

    if (!is_array($row)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No proof image found.',
        ));
    }

    $imageUrl = '';

    if (!empty($row['image_url'])) {
        $imageUrl = trim((string) $row['image_url']);
    }

    if ($imageUrl === '' && !empty($row['attachment_id'])) {
        $attachmentUrl = wp_get_attachment_url((int) $row['attachment_id']);

        if ($attachmentUrl) {
            $imageUrl = $attachmentUrl;
        }
    }

    if ($imageUrl === '' && !empty($row['image_path'])) {
        // Older rows may store a filesystem path instead of a public URL. If
        // the path is inside WordPress uploads, convert it back to baseurl.
        $path   = trim((string) $row['image_path']);
        $upload = wp_upload_dir();

        if (!empty($upload['basedir']) && !empty($upload['baseurl'])) {
            $normalBase = wp_normalize_path($upload['basedir']);
            $normalPath = wp_normalize_path($path);

            if (strpos($normalPath, $normalBase) === 0) {
                $imageUrl = str_replace($normalBase, $upload['baseurl'], $normalPath);
            }
        }

        if ($imageUrl === '' && strpos($path, '/') === 0) {
            $imageUrl = home_url($path);
        }
    }

    wp_send_json_success(array(
        'imageUrl' => $imageUrl ? esc_url_raw($imageUrl) : '',
        'found'    => $imageUrl !== '',
    ));
}QSX��C{����QGE�E7���2.(!infimumsupremum
4��&��Make upload filenames lowercaseMakes sure that image and file uploads have lowercase filenames.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_filter( 'sanitize_file_name', 'mb_strtolower' );sample, mediaglobal��
����fÀ	s��c��Disable admin barTurns off the WordPress admin bar for everyone except administrators.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_action( 'wp', function () {
	if ( ! current_user_can( 'manage_options' ) ) {
		show_admin_bar( false );
	}
} );sample, admin-barfront-end��
����fÀ��r
 ��Allow smiliesAllows smiley conversion in obscure places.

This is a sample snippet. Feel free to use it, edit it, or remove it.add_filter( 'widget_text', 'convert_smilies' );
add_filter( 'the_title', 'convert_smilies' );
add_filter( 'wp_title', 'convert_smilies' );
add_filter( 'get_bloginfo', 'convert_smilies' );sampleglobal��
����f��(���Current yearShortcode for inserting the current year into a post or page..

This is a sample snippet. Feel free to use it, edit it, or remove it.<?php echo date( 'Y' ); ?>sample, datescontent��
����fÀ��0	��MSSQL Connection/* ============================================================
   SQL SERVER 2016  →  AED_EXCELLENTVEGE
   ============================================================ */
if (!function_exists('get_mssql')) {

    function get_mssql()
    {
        static $connection = null;   // was $conn2016

        if ($connection !== null) {
            return $connection;
        }

        $server   = "192.168.100.225\\MSSQL2019SERVER";
        $database = "AED_VEGEBASKET";
        $username = "sa";
        $password = "user2025**";

        $connectionOptions = [        // was $connectionInfo
            "Database"               => $database,
            "UID"                    => $username,
            "PWD"                    => $password,
            "CharacterSet"           => "UTF-8",
            "Encrypt"                => "yes",
            "TrustServerCertificate" => "yes",
            "LoginTimeout"           => 5,
        ];

        $connection = sqlsrv_connect($server, $connectionOptions);

        if ($connection === false) {
            error_log('MSSQL connection failed: ' . print_r(sqlsrv_errors(), true));
            return null;
        }

        return $connection;
    }
}global��
�����Q��8Z��AJAX DOE&2�global��
����EĀ8�@���DELIVERY ORDER FORMadd_shortcode('delivery_order_form', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({ item, qty, kg, type: selectedType });

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
����X���H��DELIVERY ORDER FORM [1]add_shortcode('delivery_order_form_test', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn" onclick="submitDO()">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({
    itemCode: item,
    packType: selectedType,
    qty: parseFloat(qty),
    kg: parseFloat(kg),
    total: parseFloat(qty) * parseFloat(kg)
});

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
������ބ
P�	�submit apiasync function submitDO(){

    if(items.length === 0){
        alert("Please add at least 1 item");
        return;
    }

    let payload = {
        customerCode: document.querySelector('[placeholder="Search debtor..."]').value,
        docDate: document.querySelector('input[type="date"]').value,
        location: "HQ",
        lines: items.map(l => ({
            itemCode: l.itemCode,
            qty: l.total,
            uom: "KG",
            packType: l.packType,
            cartonQty: l.qty,
            kg: l.kg,
            totalKg: l.total,
            location: "HQ"
        }))
    };

    let body = {
        type: 'DELIVERY_ORDER',
        client_request_id: 'DO-' + Date.now(),
        source: 'wp-ui',
        payload
    };

    try{
        let res = await fetch("<?php echo esc_url(rest_url('ac/v1/job')); ?>", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": "<?php echo wp_create_nonce('wp_rest'); ?>"
            },
            body: JSON.stringify(body)
        });

        let data = await res.json();

        alert("Job Created: " + (data.jobId || "Success"));

    }catch(err){
        alert("Error: " + err.message);
    }
}site-head-js��
����Ҁp�cQG|�e�	�s�E�E2��-�6�(!infimumsupremum��	��MSSQL Connection/* ============================================================
   SQL SERVER 2016 @ AED_EXCELLENTVEGE
   ============================================================ */
if (!function_exists('get_mssql')) {

    function get_mssql()
    {
        static $connection = null;   // was $conn2016

        if ($connection !== null) {
            return $connection;
        }

        $server   = "192.168.100.225\\MSSQL2019SERVER";
        $database = "AED_VEGEBASKET";
        $username = "sa";
        $password = "user2025**";

        $connectionOptions = [        // was $connectionInfo
            "Database"               => $database,
            "UID"                    => $username,
            "PWD"                    => $password,
            "CharacterSet"           => "UTF-8",
            "Encrypt"                => "yes",
            "TrustServerCertificate" => "yes",
            "LoginTimeout"           => 5,
        ];

        $connection = sqlsrv_connect($server, $connectionOptions);

        if ($connection === false) {
            error_log('MSSQL connection failed: ' . print_r(sqlsrv_errors(), true));
            return null;
        }

        return $connection;
    }
}global��
���R�̀�Z��AJAX DOE&2�global��
���R���8� ���DELIVERY ORDER FORMadd_shortcode('delivery_order_form', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({ item, qty, kg, type: selectedType });

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
���R�����(��DELIVERY ORDER FORM [1]add_shortcode('delivery_order_form_test', function () {
ob_start();
?>

<style>
.do-container {
    max-width: 480px;
    margin: auto;
    padding: 16px;
    font-family: Arial;
    background: #f6fbf7;
}

/* Card */
.do-card {
    background: #fff;
    border-radius: 14px;
    padding: 16px;
    margin-bottom: 15px;
    box-shadow: 0 3px 10px rgba(0,0,0,0.05);
}

/* Label */
.do-label {
    font-size: 13px;
    color: #666;
    margin-bottom: 4px;
}

/* Input */
.do-input {
    width: 100%;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #dcdcdc;
    margin-bottom: 14px;
    box-sizing: border-box;
}

/* Type Toggle */
.type-toggle {
    display: flex;
    gap: 10px;
    margin-bottom: 14px;
}

.type-btn {
    flex: 1;
    padding: 12px;
    border-radius: 10px;
    border: 1px solid #28a745;
    background: #fff;
    color: #28a745;
    font-weight: bold;
    cursor: pointer;
}

.type-btn.active {
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
}

/* Button */
.do-btn {
    width: 100%;
    background: linear-gradient(90deg,#28a745,#1e7e34);
    color: #fff;
    border: none;
    padding: 14px;
    border-radius: 12px;
    font-weight: bold;
    cursor: pointer;
}

/* Item Card */
.do-item {
    border-top: 1px solid #eee;
    padding-top: 12px;
    margin-top: 12px;
}

.do-item b {
    color: #1e7e34;
}

/* Delete */
.del-btn {
    margin-top: 6px;
    background: #ff4d4d;
    color: #fff;
    border: none;
    padding: 6px 10px;
    border-radius: 6px;
}

/* Save section (clean form footer style) */
.save-wrapper {
    padding: 0 4px 10px;
}
</style>

<div class="do-container">

    <!-- Basic -->
    <div class="do-card">
        <div class="do-label">Date</div>
        <input type="date" class="do-input">

        <div class="do-label">Debtor Code</div>
        <input type="text" class="do-input" placeholder="Search debtor...">
    </div>

    <!-- Add Item -->
    <div class="do-card">

        <div class="do-label">Item Code</div>
        <input type="text" id="item" class="do-input" placeholder="Scan / type item">

        <div class="do-label">Type</div>
        <div class="type-toggle">
            <button type="button" class="type-btn active" onclick="selectType('Carton', this)">Carton</button>
            <button type="button" class="type-btn" onclick="selectType('Basket', this)">Basket</button>
        </div>

        <div class="do-label">Quantity (Qty)</div>
        <input type="number" id="qty" class="do-input" placeholder="Enter quantity">

        <div class="do-label">Weight (KG)</div>
        <input type="number" id="kg" class="do-input" placeholder="Enter weight">

        <button class="do-btn" onclick="addItem()">+ Add Item</button>
    </div>

    <!-- List -->
    <div class="do-card" id="list">
        <b>No items yet</b>
    </div>

    <!-- SAVE (inside form flow, clean UI) -->
    <div class="save-wrapper">
        <button class="do-btn" onclick="submitDO()">Save Delivery Order</button>
    </div>

</div>

<script>
let items = [];
let selectedType = "Carton";

// select type
function selectType(type, el){
    selectedType = type;

    document.querySelectorAll('.type-btn').forEach(btn=>{
        btn.classList.remove('active');
    });

    el.classList.add('active');
}

// enter flow
document.addEventListener("DOMContentLoaded", function () {

    document.getElementById('item').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('qty').focus();
    });

    document.getElementById('qty').addEventListener("keypress", e=>{
        if(e.key==="Enter") document.getElementById('kg').focus();
    });

    document.getElementById('kg').addEventListener("keypress", e=>{
        if(e.key==="Enter") addItem();
    });

});

// add
function addItem(){
    let item = document.getElementById('item').value;
    let qty = document.getElementById('qty').value || 0;
    let kg = document.getElementById('kg').value || 0;

    if(!item){
        alert("Please enter item");
        return;
    }

    items.push({
    itemCode: item,
    packType: selectedType,
    qty: parseFloat(qty),
    kg: parseFloat(kg),
    total: parseFloat(qty) * parseFloat(kg)
});

    render();

    document.getElementById('item').value="";
    document.getElementById('qty').value="";
    document.getElementById('kg').value="";
    document.getElementById('item').focus();
}

// delete
function del(i){
    items.splice(i,1);
    render();
}

// render
function render(){
    let html="";

    if(items.length===0){
        html="<b>No items yet</b>";
    } else {
        items.forEach((x,i)=>{
            html+=`
            <div class="do-item">
                <b>${x.item}</b><br>
                ${x.type}<br>
                Qty: ${x.qty}<br>
                KG: ${x.kg}<br>
                <button class="del-btn" onclick="del(${i})">Delete</button>
            </div>
            `;
        });
    }

    document.getElementById('list').innerHTML=html;
}
</script>

<?php
return ob_get_clean();
});global��
������ބ
0�	�submit apiasync function submitDO(){

    if(items.length === 0){
        alert("Please add at least 1 item");
        return;
    }

    let payload = {
        customerCode: document.querySelector('[placeholder="Search debtor..."]').value,
        docDate: document.querySelector('input[type="date"]').value,
        location: "HQ",
        lines: items.map(l => ({
            itemCode: l.itemCode,
            qty: l.total,
            uom: "KG",
            packType: l.packType,
            cartonQty: l.qty,
            kg: l.kg,
            totalKg: l.total,
            location: "HQ"
        }))
    };

    let body = {
        type: 'DELIVERY_ORDER',
        client_request_id: 'DO-' + Date.now(),
        source: 'wp-ui',
        payload
    };

    try{
        let res = await fetch("<?php echo esc_url(rest_url('ac/v1/job')); ?>", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": "<?php echo wp_create_nonce('wp_rest'); ?>"
            },
            body: JSON.stringify(body)
        });

        let data = await res.json();

        alert("Job Created: " + (data.jobId || "Success"));

    }catch(err){
        alert("Error: " + err.message);
    }
}site-head-js��
����Ҁpc�s��!�	
���E�E4�S(!infimumsupremumބ
-�	�submit apiasync function submitDO(){

    if(items.length === 0){
        alert("Please add at least 1 item");
        return;
    }

    let payload = {
        customerCode: document.querySelector('[placeholder="Search debtor..."]').value,
        docDate: document.querySelector('input[type="date"]').value,
        location: "HQ",
        lines: items.map(l => ({
            itemCode: l.itemCode,
            qty: l.total,
            uom: "KG",
            packType: l.packType,
            cartonQty: l.qty,
            kg: l.kg,
            totalKg: l.total,
            location: "HQ"
        }))
    };

    let body = {
        type: 'DELIVERY_ORDER',
        client_request_id: 'DO-' + Date.now(),
        source: 'wp-ui',
        payload
    };

    try{
        let res = await fetch("<?php echo esc_url(rest_url('ac/v1/job')); ?>", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-WP-Nonce": "<?php echo wp_create_nonce('wp_rest'); ?>"
            },
            body: JSON.stringify(body)
        });

        let data = await res.json();

        alert("Job Created: " + (data.jobId || "Success"));

    }catch(err){
        alert("Error: " + err.message);
    }
}site-head-js��
����ҀY�
��
�DO Receipt/**
 * DO Receipt Token REST Endpoint
 * Paste into functions.php or Code Snippets.
 */

add_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-receipt-token', array(
        'methods'             => 'POST',
        'callback'            => 'ac_create_do_receipt_token_endpoint',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_create_do_receipt_token_endpoint(WP_REST_Request $request) {
    global $wpdb;

    $job_id = absint($request->get_param('job_id'));

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $token_table = $wpdb->prefix . 'ac_do_receipt_tokens';

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT *
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Job not found.', array('status' => 404));
    }

    $status = strtoupper((string) ($job->status ?? ''));

    if ($status !== 'SUCCESS') {
        return new WP_Error('job_not_success', 'Job is not successful yet.', array('status' => 400));
    }

    $result = array();
    if (!empty($job->result)) {
        $decoded_result = json_decode($job->result, true);
        if (is_array($decoded_result)) {
            $result = $decoded_result;
        }
    }

    $payload = array();
    if (!empty($job->payload)) {
        $decoded_payload = json_decode($job->payload, true);
        if (is_array($decoded_payload)) {
            $payload = $decoded_payload;
        }
    }

    $doc_no  = $result['docNo']  ?? $result['DocNo']  ?? null;
    $doc_key = $result['docKey'] ?? $result['DocKey'] ?? null;

    $customer_code  = $payload['customerCode']  ?? $payload['debtorCode'] ?? $payload['DebtorCode'] ?? null;
    $customer_name  = $payload['customerName']  ?? $payload['debtorName'] ?? $payload['DebtorName'] ?? null;
    $customer_phone = $payload['customerPhone'] ?? $payload['phone']      ?? $payload['mobile'] ?? null;

    if (!$doc_no && !$doc_key) {
        return new WP_Error(
            'missing_doc_result',
            'Job result does not contain docNo or docKey.',
            array('status' => 400)
        );
    }

    $now = current_time('mysql');

    // Revoke old active token for the same job before creating a new one.
    $wpdb->update(
        $token_table,
        array(
            'revoked_at' => $now,
        ),
        array(
            'job_id'     => $job_id,
            'revoked_at' => null,
        ),
        array('%s'),
        array('%d', null)
    );

    $raw_token  = bin2hex(random_bytes(32));
    $token_hash = hash('sha256', $raw_token);

    $expires_at = wp_date(
        'Y-m-d H:i:s',
        current_time('timestamp') + (30 * DAY_IN_SECONDS)
    );

    $inserted = $wpdb->insert(
        $token_table,
        array(
            'job_id'         => $job_id,
            'doc_no'         => $doc_no,
            'doc_key'        => $doc_key,
            'customer_code'  => $customer_code,
            'customer_name'  => $customer_name,
            'customer_phone' => $customer_phone,
            'token_hash'     => $token_hash,
            'created_at'     => $now,
            'expires_at'     => $expires_at,
            'opened_at'      => null,
            'revoked_at'     => null,
        ),
        array(
            '%d',
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%s',
            '%s',
            '%s',
            null,
            null,
        )
    );

    if (!$inserted) {
        return new WP_Error(
            'token_insert_failed',
            'Failed to create receipt token.',
            array('status' => 500)
        );
    }

    $receipt_url = home_url('/index.php/do-receipt/?token=' . rawurlencode($raw_token));

    return rest_ensure_response(array(
        'success'     => true,
        'receipt_url' => $receipt_url,
        'doc_no'      => $doc_no,
        'doc_key'     => $doc_key,
        'expires_at'  => $expires_at,
    ));
}global��
���R���[�	 ���DO Images/**
 * DO Proof Image Upload REST Endpoint
 * Endpoint: /wp-json/ac/v1/do-proof-upload
 */

add_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-proof-upload', array(
        'methods'             => 'POST',
        'callback'            => 'ac_do_proof_upload_endpoint',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_do_proof_upload_endpoint(WP_REST_Request $request) {
    global $wpdb;

    $job_id     = absint($request->get_param('job_id'));
    $doc_no     = sanitize_text_field((string) $request->get_param('doc_no'));
    $doc_key    = absint($request->get_param('doc_key'));
    $proof_type = sanitize_text_field((string) $request->get_param('proof_type'));

    if (!$proof_type) {
        $proof_type = 'DELIVERY_PROOF';
    }

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    if (empty($_FILES['proof_image'])) {
        return new WP_Error('missing_image', 'Missing proof image.', array('status' => 400));
    }

    $file = $_FILES['proof_image'];

    if (!empty($file['error'])) {
        return new WP_Error('upload_error', 'Upload error code: ' . $file['error'], array('status' => 400));
    }

    $max_size = 8 * 1024 * 1024; // 8MB

    if (!empty($file['size']) && $file['size'] > $max_size) {
        return new WP_Error('image_too_large', 'Image is too large. Maximum 8MB.', array('status' => 400));
    }

    $allowed_mimes = array(
        'jpg|jpeg|jpe' => 'image/jpeg',
        'png'          => 'image/png',
        'webp'         => 'image/webp',
        'gif'          => 'image/gif',
    );

    $file_check = wp_check_filetype_and_ext(
        $file['tmp_name'],
        $file['name'],
        $allowed_mimes
    );

    if (empty($file_check['type']) || strpos($file_check['type'], 'image/') !== 0) {
        return new WP_Error('invalid_image_type', 'Only image files are allowed.', array('status' => 400));
    }

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $proof_table = $wpdb->prefix . 'ac_do_proof_images';

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT *
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Job not found.', array('status' => 404));
    }

    $status = strtoupper((string) ($job->status ?? ''));

    if ($status !== 'SUCCESS') {
        return new WP_Error(
            'job_not_success',
            'Proof can only be uploaded after successful DO creation.',
            array('status' => 400)
        );
    }

    /**
     * Only allow 1 proof image per DO/job.
     */
    $existing_proof_id = $wpdb->get_var($wpdb->prepare("
        SELECT id
        FROM {$proof_table}
        WHERE job_id = %d
        AND deleted_at IS NULL
        LIMIT 1
    ", $job_id));

    if ($existing_proof_id) {
        return new WP_Error(
            'proof_already_exists',
            'This order already has proof of delivery.',
            array('status' => 409)
        );
    }

    /**
     * Get doc_no and doc_key from job result if not provided.
     */
    if (!$doc_no || !$doc_key) {
        $result = array();

        if (!empty($job->result)) {
            $decoded_result = json_decode($job->result, true);

            if (is_array($decoded_result)) {
                $result = $decoded_result;
            }
        }

        if (!$doc_no) {
            $doc_no = sanitize_text_field((string) ($result['docNo'] ?? $result['DocNo'] ?? $result['doc_no'] ?? ''));
        }

        if (!$doc_key) {
            $doc_key = absint($result['docKey'] ?? $result['DocKey'] ?? $result['doc_key'] ?? 0);
        }
    }

    if (!$doc_no) {
        $doc_no = 'JOB-' . $job_id;
    }

    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';

    $upload_overrides = array(
        'test_form' => false,
        'mimes'     => $allowed_mimes,
    );

    $uploaded = wp_handle_upload($file, $upload_overrides);

    if (!empty($uploaded['error'])) {
        return new WP_Error('upload_failed', $uploaded['error'], array('status' => 500));
    }

    $image_url  = $uploaded['url'] ?? '';
    $image_path = $uploaded['file'] ?? '';
    $mime_type  = $uploaded['type'] ?? '';

    if (!$image_url || !$image_path) {
        return new WP_Error(
            'upload_missing_data',
            'Upload completed but image data is missing.',
            array('status' => 500)
        );
    }

    $attachment_title = 'DO Proof - ' . ($doc_no ?: 'Job ' . $job_id);

    $attachment = array(
        'post_mime_type' => $mime_type,
        'post_title'     => sanitize_file_name($attachment_title),
        'post_content'   => '',
        'post_status'    => 'inherit',
    );

    $attachment_id = wp_insert_attachment($attachment, $image_path);

    if (is_wp_error($attachment_id) || !$attachment_id) {
        return new WP_Error(
            'attachment_failed',
            'Failed to create media attachment.',
            array('status' => 500)
        );
    }

    $attachment_meta = wp_generate_attachment_metadata($attachment_id, $image_path);
    wp_update_attachment_metadata($attachment_id, $attachment_meta);

    $file_size = file_exists($image_path) ? filesize($image_path) : 0;
    $now       = current_time('mysql');
    $user_id   = get_current_user_id();

    /**
     * Save proof image record.
     */
    $inserted = $wpdb->insert(
        $proof_table,
        array(
            'job_id'        => $job_id,
            'doc_no'        => $doc_no,
            'doc_key'       => $doc_key ?: null,
            'proof_type'    => $proof_type,
            'attachment_id' => $attachment_id,
            'image_url'     => $image_url,
            'image_path'    => $image_path,
            'mime_type'     => $mime_type,
            'file_size'     => $file_size,
            'captured_by'   => $user_id,
            'captured_at'   => $now,
            'created_at'    => $now,
        ),
        array(
            '%d',
            '%s',
            '%d',
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
            '%d',
            '%d',
            '%s',
            '%s',
        )
    );

    if (!$inserted) {
        wp_delete_attachment($attachment_id, true);

        return new WP_Error(
            'proof_insert_failed',
            'Image uploaded but failed to save proof record.',
            array('status' => 500)
        );
    }

    /**
     * Mark DO/job as delivered.
     */
    $wpdb->update(
        $jobs_table,
        array(
            'delivery_status'       => 'COMPLETED',
            'delivery_completed_by' => $user_id,
            'delivery_completed_at' => $now,
            'delivery_note'         => 'Completed with proof of delivery',
            'updated_at'            => $now,
        ),
        array(
            'id' => $job_id,
        ),
        array(
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
        ),
        array(
            '%d',
        )
    );

    return rest_ensure_response(array(
        'success'       => true,
        'message'       => 'Order ' . $doc_no . ' Delivered.',
        'proof_id'      => $wpdb->insert_id,
        'job_id'        => $job_id,
        'doc_no'        => $doc_no,
        'doc_key'       => $doc_key,
        'attachment_id' => $attachment_id,
        'image_url'     => $image_url,
        'mime_type'     => $mime_type,
        'file_size'     => $file_size,
        'captured_at'   => $now,
    ));
}global��
���R���pc�������
	�������E�EÀj(!infimumsupremum����DO Delivery Complete Endpointadd_action('rest_api_init', function () {
    register_rest_route('ac/v1', '/do-delivery-complete', array(
        'methods'             => 'POST',
        'callback'            => 'ac_do_mark_delivery_complete',
        'permission_callback' => function () {
            return is_user_logged_in();
        },
    ));
});

function ac_do_mark_delivery_complete(WP_REST_Request $request) {
    global $wpdb;

    $jobs_table  = $wpdb->prefix . 'ac_jobs';
    $proof_table = $wpdb->prefix . 'ac_do_proof_images';

    $user_id  = get_current_user_id();
    $job_id   = absint($request->get_param('job_id'));
    $doc_no   = sanitize_text_field($request->get_param('doc_no'));
    $doc_key  = absint($request->get_param('doc_key'));
    $proof_id = absint($request->get_param('proof_id'));
    $note     = sanitize_textarea_field($request->get_param('delivery_note'));

    if (!$job_id) {
        return new WP_Error('missing_job_id', 'Missing job_id.', array('status' => 400));
    }

    $job = $wpdb->get_row($wpdb->prepare("
        SELECT id, created_by, job_type, status, delivery_status
        FROM {$jobs_table}
        WHERE id = %d
        LIMIT 1
    ", $job_id));

    if (!$job) {
        return new WP_Error('job_not_found', 'Delivery Order job not found.', array('status' => 404));
    }

    if (strtoupper($job->job_type) !== 'DELIVERY_ORDER') {
        return new WP_Error('invalid_job_type', 'This job is not a Delivery Order.', array('status' => 400));
    }

    if ((int) $job->created_by !== (int) $user_id && !current_user_can('manage_options')) {
        return new WP_Error('forbidden', 'You cannot complete another driver\'s Delivery Order.', array('status' => 403));
    }

    if ($proof_id) {
        $proof = $wpdb->get_row($wpdb->prepare("
            SELECT id, job_id, captured_by, image_url
            FROM {$proof_table}
            WHERE id = %d
            AND job_id = %d
            AND deleted_at IS NULL
            LIMIT 1
        ", $proof_id, $job_id));
    } else {
        $proof = $wpdb->get_row($wpdb->prepare("
            SELECT id, job_id, captured_by, image_url
            FROM {$proof_table}
            WHERE job_id = %d
            AND deleted_at IS NULL
            ORDER BY id DESC
            LIMIT 1
        ", $job_id));
    }

    if (!$proof) {
        return new WP_Error('proof_not_found', 'Proof image not found for this Delivery Order.', array('status' => 400));
    }

    $now = current_time('mysql');

    $updated = $wpdb->update(
        $jobs_table,
        array(
            'delivery_status'       => 'COMPLETED',
            'delivery_completed_by' => $user_id,
            'delivery_completed_at' => $now,
            'delivery_note'         => $note,
            'updated_at'            => $now,
        ),
        array(
            'id' => $job_id,
        ),
        array(
            '%s',
            '%d',
            '%s',
            '%s',
            '%s',
        ),
        array(
            '%d',
        )
    );

    if ($updated === false) {
        return new WP_Error('update_failed', $wpdb->last_error ?: 'Failed to update delivery status.', array('status' => 500));
    }

    return rest_ensure_response(array(
        'success'                 => true,
        'message'                 => 'Delivery marked complete.',
        'job_id'                  => $job_id,
        'doc_no'                  => $doc_no,
        'doc_key'                 => $doc_key,
        'proof_id'                => (int) $proof->id,
        'proof_image_url'         => $proof->image_url,
        'delivery_status'         => 'COMPLETED',
        'delivery_completed_by'   => $user_id,
        'delivery_completed_at'   => $now,
    ));
}global��
���R�n����
�AJAX DO IpohserverE&HYglobal��
���R�W�pc����-	���������"<
E?�/**
 * AJAX for Delivery Order / Cash Sale / GRN lookup
 *
 * WordPress-first version:
 * - Debtor search reads from local MySQL synced table: {$wpdb->prefix}acs_debtors
 * - Creditor search reads from local MySQL synced table: {$wpdb->prefix}acs_creditors
 * - Item search reads from local MySQL synced tables: {$wpdb->prefix}acs_items + {$wpdb->prefix}acs_item_uoms
 * - No live MSSQL/get_mssql() dependency for picker search
 * - Uses the active environment reported by the bridge (suffix _local for local env).
 * - Keeps the same AJAX action names and JSON response shape so existing frontend still works
 * - Basket proof image lookup remains optional and never blocks receipt display
 */

if (!defined('ABSPATH')) {
    exit;
}

if (!function_exists('ac_ajax_use_local_masterdata')) {
    function ac_ajax_use_local_masterdata() {
        $status = get_option('ac_bridge_sync_status', []);

        if (!is_array($status)) {
            return false;
        }

        /*
         * Bridge config rule:
         * true  = use *_local tables
         * false = use normal tables
         */

        if (array_key_exists('use_local_masterdata', $status)) {
            return filter_var($status['use_local_masterdata'], FILTER_VALIDATE_BOOLEAN);
        }

        if (array_key_exists('useLocalMasterdata', $status)) {
            return filter_var($status['useLocalMasterdata'], FILTER_VALIDATE_BOOLEAN);
        }

        if (array_key_exists('use_local', $status)) {
            return filter_var($status['use_local'], FILTER_VALIDATE_BOOLEAN);
        }

        /*
         * Backward compatibility with your current code.
         * If bridge still sends environment = local, keep supporting it.
         */
        if (array_key_exists('environment', $status)) {
            return strtolower(trim((string) $status['environment'])) === 'local';
        }

        return false;
    }
}

if (!function_exists('ac_ajax_masterdata_table')) {
    function ac_ajax_masterdata_table($base) {
        global $wpdb;

        $table = $wpdb->prefix . $base;

        if (ac_ajax_use_local_masterdata()) {
            $table .= '_local';
        }

        return $table;
    }
}

if (!function_exists('ac_ajax_remove_local_suffix')) {
    function ac_ajax_remove_local_suffix($table_name) {
        $suffix = '_local';

        if (substr($table_name, -strlen($suffix)) === $suffix) {
            return substr($table_name, 0, -strlen($suffix));
        }

        return $table_name;
    }
}

if (!function_exists('ac_ajax_current_user_can_staff_lookup')) {
    function ac_ajax_current_user_can_staff_lookup() {
        if (!is_user_logged_in()) {
            return false;
        }

        $user  = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return current_user_can('manage_options') || in_array('editor', $roles, true);
    }
}

if (!function_exists('ac_ajax_current_user_can_debtor_lookup')) {
    function ac_ajax_current_user_can_debtor_lookup() {
        if (ac_ajax_current_user_can_staff_lookup()) {
            return true;
        }

        if (!is_user_logged_in()) {
            return false;
        }

        $user  = wp_get_current_user();
        $roles = is_array($user->roles ?? null) ? $user->roles : array();

        return in_array('driver', $roles, true);
    }
}

if (!function_exists('ac_ajax_clean_ymd')) {
    function ac_ajax_clean_ymd($value) {
        $value = trim((string) $value);

        if (preg_match('/^\d{4}-\d{2}-\d{2}/', $value, $m)) {
            return $m[0];
        }

        return '';
    }
}

if (!function_exists('ac_ajax_table_exists')) {
    function ac_ajax_table_exists($table_name) {
        global $wpdb;

        if (!$wpdb || $table_name === '') {
            return false;
        }

        $found = $wpdb->get_var(
            $wpdb->prepare('SHOW TABLES LIKE %s', $table_name)
        );

        return $found === $table_name;
    }
}

if (!function_exists('ac_ajax_local_lookup_error')) {
    function ac_ajax_local_lookup_error($message, $status_code = 500) {
        wp_send_json_error(array(
            'error' => $message,
        ), $status_code);
    }
}

/**
 * Customer / Debtor search
 *
 * Source:
 * - WordPress MySQL cache table: {$wpdb->prefix}acs_debtors
 */
add_action('wp_ajax_ac_cs_debtor_search', 'ac_cs_debtor_search_ajax');

function ac_cs_debtor_search_ajax() {
    if (!ac_ajax_current_user_can_debtor_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_cs_debtor_search', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        ac_ajax_local_lookup_error('WordPress database connection not available.');
    }

    $q = trim(wp_unslash($_POST['q'] ?? $_GET['q'] ?? ''));

    if ($q === '') {
        wp_send_json_success(array('items' => array()));
    }

    $table = ac_ajax_masterdata_table('acs_debtors');

    if (!ac_ajax_table_exists($table)) {
        ac_ajax_local_lookup_error('Local debtor cache table not found: ' . $table);
    }

    $like   = '%' . $wpdb->esc_like($q) . '%';
    $starts = $wpdb->esc_like($q) . '%';

    $sql = "
        SELECT
            acc_no,
            company_name,
            sales_agent
        FROM `{$table}`
        WHERE
            is_active = 1
            AND (
                acc_no LIKE %s
                OR company_name LIKE %s
            )
        ORDER BY
            CASE WHEN acc_no LIKE %s THEN 0 ELSE 1 END,
            company_name ASC,
            acc_no ASC
        LIMIT 20
    ";

    $rows = $wpdb->get_results(
        $wpdb->prepare($sql, $like, $like, $starts),
        ARRAY_A
    );

    if ($wpdb->last_error) {
        $msg = 'Debtor search query failed. ' . $wpdb->last_error;
        error_log('[ac_cs_debtor_search] ' . $msg);
        ac_ajax_local_lookup_error($msg);
    }

    $items = array();

    foreach ((array) $rows as $row) {
        $code = trim((string)($row['acc_no'] ?? ''));
        $name = trim((string)($row['company_name'] ?? ''));

        if ($code === '' && $name === '') {
            continue;
        }

        $items[] = array(
            'code'       => $code,
            'name'       => $name,
            'salesAgent' => trim((string)($row['sales_agent'] ?? '')),
        );
    }

    wp_send_json_success(array(
        'items' => $items,
    ));
}

/**
 * Item search
 *
 * Source:
 * - WordPress MySQL cache tables:
 *   - {$wpdb->prefix}acs_items
 *   - {$wpdb->prefix}acs_item_uoms
 */
add_action('wp_ajax_ac_itemcode_suggest', 'ac_itemcode_suggest_ajax');

function ac_itemcode_suggest_ajax() {
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_itemcode_suggest', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        ac_ajax_local_lookup_error('WordPress database connection not available.');
    }

    $term = trim(wp_unslash($_POST['term'] ?? $_GET['term'] ?? $_POST['q'] ?? $_GET['q'] ?? ''));

    if ($term === '') {
        wp_send_json_success(array(
            'items' => array(),
            'debug' => array(
                'reason' => 'Empty search term',
            ),
        ));
    }

    $term_lc = strtolower($term);
    $like    = '%' . $wpdb->esc_like($term_lc) . '%';
    $starts  = $wpdb->esc_like($term_lc) . '%';

    $base_item_table = ac_ajax_masterdata_table('acs_items');
    $base_uom_table  = ac_ajax_masterdata_table('acs_item_uoms');

    /*
     * Build alternate table name safely.
     *
     * If bridge says use normal:
     * - base: wp_vege_acs_items
     * - alt : wp_vege_acs_items_local
     *
     * If bridge says use local:
     * - base: wp_vege_acs_items_local
     * - alt : wp_vege_acs_items
     */
    if (substr($base_item_table, -6) === '_local') {
        $alt_item_table = ac_ajax_remove_local_suffix($base_item_table);
    } else {
        $alt_item_table = $base_item_table . '_local';
    }

    if (substr($base_uom_table, -6) === '_local') {
        $alt_uom_table = ac_ajax_remove_local_suffix($base_uom_table);
    } else {
        $alt_uom_table = $base_uom_table . '_local';
    }

    $item_table = $base_item_table;
    $uom_table  = $base_uom_table;
    $used_alt   = false;

    if (!ac_ajax_table_exists($base_item_table)) {
        if (ac_ajax_table_exists($alt_item_table)) {
            $item_table = $alt_item_table;
            $uom_table  = ac_ajax_table_exists($alt_uom_table) ? $alt_uom_table : $base_uom_table;
            $used_alt   = true;
        } else {
            ac_ajax_local_lookup_error('Item cache table not found. Base=' . $base_item_table . ', Alt=' . $alt_item_table);
        }
    } else {
        $base_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM `{$base_item_table}`");
        $alt_count  = 0;

        if (ac_ajax_table_exists($alt_item_table)) {
            $alt_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM `{$alt_item_table}`");
        }

        if ($base_count === 0 && $alt_count > 0) {
            $item_table = $alt_item_table;

            if (ac_ajax_table_exists($alt_uom_table)) {
                $uom_table = $alt_uom_table;
            }

            $used_alt = true;
        }
    }

    if (!ac_ajax_table_exists($item_table)) {
        ac_ajax_local_lookup_error('Item cache table not found: ' . $item_table);
    }

    if (!ac_ajax_table_exists($uom_table)) {
        error_log('[ac_itemcode_suggest] UOM table missing: ' . $uom_table . ' (will return price=0)');
    }

    error_log('[ac_itemcode_suggest] Searching items table=' . $item_table . ' uom=' . $uom_table . ' used_alt=' . ($used_alt ? '1' : '0'));

    /*
     * IMPORTANT:
     * Item active filter is now STRICT.
     * Only is_active = 1 will be returned.
     * NULL, empty, T, Y, YES, TRUE are no longer accepted.
     */
    $sql = "
        SELECT
            i.item_code,
            i.description,
            COALESCE(i.desc2, '') AS description2,
            i.base_uom,
            COALESCE(iu.price, 0) AS price
        FROM `{$item_table}` i
        LEFT JOIN `{$uom_table}` iu
            ON iu.item_code = i.item_code
            AND iu.uom = i.base_uom
        WHERE
            i.is_active = 1
            AND (
                LOWER(i.item_code) LIKE %s
                OR LOWER(i.description) LIKE %s
                OR COALESCE(LOWER(i.desc2), '') LIKE %s
            )
        ORDER BY
            CASE
                WHEN LOWER(i.item_code) LIKE %s THEN 0
                WHEN LOWER(i.description) LIKE %s THEN 1
                ELSE 2
            END,
            i.description != '' DESC,
            i.description ASC,
            i.item_code ASC
        LIMIT 20
    ";

    $rows = $wpdb->get_results(
        $wpdb->prepare($sql, $like, $like, $like, $starts, $starts),
        ARRAY_A
    );

    if ($wpdb->last_error) {
        $msg = 'Item search query failed. ' . $wpdb->last_error;
        error_log('[ac_itemcode_suggest] ' . $msg);
        ac_ajax_local_lookup_error($msg);
    }

    $items = array();

    foreach ((array) $rows as $row) {
        $code = trim((string)($row['item_code'] ?? ''));
        $desc = trim((string)($row['description'] ?? ''));

        if ($code === '' && $desc === '') {
            continue;
        }

        $items[] = array(
            'code'  => $code,
            'desc'  => $desc,
            'name'  => $desc,
            'desc2' => trim((string)($row['description2'] ?? '')),
            'uom'   => trim((string)($row['base_uom'] ?? '')),
            'price' => (float)($row['price'] ?? 0),
        );
    }

    wp_send_json_success(array(
        'items' => $items,
        'debug' => array(
            'term'          => $term,
            'item_table'    => $item_table,
            'uom_table'     => $uom_table,
            'used_alt'      => $used_alt,
            'active_filter' => 'i.is_active = 1',
            'count'         => count($items),
        ),
    ));
}

/**
 * Creditor / Supplier search
 *
 * Source:
 * - WordPress MySQL cache table: {$wpdb->prefix}acs_creditors
 */
add_action('wp_ajax_ac_cs_creditor_search', 'ac_cs_creditor_search_ajax');

function ac_cs_creditor_search_ajax() {
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_cs_creditor_search', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        ac_ajax_local_lookup_error('WordPress database connection not available.');
    }

    $q = trim(wp_unslash($_POST['q'] ?? $_GET['q'] ?? ''));

    if ($q === '') {
        wp_send_json_success(array('items' => array()));
    }

    $table = ac_ajax_masterdata_table('acs_creditors');

    if (!ac_ajax_table_exists($table)) {
        ac_ajax_local_lookup_error('Local creditor cache table not found: ' . $table);
    }

    $like   = '%' . $wpdb->esc_like($q) . '%';
    $starts = $wpdb->esc_like($q) . '%';

    $sql = "
        SELECT
            acc_no,
            company_name
        FROM `{$table}`
        WHERE
            is_active = 1
            AND (
                acc_no LIKE %s
                OR company_name LIKE %s
            )
        ORDER BY
            CASE WHEN acc_no LIKE %s THEN 0 ELSE 1 END,
            company_name ASC,
            acc_no ASC
        LIMIT 20
    ";

    $rows = $wpdb->get_results(
        $wpdb->prepare($sql, $like, $like, $starts),
        ARRAY_A
    );

    if ($wpdb->last_error) {
        $msg = 'Creditor search query failed. ' . $wpdb->last_error;
        error_log('[ac_cs_creditor_search] ' . $msg);
        ac_ajax_local_lookup_error($msg);
    }

    $items = array();

    foreach ((array) $rows as $row) {
        $code = trim((string)($row['acc_no'] ?? ''));
        $name = trim((string)($row['company_name'] ?? ''));

        if ($code === '' && $name === '') {
            continue;
        }

        $items[] = array(
            'code' => $code,
            'name' => $name,
        );
    }

    wp_send_json_success(array(
        'items' => $items,
    ));
}

/**
 * Basket return proof lookup
 *
 * Kept as WordPress/MySQL lookup.
 * This endpoint is intentionally best-effort.
 * Receipt display should not fail just because optional proof storage cannot be found.
 */
add_action('wp_ajax_ac_bs_get_basket_proof', 'ac_bs_get_basket_return_proof_ajax');

function ac_bs_get_basket_return_proof_ajax() {
    if (!ac_ajax_current_user_can_staff_lookup()) {
        wp_send_json_error(array('error' => 'Unauthorized.'), 403);
    }

    check_ajax_referer('ac_bs_basket_proof', 'nonce');

    global $wpdb;

    if (!$wpdb) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'WordPress database connection not available.',
        ));
    }

    $ledgerId   = isset($_POST['ledgerId']) ? intval($_POST['ledgerId']) : 0;
    $sourceRef  = isset($_POST['sourceRef']) ? trim(sanitize_text_field(wp_unslash($_POST['sourceRef']))) : '';
    $debtorCode = isset($_POST['debtorCode']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorCode']))) : '';
    $debtorName = isset($_POST['debtorName']) ? trim(sanitize_text_field(wp_unslash($_POST['debtorName']))) : '';
    $txnDate    = isset($_POST['txnDate']) ? ac_ajax_clean_ymd(wp_unslash($_POST['txnDate'])) : '';

    $table = $wpdb->prefix . 'ac_basket_return_proof_images';

    $table_exists = $wpdb->get_var(
        $wpdb->prepare('SHOW TABLES LIKE %s', $table)
    );

    if ($table_exists !== $table) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'Basket return proof table not found.',
        ));
    }

    $where = array();
    $args  = array();

    if ($ledgerId > 0) {
        $where[] = 'ledger_id = %d';
        $args[]  = $ledgerId;
    }

    if ($sourceRef !== '') {
        $where[] = 'source_ref = %s';
        $args[]  = $sourceRef;
    }

    if ($debtorCode !== '' && $txnDate !== '') {
        $where[] = '(debtor_code = %s AND DATE(captured_at) = %s)';
        $args[]  = $debtorCode;
        $args[]  = $txnDate;
    }

    if ($debtorName !== '' && $txnDate !== '') {
        $where[] = '(debtor_name = %s AND DATE(captured_at) = %s)';
        $arg�"<J���������"<
E�����s[]  = $debtorName;
        $args[]  = $txnDate;
    }

    if (empty($where)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No lookup value received.',
        ));
    }

    $sql = "
        SELECT
            image_url,
            image_path,
            attachment_id
        FROM `{$table}`
        WHERE (" . implode(' OR ', $where) . ")
          AND deleted_at IS NULL
        ORDER BY id DESC
        LIMIT 1
    ";

    $prepared_sql = $wpdb->prepare($sql, $args);
    $row = $wpdb->get_row($prepared_sql, ARRAY_A);

    if ($wpdb->last_error) {
        error_log('[ac_bs_get_basket_proof] ' . $wpdb->last_error);

        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'Proof image query failed.',
        ));
    }

    if (!is_array($row)) {
        wp_send_json_success(array(
            'imageUrl' => '',
            'found'    => false,
            'reason'   => 'No proof image found.',
        ));
    }

    $imageUrl = '';

    if (!empty($row['image_url'])) {
        $imageUrl = trim((string) $row['image_url']);
    }

    if ($imageUrl === '' && !empty($row['attachment_id'])) {
        $attachmentUrl = wp_get_attachment_url((int) $row['attachment_id']);

        if ($attachmentUrl) {
            $imageUrl = $attachmentUrl;
        }
    }

    if ($imageUrl === '' && !empty($row['image_path'])) {
        $path   = trim((string) $row['image_path']);
        $upload = wp_upload_dir();

        if (!empty($upload['basedir']) && !empty($upload['baseurl'])) {
            $normalBase = wp_normalize_path($upload['basedir']);
            $normalPath = wp_normalize_path($path);

            if (strpos($normalPath, $normalBase) === 0) {
                $imageUrl = str_replace($normalBase, $upload['baseurl'], $normalPath);
            }
        }

        if ($imageUrl === '' && strpos($path, '/') === 0) {
            $imageUrl = home_url($path);
        }
    }

    wp_send_json_success(array(
        'imageUrl' => $imageUrl ? esc_url_raw($imageUrl) : '',
        'found'    => $imageUrl !== '',
    ));
}�"<M��

Youez - 2016 - github.com/yon3zu
LinuXploit