Completed
Branch FET/allow-prices-to-be-more-pr... (276f1f)
by
unknown
18:34 queued 15:33
created
core/services/formatters/CurrencyFormatter.php 1 patch
Indentation   +334 added lines, -334 removed lines patch added patch discarded remove patch
@@ -8,338 +8,338 @@
 block discarded – undo
8 8
 
9 9
 class CurrencyFormatter extends LocaleFloatFormatter
10 10
 {
11
-    /**
12
-     * non-localized number no symbol or code: '123456.123456'
13
-     */
14
-    const FORMAT_PRECISION_FLOAT = 0;
15
-
16
-    /**
17
-     * localized number no symbol or code: '123,456.12'
18
-     */
19
-    const FORMAT_LOCALIZED_FLOAT = 1;
20
-
21
-    /**
22
-     * localized number with currency symbol: '$123,456.12'
23
-     */
24
-    const FORMAT_LOCALIZED_CURRENCY = 2;
25
-
26
-    /**
27
-     * localized number with currency symbol and code: '$123,456.12 USD'
28
-     */
29
-    const FORMAT_LOCALIZED_CURRENCY_RAW_CODE = 3;
30
-
31
-    /**
32
-     * localized number with currency symbol and code wrapped in span: '$123,456.12 <span>USD</span>'
33
-     */
34
-    const FORMAT_LOCALIZED_CURRENCY_HTML_CODE = 4;
35
-
36
-    /**
37
-     * @var EE_Currency_Config
38
-     */
39
-    protected $currency_config;
40
-
41
-
42
-    /**
43
-     * LocaleFloatFormatter constructor.
44
-     *
45
-     * @param EE_Currency_Config $currency_config
46
-     * @param Locales            $locales
47
-     */
48
-    public function __construct(EE_Currency_Config $currency_config, Locales $locales)
49
-    {
50
-        $this->currency_config = $currency_config;
51
-        parent::__construct($locales);
52
-    }
53
-
54
-
55
-    /**
56
-     * formats the provided amount for the selected locale (defaults to site locale) and returns a string
57
-     *
58
-     * @param Locale   $locale
59
-     * @param float    $amount    unformatted number value, ex: 1234.56789
60
-     * @param int      $format    one of the CurrencyFormatter::FORMAT_* constants
61
-     * @param int|null $precision the number of decimal places to round to
62
-     * @return string             formatted amount, ex: '1,234.57'
63
-     */
64
-    protected function format(
65
-        Locale $locale,
66
-        float $amount,
67
-        int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
68
-        ?int $precision = null
69
-    ): string {
70
-        // if a specific decimal precision has been requested then use that, otherwise set it for the locale
71
-        $precision = $precision !== null ? absint($precision) : $locale->decimalPrecision();
72
-        // BUT... if a specific decimal precision has been requested with no extra locale formatting
73
-        // then bump the precision up to our max internal value
74
-        $precision = $format === CurrencyFormatter::FORMAT_PRECISION_FLOAT && $precision === $locale->decimalPrecision()
75
-            ? LocaleFloatFormatter::DECIMAL_PRECISION
76
-            : $precision;
77
-
78
-        // if only a float is requested then just return now
79
-        if ($format < CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY) {
80
-            return $format === CurrencyFormatter::FORMAT_PRECISION_FLOAT
81
-                ? $this->precisionRound($amount)
82
-                : $this->roundForLocale($amount);
83
-        }
84
-        // inserts symbols for the locale's decimal and thousands separator at the appropriate places
85
-        $formatted_amount = $this->formatGroupings(
86
-            $amount,
87
-            absint($precision),
88
-            $locale->currencyDecimalPoint(),
89
-            $locale->currencyGrouping(),
90
-            $locale->currencyThousandsSeparator()
91
-        );
92
-
93
-        // inserts the locale's currency symbol, negative sign, and spaces at the appropriate places
94
-        $formatted_amount = $this->formatSymbolAndSignPositions(
95
-            $locale,
96
-            $formatted_amount,
97
-            $amount < 0, // negative
98
-            $locale->currencySymbol()
99
-        );
100
-        return $this->appendCurrencyIsoCode($locale, $formatted_amount, $format);
101
-    }
102
-
103
-
104
-    /**
105
-     * @param Locale $locale
106
-     * @param string $amount
107
-     * @param int    $format one of the CurrencyFormatter::FORMAT_* constants
108
-     * @return string        fully formatted amount with ISO code, ex: '$ 1,234.57 USD'
109
-     */
110
-    protected function appendCurrencyIsoCode(Locale $locale, string $amount, int $format): string
111
-    {
112
-        switch ($format) {
113
-            case CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_RAW_CODE:
114
-                $iso_code = "&nbsp;{$locale->currencyIsoCode()}";
115
-                break;
116
-            case CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE:
117
-                $iso_code = "&nbsp;<span class=\"currency-code\">({$locale->currencyIsoCode()})</span>";
118
-                break;
119
-            default:
120
-                $iso_code = '';
121
-        }
122
-        // filter to allow global setting of display_code
123
-        $display_code = apply_filters(
124
-            'FHEE__EEH_Template__format_currency__display_code',
125
-            $format > CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY
126
-        );
127
-        $iso_code     = $display_code
128
-            ? $iso_code
129
-            : '';
130
-        return "{$amount}{$iso_code}";
131
-    }
132
-
133
-
134
-    /**
135
-     * inserts the locale's currency symbol, negative sign, and spaces at the appropriate places
136
-     *
137
-     * @param Locale $locale
138
-     * @param string $number
139
-     * @param bool   $is_negative
140
-     * @param string $currency_symbol
141
-     * @return string partially formatted amount, ex: '$ 1,234.57'
142
-     */
143
-    protected function formatSymbolAndSignPositions(
144
-        Locale $locale,
145
-        string $number,
146
-        bool $is_negative,
147
-        string $currency_symbol
148
-    ): string {
149
-        // format for positive or negative values
150
-        if ($is_negative) {
151
-            $add_spacer  = $locale->currencySymbolSpaceB4Negative();
152
-            $currency_b4 = $locale->currencySymbolB4Negative();
153
-            $position    = $locale->negativeSignPosition();
154
-            $sign        = $locale->negativeSign();
155
-        } else {
156
-            $add_spacer  = $locale->currencySymbolSpaceB4Positive();
157
-            $currency_b4 = $locale->currencySymbolB4Positive();
158
-            $position    = $locale->positiveSignPosition();
159
-            $sign        = $locale->positiveSign();
160
-        }
161
-        $spacer = $add_spacer
162
-            ? '&nbsp;'
163
-            : '';
164
-        switch ($position) {
165
-            case LocaleFloatFormatter::PARENTHESES:
166
-                return $currency_b4
167
-                    ? "({$currency_symbol}{$spacer}{$number})"
168
-                    : "({$number}{$spacer}{$currency_symbol})";
169
-            case LocaleFloatFormatter::SIGN_BEFORE_ALL:
170
-                return $currency_b4
171
-                    ? "{$sign}{$currency_symbol}{$spacer}{$number}"
172
-                    : "{$sign}{$number}{$spacer}{$currency_symbol}";
173
-            case LocaleFloatFormatter::SIGN_AFTER_ALL:
174
-                return $currency_b4
175
-                    ? "{$currency_symbol}{$spacer}{$number} {$sign}"
176
-                    : "{$number}{$spacer}{$currency_symbol} {$sign}";
177
-            case LocaleFloatFormatter::SIGN_BEFORE_CURRENCY:
178
-                return $currency_b4
179
-                    ? "{$sign}{$currency_symbol}{$spacer}{$number}"
180
-                    : "{$number}{$spacer}{$sign}{$currency_symbol}";
181
-            case LocaleFloatFormatter::SIGN_AFTER_CURRENCY:
182
-                return $currency_b4
183
-                    ? "{$currency_symbol}{$sign}{$spacer}{$number}"
184
-                    : "{$number}{$spacer}{$currency_symbol}{$sign}";
185
-        }
186
-        return $number;
187
-    }
188
-
189
-
190
-    /**
191
-     * formats the provided number for the selected locale (defaults to site locale) and returns a string
192
-     *
193
-     * @param float  $number       unformatted number value, ex: 1234.56789
194
-     * @param string $currency_ISO ex: "USD"
195
-     * @param int    $format       one of the CurrencyFormatter::FORMAT_* constants
196
-     * @return string              formatted value, ex: '1,234.57'
197
-     */
198
-    public function formatForCurrencyISO(
199
-        float $number,
200
-        string $currency_ISO,
201
-        int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY
202
-    ): string {
203
-        $locale = $this->getLocaleForCurrencyISO($currency_ISO);
204
-        return $this->format($locale, $number, $format);
205
-    }
206
-
207
-
208
-    /**
209
-     * formats the provided number for the selected locale (defaults to site locale) and returns a string
210
-     *
211
-     * @param float|int|string   $number unformatted number value, ex: 1234.56789
212
-     * @param int|null           $format one of the CurrencyFormatter::FORMAT_* constants
213
-     * @param int|null           $precision the number of decimal places to round to
214
-     * @param string|Locale|null $locale ex: "en_US" or Locale object
215
-     * @return string            formatted value, ex: '1,234.57'
216
-     */
217
-    public function formatForLocale(
218
-        $number,
219
-        ?int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
220
-        ?int $precision = null,
221
-        $locale = ''
222
-    ): string {
223
-        $locale = $this->getLocale($locale);
224
-        return $this->format($locale, (float) $number, $format, $precision);
225
-    }
226
-
227
-
228
-    /**
229
-     * @param string|Locale $locale locale name ex: en_US or Locale object
230
-     * @return string ex: 'USD'
231
-     */
232
-    public function getCurrencyIsoCodeForLocale($locale = ''): string
233
-    {
234
-        $locale = $this->getLocale($locale);
235
-        return $locale->currencyIsoCode();
236
-    }
237
-
238
-
239
-    /**
240
-     * @param string|Locale $locale locale name ex: en_US or Locale object
241
-     * @return string ex: '$'
242
-     */
243
-    public function getCurrencySymbolForLocale($locale = ''): string
244
-    {
245
-        $locale = $this->getLocale($locale);
246
-        return $locale->currencySymbol();
247
-    }
248
-
249
-
250
-    /**
251
-     * Schemas:
252
-     *    'precision_float': "1,234.567890"
253
-     *    'localized_float': "1,234.57"
254
-     *    'no_currency_code': "$1,234.57"
255
-     *    null: "$1,234.57<span>USD</span>"
256
-     *
257
-     * @param string $schema
258
-     * @param bool|null   $allow_fractional_subunits
259
-     * @return int
260
-     */
261
-    public function getFormatFromLegacySchema(string $schema, ?bool $allow_fractional_subunits = true): int
262
-    {
263
-        switch ($schema) {
264
-            case 'precision_float':
265
-                // return a localized float if fractional subunits are not allowed
266
-                return $allow_fractional_subunits
267
-                    ? CurrencyFormatter::FORMAT_PRECISION_FLOAT
268
-                    : CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
269
-            case 'localized_float':
270
-                return CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
271
-            case 'localized':
272
-            case 'localized_currency':
273
-            case 'no_currency_code':
274
-                return CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY;
275
-            default:
276
-                return CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE;
277
-        }
278
-    }
279
-
280
-
281
-    /**
282
-     * @param string|Locale $locale locale name ex: en_US or Locale object
283
-     * @return Locale
284
-     */
285
-    public function getLocale($locale = ''): Locale
286
-    {
287
-        $locale = $locale ?: $this->currency_config->locale();
288
-        return $this->locales->getLocale($locale);
289
-    }
290
-
291
-
292
-    /**
293
-     * @param string $currency_ISO              ex: USD
294
-     * @param bool   $fallback_to_site_locale   [optional] if true, will return the site locale
295
-     *                                          if a locale can not be identified for the supplied currency ISO
296
-     * @return Locale
297
-     */
298
-    public function getLocaleForCurrencyISO(string $currency_ISO, bool $fallback_to_site_locale = false): Locale
299
-    {
300
-        return $this->locales->getLocaleForCurrencyISO($currency_ISO, $fallback_to_site_locale);
301
-    }
302
-
303
-
304
-    /**
305
-     * @return Locale
306
-     */
307
-    public function getSiteLocale(): Locale
308
-    {
309
-        return $this->locales->getSiteLocale();
310
-    }
311
-
312
-
313
-    /**
314
-     * This removes all localized formatting from the incoming value and returns a float
315
-     *
316
-     * @param float|int|string $number formatted numeric value as string, ex: '1,234,567.89'
317
-     * @param string|Locale    $locale locale name ex: en_US or Locale object
318
-     * @return float                   unformatted number value, ex: 1234567.89
319
-     */
320
-    public function parseForLocale($number, $locale = ''): float
321
-    {
322
-        // just return the value if it's not a string
323
-        if (! is_string($number)) {
324
-            return (float) $number;
325
-        }
326
-        $locale = $this->getLocale($locale);
327
-        return $this->filterNumericValue(
328
-            str_replace(
329
-                [
330
-                    $locale->currencyIsoCode(),
331
-                    $locale->currencySymbol(),
332
-                    $locale->currencyThousandsSeparator(),
333
-                    $locale->currencyDecimalPoint(),
334
-                ],
335
-                [
336
-                    '',  // remove currency code
337
-                    '',  // remove currency symbol
338
-                    '',  // remove thousands separator
339
-                    '.', // convert decimal mark to what PHP expects
340
-                ],
341
-                $number
342
-            )
343
-        );
344
-    }
11
+	/**
12
+	 * non-localized number no symbol or code: '123456.123456'
13
+	 */
14
+	const FORMAT_PRECISION_FLOAT = 0;
15
+
16
+	/**
17
+	 * localized number no symbol or code: '123,456.12'
18
+	 */
19
+	const FORMAT_LOCALIZED_FLOAT = 1;
20
+
21
+	/**
22
+	 * localized number with currency symbol: '$123,456.12'
23
+	 */
24
+	const FORMAT_LOCALIZED_CURRENCY = 2;
25
+
26
+	/**
27
+	 * localized number with currency symbol and code: '$123,456.12 USD'
28
+	 */
29
+	const FORMAT_LOCALIZED_CURRENCY_RAW_CODE = 3;
30
+
31
+	/**
32
+	 * localized number with currency symbol and code wrapped in span: '$123,456.12 <span>USD</span>'
33
+	 */
34
+	const FORMAT_LOCALIZED_CURRENCY_HTML_CODE = 4;
35
+
36
+	/**
37
+	 * @var EE_Currency_Config
38
+	 */
39
+	protected $currency_config;
40
+
41
+
42
+	/**
43
+	 * LocaleFloatFormatter constructor.
44
+	 *
45
+	 * @param EE_Currency_Config $currency_config
46
+	 * @param Locales            $locales
47
+	 */
48
+	public function __construct(EE_Currency_Config $currency_config, Locales $locales)
49
+	{
50
+		$this->currency_config = $currency_config;
51
+		parent::__construct($locales);
52
+	}
53
+
54
+
55
+	/**
56
+	 * formats the provided amount for the selected locale (defaults to site locale) and returns a string
57
+	 *
58
+	 * @param Locale   $locale
59
+	 * @param float    $amount    unformatted number value, ex: 1234.56789
60
+	 * @param int      $format    one of the CurrencyFormatter::FORMAT_* constants
61
+	 * @param int|null $precision the number of decimal places to round to
62
+	 * @return string             formatted amount, ex: '1,234.57'
63
+	 */
64
+	protected function format(
65
+		Locale $locale,
66
+		float $amount,
67
+		int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
68
+		?int $precision = null
69
+	): string {
70
+		// if a specific decimal precision has been requested then use that, otherwise set it for the locale
71
+		$precision = $precision !== null ? absint($precision) : $locale->decimalPrecision();
72
+		// BUT... if a specific decimal precision has been requested with no extra locale formatting
73
+		// then bump the precision up to our max internal value
74
+		$precision = $format === CurrencyFormatter::FORMAT_PRECISION_FLOAT && $precision === $locale->decimalPrecision()
75
+			? LocaleFloatFormatter::DECIMAL_PRECISION
76
+			: $precision;
77
+
78
+		// if only a float is requested then just return now
79
+		if ($format < CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY) {
80
+			return $format === CurrencyFormatter::FORMAT_PRECISION_FLOAT
81
+				? $this->precisionRound($amount)
82
+				: $this->roundForLocale($amount);
83
+		}
84
+		// inserts symbols for the locale's decimal and thousands separator at the appropriate places
85
+		$formatted_amount = $this->formatGroupings(
86
+			$amount,
87
+			absint($precision),
88
+			$locale->currencyDecimalPoint(),
89
+			$locale->currencyGrouping(),
90
+			$locale->currencyThousandsSeparator()
91
+		);
92
+
93
+		// inserts the locale's currency symbol, negative sign, and spaces at the appropriate places
94
+		$formatted_amount = $this->formatSymbolAndSignPositions(
95
+			$locale,
96
+			$formatted_amount,
97
+			$amount < 0, // negative
98
+			$locale->currencySymbol()
99
+		);
100
+		return $this->appendCurrencyIsoCode($locale, $formatted_amount, $format);
101
+	}
102
+
103
+
104
+	/**
105
+	 * @param Locale $locale
106
+	 * @param string $amount
107
+	 * @param int    $format one of the CurrencyFormatter::FORMAT_* constants
108
+	 * @return string        fully formatted amount with ISO code, ex: '$ 1,234.57 USD'
109
+	 */
110
+	protected function appendCurrencyIsoCode(Locale $locale, string $amount, int $format): string
111
+	{
112
+		switch ($format) {
113
+			case CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_RAW_CODE:
114
+				$iso_code = "&nbsp;{$locale->currencyIsoCode()}";
115
+				break;
116
+			case CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE:
117
+				$iso_code = "&nbsp;<span class=\"currency-code\">({$locale->currencyIsoCode()})</span>";
118
+				break;
119
+			default:
120
+				$iso_code = '';
121
+		}
122
+		// filter to allow global setting of display_code
123
+		$display_code = apply_filters(
124
+			'FHEE__EEH_Template__format_currency__display_code',
125
+			$format > CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY
126
+		);
127
+		$iso_code     = $display_code
128
+			? $iso_code
129
+			: '';
130
+		return "{$amount}{$iso_code}";
131
+	}
132
+
133
+
134
+	/**
135
+	 * inserts the locale's currency symbol, negative sign, and spaces at the appropriate places
136
+	 *
137
+	 * @param Locale $locale
138
+	 * @param string $number
139
+	 * @param bool   $is_negative
140
+	 * @param string $currency_symbol
141
+	 * @return string partially formatted amount, ex: '$ 1,234.57'
142
+	 */
143
+	protected function formatSymbolAndSignPositions(
144
+		Locale $locale,
145
+		string $number,
146
+		bool $is_negative,
147
+		string $currency_symbol
148
+	): string {
149
+		// format for positive or negative values
150
+		if ($is_negative) {
151
+			$add_spacer  = $locale->currencySymbolSpaceB4Negative();
152
+			$currency_b4 = $locale->currencySymbolB4Negative();
153
+			$position    = $locale->negativeSignPosition();
154
+			$sign        = $locale->negativeSign();
155
+		} else {
156
+			$add_spacer  = $locale->currencySymbolSpaceB4Positive();
157
+			$currency_b4 = $locale->currencySymbolB4Positive();
158
+			$position    = $locale->positiveSignPosition();
159
+			$sign        = $locale->positiveSign();
160
+		}
161
+		$spacer = $add_spacer
162
+			? '&nbsp;'
163
+			: '';
164
+		switch ($position) {
165
+			case LocaleFloatFormatter::PARENTHESES:
166
+				return $currency_b4
167
+					? "({$currency_symbol}{$spacer}{$number})"
168
+					: "({$number}{$spacer}{$currency_symbol})";
169
+			case LocaleFloatFormatter::SIGN_BEFORE_ALL:
170
+				return $currency_b4
171
+					? "{$sign}{$currency_symbol}{$spacer}{$number}"
172
+					: "{$sign}{$number}{$spacer}{$currency_symbol}";
173
+			case LocaleFloatFormatter::SIGN_AFTER_ALL:
174
+				return $currency_b4
175
+					? "{$currency_symbol}{$spacer}{$number} {$sign}"
176
+					: "{$number}{$spacer}{$currency_symbol} {$sign}";
177
+			case LocaleFloatFormatter::SIGN_BEFORE_CURRENCY:
178
+				return $currency_b4
179
+					? "{$sign}{$currency_symbol}{$spacer}{$number}"
180
+					: "{$number}{$spacer}{$sign}{$currency_symbol}";
181
+			case LocaleFloatFormatter::SIGN_AFTER_CURRENCY:
182
+				return $currency_b4
183
+					? "{$currency_symbol}{$sign}{$spacer}{$number}"
184
+					: "{$number}{$spacer}{$currency_symbol}{$sign}";
185
+		}
186
+		return $number;
187
+	}
188
+
189
+
190
+	/**
191
+	 * formats the provided number for the selected locale (defaults to site locale) and returns a string
192
+	 *
193
+	 * @param float  $number       unformatted number value, ex: 1234.56789
194
+	 * @param string $currency_ISO ex: "USD"
195
+	 * @param int    $format       one of the CurrencyFormatter::FORMAT_* constants
196
+	 * @return string              formatted value, ex: '1,234.57'
197
+	 */
198
+	public function formatForCurrencyISO(
199
+		float $number,
200
+		string $currency_ISO,
201
+		int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY
202
+	): string {
203
+		$locale = $this->getLocaleForCurrencyISO($currency_ISO);
204
+		return $this->format($locale, $number, $format);
205
+	}
206
+
207
+
208
+	/**
209
+	 * formats the provided number for the selected locale (defaults to site locale) and returns a string
210
+	 *
211
+	 * @param float|int|string   $number unformatted number value, ex: 1234.56789
212
+	 * @param int|null           $format one of the CurrencyFormatter::FORMAT_* constants
213
+	 * @param int|null           $precision the number of decimal places to round to
214
+	 * @param string|Locale|null $locale ex: "en_US" or Locale object
215
+	 * @return string            formatted value, ex: '1,234.57'
216
+	 */
217
+	public function formatForLocale(
218
+		$number,
219
+		?int $format = CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY,
220
+		?int $precision = null,
221
+		$locale = ''
222
+	): string {
223
+		$locale = $this->getLocale($locale);
224
+		return $this->format($locale, (float) $number, $format, $precision);
225
+	}
226
+
227
+
228
+	/**
229
+	 * @param string|Locale $locale locale name ex: en_US or Locale object
230
+	 * @return string ex: 'USD'
231
+	 */
232
+	public function getCurrencyIsoCodeForLocale($locale = ''): string
233
+	{
234
+		$locale = $this->getLocale($locale);
235
+		return $locale->currencyIsoCode();
236
+	}
237
+
238
+
239
+	/**
240
+	 * @param string|Locale $locale locale name ex: en_US or Locale object
241
+	 * @return string ex: '$'
242
+	 */
243
+	public function getCurrencySymbolForLocale($locale = ''): string
244
+	{
245
+		$locale = $this->getLocale($locale);
246
+		return $locale->currencySymbol();
247
+	}
248
+
249
+
250
+	/**
251
+	 * Schemas:
252
+	 *    'precision_float': "1,234.567890"
253
+	 *    'localized_float': "1,234.57"
254
+	 *    'no_currency_code': "$1,234.57"
255
+	 *    null: "$1,234.57<span>USD</span>"
256
+	 *
257
+	 * @param string $schema
258
+	 * @param bool|null   $allow_fractional_subunits
259
+	 * @return int
260
+	 */
261
+	public function getFormatFromLegacySchema(string $schema, ?bool $allow_fractional_subunits = true): int
262
+	{
263
+		switch ($schema) {
264
+			case 'precision_float':
265
+				// return a localized float if fractional subunits are not allowed
266
+				return $allow_fractional_subunits
267
+					? CurrencyFormatter::FORMAT_PRECISION_FLOAT
268
+					: CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
269
+			case 'localized_float':
270
+				return CurrencyFormatter::FORMAT_LOCALIZED_FLOAT;
271
+			case 'localized':
272
+			case 'localized_currency':
273
+			case 'no_currency_code':
274
+				return CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY;
275
+			default:
276
+				return CurrencyFormatter::FORMAT_LOCALIZED_CURRENCY_HTML_CODE;
277
+		}
278
+	}
279
+
280
+
281
+	/**
282
+	 * @param string|Locale $locale locale name ex: en_US or Locale object
283
+	 * @return Locale
284
+	 */
285
+	public function getLocale($locale = ''): Locale
286
+	{
287
+		$locale = $locale ?: $this->currency_config->locale();
288
+		return $this->locales->getLocale($locale);
289
+	}
290
+
291
+
292
+	/**
293
+	 * @param string $currency_ISO              ex: USD
294
+	 * @param bool   $fallback_to_site_locale   [optional] if true, will return the site locale
295
+	 *                                          if a locale can not be identified for the supplied currency ISO
296
+	 * @return Locale
297
+	 */
298
+	public function getLocaleForCurrencyISO(string $currency_ISO, bool $fallback_to_site_locale = false): Locale
299
+	{
300
+		return $this->locales->getLocaleForCurrencyISO($currency_ISO, $fallback_to_site_locale);
301
+	}
302
+
303
+
304
+	/**
305
+	 * @return Locale
306
+	 */
307
+	public function getSiteLocale(): Locale
308
+	{
309
+		return $this->locales->getSiteLocale();
310
+	}
311
+
312
+
313
+	/**
314
+	 * This removes all localized formatting from the incoming value and returns a float
315
+	 *
316
+	 * @param float|int|string $number formatted numeric value as string, ex: '1,234,567.89'
317
+	 * @param string|Locale    $locale locale name ex: en_US or Locale object
318
+	 * @return float                   unformatted number value, ex: 1234567.89
319
+	 */
320
+	public function parseForLocale($number, $locale = ''): float
321
+	{
322
+		// just return the value if it's not a string
323
+		if (! is_string($number)) {
324
+			return (float) $number;
325
+		}
326
+		$locale = $this->getLocale($locale);
327
+		return $this->filterNumericValue(
328
+			str_replace(
329
+				[
330
+					$locale->currencyIsoCode(),
331
+					$locale->currencySymbol(),
332
+					$locale->currencyThousandsSeparator(),
333
+					$locale->currencyDecimalPoint(),
334
+				],
335
+				[
336
+					'',  // remove currency code
337
+					'',  // remove currency symbol
338
+					'',  // remove thousands separator
339
+					'.', // convert decimal mark to what PHP expects
340
+				],
341
+				$number
342
+			)
343
+		);
344
+	}
345 345
 }
Please login to merge, or discard this patch.
core/services/formatters/LocaleFloatFormatter.php 1 patch
Indentation   +151 added lines, -151 removed lines patch added patch discarded remove patch
@@ -15,159 +15,159 @@
 block discarded – undo
15 15
  */
16 16
 abstract class LocaleFloatFormatter implements LocaleFloatFormatterInterface, InterminableInterface
17 17
 {
18
-    /**
19
-     * number of decimal places used for high precision internal calculations and storage
20
-     */
21
-    const DECIMAL_PRECISION = 6;
18
+	/**
19
+	 * number of decimal places used for high precision internal calculations and storage
20
+	 */
21
+	const DECIMAL_PRECISION = 6;
22 22
 
23
-    /*
23
+	/*
24 24
      * the following constants represent the values returned for 'n_sign_posn' && 'p_sign_posn'
25 25
      */
26 26
 
27
-    /**
28
-     * 0 - Parentheses surround the quantity and currency_symbol
29
-     */
30
-    const PARENTHESES = 0;
31
-
32
-    /**
33
-     * 1 - The sign string precedes the quantity and currency_symbol
34
-     */
35
-    const SIGN_BEFORE_ALL = 1;
36
-
37
-    /**
38
-     * 2 - The sign string follows the quantity and currency_symbol
39
-     */
40
-    const SIGN_AFTER_ALL = 2;
41
-
42
-    /**
43
-     * 3 - The sign string immediately precedes the currency_symbol
44
-     */
45
-    const SIGN_BEFORE_CURRENCY = 3;
46
-
47
-    /**
48
-     * 4 - The sign string immediately follows the currency_symbol
49
-     */
50
-    const SIGN_AFTER_CURRENCY = 4;
51
-
52
-    /**
53
-     * @var Locales
54
-     */
55
-    protected $locales;
56
-
57
-
58
-    /**
59
-     * LocaleFloatFormatter constructor.
60
-     *
61
-     * @param Locales $locales
62
-     */
63
-    public function __construct(Locales $locales)
64
-    {
65
-        $this->locales = $locales;
66
-    }
67
-
68
-
69
-    /**
70
-     * inserts symbols for the locale's decimal and thousands separator at the appropriate places
71
-     *
72
-     * @param float  $number
73
-     * @param int    $precision
74
-     * @param string $decimal_point
75
-     * @param int    $grouping
76
-     * @param string $thousands_separator
77
-     * @return string
78
-     */
79
-    protected function formatGroupings(
80
-        float $number,
81
-        int $precision,
82
-        string $decimal_point,
83
-        int $grouping,
84
-        string $thousands_separator
85
-    ): string {
86
-        // remove sign (+-), cast to string, then break apart at the decimal place
87
-        $parts = explode('.', (string) abs($number));
88
-        // separate the integer and decimal portions of the number into separate variables
89
-        [$integer, $decimal] = $parts + [0, 0];
90
-        // ok this gets a bit crazy, but we need to insert the locale's thousand separator
91
-        // at the correct intervals for the locale, so 123456789 can be something like "123,456,879" or "1.23.45.67.89"
92
-        // so we're first going to reverse the string, then use chunk_split() to give us something like "987,654,321"
93
-        // why reverse the number first? cuz otherwise something like "1234" would become "123,4" not "1,234"
94
-        $formatted_number = chunk_split(strrev($integer), $grouping, $thousands_separator);
95
-        // so THEN we reverse the string again and remove any extra separators
96
-        $formatted_number = ltrim(strrev($formatted_number), $thousands_separator);
97
-        if ($precision === 0) {
98
-            return $formatted_number;
99
-        }
100
-        // now let's deal with the decimal places, by first adding a decimal to an otherwise non-decimal number
101
-        $decimal = "0.$decimal";
102
-        // then type cast the string to a float and round to the appropriate precision for the locale
103
-        $decimal = round((float) $decimal, $precision);
104
-        // now type cast back to a string, and remove the first two characters ( the "0." added earlier )
105
-        $decimal = substr((string) $decimal, 2, $precision);
106
-        // now add any extra zeros to the correct precision
107
-        $decimal = str_pad($decimal, $precision, '0');
108
-        // the final fully formatted result is as simple as stringing it all together
109
-        return $formatted_number . $decimal_point . $decimal;
110
-    }
111
-
112
-
113
-    /**
114
-     * Removes all characters except digits, +- and .
115
-     *
116
-     * @param float|int|string $number
117
-     * @return float
118
-     */
119
-    public function filterNumericValue($number): float
120
-    {
121
-        return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
122
-    }
123
-
124
-
125
-    /**
126
-     * formats the provided number to 6 decimal places using the site locale and returns a string
127
-     *
128
-     * @param float|int|string $number unformatted float, ex: 1.23456789
129
-     * @return string                  formatted number value, ex: '1.234568'
130
-     */
131
-    public function precisionFormat($number): string
132
-    {
133
-        $locale = $this->locales->getLocale($this->locales->getSiteLocaleName());
134
-        return $this->format($locale, $number, LocaleFloatFormatter::DECIMAL_PRECISION);
135
-    }
136
-
137
-
138
-    /**
139
-     * strips formatting using the site locale, then rounds the provided number to 6 decimal places and returns a float
140
-     *
141
-     * @param float|int|string $number    unformatted number value, ex: 1234.5678956789
142
-     * @param int|null         $precision the number of decimal places to round to
143
-     * @param int              $mode      one of the PHP_ROUND_* constants for round up, round down, etc
144
-     * @return float                      rounded value, ex: 1,234.567896
145
-     */
146
-    public function precisionRound(
147
-        $number,
148
-        ?int $precision = LocaleFloatFormatter::DECIMAL_PRECISION,
149
-        int $mode = PHP_ROUND_HALF_UP
150
-    ): float {
151
-        return round(
152
-            $this->filterNumericValue($number),
153
-            $precision,
154
-            $mode
155
-        );
156
-    }
157
-
158
-
159
-    /**
160
-     * strips formatting for the provided locale (defaults to site locale),
161
-     * then rounds the provided number and returns a float
162
-     *
163
-     * @param float|int|string $number unformatted number value, ex: 1234.56789
164
-     * @param string|Locale    $locale ex: 'en_US' or Locale object
165
-     * @param int              $mode   one of the PHP_ROUND_* constants for round up, round down, etc
166
-     * @return float                   rounded value, ex: 1,234.57
167
-     */
168
-    public function roundForLocale($number, $locale = '', int $mode = PHP_ROUND_HALF_UP): float
169
-    {
170
-        $locale = $this->locales->getLocale($locale);
171
-        return round($this->filterNumericValue($number), $locale->decimalPrecision(), $mode);
172
-    }
27
+	/**
28
+	 * 0 - Parentheses surround the quantity and currency_symbol
29
+	 */
30
+	const PARENTHESES = 0;
31
+
32
+	/**
33
+	 * 1 - The sign string precedes the quantity and currency_symbol
34
+	 */
35
+	const SIGN_BEFORE_ALL = 1;
36
+
37
+	/**
38
+	 * 2 - The sign string follows the quantity and currency_symbol
39
+	 */
40
+	const SIGN_AFTER_ALL = 2;
41
+
42
+	/**
43
+	 * 3 - The sign string immediately precedes the currency_symbol
44
+	 */
45
+	const SIGN_BEFORE_CURRENCY = 3;
46
+
47
+	/**
48
+	 * 4 - The sign string immediately follows the currency_symbol
49
+	 */
50
+	const SIGN_AFTER_CURRENCY = 4;
51
+
52
+	/**
53
+	 * @var Locales
54
+	 */
55
+	protected $locales;
56
+
57
+
58
+	/**
59
+	 * LocaleFloatFormatter constructor.
60
+	 *
61
+	 * @param Locales $locales
62
+	 */
63
+	public function __construct(Locales $locales)
64
+	{
65
+		$this->locales = $locales;
66
+	}
67
+
68
+
69
+	/**
70
+	 * inserts symbols for the locale's decimal and thousands separator at the appropriate places
71
+	 *
72
+	 * @param float  $number
73
+	 * @param int    $precision
74
+	 * @param string $decimal_point
75
+	 * @param int    $grouping
76
+	 * @param string $thousands_separator
77
+	 * @return string
78
+	 */
79
+	protected function formatGroupings(
80
+		float $number,
81
+		int $precision,
82
+		string $decimal_point,
83
+		int $grouping,
84
+		string $thousands_separator
85
+	): string {
86
+		// remove sign (+-), cast to string, then break apart at the decimal place
87
+		$parts = explode('.', (string) abs($number));
88
+		// separate the integer and decimal portions of the number into separate variables
89
+		[$integer, $decimal] = $parts + [0, 0];
90
+		// ok this gets a bit crazy, but we need to insert the locale's thousand separator
91
+		// at the correct intervals for the locale, so 123456789 can be something like "123,456,879" or "1.23.45.67.89"
92
+		// so we're first going to reverse the string, then use chunk_split() to give us something like "987,654,321"
93
+		// why reverse the number first? cuz otherwise something like "1234" would become "123,4" not "1,234"
94
+		$formatted_number = chunk_split(strrev($integer), $grouping, $thousands_separator);
95
+		// so THEN we reverse the string again and remove any extra separators
96
+		$formatted_number = ltrim(strrev($formatted_number), $thousands_separator);
97
+		if ($precision === 0) {
98
+			return $formatted_number;
99
+		}
100
+		// now let's deal with the decimal places, by first adding a decimal to an otherwise non-decimal number
101
+		$decimal = "0.$decimal";
102
+		// then type cast the string to a float and round to the appropriate precision for the locale
103
+		$decimal = round((float) $decimal, $precision);
104
+		// now type cast back to a string, and remove the first two characters ( the "0." added earlier )
105
+		$decimal = substr((string) $decimal, 2, $precision);
106
+		// now add any extra zeros to the correct precision
107
+		$decimal = str_pad($decimal, $precision, '0');
108
+		// the final fully formatted result is as simple as stringing it all together
109
+		return $formatted_number . $decimal_point . $decimal;
110
+	}
111
+
112
+
113
+	/**
114
+	 * Removes all characters except digits, +- and .
115
+	 *
116
+	 * @param float|int|string $number
117
+	 * @return float
118
+	 */
119
+	public function filterNumericValue($number): float
120
+	{
121
+		return (float) filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
122
+	}
123
+
124
+
125
+	/**
126
+	 * formats the provided number to 6 decimal places using the site locale and returns a string
127
+	 *
128
+	 * @param float|int|string $number unformatted float, ex: 1.23456789
129
+	 * @return string                  formatted number value, ex: '1.234568'
130
+	 */
131
+	public function precisionFormat($number): string
132
+	{
133
+		$locale = $this->locales->getLocale($this->locales->getSiteLocaleName());
134
+		return $this->format($locale, $number, LocaleFloatFormatter::DECIMAL_PRECISION);
135
+	}
136
+
137
+
138
+	/**
139
+	 * strips formatting using the site locale, then rounds the provided number to 6 decimal places and returns a float
140
+	 *
141
+	 * @param float|int|string $number    unformatted number value, ex: 1234.5678956789
142
+	 * @param int|null         $precision the number of decimal places to round to
143
+	 * @param int              $mode      one of the PHP_ROUND_* constants for round up, round down, etc
144
+	 * @return float                      rounded value, ex: 1,234.567896
145
+	 */
146
+	public function precisionRound(
147
+		$number,
148
+		?int $precision = LocaleFloatFormatter::DECIMAL_PRECISION,
149
+		int $mode = PHP_ROUND_HALF_UP
150
+	): float {
151
+		return round(
152
+			$this->filterNumericValue($number),
153
+			$precision,
154
+			$mode
155
+		);
156
+	}
157
+
158
+
159
+	/**
160
+	 * strips formatting for the provided locale (defaults to site locale),
161
+	 * then rounds the provided number and returns a float
162
+	 *
163
+	 * @param float|int|string $number unformatted number value, ex: 1234.56789
164
+	 * @param string|Locale    $locale ex: 'en_US' or Locale object
165
+	 * @param int              $mode   one of the PHP_ROUND_* constants for round up, round down, etc
166
+	 * @return float                   rounded value, ex: 1,234.57
167
+	 */
168
+	public function roundForLocale($number, $locale = '', int $mode = PHP_ROUND_HALF_UP): float
169
+	{
170
+		$locale = $this->locales->getLocale($locale);
171
+		return round($this->filterNumericValue($number), $locale->decimalPrecision(), $mode);
172
+	}
173 173
 }
Please login to merge, or discard this patch.
core/services/address/formatters/AddressFormatter.php 1 patch
Indentation   +34 added lines, -34 removed lines patch added patch discarded remove patch
@@ -12,38 +12,38 @@
 block discarded – undo
12 12
  */
13 13
 class AddressFormatter
14 14
 {
15
-    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
16
-    /**
17
-     * @param string $address
18
-     * @param string $address2
19
-     * @param string $city
20
-     * @param string $state
21
-     * @param string $zip
22
-     * @param string $country
23
-     * @param string $formatted_address
24
-     * @param string $sub
25
-     * @return string
26
-     */
27
-    protected function parse_formatted_address(
28
-        string $address,
29
-        string $address2,
30
-        string $city,
31
-        string $state,
32
-        string $zip,
33
-        string $country,
34
-        string $formatted_address,
35
-        string $sub
36
-    ): string {
37
-        // swap address part placeholders for the real text
38
-        $formatted_address = str_replace(
39
-            // find
40
-            array('{address}', '{address2}', '{city}', '{state}', '{zip}', '{country}'),
41
-            // replace
42
-            array($address, $address2, $city, $state, $zip, $country),
43
-            // string
44
-            $formatted_address
45
-        );
46
-        // remove placeholder from start and end, reduce repeating placeholders to singles, then replace with HTML line breaks
47
-        return preg_replace('/%+/', $sub, trim($formatted_address, '%'));
48
-    }
15
+	// phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
16
+	/**
17
+	 * @param string $address
18
+	 * @param string $address2
19
+	 * @param string $city
20
+	 * @param string $state
21
+	 * @param string $zip
22
+	 * @param string $country
23
+	 * @param string $formatted_address
24
+	 * @param string $sub
25
+	 * @return string
26
+	 */
27
+	protected function parse_formatted_address(
28
+		string $address,
29
+		string $address2,
30
+		string $city,
31
+		string $state,
32
+		string $zip,
33
+		string $country,
34
+		string $formatted_address,
35
+		string $sub
36
+	): string {
37
+		// swap address part placeholders for the real text
38
+		$formatted_address = str_replace(
39
+			// find
40
+			array('{address}', '{address2}', '{city}', '{state}', '{zip}', '{country}'),
41
+			// replace
42
+			array($address, $address2, $city, $state, $zip, $country),
43
+			// string
44
+			$formatted_address
45
+		);
46
+		// remove placeholder from start and end, reduce repeating placeholders to singles, then replace with HTML line breaks
47
+		return preg_replace('/%+/', $sub, trim($formatted_address, '%'));
48
+	}
49 49
 }
Please login to merge, or discard this patch.
core/domain/services/validation/email/EmailValidationService.php 1 patch
Indentation   +133 added lines, -133 removed lines patch added patch discarded remove patch
@@ -19,137 +19,137 @@
 block discarded – undo
19 19
  */
20 20
 class EmailValidationService implements EmailValidatorInterface
21 21
 {
22
-    public const VALIDATION_LEVEL_BASIC      = 'basic';
23
-
24
-    public const VALIDATION_LEVEL_I18N       = 'i18n';
25
-
26
-    public const VALIDATION_LEVEL_I18N_DNS   = 'i18n_dns';
27
-
28
-    public const VALIDATION_LEVEL_WP_DEFAULT = 'wp_default';
29
-
30
-
31
-    /**
32
-     * @var EE_Registration_Config
33
-     */
34
-    protected $registration_config;
35
-
36
-    /**
37
-     * @var Loader
38
-     */
39
-    protected $loader;
40
-
41
-    /**
42
-     * @var Basic
43
-     */
44
-    private $validator_basic;
45
-
46
-    /**
47
-     * @var International
48
-     */
49
-    private $validator_i18n;
50
-
51
-    /**
52
-     * @var InternationalDNS
53
-     */
54
-    private $validator_i18n_dns;
55
-
56
-    /**
57
-     * @var WordPress
58
-     */
59
-    private $validator_wordpress;
60
-
61
-
62
-
63
-    /**
64
-     * EmailValidationService constructor.
65
-     * Accepts an \EE_Config as an argument.
66
-     *
67
-     * @param EE_Registration_Config $config
68
-     * @param Loader                 $loader
69
-     */
70
-    public function __construct(EE_Registration_Config $config, Loader $loader)
71
-    {
72
-        $this->registration_config = $config;
73
-        $this->loader = $loader;
74
-    }
75
-
76
-
77
-    /**
78
-     * Validates the email address. If it's invalid, an EmailValidationException
79
-     * is thrown that describes why its invalid.
80
-     *
81
-     * @param string|null $email_address
82
-     * @return boolean
83
-     */
84
-    public function validate(?string $email_address): bool
85
-    {
86
-        // pick the correct validator according to the config
87
-        switch ($this->registration_config->email_validation_level) {
88
-            case EmailValidationService::VALIDATION_LEVEL_BASIC:
89
-                return $this->basicValidator()->validate($email_address);
90
-            case EmailValidationService::VALIDATION_LEVEL_I18N:
91
-                return $this->i18nValidator()->validate($email_address);
92
-            case EmailValidationService::VALIDATION_LEVEL_I18N_DNS:
93
-                return $this->i18nDnsValidator()->validate($email_address);
94
-            case EmailValidationService::VALIDATION_LEVEL_WP_DEFAULT:
95
-            default:
96
-                return $this->wordpressValidator()->validate($email_address);
97
-        }
98
-    }
99
-
100
-
101
-    /**
102
-     * @return Basic
103
-     */
104
-    public function basicValidator(): Basic
105
-    {
106
-        if (! $this->validator_basic instanceof Basic) {
107
-            $this->validator_basic = $this->loader->getShared(
108
-                'EventEspresso\core\domain\services\validation\email\strategies\Basic'
109
-            );
110
-        }
111
-        return $this->validator_basic;
112
-    }
113
-
114
-
115
-    /**
116
-     * @return International
117
-     */
118
-    public function i18nValidator(): International
119
-    {
120
-        if (! $this->validator_i18n instanceof Basic) {
121
-            $this->validator_i18n = $this->loader->getShared(
122
-                'EventEspresso\core\domain\services\validation\email\strategies\International'
123
-            );
124
-        }
125
-        return $this->validator_i18n;
126
-    }
127
-
128
-
129
-    /**
130
-     * @return InternationalDNS
131
-     */
132
-    public function i18nDnsValidator(): InternationalDNS
133
-    {
134
-        if (! $this->validator_i18n_dns instanceof Basic) {
135
-            $this->validator_i18n_dns = $this->loader->getShared(
136
-                'EventEspresso\core\domain\services\validation\email\strategies\InternationalDNS'
137
-            );
138
-        }
139
-        return $this->validator_i18n_dns;
140
-    }
141
-
142
-
143
-    /**
144
-     * @return WordPress
145
-     */
146
-    public function wordpressValidator(): WordPress
147
-    {
148
-        if (! $this->validator_wordpress instanceof Basic) {
149
-            $this->validator_wordpress = $this->loader->getShared(
150
-                'EventEspresso\core\domain\services\validation\email\strategies\WordPress'
151
-            );
152
-        }
153
-        return $this->validator_wordpress;
154
-    }
22
+	public const VALIDATION_LEVEL_BASIC      = 'basic';
23
+
24
+	public const VALIDATION_LEVEL_I18N       = 'i18n';
25
+
26
+	public const VALIDATION_LEVEL_I18N_DNS   = 'i18n_dns';
27
+
28
+	public const VALIDATION_LEVEL_WP_DEFAULT = 'wp_default';
29
+
30
+
31
+	/**
32
+	 * @var EE_Registration_Config
33
+	 */
34
+	protected $registration_config;
35
+
36
+	/**
37
+	 * @var Loader
38
+	 */
39
+	protected $loader;
40
+
41
+	/**
42
+	 * @var Basic
43
+	 */
44
+	private $validator_basic;
45
+
46
+	/**
47
+	 * @var International
48
+	 */
49
+	private $validator_i18n;
50
+
51
+	/**
52
+	 * @var InternationalDNS
53
+	 */
54
+	private $validator_i18n_dns;
55
+
56
+	/**
57
+	 * @var WordPress
58
+	 */
59
+	private $validator_wordpress;
60
+
61
+
62
+
63
+	/**
64
+	 * EmailValidationService constructor.
65
+	 * Accepts an \EE_Config as an argument.
66
+	 *
67
+	 * @param EE_Registration_Config $config
68
+	 * @param Loader                 $loader
69
+	 */
70
+	public function __construct(EE_Registration_Config $config, Loader $loader)
71
+	{
72
+		$this->registration_config = $config;
73
+		$this->loader = $loader;
74
+	}
75
+
76
+
77
+	/**
78
+	 * Validates the email address. If it's invalid, an EmailValidationException
79
+	 * is thrown that describes why its invalid.
80
+	 *
81
+	 * @param string|null $email_address
82
+	 * @return boolean
83
+	 */
84
+	public function validate(?string $email_address): bool
85
+	{
86
+		// pick the correct validator according to the config
87
+		switch ($this->registration_config->email_validation_level) {
88
+			case EmailValidationService::VALIDATION_LEVEL_BASIC:
89
+				return $this->basicValidator()->validate($email_address);
90
+			case EmailValidationService::VALIDATION_LEVEL_I18N:
91
+				return $this->i18nValidator()->validate($email_address);
92
+			case EmailValidationService::VALIDATION_LEVEL_I18N_DNS:
93
+				return $this->i18nDnsValidator()->validate($email_address);
94
+			case EmailValidationService::VALIDATION_LEVEL_WP_DEFAULT:
95
+			default:
96
+				return $this->wordpressValidator()->validate($email_address);
97
+		}
98
+	}
99
+
100
+
101
+	/**
102
+	 * @return Basic
103
+	 */
104
+	public function basicValidator(): Basic
105
+	{
106
+		if (! $this->validator_basic instanceof Basic) {
107
+			$this->validator_basic = $this->loader->getShared(
108
+				'EventEspresso\core\domain\services\validation\email\strategies\Basic'
109
+			);
110
+		}
111
+		return $this->validator_basic;
112
+	}
113
+
114
+
115
+	/**
116
+	 * @return International
117
+	 */
118
+	public function i18nValidator(): International
119
+	{
120
+		if (! $this->validator_i18n instanceof Basic) {
121
+			$this->validator_i18n = $this->loader->getShared(
122
+				'EventEspresso\core\domain\services\validation\email\strategies\International'
123
+			);
124
+		}
125
+		return $this->validator_i18n;
126
+	}
127
+
128
+
129
+	/**
130
+	 * @return InternationalDNS
131
+	 */
132
+	public function i18nDnsValidator(): InternationalDNS
133
+	{
134
+		if (! $this->validator_i18n_dns instanceof Basic) {
135
+			$this->validator_i18n_dns = $this->loader->getShared(
136
+				'EventEspresso\core\domain\services\validation\email\strategies\InternationalDNS'
137
+			);
138
+		}
139
+		return $this->validator_i18n_dns;
140
+	}
141
+
142
+
143
+	/**
144
+	 * @return WordPress
145
+	 */
146
+	public function wordpressValidator(): WordPress
147
+	{
148
+		if (! $this->validator_wordpress instanceof Basic) {
149
+			$this->validator_wordpress = $this->loader->getShared(
150
+				'EventEspresso\core\domain\services\validation\email\strategies\WordPress'
151
+			);
152
+		}
153
+		return $this->validator_wordpress;
154
+	}
155 155
 }
Please login to merge, or discard this patch.
core/db_models/fields/EE_Money_Field.php 1 patch
Indentation   +162 added lines, -162 removed lines patch added patch discarded remove patch
@@ -9,166 +9,166 @@
 block discarded – undo
9 9
  */
10 10
 class EE_Money_Field extends EE_Float_Field
11 11
 {
12
-    /**
13
-     * if true, then money values will be accurate to 6 decimal places
14
-     * if false, then money values will be rounded to the correct number of subunits for the site's currency
15
-     *
16
-     * @var   bool
17
-     * @since $VID:$
18
-     */
19
-    protected $allow_fractional_subunits;
20
-
21
-    /**
22
-     * @var CurrencyFormatter
23
-     * @since $VID:$
24
-     */
25
-    protected $currency_formatter;
26
-
27
-
28
-    /**
29
-     * @param string     $table_column
30
-     * @param string     $nice_name
31
-     * @param bool|null  $nullable
32
-     * @param float|null $default_value
33
-     * @param bool|null  $allow_fractional_subunits
34
-     */
35
-    public function __construct(
36
-        string $table_column,
37
-        string $nice_name,
38
-        ?bool $nullable = false,
39
-        ?float $default_value = 0,
40
-        ?bool $allow_fractional_subunits = true
41
-    ) {
42
-        parent::__construct($table_column, $nice_name, $nullable, $default_value);
43
-        $this->allow_fractional_subunits = $allow_fractional_subunits;
44
-        // in a better world this would have been injected upon construction
45
-        if (! $this->currency_formatter instanceof CurrencyFormatter) {
46
-            $this->currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
47
-        }
48
-        $this->setSchemaType('object');
49
-    }
50
-
51
-
52
-    /**
53
-     * Returns whether or not this money field allows partial penny amounts
54
-     *
55
-     * @return boolean
56
-     */
57
-    public function allowFractionalSubunits(): ?bool
58
-    {
59
-        return $this->allow_fractional_subunits;
60
-    }
61
-
62
-
63
-    /**
64
-     * Schemas:
65
-     *    'localized_float': '3,023.00'
66
-     *    'no_currency_code': '$3,023.00'
67
-     *    null: '$3,023.00<span>USD</span>'
68
-     *
69
-     * @param float|int|string $amount
70
-     * @param string|null      $schema
71
-     * @return string
72
-     * @since $VID:$
73
-     */
74
-    public function prepare_for_get($amount, ?string $schema = 'precision_float'): string
75
-    {
76
-        $schema = $schema ? $schema : 'precision_float';
77
-        return $this->currency_formatter->formatForLocale(
78
-            (float) $amount,
79
-            $this->currency_formatter->getFormatFromLegacySchema($schema, $this->allow_fractional_subunits)
80
-        );
81
-    }
82
-
83
-
84
-    /**
85
-     * Schemas:
86
-     *    'localized_float': "3,023.00"
87
-     *    'no_currency_code': "$3,023.00"
88
-     *    null: "$3,023.00<span>USD</span>"
89
-     *
90
-     * @param float|int|string $amount
91
-     * @param string|null      $schema
92
-     * @return string
93
-     */
94
-    public function prepare_for_pretty_echoing($amount, ?string $schema = 'localized_currency'): string
95
-    {
96
-        $schema = $schema ?? 'localized_currency';
97
-        return $this->currency_formatter->formatForLocale(
98
-            (float) $amount,
99
-            $this->currency_formatter->getFormatFromLegacySchema($schema, $this->allow_fractional_subunits)
100
-        );
101
-    }
102
-
103
-
104
-    /**
105
-     * Converts periods and commas according to the country's currency settings.
106
-     * Strips out money-related formatting to turn it into a proper float.
107
-     * If fractional subunits are not allowed,
108
-     * it rounds the float to the correct number of decimal places for this country's currency.
109
-     *
110
-     * @param float|int|string $amount
111
-     * @param string|null      $schema
112
-     * @return float
113
-     */
114
-    public function prepare_for_set($amount, $schema = ''): float
115
-    {
116
-        $amount = $this->currency_formatter->parseForLocale($amount);
117
-        return $this->prepare_for_set_from_db($amount);
118
-    }
119
-
120
-
121
-    /**
122
-     * @param float $amount
123
-     * @return float
124
-     */
125
-    public function prepare_for_set_from_db($amount): float
126
-    {
127
-        return $this->allowFractionalSubunits()
128
-            ? $this->currency_formatter->precisionRound($amount)
129
-            : $this->currency_formatter->roundForLocale($amount);
130
-    }
131
-
132
-
133
-    /**
134
-     * @return array[]
135
-     */
136
-    public function getSchemaProperties(): array
137
-    {
138
-        return [
139
-            'raw'    => [
140
-                'description' => sprintf(
141
-                    esc_html__(
142
-                        '%s - the raw value as it exists in the database as a simple float.',
143
-                        'event_espresso'
144
-                    ),
145
-                    $this->get_nicename()
146
-                ),
147
-                'type'        => 'number',
148
-            ],
149
-            'pretty' => [
150
-                'description' => sprintf(
151
-                    esc_html__(
152
-                        '%s - formatted for display in the set currency and decimal places.',
153
-                        'event_espresso'
154
-                    ),
155
-                    $this->get_nicename()
156
-                ),
157
-                'type'        => 'string',
158
-                'format'      => 'money',
159
-            ],
160
-        ];
161
-    }
162
-
163
-
164
-    /**
165
-     * Returns whether or not this money field allows partial penny amounts
166
-     *
167
-     * @deprecatd $VID:$
168
-     * @return boolean
169
-     */
170
-    public function whole_pennies_only(): ?bool
171
-    {
172
-        return $this->allow_fractional_subunits;
173
-    }
12
+	/**
13
+	 * if true, then money values will be accurate to 6 decimal places
14
+	 * if false, then money values will be rounded to the correct number of subunits for the site's currency
15
+	 *
16
+	 * @var   bool
17
+	 * @since $VID:$
18
+	 */
19
+	protected $allow_fractional_subunits;
20
+
21
+	/**
22
+	 * @var CurrencyFormatter
23
+	 * @since $VID:$
24
+	 */
25
+	protected $currency_formatter;
26
+
27
+
28
+	/**
29
+	 * @param string     $table_column
30
+	 * @param string     $nice_name
31
+	 * @param bool|null  $nullable
32
+	 * @param float|null $default_value
33
+	 * @param bool|null  $allow_fractional_subunits
34
+	 */
35
+	public function __construct(
36
+		string $table_column,
37
+		string $nice_name,
38
+		?bool $nullable = false,
39
+		?float $default_value = 0,
40
+		?bool $allow_fractional_subunits = true
41
+	) {
42
+		parent::__construct($table_column, $nice_name, $nullable, $default_value);
43
+		$this->allow_fractional_subunits = $allow_fractional_subunits;
44
+		// in a better world this would have been injected upon construction
45
+		if (! $this->currency_formatter instanceof CurrencyFormatter) {
46
+			$this->currency_formatter = LoaderFactory::getLoader()->getShared(CurrencyFormatter::class);
47
+		}
48
+		$this->setSchemaType('object');
49
+	}
50
+
51
+
52
+	/**
53
+	 * Returns whether or not this money field allows partial penny amounts
54
+	 *
55
+	 * @return boolean
56
+	 */
57
+	public function allowFractionalSubunits(): ?bool
58
+	{
59
+		return $this->allow_fractional_subunits;
60
+	}
61
+
62
+
63
+	/**
64
+	 * Schemas:
65
+	 *    'localized_float': '3,023.00'
66
+	 *    'no_currency_code': '$3,023.00'
67
+	 *    null: '$3,023.00<span>USD</span>'
68
+	 *
69
+	 * @param float|int|string $amount
70
+	 * @param string|null      $schema
71
+	 * @return string
72
+	 * @since $VID:$
73
+	 */
74
+	public function prepare_for_get($amount, ?string $schema = 'precision_float'): string
75
+	{
76
+		$schema = $schema ? $schema : 'precision_float';
77
+		return $this->currency_formatter->formatForLocale(
78
+			(float) $amount,
79
+			$this->currency_formatter->getFormatFromLegacySchema($schema, $this->allow_fractional_subunits)
80
+		);
81
+	}
82
+
83
+
84
+	/**
85
+	 * Schemas:
86
+	 *    'localized_float': "3,023.00"
87
+	 *    'no_currency_code': "$3,023.00"
88
+	 *    null: "$3,023.00<span>USD</span>"
89
+	 *
90
+	 * @param float|int|string $amount
91
+	 * @param string|null      $schema
92
+	 * @return string
93
+	 */
94
+	public function prepare_for_pretty_echoing($amount, ?string $schema = 'localized_currency'): string
95
+	{
96
+		$schema = $schema ?? 'localized_currency';
97
+		return $this->currency_formatter->formatForLocale(
98
+			(float) $amount,
99
+			$this->currency_formatter->getFormatFromLegacySchema($schema, $this->allow_fractional_subunits)
100
+		);
101
+	}
102
+
103
+
104
+	/**
105
+	 * Converts periods and commas according to the country's currency settings.
106
+	 * Strips out money-related formatting to turn it into a proper float.
107
+	 * If fractional subunits are not allowed,
108
+	 * it rounds the float to the correct number of decimal places for this country's currency.
109
+	 *
110
+	 * @param float|int|string $amount
111
+	 * @param string|null      $schema
112
+	 * @return float
113
+	 */
114
+	public function prepare_for_set($amount, $schema = ''): float
115
+	{
116
+		$amount = $this->currency_formatter->parseForLocale($amount);
117
+		return $this->prepare_for_set_from_db($amount);
118
+	}
119
+
120
+
121
+	/**
122
+	 * @param float $amount
123
+	 * @return float
124
+	 */
125
+	public function prepare_for_set_from_db($amount): float
126
+	{
127
+		return $this->allowFractionalSubunits()
128
+			? $this->currency_formatter->precisionRound($amount)
129
+			: $this->currency_formatter->roundForLocale($amount);
130
+	}
131
+
132
+
133
+	/**
134
+	 * @return array[]
135
+	 */
136
+	public function getSchemaProperties(): array
137
+	{
138
+		return [
139
+			'raw'    => [
140
+				'description' => sprintf(
141
+					esc_html__(
142
+						'%s - the raw value as it exists in the database as a simple float.',
143
+						'event_espresso'
144
+					),
145
+					$this->get_nicename()
146
+				),
147
+				'type'        => 'number',
148
+			],
149
+			'pretty' => [
150
+				'description' => sprintf(
151
+					esc_html__(
152
+						'%s - formatted for display in the set currency and decimal places.',
153
+						'event_espresso'
154
+					),
155
+					$this->get_nicename()
156
+				),
157
+				'type'        => 'string',
158
+				'format'      => 'money',
159
+			],
160
+		];
161
+	}
162
+
163
+
164
+	/**
165
+	 * Returns whether or not this money field allows partial penny amounts
166
+	 *
167
+	 * @deprecatd $VID:$
168
+	 * @return boolean
169
+	 */
170
+	public function whole_pennies_only(): ?bool
171
+	{
172
+		return $this->allow_fractional_subunits;
173
+	}
174 174
 }
Please login to merge, or discard this patch.
core/db_models/fields/EE_Integer_Field.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -9,72 +9,72 @@
 block discarded – undo
9 9
  */
10 10
 class EE_Integer_Field extends EE_Model_Field_Base
11 11
 {
12
-    /**
13
-     * @var NumberFormatter
14
-     * @since $VID:$
15
-     */
16
-    protected $number_formatter;
12
+	/**
13
+	 * @var NumberFormatter
14
+	 * @since $VID:$
15
+	 */
16
+	protected $number_formatter;
17 17
 
18
-    /**
19
-     * @param string $table_column
20
-     * @param string $nicename
21
-     * @param bool   $nullable
22
-     * @param null   $default_value
23
-     */
24
-    public function __construct($table_column, $nicename, $nullable, $default_value = null)
25
-    {
26
-        parent::__construct($table_column, $nicename, $nullable, $default_value);
27
-        if (! $this->number_formatter instanceof NumberFormatter) {
28
-            $this->number_formatter = LoaderFactory::getLoader()->getShared(NumberFormatter::class);
29
-        }
30
-        $this->setSchemaType('integer');
31
-    }
18
+	/**
19
+	 * @param string $table_column
20
+	 * @param string $nicename
21
+	 * @param bool   $nullable
22
+	 * @param null   $default_value
23
+	 */
24
+	public function __construct($table_column, $nicename, $nullable, $default_value = null)
25
+	{
26
+		parent::__construct($table_column, $nicename, $nullable, $default_value);
27
+		if (! $this->number_formatter instanceof NumberFormatter) {
28
+			$this->number_formatter = LoaderFactory::getLoader()->getShared(NumberFormatter::class);
29
+		}
30
+		$this->setSchemaType('integer');
31
+	}
32 32
 
33 33
 
34
-    /**
35
-     * @param float|int|string $amount
36
-     * @return int
37
-     * @since $VID:$
38
-     */
39
-    public function prepare_for_get($amount)
40
-    {
41
-        return (int) $this->number_formatter->filterNumericValue($amount);
42
-    }
34
+	/**
35
+	 * @param float|int|string $amount
36
+	 * @return int
37
+	 * @since $VID:$
38
+	 */
39
+	public function prepare_for_get($amount)
40
+	{
41
+		return (int) $this->number_formatter->filterNumericValue($amount);
42
+	}
43 43
 
44 44
 
45
-    /**
46
-     * Returns the number formatted according to local custom (set by the country of the blog).
47
-     *
48
-     * @param float        $value
49
-     * @param null|string  $schema
50
-     * @return string
51
-     */
52
-    public function prepare_for_pretty_echoing($value, $schema = null)
53
-    {
54
-        return $this->number_formatter->formatForLocale($value);
55
-    }
45
+	/**
46
+	 * Returns the number formatted according to local custom (set by the country of the blog).
47
+	 *
48
+	 * @param float        $value
49
+	 * @param null|string  $schema
50
+	 * @return string
51
+	 */
52
+	public function prepare_for_pretty_echoing($value, $schema = null)
53
+	{
54
+		return $this->number_formatter->formatForLocale($value);
55
+	}
56 56
 
57 57
 
58
-    /**
59
-     * If provided a string, strips out number-related formatting, like commas, periods, spaces, other junk, etc.
60
-     * However, treats commas and periods as thousand-separators or decimal marks, as per the currency's config.
61
-     *
62
-     * @param float|int|string $value
63
-     * @return int
64
-     */
65
-    public function prepare_for_set($value)
66
-    {
67
-        $value = $this->number_formatter->parseForLocale($value);
68
-        return (int) $this->prepare_for_set_from_db($value);
69
-    }
58
+	/**
59
+	 * If provided a string, strips out number-related formatting, like commas, periods, spaces, other junk, etc.
60
+	 * However, treats commas and periods as thousand-separators or decimal marks, as per the currency's config.
61
+	 *
62
+	 * @param float|int|string $value
63
+	 * @return int
64
+	 */
65
+	public function prepare_for_set($value)
66
+	{
67
+		$value = $this->number_formatter->parseForLocale($value);
68
+		return (int) $this->prepare_for_set_from_db($value);
69
+	}
70 70
 
71 71
 
72
-    /**
73
-     * @param float $value
74
-     * @return int
75
-     */
76
-    public function prepare_for_set_from_db($value)
77
-    {
78
-        return (int) $this->number_formatter->precisionRound($value, 0);
79
-    }
72
+	/**
73
+	 * @param float $value
74
+	 * @return int
75
+	 */
76
+	public function prepare_for_set_from_db($value)
77
+	{
78
+		return (int) $this->number_formatter->precisionRound($value, 0);
79
+	}
80 80
 }
Please login to merge, or discard this patch.
core/libraries/form_sections/inputs/EE_Locale_Select_Input.php 1 patch
Indentation   +83 added lines, -83 removed lines patch added patch discarded remove patch
@@ -16,102 +16,102 @@
 block discarded – undo
16 16
  */
17 17
 class EE_Locale_Select_Input extends EE_Select_Input
18 18
 {
19
-    /**
20
-     * @var array
21
-     */
22
-    private $available_languages;
19
+	/**
20
+	 * @var array
21
+	 */
22
+	private $available_languages;
23 23
 
24
-    /**
25
-     * @var string
26
-     */
27
-    private $selected;
24
+	/**
25
+	 * @var string
26
+	 */
27
+	private $selected;
28 28
 
29
-    /**
30
-     * @var array
31
-     */
32
-    private $translations;
29
+	/**
30
+	 * @var array
31
+	 */
32
+	private $translations;
33 33
 
34
-    /**
35
-     * @param array $input_settings
36
-     * @throws InvalidDataTypeException
37
-     * @throws InvalidInterfaceException
38
-     */
39
-    public function __construct($input_settings = [])
40
-    {
41
-        require_once ABSPATH . 'wp-admin/includes/translation-install.php';
42
-        $this->available_languages = get_available_languages();
43
-        $this->translations        = wp_get_available_translations();
34
+	/**
35
+	 * @param array $input_settings
36
+	 * @throws InvalidDataTypeException
37
+	 * @throws InvalidInterfaceException
38
+	 */
39
+	public function __construct($input_settings = [])
40
+	{
41
+		require_once ABSPATH . 'wp-admin/includes/translation-install.php';
42
+		$this->available_languages = get_available_languages();
43
+		$this->translations        = wp_get_available_translations();
44 44
 
45
-        $lang = get_site_option('WPLANG');
46
-        $this->selected = in_array($lang, $this->available_languages, true) ? $lang : 'en_US';
45
+		$lang = get_site_option('WPLANG');
46
+		$this->selected = in_array($lang, $this->available_languages, true) ? $lang : 'en_US';
47 47
 
48
-        $input_settings['default'] = $input_settings['default'] ?? $this->selected;
49
-        $input_settings['html_class'] = isset($input_settings['html_class'])
50
-            ? $input_settings['html_class'] . ' ee-locale-select-js'
51
-            : 'ee-locale-select-js';
52
-        parent::__construct($this->getLocaleOptions(), $input_settings);
53
-    }
48
+		$input_settings['default'] = $input_settings['default'] ?? $this->selected;
49
+		$input_settings['html_class'] = isset($input_settings['html_class'])
50
+			? $input_settings['html_class'] . ' ee-locale-select-js'
51
+			: 'ee-locale-select-js';
52
+		parent::__construct($this->getLocaleOptions(), $input_settings);
53
+	}
54 54
 
55 55
 
56
-    /**
57
-     * @return array
58
-     */
59
-    private function getLocaleOptions(): array
60
-    {
61
-        /*
56
+	/**
57
+	 * @return array
58
+	 */
59
+	private function getLocaleOptions(): array
60
+	{
61
+		/*
62 62
          * $this->available_languages should only contain the locales.
63 63
          * Find the locale in $translations to get the native name. Fall back to locale.
64 64
          */
65
-        $languages = [];
66
-        foreach ($this->available_languages as $locale) {
67
-            if (isset($this->translations[ $locale ])) {
68
-                $languages[] = [
69
-                    'locale'      => $this->translations[ $locale ]['language'],
70
-                    'native_name' => $this->translations[ $locale ]['native_name'],
71
-                    'language'    => current($this->translations[ $locale ]['iso']),
72
-                ];
73
-            } else {
74
-                $languages[] = [
75
-                    'locale'      => $locale,
76
-                    'native_name' => $locale,
77
-                    'language'    => '',
78
-                ];
79
-            }
80
-        }
65
+		$languages = [];
66
+		foreach ($this->available_languages as $locale) {
67
+			if (isset($this->translations[ $locale ])) {
68
+				$languages[] = [
69
+					'locale'      => $this->translations[ $locale ]['language'],
70
+					'native_name' => $this->translations[ $locale ]['native_name'],
71
+					'language'    => current($this->translations[ $locale ]['iso']),
72
+				];
73
+			} else {
74
+				$languages[] = [
75
+					'locale'      => $locale,
76
+					'native_name' => $locale,
77
+					'language'    => '',
78
+				];
79
+			}
80
+		}
81 81
 
82
-        // default.
83
-        $options = [
84
-            'en' => ['en_US' => 'en_US : English (United States)']
85
-        ];
82
+		// default.
83
+		$options = [
84
+			'en' => ['en_US' => 'en_US : English (United States)']
85
+		];
86 86
 
87
-        // List installed languages.
88
-        foreach ($languages as $language_data) {
89
-            $language = $language_data['language'];
87
+		// List installed languages.
88
+		foreach ($languages as $language_data) {
89
+			$language = $language_data['language'];
90 90
 
91
-            $locale = $language_data['locale'];
92
-            $locale = WordPressLocales::hasLocaleForLanguage($locale)
93
-                ? WordPressLocales::getLocaleForLanguage($locale)
94
-                : $locale;
95
-            $locale = esc_attr($locale);
91
+			$locale = $language_data['locale'];
92
+			$locale = WordPressLocales::hasLocaleForLanguage($locale)
93
+				? WordPressLocales::getLocaleForLanguage($locale)
94
+				: $locale;
95
+			$locale = esc_attr($locale);
96 96
 
97
-            if (WordPressLocales::hasExtraLocalesForLanguage($locale)) {
98
-                $extra_locales = WordPressLocales::getExtraLocalesForLanguage($locale);
99
-                foreach ($extra_locales as $extra_locale => $native_name) {
100
-                    $options[ $language ][ $extra_locale ] = $extra_locale . ' : ' . esc_html($native_name);
101
-                }
102
-            } else {
103
-                $options[ $language ][ $locale ] = $locale . ' : ' . esc_html($language_data['native_name']);
104
-            }
105
-        }
97
+			if (WordPressLocales::hasExtraLocalesForLanguage($locale)) {
98
+				$extra_locales = WordPressLocales::getExtraLocalesForLanguage($locale);
99
+				foreach ($extra_locales as $extra_locale => $native_name) {
100
+					$options[ $language ][ $extra_locale ] = $extra_locale . ' : ' . esc_html($native_name);
101
+				}
102
+			} else {
103
+				$options[ $language ][ $locale ] = $locale . ' : ' . esc_html($language_data['native_name']);
104
+			}
105
+		}
106 106
 
107
-        // now sort it
108
-        ksort($options, SORT_NATURAL | SORT_FLAG_CASE);
109
-        foreach ($options as $key => $option) {
110
-            if (is_array($option)) {
111
-                ksort($option, SORT_NATURAL | SORT_FLAG_CASE);
112
-                $options[ $key ] = $option;
113
-            }
114
-        }
115
-        return $options;
116
-    }
107
+		// now sort it
108
+		ksort($options, SORT_NATURAL | SORT_FLAG_CASE);
109
+		foreach ($options as $key => $option) {
110
+			if (is_array($option)) {
111
+				ksort($option, SORT_NATURAL | SORT_FLAG_CASE);
112
+				$options[ $key ] = $option;
113
+			}
114
+		}
115
+		return $options;
116
+	}
117 117
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/ModelDataTranslator.php 1 patch
Indentation   +657 added lines, -657 removed lines patch added patch discarded remove patch
@@ -39,661 +39,661 @@
 block discarded – undo
39 39
  */
40 40
 class ModelDataTranslator
41 41
 {
42
-    /**
43
-     * We used to use -1 for infinity in the rest api, but that's ambiguous for
44
-     * fields that COULD contain -1; so we use null
45
-     */
46
-    const EE_INF_IN_REST = null;
47
-
48
-
49
-    /**
50
-     * Prepares a possible array of input values from JSON for use by the models
51
-     *
52
-     * @param EE_Model_Field_Base $field_obj
53
-     * @param mixed               $original_value
54
-     * @param string              $requested_version
55
-     * @param string              $timezone_string treat values as being in this timezone
56
-     * @return mixed
57
-     * @throws RestException
58
-     * @throws EE_Error
59
-     */
60
-    public static function prepareFieldValuesFromJson(
61
-        $field_obj,
62
-        $original_value,
63
-        $requested_version,
64
-        $timezone_string = 'UTC'
65
-    ) {
66
-        if (is_array($original_value) && ! $field_obj instanceof EE_Serialized_Text_Field) {
67
-            $new_value = [];
68
-            foreach ($original_value as $array_key => $array_item) {
69
-                $new_value[ $array_key ] = ModelDataTranslator::prepareFieldValueFromJson(
70
-                    $field_obj,
71
-                    $array_item,
72
-                    $requested_version,
73
-                    $timezone_string
74
-                );
75
-            }
76
-        } else {
77
-            $new_value = ModelDataTranslator::prepareFieldValueFromJson(
78
-                $field_obj,
79
-                $original_value,
80
-                $requested_version,
81
-                $timezone_string
82
-            );
83
-        }
84
-        return $new_value;
85
-    }
86
-
87
-
88
-    /**
89
-     * Prepares an array of field values FOR use in JSON/REST API
90
-     *
91
-     * @param EE_Model_Field_Base $field_obj
92
-     * @param mixed               $original_value
93
-     * @param string              $request_version (eg 4.8.36)
94
-     * @return array
95
-     * @throws EE_Error
96
-     */
97
-    public static function prepareFieldValuesForJson($field_obj, $original_value, $request_version)
98
-    {
99
-        if (is_array($original_value)) {
100
-            $new_value = [];
101
-            foreach ($original_value as $key => $value) {
102
-                $new_value[ $key ] = ModelDataTranslator::prepareFieldValuesForJson(
103
-                    $field_obj,
104
-                    $value,
105
-                    $request_version
106
-                );
107
-            }
108
-        } else {
109
-            $new_value = ModelDataTranslator::prepareFieldValueForJson(
110
-                $field_obj,
111
-                $original_value,
112
-                $request_version
113
-            );
114
-        }
115
-        return $new_value;
116
-    }
117
-
118
-
119
-    /**
120
-     * Prepares incoming data from the json or request parameters for the models'
121
-     * "$query_params".
122
-     *
123
-     * @param EE_Model_Field_Base $field_obj
124
-     * @param mixed               $original_value
125
-     * @param string              $requested_version
126
-     * @param string              $timezone_string treat values as being in this timezone
127
-     * @return mixed
128
-     * @throws RestException
129
-     * @throws DomainException
130
-     * @throws EE_Error
131
-     */
132
-    public static function prepareFieldValueFromJson(
133
-        $field_obj,
134
-        $original_value,
135
-        $requested_version,
136
-        $timezone_string = 'UTC'
137
-    ) {
138
-        // check if they accidentally submitted an error value. If so throw an exception
139
-        if (
140
-            is_array($original_value)
141
-            && isset($original_value['error_code'], $original_value['error_message'])
142
-        ) {
143
-            throw new RestException(
144
-                'rest_submitted_error_value',
145
-                sprintf(
146
-                    esc_html__(
147
-                        'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
148
-                        'event_espresso'
149
-                    ),
150
-                    $field_obj->get_name()
151
-                ),
152
-                [
153
-                    'status' => 400,
154
-                ]
155
-            );
156
-        }
157
-        $timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
158
-        $new_value = null;
159
-        // double-check for serialized PHP. We never accept serialized PHP. No way Jose.
160
-        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
161
-        $timezone_string = $timezone_string !== ''
162
-            ? $timezone_string
163
-            : get_option('timezone_string', '');
164
-        // walk through the submitted data and double-check for serialized PHP.
165
-        // We never accept serialized PHP. No way Jose.
166
-        ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
167
-        if (
168
-            $field_obj instanceof EE_Infinite_Integer_Field
169
-            && in_array($original_value, [null, ''], true)
170
-        ) {
171
-            $new_value = EE_INF;
172
-        } elseif ($field_obj instanceof EE_Datetime_Field) {
173
-            $new_value = self::getTimestampWithTimezoneOffset($original_value, $field_obj, $timezone_string);
174
-            $new_value = rest_parse_date($new_value);
175
-            if ($new_value === false) {
176
-                throw new RestException(
177
-                    'invalid_format_for_timestamp',
178
-                    sprintf(
179
-                        esc_html__(
180
-                            'Timestamps received on a request as the value for Date and Time fields must be in %1$s/%2$s format.  The timestamp provided (%3$s) is not that format.',
181
-                            'event_espresso'
182
-                        ),
183
-                        'RFC3339',
184
-                        'ISO8601',
185
-                        $original_value
186
-                    ),
187
-                    [
188
-                        'status' => 400,
189
-                    ]
190
-                );
191
-            }
192
-        } elseif ($field_obj instanceof EE_Boolean_Field) {
193
-            // Interpreted the strings "false", "true", "on", "off" appropriately.
194
-            $new_value = filter_var($original_value, FILTER_VALIDATE_BOOLEAN);
195
-        } else {
196
-            $new_value = $original_value;
197
-        }
198
-        return $new_value;
199
-    }
200
-
201
-
202
-    /**
203
-     * This checks if the incoming timestamp has timezone information already on it and if it doesn't then adds timezone
204
-     * information via details obtained from the host site.
205
-     *
206
-     * @param string            $original_timestamp
207
-     * @param EE_Datetime_Field $datetime_field
208
-     * @param                   $timezone_string
209
-     * @return string
210
-     * @throws DomainException
211
-     */
212
-    private static function getTimestampWithTimezoneOffset(
213
-        $original_timestamp,
214
-        EE_Datetime_Field $datetime_field,
215
-        $timezone_string
216
-    ) {
217
-        // already have timezone information?
218
-        if (preg_match('/Z|([+-])(\d{2}:\d{2})/', $original_timestamp)) {
219
-            // yes, we're ignoring the timezone.
220
-            return $original_timestamp;
221
-        }
222
-        // need to append timezone
223
-        list($offset_sign, $offset_secs) = self::parseTimezoneOffset(
224
-            $datetime_field->get_timezone_offset(
225
-                new DateTimeZone($timezone_string),
226
-                $original_timestamp
227
-            )
228
-        );
229
-        $offset_string =
230
-            str_pad(
231
-                floor($offset_secs / HOUR_IN_SECONDS),
232
-                2,
233
-                '0',
234
-                STR_PAD_LEFT
235
-            )
236
-            . ':'
237
-            . str_pad(
238
-                ($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
239
-                2,
240
-                '0',
241
-                STR_PAD_LEFT
242
-            );
243
-        return $original_timestamp . $offset_sign . $offset_string;
244
-    }
245
-
246
-
247
-    /**
248
-     * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
249
-     * think that can happen). If $data is an array, recurses into its keys and values
250
-     *
251
-     * @param mixed $data
252
-     * @return void
253
-     * @throws RestException
254
-     */
255
-    public static function throwExceptionIfContainsSerializedData($data)
256
-    {
257
-        if (is_array($data)) {
258
-            foreach ($data as $key => $value) {
259
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
260
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
261
-            }
262
-        } else {
263
-            if (is_serialized($data) || is_object($data)) {
264
-                throw new RestException(
265
-                    'serialized_data_submission_prohibited',
266
-                    esc_html__(
267
-                    // @codingStandardsIgnoreStart
268
-                        'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
269
-                        // @codingStandardsIgnoreEnd
270
-                        'event_espresso'
271
-                    )
272
-                );
273
-            }
274
-        }
275
-    }
276
-
277
-
278
-    /**
279
-     * determines what's going on with them timezone strings
280
-     *
281
-     * @param int $timezone_offset
282
-     * @return array
283
-     */
284
-    private static function parseTimezoneOffset($timezone_offset)
285
-    {
286
-        $first_char = substr((string) $timezone_offset, 0, 1);
287
-        if ($first_char === '+' || $first_char === '-') {
288
-            $offset_sign = $first_char;
289
-            $offset_secs = substr((string) $timezone_offset, 1);
290
-        } else {
291
-            $offset_sign = '+';
292
-            $offset_secs = $timezone_offset;
293
-        }
294
-        return [$offset_sign, $offset_secs];
295
-    }
296
-
297
-
298
-    /**
299
-     * Prepares a field's value for display in the API
300
-     *
301
-     * @param EE_Model_Field_Base $field_obj
302
-     * @param mixed               $original_value
303
-     * @param string              $requested_version
304
-     * @return mixed
305
-     * @throws EE_Error
306
-     */
307
-    public static function prepareFieldValueForJson($field_obj, $original_value, $requested_version)
308
-    {
309
-        if ($original_value === EE_INF) {
310
-            $new_value = ModelDataTranslator::EE_INF_IN_REST;
311
-        } elseif ($field_obj instanceof EE_Datetime_Field) {
312
-            if (is_string($original_value)) {
313
-                // did they submit a string of a unix timestamp?
314
-                if (is_numeric($original_value)) {
315
-                    $datetime_obj = new DateTime();
316
-                    $datetime_obj->setTimestamp((int) $original_value);
317
-                } else {
318
-                    // first, check if its a MySQL timestamp in GMT
319
-                    $datetime_obj = DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
320
-                }
321
-                if (! $datetime_obj instanceof DateTime) {
322
-                    // so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
323
-                    $datetime_obj = $field_obj->prepare_for_set($original_value);
324
-                }
325
-                $original_value = $datetime_obj;
326
-            }
327
-            if ($original_value instanceof DateTime) {
328
-                $new_value = $original_value->format('Y-m-d H:i:s');
329
-            } elseif (is_int($original_value) || is_float($original_value)) {
330
-                $new_value = date('Y-m-d H:i:s', $original_value);
331
-            } elseif ($original_value === null || $original_value === '') {
332
-                $new_value = null;
333
-            } else {
334
-                // so it's not a datetime object, unix timestamp (as string or int),
335
-                // MySQL timestamp, or even a string in the field object's format. So no idea what it is
336
-                throw new EE_Error(
337
-                    sprintf(
338
-                        esc_html__(
339
-                        // @codingStandardsIgnoreStart
340
-                            'The value "%1$s" for the field "%2$s" on model "%3$s" could not be understood. It should be a PHP DateTime, unix timestamp, MySQL date, or string in the format "%4$s".',
341
-                            // @codingStandardsIgnoreEnd
342
-                            'event_espresso'
343
-                        ),
344
-                        $original_value,
345
-                        $field_obj->get_name(),
346
-                        $field_obj->get_model_name(),
347
-                        $field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
348
-                    )
349
-                );
350
-            }
351
-            if ($new_value !== null) {
352
-                // phpcs:disable PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
353
-                $new_value = mysql_to_rfc3339($new_value);
354
-                // phpcs:enable
355
-            }
356
-        } else {
357
-            $new_value = $original_value;
358
-        }
359
-        // are we about to send an object? just don't. We have no good way to represent it in JSON.
360
-        // can't just check using is_object() because that missed PHP incomplete objects
361
-        if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
362
-            $new_value = [
363
-                'error_code'    => 'php_object_not_return',
364
-                'error_message' => esc_html__(
365
-                    'The value of this field in the database is a PHP object, which can\'t be represented in JSON.',
366
-                    'event_espresso'
367
-                ),
368
-            ];
369
-        }
370
-        return apply_filters(
371
-            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
372
-            $new_value,
373
-            $field_obj,
374
-            $original_value,
375
-            $requested_version
376
-        );
377
-    }
378
-
379
-
380
-    /**
381
-     * Prepares condition-query-parameters (like what's in where and having) from
382
-     * the format expected in the API to use in the models
383
-     *
384
-     * @param array    $inputted_query_params_of_this_type
385
-     * @param EEM_Base $model
386
-     * @param string   $requested_version
387
-     * @param boolean  $writing whether this data will be written to the DB, or if we're just building a query.
388
-     *                          If we're writing to the DB, we don't expect any operators, or any logic query
389
-     *                          parameters, and we also won't accept serialized data unless the current user has
390
-     *                          unfiltered_html.
391
-     * @return array
392
-     * @throws DomainException
393
-     * @throws EE_Error
394
-     * @throws RestException
395
-     * @throws InvalidDataTypeException
396
-     * @throws InvalidInterfaceException
397
-     * @throws InvalidArgumentException
398
-     * @throws ReflectionException
399
-     */
400
-    public static function prepareConditionsQueryParamsForModels(
401
-        $inputted_query_params_of_this_type,
402
-        EEM_Base $model,
403
-        $requested_version,
404
-        $writing = false
405
-    ) {
406
-        $query_param_for_models = [];
407
-        $context                = new RestIncomingQueryParamContext($model, $requested_version, $writing);
408
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
409
-            $query_param_meta = new RestIncomingQueryParamMetadata($query_param_key, $query_param_value, $context);
410
-            if ($query_param_meta->getField() instanceof EE_Model_Field_Base) {
411
-                $translated_value = $query_param_meta->determineConditionsQueryParameterValue();
412
-                if (
413
-                    $translated_value === null
414
-                    || (
415
-                        isset($query_param_for_models[ $query_param_meta->getQueryParamKey() ])
416
-                        && $query_param_meta->isGmtField()
417
-                    )
418
-                ) {
419
-                    // they have already provided a non-gmt field, ignore the gmt one. That's what WP core
420
-                    // currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
421
-                    // OR we couldn't create a translated value from their input
422
-                    continue;
423
-                }
424
-                $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $translated_value;
425
-            } else {
426
-                $nested_query_params = $query_param_meta->determineNestedConditionQueryParameters();
427
-                if ($nested_query_params) {
428
-                    $query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $nested_query_params;
429
-                }
430
-            }
431
-        }
432
-        return $query_param_for_models;
433
-    }
434
-
435
-
436
-    /**
437
-     * Mostly checks if the last 4 characters are "_gmt", indicating its a
438
-     * gmt date field name
439
-     *
440
-     * @param string $field_name
441
-     * @return boolean
442
-     */
443
-    public static function isGmtDateFieldName($field_name)
444
-    {
445
-        return substr(
446
-            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name),
447
-            -4,
448
-            4
449
-        ) === '_gmt';
450
-    }
451
-
452
-
453
-    /**
454
-     * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
455
-     *
456
-     * @param string $field_name
457
-     * @return string
458
-     */
459
-    public static function removeGmtFromFieldName($field_name)
460
-    {
461
-        if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
462
-            return $field_name;
463
-        }
464
-        $query_param_sans_stars              =
465
-            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
466
-                $field_name
467
-            );
468
-        $query_param_sans_gmt_and_sans_stars = substr(
469
-            $query_param_sans_stars,
470
-            0,
471
-            strrpos(
472
-                $field_name,
473
-                '_gmt'
474
-            )
475
-        );
476
-        return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
477
-    }
478
-
479
-
480
-    /**
481
-     * Takes a field name from the REST API and prepares it for the model querying
482
-     *
483
-     * @param string $field_name
484
-     * @return string
485
-     */
486
-    public static function prepareFieldNameFromJson($field_name)
487
-    {
488
-        if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
489
-            return ModelDataTranslator::removeGmtFromFieldName($field_name);
490
-        }
491
-        return $field_name;
492
-    }
493
-
494
-
495
-    /**
496
-     * Takes array of field names from REST API and prepares for models
497
-     *
498
-     * @param array $field_names
499
-     * @return array of field names (possibly include model prefixes)
500
-     */
501
-    public static function prepareFieldNamesFromJson(array $field_names)
502
-    {
503
-        $new_array = [];
504
-        foreach ($field_names as $key => $field_name) {
505
-            $new_array[ $key ] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
506
-        }
507
-        return $new_array;
508
-    }
509
-
510
-
511
-    /**
512
-     * Takes array where array keys are field names (possibly with model path prefixes)
513
-     * from the REST API and prepares them for model querying
514
-     *
515
-     * @param array $field_names_as_keys
516
-     * @return array
517
-     */
518
-    public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys)
519
-    {
520
-        $new_array = [];
521
-        foreach ($field_names_as_keys as $field_name => $value) {
522
-            $new_array[ ModelDataTranslator::prepareFieldNameFromJson($field_name) ] = $value;
523
-        }
524
-        return $new_array;
525
-    }
526
-
527
-
528
-    /**
529
-     * Prepares an array of model query params for use in the REST API
530
-     *
531
-     * @param array    $model_query_params
532
-     * @param EEM_Base $model
533
-     * @param string   $requested_version  eg "4.8.36". If null is provided, defaults to the latest release of the EE4
534
-     *                                     REST API
535
-     * @return array which can be passed into the EE4 REST API when querying a model resource
536
-     * @throws EE_Error
537
-     * @throws ReflectionException
538
-     */
539
-    public static function prepareQueryParamsForRestApi(
540
-        array $model_query_params,
541
-        EEM_Base $model,
542
-        $requested_version = null
543
-    ) {
544
-        if ($requested_version === null) {
545
-            $requested_version = EED_Core_Rest_Api::latest_rest_api_version();
546
-        }
547
-        $rest_query_params = $model_query_params;
548
-        if (isset($model_query_params[0])) {
549
-            $rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
550
-                $model_query_params[0],
551
-                $model,
552
-                $requested_version
553
-            );
554
-            unset($rest_query_params[0]);
555
-        }
556
-        if (isset($model_query_params['having'])) {
557
-            $rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
558
-                $model_query_params['having'],
559
-                $model,
560
-                $requested_version
561
-            );
562
-        }
563
-        return apply_filters(
564
-            'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
565
-            $rest_query_params,
566
-            $model_query_params,
567
-            $model,
568
-            $requested_version
569
-        );
570
-    }
571
-
572
-
573
-    /**
574
-     * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
575
-     *
576
-     * @param array    $inputted_query_params_of_this_type  eg like the "where" or "having" conditions query params
577
-     * @param EEM_Base $model
578
-     * @param string   $requested_version                   eg "4.8.36"
579
-     * @return array ready for use in the rest api query params
580
-     * @throws EE_Error
581
-     * @throws ObjectDetectedException if somehow a PHP object were in the query params' values,
582
-     * @throws ReflectionException
583
-     *                                                      (which would be really unusual)
584
-     * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
585
-     */
586
-    public static function prepareConditionsQueryParamsForRestApi(
587
-        $inputted_query_params_of_this_type,
588
-        EEM_Base $model,
589
-        $requested_version
590
-    ) {
591
-        $query_param_for_models = [];
592
-        foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
593
-            $field = ModelDataTranslator::deduceFieldFromQueryParam(
594
-                ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
595
-                $model
596
-            );
597
-            if ($field instanceof EE_Model_Field_Base) {
598
-                // did they specify an operator?
599
-                if (is_array($query_param_value)) {
600
-                    $op               = $query_param_value[0];
601
-                    $translated_value = [$op];
602
-                    if (isset($query_param_value[1])) {
603
-                        $value               = $query_param_value[1];
604
-                        $translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
605
-                            $field,
606
-                            $value,
607
-                            $requested_version
608
-                        );
609
-                    }
610
-                } else {
611
-                    $translated_value = ModelDataTranslator::prepareFieldValueForJson(
612
-                        $field,
613
-                        $query_param_value,
614
-                        $requested_version
615
-                    );
616
-                }
617
-                $query_param_for_models[ $query_param_key ] = $translated_value;
618
-            } else {
619
-                // so it's not for a field, assume it's a logic query param key
620
-                $query_param_for_models[ $query_param_key ] =
621
-                    ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
622
-                        $query_param_value,
623
-                        $model,
624
-                        $requested_version
625
-                    );
626
-            }
627
-        }
628
-        return $query_param_for_models;
629
-    }
630
-
631
-
632
-    /**
633
-     * @param $condition_query_param_key
634
-     * @return string
635
-     */
636
-    public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key)
637
-    {
638
-        $pos_of_star = strpos($condition_query_param_key, '*');
639
-        if ($pos_of_star === false) {
640
-            return $condition_query_param_key;
641
-        }
642
-        return substr($condition_query_param_key, 0, $pos_of_star);
643
-    }
644
-
645
-
646
-    /**
647
-     * Takes the input parameter and finds the model field that it indicates.
648
-     *
649
-     * @param string   $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
650
-     * @param EEM_Base $model
651
-     * @return EE_Model_Field_Base
652
-     * @throws EE_Error
653
-     * @throws ReflectionException
654
-     */
655
-    public static function deduceFieldFromQueryParam($query_param_name, EEM_Base $model)
656
-    {
657
-        // ok, now proceed with deducing which part is the model's name, and which is the field's name
658
-        // which will help us find the database table and column
659
-        $query_param_parts = explode('.', $query_param_name);
660
-        if (empty($query_param_parts)) {
661
-            throw new EE_Error(
662
-                sprintf(
663
-                    esc_html__(
664
-                        '_extract_column_name is empty when trying to extract column and table name from %s',
665
-                        'event_espresso'
666
-                    ),
667
-                    $query_param_name
668
-                )
669
-            );
670
-        }
671
-        $number_of_parts       = count($query_param_parts);
672
-        $last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
673
-        $field_name            = $last_query_param_part;
674
-        if ($number_of_parts !== 1) {
675
-            // the last part is the column name, and there are only 2parts. therefore...
676
-            $model = EE_Registry::instance()->load_model($query_param_parts[ $number_of_parts - 2 ]);
677
-        }
678
-        try {
679
-            return $model->field_settings_for($field_name, false);
680
-        } catch (EE_Error $e) {
681
-            return null;
682
-        }
683
-    }
684
-
685
-
686
-    /**
687
-     * Returns true if $data can be easily represented in JSON.
688
-     * Basically, objects and resources can't be represented in JSON easily.
689
-     *
690
-     * @param mixed $data
691
-     * @return bool
692
-     */
693
-    protected static function isRepresentableInJson($data)
694
-    {
695
-        return is_scalar($data)
696
-               || is_array($data)
697
-               || is_null($data);
698
-    }
42
+	/**
43
+	 * We used to use -1 for infinity in the rest api, but that's ambiguous for
44
+	 * fields that COULD contain -1; so we use null
45
+	 */
46
+	const EE_INF_IN_REST = null;
47
+
48
+
49
+	/**
50
+	 * Prepares a possible array of input values from JSON for use by the models
51
+	 *
52
+	 * @param EE_Model_Field_Base $field_obj
53
+	 * @param mixed               $original_value
54
+	 * @param string              $requested_version
55
+	 * @param string              $timezone_string treat values as being in this timezone
56
+	 * @return mixed
57
+	 * @throws RestException
58
+	 * @throws EE_Error
59
+	 */
60
+	public static function prepareFieldValuesFromJson(
61
+		$field_obj,
62
+		$original_value,
63
+		$requested_version,
64
+		$timezone_string = 'UTC'
65
+	) {
66
+		if (is_array($original_value) && ! $field_obj instanceof EE_Serialized_Text_Field) {
67
+			$new_value = [];
68
+			foreach ($original_value as $array_key => $array_item) {
69
+				$new_value[ $array_key ] = ModelDataTranslator::prepareFieldValueFromJson(
70
+					$field_obj,
71
+					$array_item,
72
+					$requested_version,
73
+					$timezone_string
74
+				);
75
+			}
76
+		} else {
77
+			$new_value = ModelDataTranslator::prepareFieldValueFromJson(
78
+				$field_obj,
79
+				$original_value,
80
+				$requested_version,
81
+				$timezone_string
82
+			);
83
+		}
84
+		return $new_value;
85
+	}
86
+
87
+
88
+	/**
89
+	 * Prepares an array of field values FOR use in JSON/REST API
90
+	 *
91
+	 * @param EE_Model_Field_Base $field_obj
92
+	 * @param mixed               $original_value
93
+	 * @param string              $request_version (eg 4.8.36)
94
+	 * @return array
95
+	 * @throws EE_Error
96
+	 */
97
+	public static function prepareFieldValuesForJson($field_obj, $original_value, $request_version)
98
+	{
99
+		if (is_array($original_value)) {
100
+			$new_value = [];
101
+			foreach ($original_value as $key => $value) {
102
+				$new_value[ $key ] = ModelDataTranslator::prepareFieldValuesForJson(
103
+					$field_obj,
104
+					$value,
105
+					$request_version
106
+				);
107
+			}
108
+		} else {
109
+			$new_value = ModelDataTranslator::prepareFieldValueForJson(
110
+				$field_obj,
111
+				$original_value,
112
+				$request_version
113
+			);
114
+		}
115
+		return $new_value;
116
+	}
117
+
118
+
119
+	/**
120
+	 * Prepares incoming data from the json or request parameters for the models'
121
+	 * "$query_params".
122
+	 *
123
+	 * @param EE_Model_Field_Base $field_obj
124
+	 * @param mixed               $original_value
125
+	 * @param string              $requested_version
126
+	 * @param string              $timezone_string treat values as being in this timezone
127
+	 * @return mixed
128
+	 * @throws RestException
129
+	 * @throws DomainException
130
+	 * @throws EE_Error
131
+	 */
132
+	public static function prepareFieldValueFromJson(
133
+		$field_obj,
134
+		$original_value,
135
+		$requested_version,
136
+		$timezone_string = 'UTC'
137
+	) {
138
+		// check if they accidentally submitted an error value. If so throw an exception
139
+		if (
140
+			is_array($original_value)
141
+			&& isset($original_value['error_code'], $original_value['error_message'])
142
+		) {
143
+			throw new RestException(
144
+				'rest_submitted_error_value',
145
+				sprintf(
146
+					esc_html__(
147
+						'You tried to submit a JSON error object as a value for %1$s. That\'s not allowed.',
148
+						'event_espresso'
149
+					),
150
+					$field_obj->get_name()
151
+				),
152
+				[
153
+					'status' => 400,
154
+				]
155
+			);
156
+		}
157
+		$timezone_string = EEH_DTT_Helper::get_valid_timezone_string($timezone_string);
158
+		$new_value = null;
159
+		// double-check for serialized PHP. We never accept serialized PHP. No way Jose.
160
+		ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
161
+		$timezone_string = $timezone_string !== ''
162
+			? $timezone_string
163
+			: get_option('timezone_string', '');
164
+		// walk through the submitted data and double-check for serialized PHP.
165
+		// We never accept serialized PHP. No way Jose.
166
+		ModelDataTranslator::throwExceptionIfContainsSerializedData($original_value);
167
+		if (
168
+			$field_obj instanceof EE_Infinite_Integer_Field
169
+			&& in_array($original_value, [null, ''], true)
170
+		) {
171
+			$new_value = EE_INF;
172
+		} elseif ($field_obj instanceof EE_Datetime_Field) {
173
+			$new_value = self::getTimestampWithTimezoneOffset($original_value, $field_obj, $timezone_string);
174
+			$new_value = rest_parse_date($new_value);
175
+			if ($new_value === false) {
176
+				throw new RestException(
177
+					'invalid_format_for_timestamp',
178
+					sprintf(
179
+						esc_html__(
180
+							'Timestamps received on a request as the value for Date and Time fields must be in %1$s/%2$s format.  The timestamp provided (%3$s) is not that format.',
181
+							'event_espresso'
182
+						),
183
+						'RFC3339',
184
+						'ISO8601',
185
+						$original_value
186
+					),
187
+					[
188
+						'status' => 400,
189
+					]
190
+				);
191
+			}
192
+		} elseif ($field_obj instanceof EE_Boolean_Field) {
193
+			// Interpreted the strings "false", "true", "on", "off" appropriately.
194
+			$new_value = filter_var($original_value, FILTER_VALIDATE_BOOLEAN);
195
+		} else {
196
+			$new_value = $original_value;
197
+		}
198
+		return $new_value;
199
+	}
200
+
201
+
202
+	/**
203
+	 * This checks if the incoming timestamp has timezone information already on it and if it doesn't then adds timezone
204
+	 * information via details obtained from the host site.
205
+	 *
206
+	 * @param string            $original_timestamp
207
+	 * @param EE_Datetime_Field $datetime_field
208
+	 * @param                   $timezone_string
209
+	 * @return string
210
+	 * @throws DomainException
211
+	 */
212
+	private static function getTimestampWithTimezoneOffset(
213
+		$original_timestamp,
214
+		EE_Datetime_Field $datetime_field,
215
+		$timezone_string
216
+	) {
217
+		// already have timezone information?
218
+		if (preg_match('/Z|([+-])(\d{2}:\d{2})/', $original_timestamp)) {
219
+			// yes, we're ignoring the timezone.
220
+			return $original_timestamp;
221
+		}
222
+		// need to append timezone
223
+		list($offset_sign, $offset_secs) = self::parseTimezoneOffset(
224
+			$datetime_field->get_timezone_offset(
225
+				new DateTimeZone($timezone_string),
226
+				$original_timestamp
227
+			)
228
+		);
229
+		$offset_string =
230
+			str_pad(
231
+				floor($offset_secs / HOUR_IN_SECONDS),
232
+				2,
233
+				'0',
234
+				STR_PAD_LEFT
235
+			)
236
+			. ':'
237
+			. str_pad(
238
+				($offset_secs % HOUR_IN_SECONDS) / MINUTE_IN_SECONDS,
239
+				2,
240
+				'0',
241
+				STR_PAD_LEFT
242
+			);
243
+		return $original_timestamp . $offset_sign . $offset_string;
244
+	}
245
+
246
+
247
+	/**
248
+	 * Throws an exception if $data is a serialized PHP string (or somehow an actually PHP object, although I don't
249
+	 * think that can happen). If $data is an array, recurses into its keys and values
250
+	 *
251
+	 * @param mixed $data
252
+	 * @return void
253
+	 * @throws RestException
254
+	 */
255
+	public static function throwExceptionIfContainsSerializedData($data)
256
+	{
257
+		if (is_array($data)) {
258
+			foreach ($data as $key => $value) {
259
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($key);
260
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($value);
261
+			}
262
+		} else {
263
+			if (is_serialized($data) || is_object($data)) {
264
+				throw new RestException(
265
+					'serialized_data_submission_prohibited',
266
+					esc_html__(
267
+					// @codingStandardsIgnoreStart
268
+						'You tried to submit a string of serialized text. Serialized PHP is prohibited over the EE4 REST API.',
269
+						// @codingStandardsIgnoreEnd
270
+						'event_espresso'
271
+					)
272
+				);
273
+			}
274
+		}
275
+	}
276
+
277
+
278
+	/**
279
+	 * determines what's going on with them timezone strings
280
+	 *
281
+	 * @param int $timezone_offset
282
+	 * @return array
283
+	 */
284
+	private static function parseTimezoneOffset($timezone_offset)
285
+	{
286
+		$first_char = substr((string) $timezone_offset, 0, 1);
287
+		if ($first_char === '+' || $first_char === '-') {
288
+			$offset_sign = $first_char;
289
+			$offset_secs = substr((string) $timezone_offset, 1);
290
+		} else {
291
+			$offset_sign = '+';
292
+			$offset_secs = $timezone_offset;
293
+		}
294
+		return [$offset_sign, $offset_secs];
295
+	}
296
+
297
+
298
+	/**
299
+	 * Prepares a field's value for display in the API
300
+	 *
301
+	 * @param EE_Model_Field_Base $field_obj
302
+	 * @param mixed               $original_value
303
+	 * @param string              $requested_version
304
+	 * @return mixed
305
+	 * @throws EE_Error
306
+	 */
307
+	public static function prepareFieldValueForJson($field_obj, $original_value, $requested_version)
308
+	{
309
+		if ($original_value === EE_INF) {
310
+			$new_value = ModelDataTranslator::EE_INF_IN_REST;
311
+		} elseif ($field_obj instanceof EE_Datetime_Field) {
312
+			if (is_string($original_value)) {
313
+				// did they submit a string of a unix timestamp?
314
+				if (is_numeric($original_value)) {
315
+					$datetime_obj = new DateTime();
316
+					$datetime_obj->setTimestamp((int) $original_value);
317
+				} else {
318
+					// first, check if its a MySQL timestamp in GMT
319
+					$datetime_obj = DateTime::createFromFormat('Y-m-d H:i:s', $original_value);
320
+				}
321
+				if (! $datetime_obj instanceof DateTime) {
322
+					// so it's not a unix timestamp or a MySQL timestamp. Maybe its in the field's date/time format?
323
+					$datetime_obj = $field_obj->prepare_for_set($original_value);
324
+				}
325
+				$original_value = $datetime_obj;
326
+			}
327
+			if ($original_value instanceof DateTime) {
328
+				$new_value = $original_value->format('Y-m-d H:i:s');
329
+			} elseif (is_int($original_value) || is_float($original_value)) {
330
+				$new_value = date('Y-m-d H:i:s', $original_value);
331
+			} elseif ($original_value === null || $original_value === '') {
332
+				$new_value = null;
333
+			} else {
334
+				// so it's not a datetime object, unix timestamp (as string or int),
335
+				// MySQL timestamp, or even a string in the field object's format. So no idea what it is
336
+				throw new EE_Error(
337
+					sprintf(
338
+						esc_html__(
339
+						// @codingStandardsIgnoreStart
340
+							'The value "%1$s" for the field "%2$s" on model "%3$s" could not be understood. It should be a PHP DateTime, unix timestamp, MySQL date, or string in the format "%4$s".',
341
+							// @codingStandardsIgnoreEnd
342
+							'event_espresso'
343
+						),
344
+						$original_value,
345
+						$field_obj->get_name(),
346
+						$field_obj->get_model_name(),
347
+						$field_obj->get_time_format() . ' ' . $field_obj->get_time_format()
348
+					)
349
+				);
350
+			}
351
+			if ($new_value !== null) {
352
+				// phpcs:disable PHPCompatibility.Extensions.RemovedExtensions.mysql_DeprecatedRemoved
353
+				$new_value = mysql_to_rfc3339($new_value);
354
+				// phpcs:enable
355
+			}
356
+		} else {
357
+			$new_value = $original_value;
358
+		}
359
+		// are we about to send an object? just don't. We have no good way to represent it in JSON.
360
+		// can't just check using is_object() because that missed PHP incomplete objects
361
+		if (! ModelDataTranslator::isRepresentableInJson($new_value)) {
362
+			$new_value = [
363
+				'error_code'    => 'php_object_not_return',
364
+				'error_message' => esc_html__(
365
+					'The value of this field in the database is a PHP object, which can\'t be represented in JSON.',
366
+					'event_espresso'
367
+				),
368
+			];
369
+		}
370
+		return apply_filters(
371
+			'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_field_for_rest_api',
372
+			$new_value,
373
+			$field_obj,
374
+			$original_value,
375
+			$requested_version
376
+		);
377
+	}
378
+
379
+
380
+	/**
381
+	 * Prepares condition-query-parameters (like what's in where and having) from
382
+	 * the format expected in the API to use in the models
383
+	 *
384
+	 * @param array    $inputted_query_params_of_this_type
385
+	 * @param EEM_Base $model
386
+	 * @param string   $requested_version
387
+	 * @param boolean  $writing whether this data will be written to the DB, or if we're just building a query.
388
+	 *                          If we're writing to the DB, we don't expect any operators, or any logic query
389
+	 *                          parameters, and we also won't accept serialized data unless the current user has
390
+	 *                          unfiltered_html.
391
+	 * @return array
392
+	 * @throws DomainException
393
+	 * @throws EE_Error
394
+	 * @throws RestException
395
+	 * @throws InvalidDataTypeException
396
+	 * @throws InvalidInterfaceException
397
+	 * @throws InvalidArgumentException
398
+	 * @throws ReflectionException
399
+	 */
400
+	public static function prepareConditionsQueryParamsForModels(
401
+		$inputted_query_params_of_this_type,
402
+		EEM_Base $model,
403
+		$requested_version,
404
+		$writing = false
405
+	) {
406
+		$query_param_for_models = [];
407
+		$context                = new RestIncomingQueryParamContext($model, $requested_version, $writing);
408
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
409
+			$query_param_meta = new RestIncomingQueryParamMetadata($query_param_key, $query_param_value, $context);
410
+			if ($query_param_meta->getField() instanceof EE_Model_Field_Base) {
411
+				$translated_value = $query_param_meta->determineConditionsQueryParameterValue();
412
+				if (
413
+					$translated_value === null
414
+					|| (
415
+						isset($query_param_for_models[ $query_param_meta->getQueryParamKey() ])
416
+						&& $query_param_meta->isGmtField()
417
+					)
418
+				) {
419
+					// they have already provided a non-gmt field, ignore the gmt one. That's what WP core
420
+					// currently does (they might change it though). See https://core.trac.wordpress.org/ticket/39954
421
+					// OR we couldn't create a translated value from their input
422
+					continue;
423
+				}
424
+				$query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $translated_value;
425
+			} else {
426
+				$nested_query_params = $query_param_meta->determineNestedConditionQueryParameters();
427
+				if ($nested_query_params) {
428
+					$query_param_for_models[ $query_param_meta->getQueryParamKey() ] = $nested_query_params;
429
+				}
430
+			}
431
+		}
432
+		return $query_param_for_models;
433
+	}
434
+
435
+
436
+	/**
437
+	 * Mostly checks if the last 4 characters are "_gmt", indicating its a
438
+	 * gmt date field name
439
+	 *
440
+	 * @param string $field_name
441
+	 * @return boolean
442
+	 */
443
+	public static function isGmtDateFieldName($field_name)
444
+	{
445
+		return substr(
446
+			ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($field_name),
447
+			-4,
448
+			4
449
+		) === '_gmt';
450
+	}
451
+
452
+
453
+	/**
454
+	 * Removes the last "_gmt" part of a field name (and if there is no "_gmt" at the end, leave it alone)
455
+	 *
456
+	 * @param string $field_name
457
+	 * @return string
458
+	 */
459
+	public static function removeGmtFromFieldName($field_name)
460
+	{
461
+		if (! ModelDataTranslator::isGmtDateFieldName($field_name)) {
462
+			return $field_name;
463
+		}
464
+		$query_param_sans_stars              =
465
+			ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
466
+				$field_name
467
+			);
468
+		$query_param_sans_gmt_and_sans_stars = substr(
469
+			$query_param_sans_stars,
470
+			0,
471
+			strrpos(
472
+				$field_name,
473
+				'_gmt'
474
+			)
475
+		);
476
+		return str_replace($query_param_sans_stars, $query_param_sans_gmt_and_sans_stars, $field_name);
477
+	}
478
+
479
+
480
+	/**
481
+	 * Takes a field name from the REST API and prepares it for the model querying
482
+	 *
483
+	 * @param string $field_name
484
+	 * @return string
485
+	 */
486
+	public static function prepareFieldNameFromJson($field_name)
487
+	{
488
+		if (ModelDataTranslator::isGmtDateFieldName($field_name)) {
489
+			return ModelDataTranslator::removeGmtFromFieldName($field_name);
490
+		}
491
+		return $field_name;
492
+	}
493
+
494
+
495
+	/**
496
+	 * Takes array of field names from REST API and prepares for models
497
+	 *
498
+	 * @param array $field_names
499
+	 * @return array of field names (possibly include model prefixes)
500
+	 */
501
+	public static function prepareFieldNamesFromJson(array $field_names)
502
+	{
503
+		$new_array = [];
504
+		foreach ($field_names as $key => $field_name) {
505
+			$new_array[ $key ] = ModelDataTranslator::prepareFieldNameFromJson($field_name);
506
+		}
507
+		return $new_array;
508
+	}
509
+
510
+
511
+	/**
512
+	 * Takes array where array keys are field names (possibly with model path prefixes)
513
+	 * from the REST API and prepares them for model querying
514
+	 *
515
+	 * @param array $field_names_as_keys
516
+	 * @return array
517
+	 */
518
+	public static function prepareFieldNamesInArrayKeysFromJson(array $field_names_as_keys)
519
+	{
520
+		$new_array = [];
521
+		foreach ($field_names_as_keys as $field_name => $value) {
522
+			$new_array[ ModelDataTranslator::prepareFieldNameFromJson($field_name) ] = $value;
523
+		}
524
+		return $new_array;
525
+	}
526
+
527
+
528
+	/**
529
+	 * Prepares an array of model query params for use in the REST API
530
+	 *
531
+	 * @param array    $model_query_params
532
+	 * @param EEM_Base $model
533
+	 * @param string   $requested_version  eg "4.8.36". If null is provided, defaults to the latest release of the EE4
534
+	 *                                     REST API
535
+	 * @return array which can be passed into the EE4 REST API when querying a model resource
536
+	 * @throws EE_Error
537
+	 * @throws ReflectionException
538
+	 */
539
+	public static function prepareQueryParamsForRestApi(
540
+		array $model_query_params,
541
+		EEM_Base $model,
542
+		$requested_version = null
543
+	) {
544
+		if ($requested_version === null) {
545
+			$requested_version = EED_Core_Rest_Api::latest_rest_api_version();
546
+		}
547
+		$rest_query_params = $model_query_params;
548
+		if (isset($model_query_params[0])) {
549
+			$rest_query_params['where'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
550
+				$model_query_params[0],
551
+				$model,
552
+				$requested_version
553
+			);
554
+			unset($rest_query_params[0]);
555
+		}
556
+		if (isset($model_query_params['having'])) {
557
+			$rest_query_params['having'] = ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
558
+				$model_query_params['having'],
559
+				$model,
560
+				$requested_version
561
+			);
562
+		}
563
+		return apply_filters(
564
+			'FHEE__EventEspresso\core\libraries\rest_api\Model_Data_Translator__prepare_query_params_for_rest_api',
565
+			$rest_query_params,
566
+			$model_query_params,
567
+			$model,
568
+			$requested_version
569
+		);
570
+	}
571
+
572
+
573
+	/**
574
+	 * Prepares all the sub-conditions query parameters (eg having or where conditions) for use in the rest api
575
+	 *
576
+	 * @param array    $inputted_query_params_of_this_type  eg like the "where" or "having" conditions query params
577
+	 * @param EEM_Base $model
578
+	 * @param string   $requested_version                   eg "4.8.36"
579
+	 * @return array ready for use in the rest api query params
580
+	 * @throws EE_Error
581
+	 * @throws ObjectDetectedException if somehow a PHP object were in the query params' values,
582
+	 * @throws ReflectionException
583
+	 *                                                      (which would be really unusual)
584
+	 * @see https://github.com/eventespresso/event-espresso-core/tree/master/docs/G--Model-System/model-query-params.md#0-where-conditions
585
+	 */
586
+	public static function prepareConditionsQueryParamsForRestApi(
587
+		$inputted_query_params_of_this_type,
588
+		EEM_Base $model,
589
+		$requested_version
590
+	) {
591
+		$query_param_for_models = [];
592
+		foreach ($inputted_query_params_of_this_type as $query_param_key => $query_param_value) {
593
+			$field = ModelDataTranslator::deduceFieldFromQueryParam(
594
+				ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey($query_param_key),
595
+				$model
596
+			);
597
+			if ($field instanceof EE_Model_Field_Base) {
598
+				// did they specify an operator?
599
+				if (is_array($query_param_value)) {
600
+					$op               = $query_param_value[0];
601
+					$translated_value = [$op];
602
+					if (isset($query_param_value[1])) {
603
+						$value               = $query_param_value[1];
604
+						$translated_value[1] = ModelDataTranslator::prepareFieldValuesForJson(
605
+							$field,
606
+							$value,
607
+							$requested_version
608
+						);
609
+					}
610
+				} else {
611
+					$translated_value = ModelDataTranslator::prepareFieldValueForJson(
612
+						$field,
613
+						$query_param_value,
614
+						$requested_version
615
+					);
616
+				}
617
+				$query_param_for_models[ $query_param_key ] = $translated_value;
618
+			} else {
619
+				// so it's not for a field, assume it's a logic query param key
620
+				$query_param_for_models[ $query_param_key ] =
621
+					ModelDataTranslator::prepareConditionsQueryParamsForRestApi(
622
+						$query_param_value,
623
+						$model,
624
+						$requested_version
625
+					);
626
+			}
627
+		}
628
+		return $query_param_for_models;
629
+	}
630
+
631
+
632
+	/**
633
+	 * @param $condition_query_param_key
634
+	 * @return string
635
+	 */
636
+	public static function removeStarsAndAnythingAfterFromConditionQueryParamKey($condition_query_param_key)
637
+	{
638
+		$pos_of_star = strpos($condition_query_param_key, '*');
639
+		if ($pos_of_star === false) {
640
+			return $condition_query_param_key;
641
+		}
642
+		return substr($condition_query_param_key, 0, $pos_of_star);
643
+	}
644
+
645
+
646
+	/**
647
+	 * Takes the input parameter and finds the model field that it indicates.
648
+	 *
649
+	 * @param string   $query_param_name like Registration.Transaction.TXN_ID, Event.Datetime.start_time, or REG_ID
650
+	 * @param EEM_Base $model
651
+	 * @return EE_Model_Field_Base
652
+	 * @throws EE_Error
653
+	 * @throws ReflectionException
654
+	 */
655
+	public static function deduceFieldFromQueryParam($query_param_name, EEM_Base $model)
656
+	{
657
+		// ok, now proceed with deducing which part is the model's name, and which is the field's name
658
+		// which will help us find the database table and column
659
+		$query_param_parts = explode('.', $query_param_name);
660
+		if (empty($query_param_parts)) {
661
+			throw new EE_Error(
662
+				sprintf(
663
+					esc_html__(
664
+						'_extract_column_name is empty when trying to extract column and table name from %s',
665
+						'event_espresso'
666
+					),
667
+					$query_param_name
668
+				)
669
+			);
670
+		}
671
+		$number_of_parts       = count($query_param_parts);
672
+		$last_query_param_part = $query_param_parts[ count($query_param_parts) - 1 ];
673
+		$field_name            = $last_query_param_part;
674
+		if ($number_of_parts !== 1) {
675
+			// the last part is the column name, and there are only 2parts. therefore...
676
+			$model = EE_Registry::instance()->load_model($query_param_parts[ $number_of_parts - 2 ]);
677
+		}
678
+		try {
679
+			return $model->field_settings_for($field_name, false);
680
+		} catch (EE_Error $e) {
681
+			return null;
682
+		}
683
+	}
684
+
685
+
686
+	/**
687
+	 * Returns true if $data can be easily represented in JSON.
688
+	 * Basically, objects and resources can't be represented in JSON easily.
689
+	 *
690
+	 * @param mixed $data
691
+	 * @return bool
692
+	 */
693
+	protected static function isRepresentableInJson($data)
694
+	{
695
+		return is_scalar($data)
696
+			   || is_array($data)
697
+			   || is_null($data);
698
+	}
699 699
 }
Please login to merge, or discard this patch.
core/libraries/rest_api/RestIncomingQueryParamMetadata.php 1 patch
Indentation   +787 added lines, -787 removed lines patch added patch discarded remove patch
@@ -29,793 +29,793 @@
 block discarded – undo
29 29
  */
30 30
 class RestIncomingQueryParamMetadata
31 31
 {
32
-    private $query_param_key;
33
-
34
-    private $query_param_value;
35
-
36
-    /**
37
-     * @var RestIncomingQueryParamContext
38
-     */
39
-    private $context;
40
-
41
-    /**
42
-     * @var EE_Model_Field_Base|null
43
-     */
44
-    private $field;
45
-
46
-    /**
47
-     * @var string same as $query_param_key but has the * and anything after it removed
48
-     */
49
-    private $query_param_key_sans_stars;
50
-
51
-    /**
52
-     * @var string for timezone or timezone offset
53
-     */
54
-    private $timezone;
55
-
56
-    /**
57
-     * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
58
-     */
59
-    private $is_gmt_field = false;
60
-
61
-
62
-    /**
63
-     * RestIncomingQueryParamMetadata constructor.
64
-     * You probably want to call
65
-     *
66
-     * @param string                        $query_param_key
67
-     * @param string                        $query_param_value
68
-     * @param RestIncomingQueryParamContext $context
69
-     * @throws EE_Error
70
-     * @throws EE_Error
71
-     * @throws ReflectionException
72
-     */
73
-    public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
74
-    {
75
-        $this->query_param_key   = $query_param_key;
76
-        $this->query_param_value = $query_param_value;
77
-        $this->context           = $context;
78
-        $this->determineFieldAndTimezone();
79
-    }
80
-
81
-
82
-    /**
83
-     * Gets the query parameter key. This may have been modified (see setQueryParamValue())
84
-     *
85
-     * @return string
86
-     */
87
-    public function getQueryParamKey()
88
-    {
89
-        return $this->query_param_key;
90
-    }
91
-
92
-
93
-    /**
94
-     * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
95
-     * query parameters into the legacy structure)
96
-     *
97
-     * @param string|array|int|float $query_param_value
98
-     */
99
-    private function setQueryParamValue($query_param_value)
100
-    {
101
-        $this->query_param_value = $query_param_value;
102
-    }
103
-
104
-
105
-    /**
106
-     * Gets the original query parameter value passed in.
107
-     *
108
-     * @return array|string
109
-     */
110
-    public function getQueryParamValue()
111
-    {
112
-        return $this->query_param_value;
113
-    }
114
-
115
-
116
-    /**
117
-     * Gets the context object.
118
-     *
119
-     * @return RestIncomingQueryParamContext
120
-     */
121
-    public function getContext()
122
-    {
123
-        return $this->context;
124
-    }
125
-
126
-
127
-    /**
128
-     * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
129
-     *
130
-     * @param string $query_param_key
131
-     */
132
-    private function setQueryParamKey($query_param_key)
133
-    {
134
-        $this->query_param_key = $query_param_key;
135
-    }
136
-
137
-
138
-    /**
139
-     * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
140
-     * did not indicate a field, eg if it were `OR`).
141
-     *
142
-     * @return EE_Model_Field_Base|null
143
-     */
144
-    public function getField()
145
-    {
146
-        return $this->field;
147
-    }
148
-
149
-
150
-    /**
151
-     * Gets the query parameter key (with the star and everything afterwards removed).
152
-     *
153
-     * @return string
154
-     */
155
-    public function getQueryParamKeySansStars()
156
-    {
157
-        return $this->query_param_key_sans_stars;
158
-    }
159
-
160
-
161
-    /**
162
-     * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
163
-     *
164
-     * @return string
165
-     */
166
-    public function getTimezone()
167
-    {
168
-        return $this->timezone;
169
-    }
170
-
171
-
172
-    /**
173
-     * Returns whether or not this is a GMT field
174
-     *
175
-     * @return boolean
176
-     */
177
-    public function isGmtField()
178
-    {
179
-        return $this->is_gmt_field;
180
-    }
181
-
182
-
183
-    /**
184
-     * Sets the field indicated by the query parameter key (might be null).
185
-     *
186
-     * @param EE_Model_Field_Base|null $field
187
-     */
188
-    private function setField(EE_Model_Field_Base $field = null)
189
-    {
190
-        $this->field = $field;
191
-    }
192
-
193
-
194
-    /**
195
-     * Sets the query parameter key-with-stars-removed.
196
-     *
197
-     * @param string $query_param_key_sans_stars
198
-     */
199
-    private function setQueryParamKeySansStars($query_param_key_sans_stars)
200
-    {
201
-        $this->query_param_key_sans_stars = $query_param_key_sans_stars;
202
-    }
203
-
204
-
205
-    /**
206
-     * Sets the timezone (this could be a timezone offset string).
207
-     *
208
-     * @param string $timezone
209
-     */
210
-    private function setTimezone($timezone)
211
-    {
212
-        $this->timezone = $timezone;
213
-    }
214
-
215
-
216
-    /**
217
-     * @param mixed $is_gmt_field
218
-     */
219
-    private function setIsGmtField($is_gmt_field)
220
-    {
221
-        $this->is_gmt_field = $is_gmt_field;
222
-    }
223
-
224
-
225
-    /**
226
-     * Determines what field, query param name, and query param name without stars, and timezone to use.
227
-     *
228
-     * @return void {
229
-     * @throws EE_Error
230
-     * @throws InvalidDataTypeException
231
-     * @throws InvalidInterfaceException
232
-     * @throws InvalidArgumentException
233
-     * @throws ReflectionException
234
-     * @since 4.9.72.p
235
-     */
236
-    private function determineFieldAndTimezone()
237
-    {
238
-        $this->setQueryParamKeySansStars(
239
-            ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
240
-                $this->getQueryParamKey()
241
-            )
242
-        );
243
-        $this->setField(
244
-            ModelDataTranslator::deduceFieldFromQueryParam(
245
-                $this->getQueryParamKeySansStars(),
246
-                $this->getContext()->getModel()
247
-            )
248
-        );
249
-        // double-check is it a *_gmt field?
250
-        if (
251
-            ! $this->getField() instanceof EE_Model_Field_Base
252
-            && ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
253
-        ) {
254
-            // yep, take off '_gmt', and find the field
255
-            $this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
256
-            $this->setField(
257
-                ModelDataTranslator::deduceFieldFromQueryParam(
258
-                    $this->getQueryParamKey(),
259
-                    $this->context->getModel()
260
-                )
261
-            );
262
-            $this->setTimezone('UTC');
263
-            $this->setIsGmtField(true);
264
-        } elseif ($this->getField() instanceof EE_Datetime_Field) {
265
-            // so it's not a GMT field. Set the timezone on the model to the default
266
-            $this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
267
-        } else {
268
-            // just keep using what's already set for the timezone
269
-            $this->setTimezone($this->context->getModel()->get_timezone());
270
-        }
271
-        $this->assertOnlyAdminCanReadPasswordFields();
272
-    }
273
-
274
-
275
-    /**
276
-     * Throws an exception if a non-admin is trying to query by password.
277
-     *
278
-     * @throws RestException
279
-     * @since 4.9.74.p
280
-     */
281
-    private function assertOnlyAdminCanReadPasswordFields()
282
-    {
283
-        if (
284
-            $this->getField() instanceof EE_Password_Field
285
-            && ! current_user_can(EE_Restriction_Generator_Base::get_default_restrictions_cap())
286
-        ) {
287
-            // only full admins can query by password. sorry bub!
288
-            throw new RestException(
289
-                'only_admins_can_query_by_password',
290
-                // @codingStandardsIgnoreStart
291
-                esc_html__(
292
-                    'You attempted to filter by a password field without the needed privileges. Only a full admin is allowed to do that.',
293
-                    'event_espresso'
294
-                ),
295
-                // @codingStandardsIgnoreEnd
296
-                [
297
-                    'status' => 403,
298
-                ]
299
-            );
300
-        }
301
-    }
302
-
303
-
304
-    /**
305
-     * Given a ton of input, determines the value to use for the models.
306
-     *
307
-     * @return array|null
308
-     * @throws DomainException
309
-     * @throws EE_Error
310
-     * @throws RestException
311
-     * @throws DomainException
312
-     * @since 4.9.72.p
313
-     */
314
-    public function determineConditionsQueryParameterValue()
315
-    {
316
-        if ($this->valueIsArrayDuringRead()) {
317
-            return $this->determineModelValueGivenRestInputArray();
318
-        }
319
-        return $this->prepareValuesFromJson($this->getQueryParamValue());
320
-    }
321
-
322
-
323
-    /**
324
-     * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
325
-     *
326
-     * @return array|null
327
-     * @throws RestException
328
-     * @throws EE_Error
329
-     * @since 4.9.72.p
330
-     */
331
-    private function determineModelValueGivenRestInputArray()
332
-    {
333
-        $this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
334
-        // did they specify an operator?
335
-        if ($this->valueIsLegacySpecifiedOperator()) {
336
-            $query_param_value = $this->getQueryParamValue();
337
-            $sub_array_key     = $query_param_value[0];
338
-            $translated_value  = [$sub_array_key];
339
-            if ($this->operatorIsNAry($sub_array_key)) {
340
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
341
-            } elseif ($this->operatorIsTernary($sub_array_key)) {
342
-                $translated_value[] = [
343
-                    $this->prepareValuesFromJson($query_param_value[1][0]),
344
-                    $this->prepareValuesFromJson($query_param_value[1][1]),
345
-                ];
346
-            } elseif ($this->operatorIsLike($sub_array_key)) {
347
-                // we want to leave this value mostly-as-is (eg don't force it to be a float
348
-                // or a boolean or an enum value. Leave it as-is with wildcards etc)
349
-                // but do verify it at least doesn't have any serialized data
350
-                ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
351
-                $translated_value[] = $query_param_value[1];
352
-            } elseif ($this->operatorIsUnary($sub_array_key)) {
353
-                // no arguments should have been provided, so don't look for any
354
-            } elseif ($this->operatorIsBinary($sub_array_key)) {
355
-                // it's a valid operator, but none of the exceptions. Treat it normally.
356
-                $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
357
-            } else {
358
-                // so they provided a valid operator, but wrong number of arguments
359
-                $this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
360
-                $translated_value = null;
361
-            }
362
-        } else {
363
-            // so they didn't provide a valid operator
364
-            // if we aren't in debug mode, then just try our best to fulfill the user's request
365
-            $this->throwInvalidOperatorExceptionIfDebugging();
366
-            $translated_value = null;
367
-        }
368
-        return $translated_value;
369
-    }
370
-
371
-
372
-    /**
373
-     * Returns if this request is a "read" request and the value provided was an array.
374
-     * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
375
-     *
376
-     * @return boolean
377
-     * @since 4.9.72.p
378
-     */
379
-    private function valueIsArrayDuringRead()
380
-    {
381
-        return ! $this->getContext()->isWriting() && is_array($this->getQueryParamValue());
382
-    }
383
-
384
-
385
-    /**
386
-     * Returns if the value provided was an associative array (we should have already verified it's an array of some
387
-     * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
388
-     *
389
-     * @return boolean
390
-     * @since 4.9.72.p
391
-     */
392
-    private function valueIsAssociativeArray()
393
-    {
394
-        return ! EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue());
395
-    }
396
-
397
-
398
-    /**
399
-     * Checks if the array value is itself an array that fits into the simplified specified operator structure
400
-     * (eg `array('!=' => 123)`).
401
-     *
402
-     * @return boolean
403
-     * @since 4.9.72.p
404
-     */
405
-    private function valueIsSimplifiedSpecifiedOperator()
406
-    {
407
-        return count($this->getQueryParamValue()) === 1
408
-               && array_key_exists(
409
-                   key($this->getQueryParamValue()),
410
-                   $this->getContext()->getModel()->valid_operators()
411
-               );
412
-    }
413
-
414
-
415
-    /**
416
-     * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
417
-     * of either comma-separated-values, or a JSON array.
418
-     *
419
-     * @param $sub_array_key
420
-     * @param $sub_array_value
421
-     * @throws RestException
422
-     * @since 4.9.72.p
423
-     */
424
-    private function assertSubValueIsNotArray($sub_array_key, $sub_array_value)
425
-    {
426
-        if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
427
-            throw new RestException(
428
-                'csv_or_json_string_only',
429
-                sprintf(
430
-                /* translators: 1: variable name*/
431
-                    esc_html__(
432
-                        'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
433
-                        'event_espresso'
434
-                    ),
435
-                    $sub_array_key
436
-                ),
437
-                [
438
-                    'status' => 400,
439
-                ]
440
-            );
441
-        }
442
-    }
443
-
444
-
445
-    /**
446
-     * Determines if the sub-array key is an operator taking 3 or more operators.
447
-     *
448
-     * @param $sub_array_key
449
-     * @return boolean
450
-     * @since 4.9.72.p
451
-     */
452
-    private function subArrayKeyIsNonBinaryOperator($sub_array_key)
453
-    {
454
-        return array_key_exists(
455
-            $sub_array_key,
456
-            array_merge(
457
-                $this->getContext()->getModel()->valid_in_style_operators(),
458
-                $this->getContext()->getModel()->valid_between_style_operators()
459
-            )
460
-        );
461
-    }
462
-
463
-
464
-    /**
465
-     * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
466
-     *
467
-     * @param string $sub_array_key
468
-     * @return boolean
469
-     * @since 4.9.72.p
470
-     */
471
-    private function subArrayKeyIsUnaryOperator($sub_array_key)
472
-    {
473
-        return array_key_exists(
474
-            $sub_array_key,
475
-            $this->getContext()->getModel()->valid_null_style_operators()
476
-        );
477
-    }
478
-
479
-
480
-    /**
481
-     * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
482
-     * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
483
-     *
484
-     * @param $sub_array_value
485
-     * @return array|mixed|object
486
-     * @since 4.9.72.p
487
-     */
488
-    private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
489
-    {
490
-        // the value should be JSON or CSV
491
-        $values = json_decode($sub_array_value);
492
-        if (! is_array($values)) {
493
-            $values = array_filter(
494
-                array_map(
495
-                    'trim',
496
-                    explode(
497
-                        ',',
498
-                        $sub_array_value
499
-                    )
500
-                )
501
-            );
502
-        }
503
-        return $values;
504
-    }
505
-
506
-
507
-    /**
508
-     * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
509
-     *
510
-     * @throws RestException
511
-     * @since 4.9.72.p
512
-     */
513
-    private function assertSimplifiedSpecifiedOperator()
514
-    {
515
-        if (! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
516
-            throw new RestException(
517
-                'numerically_indexed_array_of_values_only',
518
-                sprintf(
519
-                /* translators: 1: variable name*/
520
-                    esc_html__(
521
-                        'The array provided for the parameter "%1$s" should be numerically indexed.',
522
-                        'event_espresso'
523
-                    ),
524
-                    $this->getQueryParamKey()
525
-                ),
526
-                [
527
-                    'status' => 400,
528
-                ]
529
-            );
530
-        }
531
-    }
532
-
533
-
534
-    /**
535
-     * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
536
-     *
537
-     * @throws RestException
538
-     * @since 4.9.72.p
539
-     */
540
-    private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
541
-    {
542
-        if ($this->valueIsAssociativeArray()) {
543
-            $this->assertSimplifiedSpecifiedOperator();
544
-            $query_param_value = $this->getQueryParamValue();
545
-            $sub_array_value   = reset($query_param_value);
546
-            $sub_array_key     = key($query_param_value);
547
-            $this->assertSubValueIsNotArray($sub_array_key, $sub_array_value);
548
-            // they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
549
-            if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
550
-                $this->setQueryParamValue(
551
-                    [
552
-                        $sub_array_key,
553
-                        $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value),
554
-                    ]
555
-                );
556
-            } elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
557
-                $this->setQueryParamValue([$sub_array_key]);
558
-            } else {
559
-                $this->setQueryParamValue([$sub_array_key, $sub_array_value]);
560
-            }
561
-        }
562
-    }
563
-
564
-
565
-    /**
566
-     * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
567
-     *
568
-     * @return boolean
569
-     * @since 4.9.72.p
570
-     */
571
-    private function valueIsLegacySpecifiedOperator()
572
-    {
573
-        $valid_operators   = $this->getContext()->getModel()->valid_operators();
574
-        $query_param_value = $this->getQueryParamValue();
575
-        return isset($query_param_value[0])
576
-               && isset($valid_operators[ $query_param_value[0] ]);
577
-    }
578
-
579
-
580
-    /**
581
-     * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
582
-     *
583
-     * @param $operator
584
-     * @return boolean
585
-     * @since 4.9.72.p
586
-     */
587
-    private function operatorIsNAry($operator)
588
-    {
589
-        $valueArray = $this->getQueryParamValue();
590
-        return array_key_exists(
591
-            $operator,
592
-            $this->getContext()->getModel()->valid_in_style_operators()
593
-        )
594
-               && isset($valueArray[1])
595
-               && is_array($valueArray[1])
596
-               && ! isset($valueArray[2]);
597
-    }
598
-
599
-
600
-    /**
601
-     * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
602
-     * So we're looking for a value that looks like
603
-     * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
604
-     *
605
-     * @param $operator
606
-     * @return boolean
607
-     * @since 4.9.72.p
608
-     */
609
-    private function operatorIsTernary($operator)
610
-    {
611
-        $query_param_value = $this->getQueryParamValue();
612
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
613
-               && isset($query_param_value[1])
614
-               && is_array($query_param_value[1])
615
-               && isset($query_param_value[1][0], $query_param_value[1][1])
616
-               && ! isset($query_param_value[1][2])
617
-               && ! isset($query_param_value[2]);
618
-    }
619
-
620
-
621
-    /**
622
-     * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
623
-     *
624
-     * @param $operator
625
-     * @return boolean
626
-     * @since 4.9.72.p
627
-     */
628
-    private function operatorIsLike($operator)
629
-    {
630
-        $query_param_value = $this->getQueryParamValue();
631
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
632
-               && isset($query_param_value[1])
633
-               && ! isset($query_param_value[2]);
634
-    }
635
-
636
-
637
-    /**
638
-     * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
639
-     *
640
-     * @param $operator
641
-     * @return boolean
642
-     * @since 4.9.72.p
643
-     */
644
-    private function operatorIsUnary($operator)
645
-    {
646
-        $query_param_value = $this->getQueryParamValue();
647
-        return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
648
-               && ! isset($query_param_value[1]);
649
-    }
650
-
651
-
652
-    /**
653
-     * Returns true if the operator specified is a binary operator (eg `=`, `!=`)
654
-     *
655
-     * @param $operator
656
-     * @return boolean
657
-     * @since 4.9.72.p
658
-     */
659
-    private function operatorIsBinary($operator)
660
-    {
661
-        $query_param_value = $this->getQueryParamValue();
662
-        $model             = $this->getContext()->getModel();
663
-        return isset($query_param_value[1])
664
-               && ! isset($query_param_value[2])
665
-               && ! array_key_exists(
666
-                   $operator,
667
-                   array_merge(
668
-                       $model->valid_in_style_operators(),
669
-                       $model->valid_null_style_operators(),
670
-                       $model->valid_like_style_operators(),
671
-                       $model->valid_between_style_operators()
672
-                   )
673
-               );
674
-    }
675
-
676
-
677
-    /**
678
-     * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
679
-     *
680
-     * @param $operator
681
-     * @throws RestException
682
-     * @since 4.9.72.p
683
-     */
684
-    private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
685
-    {
686
-        if (EED_Core_Rest_Api::debugMode()) {
687
-            throw new RestException(
688
-                'wrong_number_of_arguments',
689
-                sprintf(
690
-                    esc_html__(
691
-                        'The operator you provided, "%1$s" had the wrong number of arguments',
692
-                        'event_espresso'
693
-                    ),
694
-                    $operator
695
-                ),
696
-                [
697
-                    'status' => 400,
698
-                ]
699
-            );
700
-        }
701
-    }
702
-
703
-
704
-    /**
705
-     * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
706
-     *
707
-     * @param $value
708
-     * @return mixed
709
-     * @throws EE_Error
710
-     * @throws RestException
711
-     * @since 4.9.72.p
712
-     */
713
-    private function prepareValuesFromJson($value)
714
-    {
715
-        return ModelDataTranslator::prepareFieldValuesFromJson(
716
-            $this->getField(),
717
-            $value,
718
-            $this->getContext()->getRequestedVersion(),
719
-            $this->getTimezone()
720
-        );
721
-    }
722
-
723
-
724
-    /**
725
-     * Throws an exception if an invalid operator was specified and we're debugging.
726
-     *
727
-     * @throws RestException
728
-     * @since 4.9.72.p
729
-     */
730
-    private function throwInvalidOperatorExceptionIfDebugging()
731
-    {
732
-        // so they didn't provide a valid operator
733
-        if (EED_Core_Rest_Api::debugMode()) {
734
-            throw new RestException(
735
-                'invalid_operator',
736
-                sprintf(
737
-                    esc_html__(
738
-                        'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
739
-                        'event_espresso'
740
-                    ),
741
-                    $this->getQueryParamKey(),
742
-                    $this->getQueryParamValue()
743
-                ),
744
-                [
745
-                    'status' => 400,
746
-                ]
747
-            );
748
-        }
749
-    }
750
-
751
-
752
-    /**
753
-     * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
754
-     *
755
-     * @return boolean
756
-     * @since 4.9.72.p
757
-     */
758
-    private function isLogicQueryParam()
759
-    {
760
-        return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
761
-    }
762
-
763
-
764
-    /**
765
-     * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
766
-     *
767
-     * @return array
768
-     * @throws DomainException
769
-     * @throws EE_Error
770
-     * @throws RestException
771
-     * @throws InvalidDataTypeException
772
-     * @throws InvalidInterfaceException
773
-     * @throws InvalidArgumentException
774
-     * @since 4.9.72.p
775
-     */
776
-    public function determineNestedConditionQueryParameters()
777
-    {
778
-        // so this param doesn't correspond to a field eh?
779
-        if ($this->getContext()->isWriting()) {
780
-            // always tell API clients about invalid parameters when they're creating data. Otherwise,
781
-            // they are probably going to create invalid data
782
-            throw new RestException(
783
-                'invalid_field',
784
-                sprintf(
785
-                /* translators: 1: variable name */
786
-                    esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
787
-                    $this->getQueryParamKey()
788
-                )
789
-            );
790
-        }
791
-        // so it's not for a field, is it a logic query param key?
792
-        if ($this->isLogicQueryParam()) {
793
-            return ModelDataTranslator::prepareConditionsQueryParamsForModels(
794
-                $this->getQueryParamValue(),
795
-                $this->getContext()->getModel(),
796
-                $this->getContext()->getRequestedVersion()
797
-            );
798
-        }
799
-        if (EED_Core_Rest_Api::debugMode()) {
800
-            // only tell API clients they got it wrong if we're in debug mode
801
-            // otherwise try our best ot fulfill their request by ignoring this invalid data
802
-            throw new RestException(
803
-                'invalid_parameter',
804
-                sprintf(
805
-                /* translators: 1: variable name */
806
-                    esc_html__(
807
-                        'You provided an invalid parameter, with key "%1$s"',
808
-                        'event_espresso'
809
-                    ),
810
-                    $this->getQueryParamKey()
811
-                ),
812
-                [
813
-                    'status' => 400,
814
-                ]
815
-            );
816
-        }
817
-        return null;
818
-    }
32
+	private $query_param_key;
33
+
34
+	private $query_param_value;
35
+
36
+	/**
37
+	 * @var RestIncomingQueryParamContext
38
+	 */
39
+	private $context;
40
+
41
+	/**
42
+	 * @var EE_Model_Field_Base|null
43
+	 */
44
+	private $field;
45
+
46
+	/**
47
+	 * @var string same as $query_param_key but has the * and anything after it removed
48
+	 */
49
+	private $query_param_key_sans_stars;
50
+
51
+	/**
52
+	 * @var string for timezone or timezone offset
53
+	 */
54
+	private $timezone;
55
+
56
+	/**
57
+	 * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`)
58
+	 */
59
+	private $is_gmt_field = false;
60
+
61
+
62
+	/**
63
+	 * RestIncomingQueryParamMetadata constructor.
64
+	 * You probably want to call
65
+	 *
66
+	 * @param string                        $query_param_key
67
+	 * @param string                        $query_param_value
68
+	 * @param RestIncomingQueryParamContext $context
69
+	 * @throws EE_Error
70
+	 * @throws EE_Error
71
+	 * @throws ReflectionException
72
+	 */
73
+	public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context)
74
+	{
75
+		$this->query_param_key   = $query_param_key;
76
+		$this->query_param_value = $query_param_value;
77
+		$this->context           = $context;
78
+		$this->determineFieldAndTimezone();
79
+	}
80
+
81
+
82
+	/**
83
+	 * Gets the query parameter key. This may have been modified (see setQueryParamValue())
84
+	 *
85
+	 * @return string
86
+	 */
87
+	public function getQueryParamKey()
88
+	{
89
+		return $this->query_param_key;
90
+	}
91
+
92
+
93
+	/**
94
+	 * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST
95
+	 * query parameters into the legacy structure)
96
+	 *
97
+	 * @param string|array|int|float $query_param_value
98
+	 */
99
+	private function setQueryParamValue($query_param_value)
100
+	{
101
+		$this->query_param_value = $query_param_value;
102
+	}
103
+
104
+
105
+	/**
106
+	 * Gets the original query parameter value passed in.
107
+	 *
108
+	 * @return array|string
109
+	 */
110
+	public function getQueryParamValue()
111
+	{
112
+		return $this->query_param_value;
113
+	}
114
+
115
+
116
+	/**
117
+	 * Gets the context object.
118
+	 *
119
+	 * @return RestIncomingQueryParamContext
120
+	 */
121
+	public function getContext()
122
+	{
123
+		return $this->context;
124
+	}
125
+
126
+
127
+	/**
128
+	 * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative.
129
+	 *
130
+	 * @param string $query_param_key
131
+	 */
132
+	private function setQueryParamKey($query_param_key)
133
+	{
134
+		$this->query_param_key = $query_param_key;
135
+	}
136
+
137
+
138
+	/**
139
+	 * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key
140
+	 * did not indicate a field, eg if it were `OR`).
141
+	 *
142
+	 * @return EE_Model_Field_Base|null
143
+	 */
144
+	public function getField()
145
+	{
146
+		return $this->field;
147
+	}
148
+
149
+
150
+	/**
151
+	 * Gets the query parameter key (with the star and everything afterwards removed).
152
+	 *
153
+	 * @return string
154
+	 */
155
+	public function getQueryParamKeySansStars()
156
+	{
157
+		return $this->query_param_key_sans_stars;
158
+	}
159
+
160
+
161
+	/**
162
+	 * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields).
163
+	 *
164
+	 * @return string
165
+	 */
166
+	public function getTimezone()
167
+	{
168
+		return $this->timezone;
169
+	}
170
+
171
+
172
+	/**
173
+	 * Returns whether or not this is a GMT field
174
+	 *
175
+	 * @return boolean
176
+	 */
177
+	public function isGmtField()
178
+	{
179
+		return $this->is_gmt_field;
180
+	}
181
+
182
+
183
+	/**
184
+	 * Sets the field indicated by the query parameter key (might be null).
185
+	 *
186
+	 * @param EE_Model_Field_Base|null $field
187
+	 */
188
+	private function setField(EE_Model_Field_Base $field = null)
189
+	{
190
+		$this->field = $field;
191
+	}
192
+
193
+
194
+	/**
195
+	 * Sets the query parameter key-with-stars-removed.
196
+	 *
197
+	 * @param string $query_param_key_sans_stars
198
+	 */
199
+	private function setQueryParamKeySansStars($query_param_key_sans_stars)
200
+	{
201
+		$this->query_param_key_sans_stars = $query_param_key_sans_stars;
202
+	}
203
+
204
+
205
+	/**
206
+	 * Sets the timezone (this could be a timezone offset string).
207
+	 *
208
+	 * @param string $timezone
209
+	 */
210
+	private function setTimezone($timezone)
211
+	{
212
+		$this->timezone = $timezone;
213
+	}
214
+
215
+
216
+	/**
217
+	 * @param mixed $is_gmt_field
218
+	 */
219
+	private function setIsGmtField($is_gmt_field)
220
+	{
221
+		$this->is_gmt_field = $is_gmt_field;
222
+	}
223
+
224
+
225
+	/**
226
+	 * Determines what field, query param name, and query param name without stars, and timezone to use.
227
+	 *
228
+	 * @return void {
229
+	 * @throws EE_Error
230
+	 * @throws InvalidDataTypeException
231
+	 * @throws InvalidInterfaceException
232
+	 * @throws InvalidArgumentException
233
+	 * @throws ReflectionException
234
+	 * @since 4.9.72.p
235
+	 */
236
+	private function determineFieldAndTimezone()
237
+	{
238
+		$this->setQueryParamKeySansStars(
239
+			ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey(
240
+				$this->getQueryParamKey()
241
+			)
242
+		);
243
+		$this->setField(
244
+			ModelDataTranslator::deduceFieldFromQueryParam(
245
+				$this->getQueryParamKeySansStars(),
246
+				$this->getContext()->getModel()
247
+			)
248
+		);
249
+		// double-check is it a *_gmt field?
250
+		if (
251
+			! $this->getField() instanceof EE_Model_Field_Base
252
+			&& ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars())
253
+		) {
254
+			// yep, take off '_gmt', and find the field
255
+			$this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars()));
256
+			$this->setField(
257
+				ModelDataTranslator::deduceFieldFromQueryParam(
258
+					$this->getQueryParamKey(),
259
+					$this->context->getModel()
260
+				)
261
+			);
262
+			$this->setTimezone('UTC');
263
+			$this->setIsGmtField(true);
264
+		} elseif ($this->getField() instanceof EE_Datetime_Field) {
265
+			// so it's not a GMT field. Set the timezone on the model to the default
266
+			$this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string());
267
+		} else {
268
+			// just keep using what's already set for the timezone
269
+			$this->setTimezone($this->context->getModel()->get_timezone());
270
+		}
271
+		$this->assertOnlyAdminCanReadPasswordFields();
272
+	}
273
+
274
+
275
+	/**
276
+	 * Throws an exception if a non-admin is trying to query by password.
277
+	 *
278
+	 * @throws RestException
279
+	 * @since 4.9.74.p
280
+	 */
281
+	private function assertOnlyAdminCanReadPasswordFields()
282
+	{
283
+		if (
284
+			$this->getField() instanceof EE_Password_Field
285
+			&& ! current_user_can(EE_Restriction_Generator_Base::get_default_restrictions_cap())
286
+		) {
287
+			// only full admins can query by password. sorry bub!
288
+			throw new RestException(
289
+				'only_admins_can_query_by_password',
290
+				// @codingStandardsIgnoreStart
291
+				esc_html__(
292
+					'You attempted to filter by a password field without the needed privileges. Only a full admin is allowed to do that.',
293
+					'event_espresso'
294
+				),
295
+				// @codingStandardsIgnoreEnd
296
+				[
297
+					'status' => 403,
298
+				]
299
+			);
300
+		}
301
+	}
302
+
303
+
304
+	/**
305
+	 * Given a ton of input, determines the value to use for the models.
306
+	 *
307
+	 * @return array|null
308
+	 * @throws DomainException
309
+	 * @throws EE_Error
310
+	 * @throws RestException
311
+	 * @throws DomainException
312
+	 * @since 4.9.72.p
313
+	 */
314
+	public function determineConditionsQueryParameterValue()
315
+	{
316
+		if ($this->valueIsArrayDuringRead()) {
317
+			return $this->determineModelValueGivenRestInputArray();
318
+		}
319
+		return $this->prepareValuesFromJson($this->getQueryParamValue());
320
+	}
321
+
322
+
323
+	/**
324
+	 * Given that the array value provided was itself an array, handles finding the correct value to pass to the model.
325
+	 *
326
+	 * @return array|null
327
+	 * @throws RestException
328
+	 * @throws EE_Error
329
+	 * @since 4.9.72.p
330
+	 */
331
+	private function determineModelValueGivenRestInputArray()
332
+	{
333
+		$this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax();
334
+		// did they specify an operator?
335
+		if ($this->valueIsLegacySpecifiedOperator()) {
336
+			$query_param_value = $this->getQueryParamValue();
337
+			$sub_array_key     = $query_param_value[0];
338
+			$translated_value  = [$sub_array_key];
339
+			if ($this->operatorIsNAry($sub_array_key)) {
340
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
341
+			} elseif ($this->operatorIsTernary($sub_array_key)) {
342
+				$translated_value[] = [
343
+					$this->prepareValuesFromJson($query_param_value[1][0]),
344
+					$this->prepareValuesFromJson($query_param_value[1][1]),
345
+				];
346
+			} elseif ($this->operatorIsLike($sub_array_key)) {
347
+				// we want to leave this value mostly-as-is (eg don't force it to be a float
348
+				// or a boolean or an enum value. Leave it as-is with wildcards etc)
349
+				// but do verify it at least doesn't have any serialized data
350
+				ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]);
351
+				$translated_value[] = $query_param_value[1];
352
+			} elseif ($this->operatorIsUnary($sub_array_key)) {
353
+				// no arguments should have been provided, so don't look for any
354
+			} elseif ($this->operatorIsBinary($sub_array_key)) {
355
+				// it's a valid operator, but none of the exceptions. Treat it normally.
356
+				$translated_value[] = $this->prepareValuesFromJson($query_param_value[1]);
357
+			} else {
358
+				// so they provided a valid operator, but wrong number of arguments
359
+				$this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key);
360
+				$translated_value = null;
361
+			}
362
+		} else {
363
+			// so they didn't provide a valid operator
364
+			// if we aren't in debug mode, then just try our best to fulfill the user's request
365
+			$this->throwInvalidOperatorExceptionIfDebugging();
366
+			$translated_value = null;
367
+		}
368
+		return $translated_value;
369
+	}
370
+
371
+
372
+	/**
373
+	 * Returns if this request is a "read" request and the value provided was an array.
374
+	 * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not.
375
+	 *
376
+	 * @return boolean
377
+	 * @since 4.9.72.p
378
+	 */
379
+	private function valueIsArrayDuringRead()
380
+	{
381
+		return ! $this->getContext()->isWriting() && is_array($this->getQueryParamValue());
382
+	}
383
+
384
+
385
+	/**
386
+	 * Returns if the value provided was an associative array (we should have already verified it's an array of some
387
+	 * sort). If the value is an associative array, it had better be in the simplified specified operator structure.
388
+	 *
389
+	 * @return boolean
390
+	 * @since 4.9.72.p
391
+	 */
392
+	private function valueIsAssociativeArray()
393
+	{
394
+		return ! EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue());
395
+	}
396
+
397
+
398
+	/**
399
+	 * Checks if the array value is itself an array that fits into the simplified specified operator structure
400
+	 * (eg `array('!=' => 123)`).
401
+	 *
402
+	 * @return boolean
403
+	 * @since 4.9.72.p
404
+	 */
405
+	private function valueIsSimplifiedSpecifiedOperator()
406
+	{
407
+		return count($this->getQueryParamValue()) === 1
408
+			   && array_key_exists(
409
+				   key($this->getQueryParamValue()),
410
+				   $this->getContext()->getModel()->valid_operators()
411
+			   );
412
+	}
413
+
414
+
415
+	/**
416
+	 * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string,
417
+	 * of either comma-separated-values, or a JSON array.
418
+	 *
419
+	 * @param $sub_array_key
420
+	 * @param $sub_array_value
421
+	 * @throws RestException
422
+	 * @since 4.9.72.p
423
+	 */
424
+	private function assertSubValueIsNotArray($sub_array_key, $sub_array_value)
425
+	{
426
+		if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) {
427
+			throw new RestException(
428
+				'csv_or_json_string_only',
429
+				sprintf(
430
+				/* translators: 1: variable name*/
431
+					esc_html__(
432
+						'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.',
433
+						'event_espresso'
434
+					),
435
+					$sub_array_key
436
+				),
437
+				[
438
+					'status' => 400,
439
+				]
440
+			);
441
+		}
442
+	}
443
+
444
+
445
+	/**
446
+	 * Determines if the sub-array key is an operator taking 3 or more operators.
447
+	 *
448
+	 * @param $sub_array_key
449
+	 * @return boolean
450
+	 * @since 4.9.72.p
451
+	 */
452
+	private function subArrayKeyIsNonBinaryOperator($sub_array_key)
453
+	{
454
+		return array_key_exists(
455
+			$sub_array_key,
456
+			array_merge(
457
+				$this->getContext()->getModel()->valid_in_style_operators(),
458
+				$this->getContext()->getModel()->valid_between_style_operators()
459
+			)
460
+		);
461
+	}
462
+
463
+
464
+	/**
465
+	 * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument.
466
+	 *
467
+	 * @param string $sub_array_key
468
+	 * @return boolean
469
+	 * @since 4.9.72.p
470
+	 */
471
+	private function subArrayKeyIsUnaryOperator($sub_array_key)
472
+	{
473
+		return array_key_exists(
474
+			$sub_array_key,
475
+			$this->getContext()->getModel()->valid_null_style_operators()
476
+		);
477
+	}
478
+
479
+
480
+	/**
481
+	 * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON
482
+	 * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`.
483
+	 *
484
+	 * @param $sub_array_value
485
+	 * @return array|mixed|object
486
+	 * @since 4.9.72.p
487
+	 */
488
+	private function extractQuickStyleSpecifiedOperatorValue($sub_array_value)
489
+	{
490
+		// the value should be JSON or CSV
491
+		$values = json_decode($sub_array_value);
492
+		if (! is_array($values)) {
493
+			$values = array_filter(
494
+				array_map(
495
+					'trim',
496
+					explode(
497
+						',',
498
+						$sub_array_value
499
+					)
500
+				)
501
+			);
502
+		}
503
+		return $values;
504
+	}
505
+
506
+
507
+	/**
508
+	 * Throws an exception if the value isn't a simplified specified operator (only called when we expect that).
509
+	 *
510
+	 * @throws RestException
511
+	 * @since 4.9.72.p
512
+	 */
513
+	private function assertSimplifiedSpecifiedOperator()
514
+	{
515
+		if (! $this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) {
516
+			throw new RestException(
517
+				'numerically_indexed_array_of_values_only',
518
+				sprintf(
519
+				/* translators: 1: variable name*/
520
+					esc_html__(
521
+						'The array provided for the parameter "%1$s" should be numerically indexed.',
522
+						'event_espresso'
523
+					),
524
+					$this->getQueryParamKey()
525
+				),
526
+				[
527
+					'status' => 400,
528
+				]
529
+			);
530
+		}
531
+	}
532
+
533
+
534
+	/**
535
+	 * If query_param_value were in the simplified specific operator structure, change it into the legacy structure.
536
+	 *
537
+	 * @throws RestException
538
+	 * @since 4.9.72.p
539
+	 */
540
+	private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax()
541
+	{
542
+		if ($this->valueIsAssociativeArray()) {
543
+			$this->assertSimplifiedSpecifiedOperator();
544
+			$query_param_value = $this->getQueryParamValue();
545
+			$sub_array_value   = reset($query_param_value);
546
+			$sub_array_key     = key($query_param_value);
547
+			$this->assertSubValueIsNotArray($sub_array_key, $sub_array_value);
548
+			// they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5"
549
+			if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) {
550
+				$this->setQueryParamValue(
551
+					[
552
+						$sub_array_key,
553
+						$this->extractQuickStyleSpecifiedOperatorValue($sub_array_value),
554
+					]
555
+				);
556
+			} elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) {
557
+				$this->setQueryParamValue([$sub_array_key]);
558
+			} else {
559
+				$this->setQueryParamValue([$sub_array_key, $sub_array_value]);
560
+			}
561
+		}
562
+	}
563
+
564
+
565
+	/**
566
+	 * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`.
567
+	 *
568
+	 * @return boolean
569
+	 * @since 4.9.72.p
570
+	 */
571
+	private function valueIsLegacySpecifiedOperator()
572
+	{
573
+		$valid_operators   = $this->getContext()->getModel()->valid_operators();
574
+		$query_param_value = $this->getQueryParamValue();
575
+		return isset($query_param_value[0])
576
+			   && isset($valid_operators[ $query_param_value[0] ]);
577
+	}
578
+
579
+
580
+	/**
581
+	 * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN".
582
+	 *
583
+	 * @param $operator
584
+	 * @return boolean
585
+	 * @since 4.9.72.p
586
+	 */
587
+	private function operatorIsNAry($operator)
588
+	{
589
+		$valueArray = $this->getQueryParamValue();
590
+		return array_key_exists(
591
+			$operator,
592
+			$this->getContext()->getModel()->valid_in_style_operators()
593
+		)
594
+			   && isset($valueArray[1])
595
+			   && is_array($valueArray[1])
596
+			   && ! isset($valueArray[2]);
597
+	}
598
+
599
+
600
+	/**
601
+	 * Returns true if the operator accepts 3 arguments (eg "BETWEEN").
602
+	 * So we're looking for a value that looks like
603
+	 * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`.
604
+	 *
605
+	 * @param $operator
606
+	 * @return boolean
607
+	 * @since 4.9.72.p
608
+	 */
609
+	private function operatorIsTernary($operator)
610
+	{
611
+		$query_param_value = $this->getQueryParamValue();
612
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators())
613
+			   && isset($query_param_value[1])
614
+			   && is_array($query_param_value[1])
615
+			   && isset($query_param_value[1][0], $query_param_value[1][1])
616
+			   && ! isset($query_param_value[1][2])
617
+			   && ! isset($query_param_value[2]);
618
+	}
619
+
620
+
621
+	/**
622
+	 * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone.
623
+	 *
624
+	 * @param $operator
625
+	 * @return boolean
626
+	 * @since 4.9.72.p
627
+	 */
628
+	private function operatorIsLike($operator)
629
+	{
630
+		$query_param_value = $this->getQueryParamValue();
631
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators())
632
+			   && isset($query_param_value[1])
633
+			   && ! isset($query_param_value[2]);
634
+	}
635
+
636
+
637
+	/**
638
+	 * Returns true if the operator only takes one argument (eg it's like `IS NULL`).
639
+	 *
640
+	 * @param $operator
641
+	 * @return boolean
642
+	 * @since 4.9.72.p
643
+	 */
644
+	private function operatorIsUnary($operator)
645
+	{
646
+		$query_param_value = $this->getQueryParamValue();
647
+		return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators())
648
+			   && ! isset($query_param_value[1]);
649
+	}
650
+
651
+
652
+	/**
653
+	 * Returns true if the operator specified is a binary operator (eg `=`, `!=`)
654
+	 *
655
+	 * @param $operator
656
+	 * @return boolean
657
+	 * @since 4.9.72.p
658
+	 */
659
+	private function operatorIsBinary($operator)
660
+	{
661
+		$query_param_value = $this->getQueryParamValue();
662
+		$model             = $this->getContext()->getModel();
663
+		return isset($query_param_value[1])
664
+			   && ! isset($query_param_value[2])
665
+			   && ! array_key_exists(
666
+				   $operator,
667
+				   array_merge(
668
+					   $model->valid_in_style_operators(),
669
+					   $model->valid_null_style_operators(),
670
+					   $model->valid_like_style_operators(),
671
+					   $model->valid_between_style_operators()
672
+				   )
673
+			   );
674
+	}
675
+
676
+
677
+	/**
678
+	 * If we're debugging, throws an exception saying that the wrong number of arguments was provided.
679
+	 *
680
+	 * @param $operator
681
+	 * @throws RestException
682
+	 * @since 4.9.72.p
683
+	 */
684
+	private function throwWrongNumberOfArgsExceptionIfDebugging($operator)
685
+	{
686
+		if (EED_Core_Rest_Api::debugMode()) {
687
+			throw new RestException(
688
+				'wrong_number_of_arguments',
689
+				sprintf(
690
+					esc_html__(
691
+						'The operator you provided, "%1$s" had the wrong number of arguments',
692
+						'event_espresso'
693
+					),
694
+					$operator
695
+				),
696
+				[
697
+					'status' => 400,
698
+				]
699
+			);
700
+		}
701
+	}
702
+
703
+
704
+	/**
705
+	 * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY.
706
+	 *
707
+	 * @param $value
708
+	 * @return mixed
709
+	 * @throws EE_Error
710
+	 * @throws RestException
711
+	 * @since 4.9.72.p
712
+	 */
713
+	private function prepareValuesFromJson($value)
714
+	{
715
+		return ModelDataTranslator::prepareFieldValuesFromJson(
716
+			$this->getField(),
717
+			$value,
718
+			$this->getContext()->getRequestedVersion(),
719
+			$this->getTimezone()
720
+		);
721
+	}
722
+
723
+
724
+	/**
725
+	 * Throws an exception if an invalid operator was specified and we're debugging.
726
+	 *
727
+	 * @throws RestException
728
+	 * @since 4.9.72.p
729
+	 */
730
+	private function throwInvalidOperatorExceptionIfDebugging()
731
+	{
732
+		// so they didn't provide a valid operator
733
+		if (EED_Core_Rest_Api::debugMode()) {
734
+			throw new RestException(
735
+				'invalid_operator',
736
+				sprintf(
737
+					esc_html__(
738
+						'You provided an invalid parameter, with key "%1$s" and value "%2$s"',
739
+						'event_espresso'
740
+					),
741
+					$this->getQueryParamKey(),
742
+					$this->getQueryParamValue()
743
+				),
744
+				[
745
+					'status' => 400,
746
+				]
747
+			);
748
+		}
749
+	}
750
+
751
+
752
+	/**
753
+	 * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc.
754
+	 *
755
+	 * @return boolean
756
+	 * @since 4.9.72.p
757
+	 */
758
+	private function isLogicQueryParam()
759
+	{
760
+		return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys());
761
+	}
762
+
763
+
764
+	/**
765
+	 * If the query param isn't for a field, it must be a nested query parameter which requires different logic.
766
+	 *
767
+	 * @return array
768
+	 * @throws DomainException
769
+	 * @throws EE_Error
770
+	 * @throws RestException
771
+	 * @throws InvalidDataTypeException
772
+	 * @throws InvalidInterfaceException
773
+	 * @throws InvalidArgumentException
774
+	 * @since 4.9.72.p
775
+	 */
776
+	public function determineNestedConditionQueryParameters()
777
+	{
778
+		// so this param doesn't correspond to a field eh?
779
+		if ($this->getContext()->isWriting()) {
780
+			// always tell API clients about invalid parameters when they're creating data. Otherwise,
781
+			// they are probably going to create invalid data
782
+			throw new RestException(
783
+				'invalid_field',
784
+				sprintf(
785
+				/* translators: 1: variable name */
786
+					esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'),
787
+					$this->getQueryParamKey()
788
+				)
789
+			);
790
+		}
791
+		// so it's not for a field, is it a logic query param key?
792
+		if ($this->isLogicQueryParam()) {
793
+			return ModelDataTranslator::prepareConditionsQueryParamsForModels(
794
+				$this->getQueryParamValue(),
795
+				$this->getContext()->getModel(),
796
+				$this->getContext()->getRequestedVersion()
797
+			);
798
+		}
799
+		if (EED_Core_Rest_Api::debugMode()) {
800
+			// only tell API clients they got it wrong if we're in debug mode
801
+			// otherwise try our best ot fulfill their request by ignoring this invalid data
802
+			throw new RestException(
803
+				'invalid_parameter',
804
+				sprintf(
805
+				/* translators: 1: variable name */
806
+					esc_html__(
807
+						'You provided an invalid parameter, with key "%1$s"',
808
+						'event_espresso'
809
+					),
810
+					$this->getQueryParamKey()
811
+				),
812
+				[
813
+					'status' => 400,
814
+				]
815
+			);
816
+		}
817
+		return null;
818
+	}
819 819
 }
820 820
 // End of file RestQueryParamMetadata.php
821 821
 // Location: EventEspresso\core\libraries\rest_api/RestQueryParamMetadata.php
Please login to merge, or discard this patch.