PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.0
1.7.0 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 All 50 releases
← All changes | app/Helpers/Helper.php +90 -23 1.6.2 → 1.7.0 View file →
@@ -11,8 +11,9 @@
11 11 use FluentCart\App\Models\Customer;
12 12 use FluentCart\App\Models\ProductVariation;
13 13 use FluentCart\App\Models\User;
14 14 use FluentCart\App\Services\DateTime\DateTime;
15 +use FluentCart\App\Services\Payments\PaymentHelper;
15 16 use FluentCart\App\Services\Localization\LocalizationManager;
16 17 use FluentCart\App\Services\Translations\TransStrings;
17 18 use FluentCart\App\Services\URL;
18 19 use FluentCart\Framework\Http\URL as BaseUrl;
@@ -42,8 +43,53 @@
42 43 *
43 44 * @param array $otherInfo A variant's other_info payload
44 45 * @return string|null Error message, or null when valid
45 46 */
47 + /**
48 + * One resolver for every `photo` a FluentCart record appends: the
49 + * uploaded customer photo for the user when one is set, otherwise the
50 + * WordPress avatar by user id or email. Gravatar is asked with
51 + * default=404, so an address with no avatar of its own fails to load
52 + * and the UI falls back to its placeholder icon instead of Gravatar's
53 + * stock image. esc_url_raw throughout — the value travels through
54 + * JSON, where esc_url's entity-encoded ampersands would break the
55 + * query string.
56 + *
57 + * @param int|string|null $userId user id owning a possible uploaded photo
58 + * @param string|null $email fallback identity when there is no user
59 + * @return string the photo URL, or '' when avatars are off or the
60 + * record carries no identity to resolve one from
61 + */
62 + public static function getUserAvatarUrl($userId, $email, int $size = 100): string
63 + {
64 + if (!get_option('show_avatars')) {
65 + return '';
66 + }
67 +
68 + if ($userId) {
69 + $customPhotoUrl = esc_url_raw(
70 + (string) get_user_meta((int) $userId, 'fc_customer_photo_url', true)
71 + );
72 +
73 + if ($customPhotoUrl) {
74 + return $customPhotoUrl;
75 + }
76 + }
77 +
78 + $userIdOrEmail = ((int) $userId) ?: (string) $email;
79 +
80 + if (!$userIdOrEmail) {
81 + return '';
82 + }
83 +
84 + $url = get_avatar_url($userIdOrEmail, [
85 + 'size' => $size,
86 + 'default' => '404',
87 + ]);
88 +
89 + return $url ? esc_url_raw($url) : '';
90 + }
91 +
46 92 public static function installmentTimesError($otherInfo)
47 93 {
48 94 if (Arr::get($otherInfo, 'installment', 'no') !== 'yes') {
49 95 return null;
@@ -608,10 +654,12 @@
608 654 if (!$user) {
609 655 return null;
610 656 }
611 657
658 + // Identity only — see CustomerResource::getCurrentCustomer() for why an
659 + // email match must never reach a customer row.
612 660 return Customer::query()->where('user_id', $user->ID)
613 - ->orWhere('email', $user->user_email)
661 + ->orderBy('id', 'ASC')
614 662 ->first();
615 663 }
616 664
617 665 /**
@@ -1401,14 +1449,16 @@
1401 1449 $month = (int) Arr::get($anchor, 'month', 0);
1402 1450
1403 1451 if ($month && $day) {
1404 1452 /* translators: %s is a date, e.g. "on Aug 19" */
1405 - return sprintf(__('on %s', 'fluent-cart'), gmdate('M', gmmktime(12, 0, 0, $month, 1, 2001)) . ' ' . $day);
1453 + // wp_date(), not gmdate(): gmdate() has no locale, so this month
1454 + // name stayed English inside an otherwise translated sentence.
1455 + return sprintf(__('on %s', 'fluent-cart'), wp_date('M', gmmktime(12, 0, 0, $month, 1, 2001), new \DateTimeZone('UTC')) . ' ' . $day);
1406 1456 }
1407 1457
1408 1458 if ($month) {
1409 1459 /* translators: %s is a month name, e.g. "in August" */
1410 - return sprintf(__('in %s', 'fluent-cart'), gmdate('F', gmmktime(12, 0, 0, $month, 1, 2001)));
1460 + return sprintf(__('in %s', 'fluent-cart'), wp_date('F', gmmktime(12, 0, 0, $month, 1, 2001), new \DateTimeZone('UTC')));
1411 1461 }
1412 1462
1413 1463 /* translators: %s is an ordinal day of month, e.g. "on the 10th" */
1414 1464 return sprintf(__('on the %s', 'fluent-cart'), gmdate('jS', gmmktime(12, 0, 0, 1, $day, 2001)));
@@ -1923,26 +1973,9 @@
1923 1973 }
1924 1974
1925 1975 public static function subscriptionIntervalInDays($interval)
1926 1976 {
1927 - switch ($interval) {
1928 - case 'daily':
1929 - return 1;
1930 - case 'weekly':
1931 - return 7;
1932 - case 'monthly':
1933 - return 30;
1934 - case 'quarterly':
1935 - return 90;
1936 - case 'half_yearly':
1937 - return 182;
1938 - case 'yearly':
1939 - return 365;
1940 - default:
1941 - return apply_filters('fluent_cart/subscription_interval_in_days', 0, [
1942 - 'interval' => $interval,
1943 - ]);
1944 - }
1977 + return PaymentHelper::getIntervalDays($interval);
1945 1978 }
1946 1979
1947 1980 public static function parseTermIdsForFilter($filters): array
1948 1981 {
@@ -1997,12 +2030,12 @@
1997 2030 {
1998 2031 $allChildVariants = Arr::pluck($variants, 'other_info.bundle_child_ids');
1999 2032
2000 2033 $allChildVariants = array_unique(Arr::flatten($allChildVariants));
2001 - $allChildVariants = Arr::except(
2034 + $allChildVariants = array_values(array_diff(
2002 2035 $allChildVariants,
2003 2036 Arr::pluck($variants, 'id')
2004 - );
2037 + ));
2005 2038 $allChildVariants = array_filter($allChildVariants);
2006 2039 $childVariants = ProductVariation::query()
2007 2040 ->whereIn('id', $allChildVariants)
2008 2041 ->with('product:ID,post_title')
@@ -2123,6 +2156,40 @@
2123 2156 }
2124 2157 return (int) $order->tax_behavior === 2
2125 2158 ? esc_html__('(Included)', 'fluent-cart')
2126 2159 : esc_html__('(Excluded)', 'fluent-cart');
2160 + }
2161 +
2162 + /**
2163 + * Product placeholder illustration (an open box holding a picture) shown
2164 + * wherever a product has no image. The web copy is a 1200px WebP (sharp on 2x screens); email
2165 + * clients (Outlook) and PDF renderers get a 300px PNG of the same art.
2166 + */
2167 + const PRODUCT_PLACEHOLDER_IMAGE = 'images/product-placeholder.webp';
2168 + const PRODUCT_PLACEHOLDER_EMAIL_IMAGE = 'images/product-placeholder.png';
2169 +
2170 + public static function getProductPlaceholderUrl(): string
2171 + {
2172 + return \FluentCart\App\Vite::getAssetUrl(static::PRODUCT_PLACEHOLDER_IMAGE);
2173 + }
2174 +
2175 + public static function getProductPlaceholderEmailUrl(): string
2176 + {
2177 + return \FluentCart\App\Vite::getAssetUrl(static::PRODUCT_PLACEHOLDER_EMAIL_IMAGE);
2178 + }
2179 +
2180 + /**
2181 + * A product image URL, or the placeholder when there is none. Carts store
2182 + * the thumbnail URL in cart_data, so carts built before the illustration
2183 + * still carry the old generic icon (images/placeholder.svg); map that too.
2184 + */
2185 + public static function productImageOrPlaceholder($url): string
2186 + {
2187 + $url = is_string($url) ? $url : '';
2188 +
2189 + if ($url === '' || preg_match('#/images/placeholder\.svg(\?.*)?$#', $url)) {
2190 + return static::getProductPlaceholderUrl();
2191 + }
2192 +
2193 + return $url;
2127 2194 }
2128 2195 }