| @@ -5,8 +5,9 @@ | ||
| 5 | 5 | use BitCode\BitForm\Core\Cryptography\Cryptography; |
| 6 | 6 | use BitCode\BitForm\Core\Database\FormEntryModel; |
| 7 | 7 | use BitCode\BitForm\Core\Util\FileHandler; |
| 8 | 8 | use BitCode\BitForm\Core\Util\Log; |
| 9 | +use BitCode\BitForm\Core\WorkFlow\WorkflowExecutor; | |
| 9 | 10 | use Exception; |
| 10 | 11 | use WP_Error; |
| 11 | 12 | |
| 12 | 13 | class Helpers |
| @@ -417,10 +418,17 @@ | ||
| 417 | 418 | |
| 418 | 419 | // Validate trigger token from transient |
| 419 | 420 | $transientData = get_transient("bitform_trigger_transient_{$entryID}"); |
| 420 | 421 | |
| 422 | + // Object caches (W3TC, SG Optimizer, Redis) can drop the transient before the | |
| 423 | + // browser trigger arrives; the queue log row holds the same payload durably. | |
| 421 | 424 | if (empty($transientData)) { |
| 422 | - Log::debug_log('Trigger token transient missing for entryID=' . $entryID . ', logID=' . $logID); | |
| 425 | + $queueLogId = isset($request->cronNotOk[2]) && is_numeric($request->cronNotOk[2]) ? absint($request->cronNotOk[2]) : 0; | |
| 426 | + $transientData = WorkflowExecutor::loadTriggerDataFromLog($queueLogId); | |
| 427 | + Log::debug_log('Trigger token transient missing for entryID=' . $entryID . ', logID=' . $logID . '; durable copy ' . (empty($transientData) ? 'not found' : 'used')); | |
| 428 | + } | |
| 429 | + | |
| 430 | + if (empty($transientData)) { | |
| 423 | 431 | return [ |
| 424 | 432 | 'valid' => false, |
| 425 | 433 | 'error' => 'Trigger token expired or missing', |
| 426 | 434 | 'triggerData' => null, |
| @@ -427,12 +435,12 @@ | ||
| 427 | 435 | 'isAdminBypass' => false |
| 428 | 436 | ]; |
| 429 | 437 | } |
| 430 | 438 | |
| 431 | - $triggerData = is_string($transientData) ? json_decode($transientData) : $transientData; | |
| 439 | + $triggerData = is_string($transientData) ? json_decode($transientData, true) : (array) $transientData; | |
| 432 | 440 | // Verify token matches and belongs to this entry/log |
| 433 | 441 | if ( |
| 434 | - !isset($triggerData['trigger_token']) | |
| 442 | + !isset($triggerData['trigger_token'], $triggerData['entryID'], $triggerData['logID']) | |
| 435 | 443 | || !hash_equals($triggerData['trigger_token'], $submittedToken) |
| 436 | 444 | || (int)$triggerData['entryID'] !== $entryID |
| 437 | 445 | || (int)$triggerData['logID'] !== $logID |
| 438 | 446 | ) { |
| @@ -703,10 +711,10 @@ | ||
| 703 | 711 | public static function replaceShortcodeInErrorMsg($msg, $field) |
| 704 | 712 | { |
| 705 | 713 | $shortcodes = [ |
| 706 | 714 | '${field.label}' => isset($field->lbl) ? $field->lbl : '', |
| 707 | - '${field.minimum}' => isset($field->mn) ? $field->mn : '', | |
| 708 | - '${field.maximum}' => isset($field->mx) ? $field->mx : '', | |
| 715 | + '${field.minimum}' => isset($field->mn) ? self::readableRangeValue($field->mn, isset($field->typ) ? $field->typ : '') : '', | |
| 716 | + '${field.maximum}' => isset($field->mx) ? self::readableRangeValue($field->mx, isset($field->typ) ? $field->typ : '') : '', | |
| 709 | 717 | '${field.minimum_file}' => isset($field->config->minFile) ? $field->config->minFile : '', |
| 710 | 718 | '${field.maximum_file}' => isset($field->config->maxFile) ? $field->config->maxFile : '', |
| 711 | 719 | '${field.maximum_size}' => isset($field->config->maxSize) ? $field->config->maxSize : '', |
| 712 | 720 | '${field.minimum_amount}' => isset($field->config->minValue) ? $field->config->minValue : '', |
| @@ -713,8 +721,58 @@ | ||
| 713 | 721 | '${field.maximum_amount}' => isset($field->config->maxValue) ? $field->config->maxValue : '', |
| 714 | 722 | ]; |
| 715 | 723 | $msg = str_replace(array_keys($shortcodes), array_values($shortcodes), $msg); |
| 716 | 724 | return $msg; |
| 725 | + } | |
| 726 | + | |
| 727 | + /** | |
| 728 | + * Date-like min/max are stored in the input's wire format (2026-10-01T09:00, 2026-W40); show | |
| 729 | + * them to visitors in the site's date/time format instead. Anything else is returned as is. | |
| 730 | + * | |
| 731 | + * @param mixed $value field mn/mx | |
| 732 | + * @param string $type field type | |
| 733 | + * | |
| 734 | + * @return mixed | |
| 735 | + */ | |
| 736 | + public static function readableRangeValue($value, $type) | |
| 737 | + { | |
| 738 | + if (!is_string($value) || '' === $value) { | |
| 739 | + return $value; | |
| 740 | + } | |
| 741 | + $dateFormat = (string) get_option('date_format', 'F j, Y'); | |
| 742 | + $timeFormat = (string) get_option('time_format', 'g:i a'); | |
| 743 | + $utc = new \DateTimeZone('UTC'); | |
| 744 | + $formatTimestamp = function ($format, $timestamp) use ($utc) { | |
| 745 | + return function_exists('wp_date') ? wp_date($format, $timestamp, $utc) : gmdate($format, $timestamp); | |
| 746 | + }; | |
| 747 | + | |
| 748 | + switch ($type) { | |
| 749 | + case 'date': | |
| 750 | + $date = \DateTime::createFromFormat('!Y-m-d', $value, $utc); | |
| 751 | + return $date ? $formatTimestamp($dateFormat, $date->getTimestamp()) : $value; | |
| 752 | + | |
| 753 | + case 'datetime-local': | |
| 754 | + $date = \DateTime::createFromFormat('!Y-m-d\TH:i', substr($value, 0, 16), $utc); | |
| 755 | + return $date ? $formatTimestamp($dateFormat . ' ' . $timeFormat, $date->getTimestamp()) : $value; | |
| 756 | + | |
| 757 | + case 'time': | |
| 758 | + $date = \DateTime::createFromFormat('!H:i', substr($value, 0, 5), $utc); | |
| 759 | + return $date ? $formatTimestamp($timeFormat, $date->getTimestamp()) : $value; | |
| 760 | + | |
| 761 | + case 'month': | |
| 762 | + $date = \DateTime::createFromFormat('!Y-m', $value, $utc); | |
| 763 | + return $date ? $formatTimestamp('F Y', $date->getTimestamp()) : $value; | |
| 764 | + | |
| 765 | + case 'week': | |
| 766 | + if (1 === preg_match('/^(\d{4})-W(\d{1,2})$/i', $value, $match)) { | |
| 767 | + // translators: 1: ISO week number, 2: year | |
| 768 | + return sprintf(__('week %1$d of %2$d', 'bit-form'), (int) $match[2], (int) $match[1]); | |
| 769 | + } | |
| 770 | + return $value; | |
| 771 | + | |
| 772 | + default: | |
| 773 | + return $value; | |
| 774 | + } | |
| 717 | 775 | } |
| 718 | 776 | |
| 719 | 777 | public static function getDefaultGlobalMessages() |
| 720 | 778 | { |