@@ -26,681 +26,681 @@ |
||
| 26 | 26 | */ |
| 27 | 27 | class RestIncomingQueryParamMetadata |
| 28 | 28 | { |
| 29 | - private $query_param_key; |
|
| 30 | - private $query_param_value; |
|
| 31 | - /** |
|
| 32 | - * @var RestIncomingQueryParamContext |
|
| 33 | - */ |
|
| 34 | - private $context; |
|
| 35 | - |
|
| 36 | - /** |
|
| 37 | - * @var EE_Model_Field_Base|null |
|
| 38 | - */ |
|
| 39 | - private $field; |
|
| 40 | - |
|
| 41 | - /** |
|
| 42 | - * @var string same as $query_param_key but has the * and anything after it removed |
|
| 43 | - */ |
|
| 44 | - private $query_param_key_sans_stars; |
|
| 45 | - |
|
| 46 | - /** |
|
| 47 | - * @var string for timezone or timezone offset |
|
| 48 | - */ |
|
| 49 | - private $timezone; |
|
| 50 | - |
|
| 51 | - /** |
|
| 52 | - * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`) |
|
| 53 | - */ |
|
| 54 | - private $is_gmt_field = false; |
|
| 55 | - |
|
| 56 | - /** |
|
| 57 | - * RestIncomingQueryParamMetadata constructor. |
|
| 58 | - * You probably want to call |
|
| 59 | - * @param string $query_param_key |
|
| 60 | - * @param string $query_param_value |
|
| 61 | - * @param RestIncomingQueryParamContext $context |
|
| 62 | - */ |
|
| 63 | - public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context) |
|
| 64 | - { |
|
| 65 | - $this->query_param_key = $query_param_key; |
|
| 66 | - $this->query_param_value = $query_param_value; |
|
| 67 | - $this->context = $context; |
|
| 68 | - $this->determineFieldAndTimezone(); |
|
| 69 | - } |
|
| 70 | - |
|
| 71 | - /** |
|
| 72 | - * Gets the query parameter key. This may have been modified (see setQueryParamValue()) |
|
| 73 | - * @return string |
|
| 74 | - */ |
|
| 75 | - public function getQueryParamKey() |
|
| 76 | - { |
|
| 77 | - return $this->query_param_key; |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - /** |
|
| 81 | - * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST |
|
| 82 | - * query parameters into the legacy structure) |
|
| 83 | - * @param string|array|int|float $query_param_value |
|
| 84 | - */ |
|
| 85 | - private function setQueryParamValue($query_param_value) |
|
| 86 | - { |
|
| 87 | - $this->query_param_value = $query_param_value; |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - /** |
|
| 91 | - * Gets the original query parameter value passed in. |
|
| 92 | - * @return string |
|
| 93 | - */ |
|
| 94 | - public function getQueryParamValue() |
|
| 95 | - { |
|
| 96 | - return $this->query_param_value; |
|
| 97 | - } |
|
| 98 | - |
|
| 99 | - /** |
|
| 100 | - * Gets the context object. |
|
| 101 | - * @return RestIncomingQueryParamContext |
|
| 102 | - */ |
|
| 103 | - public function getContext() |
|
| 104 | - { |
|
| 105 | - return $this->context; |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - /** |
|
| 109 | - * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative. |
|
| 110 | - * @param string $query_param_key |
|
| 111 | - */ |
|
| 112 | - private function setQueryParamKey($query_param_key) |
|
| 113 | - { |
|
| 114 | - $this->query_param_key = $query_param_key; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - /** |
|
| 118 | - * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key |
|
| 119 | - * did not indicate a field, eg if it were `OR`). |
|
| 120 | - * @return EE_Model_Field_Base|null |
|
| 121 | - */ |
|
| 122 | - public function getField() |
|
| 123 | - { |
|
| 124 | - return $this->field; |
|
| 125 | - } |
|
| 126 | - |
|
| 127 | - /** |
|
| 128 | - * Gets the query parameter key (with the star and everything afterwards removed). |
|
| 129 | - * @return string |
|
| 130 | - */ |
|
| 131 | - public function getQueryParamKeySansStars() |
|
| 132 | - { |
|
| 133 | - return $this->query_param_key_sans_stars; |
|
| 134 | - } |
|
| 135 | - |
|
| 136 | - /** |
|
| 137 | - * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields). |
|
| 138 | - * @return string |
|
| 139 | - */ |
|
| 140 | - public function getTimezone() |
|
| 141 | - { |
|
| 142 | - return $this->timezone; |
|
| 143 | - } |
|
| 144 | - |
|
| 145 | - /** |
|
| 146 | - * Returns whether or not this is a GMT field |
|
| 147 | - * @return boolean |
|
| 148 | - */ |
|
| 149 | - public function isGmtField() |
|
| 150 | - { |
|
| 151 | - return $this->is_gmt_field; |
|
| 152 | - } |
|
| 153 | - |
|
| 154 | - /** |
|
| 155 | - * Sets the field indicated by the query parameter key (might be null). |
|
| 156 | - * @param EE_Model_Field_Base|null $field |
|
| 157 | - */ |
|
| 158 | - private function setField(EE_Model_Field_Base $field = null) |
|
| 159 | - { |
|
| 160 | - $this->field = $field; |
|
| 161 | - } |
|
| 162 | - |
|
| 163 | - /** |
|
| 164 | - * Sets the query parameter key-with-stars-removed. |
|
| 165 | - * @param string $query_param_key_sans_stars |
|
| 166 | - */ |
|
| 167 | - private function setQueryParamKeySansStars($query_param_key_sans_stars) |
|
| 168 | - { |
|
| 169 | - $this->query_param_key_sans_stars = $query_param_key_sans_stars; |
|
| 170 | - } |
|
| 171 | - |
|
| 172 | - /** |
|
| 173 | - * Sets the timezone (this could be a timezeon offset string). |
|
| 174 | - * @param string $timezone |
|
| 175 | - */ |
|
| 176 | - private function setTimezone($timezone) |
|
| 177 | - { |
|
| 178 | - $this->timezone = $timezone; |
|
| 179 | - } |
|
| 180 | - |
|
| 181 | - /** |
|
| 182 | - * @param mixed $is_gmt_field |
|
| 183 | - */ |
|
| 184 | - private function setIsGmtField($is_gmt_field) |
|
| 185 | - { |
|
| 186 | - $this->is_gmt_field = $is_gmt_field; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - /** |
|
| 190 | - * Determines what field, query param name, and query param name without stars, and timezone to use. |
|
| 191 | - * @since $VID:$ |
|
| 192 | - * @type EE_Model_Field_Base $field |
|
| 193 | - * @return void { |
|
| 194 | - * @throws EE_Error |
|
| 195 | - * @throws InvalidDataTypeException |
|
| 196 | - * @throws InvalidInterfaceException |
|
| 197 | - * @throws InvalidArgumentException |
|
| 198 | - */ |
|
| 199 | - private function determineFieldAndTimezone() |
|
| 200 | - { |
|
| 201 | - $this->setQueryParamKeySansStars(ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey( |
|
| 202 | - $this->getQueryParamKey() |
|
| 203 | - )); |
|
| 204 | - $this->setField(ModelDataTranslator::deduceFieldFromQueryParam( |
|
| 205 | - $this->getQueryParamKeySansStars(), |
|
| 206 | - $this->getContext()->getModel() |
|
| 207 | - )); |
|
| 208 | - // double-check is it a *_gmt field? |
|
| 209 | - if (!$this->getField() instanceof EE_Model_Field_Base |
|
| 210 | - && ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars()) |
|
| 211 | - ) { |
|
| 212 | - // yep, take off '_gmt', and find the field |
|
| 213 | - $this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars())); |
|
| 214 | - $this->setField(ModelDataTranslator::deduceFieldFromQueryParam( |
|
| 215 | - $this->getQueryParamKey(), |
|
| 216 | - $this->context->getModel() |
|
| 217 | - )); |
|
| 218 | - $this->setTimezone('UTC'); |
|
| 219 | - $this->setIsGmtField(true); |
|
| 220 | - } elseif ($this->getField() instanceof EE_Datetime_Field) { |
|
| 221 | - // so it's not a GMT field. Set the timezone on the model to the default |
|
| 222 | - $this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string()); |
|
| 223 | - } else { |
|
| 224 | - // just keep using what's already set for the timezone |
|
| 225 | - $this->setTimezone($this->context->getModel()->get_timezone()); |
|
| 226 | - } |
|
| 227 | - } |
|
| 228 | - |
|
| 229 | - /** |
|
| 230 | - * Given a ton of input, determines the value to use for the models. |
|
| 231 | - * @since $VID:$ |
|
| 232 | - * @return array|null |
|
| 233 | - * @throws DomainException |
|
| 234 | - * @throws EE_Error |
|
| 235 | - * @throws RestException |
|
| 236 | - * @throws DomainException |
|
| 237 | - */ |
|
| 238 | - public function determineConditionsQueryParameterValue() |
|
| 239 | - { |
|
| 240 | - if ($this->valueIsArrayDuringRead()) { |
|
| 241 | - return $this->determineModelValueGivenRestInputArray(); |
|
| 242 | - } |
|
| 243 | - return ModelDataTranslator::prepareFieldValueFromJson( |
|
| 244 | - $this->getField(), |
|
| 245 | - $this->getQueryParamValue(), |
|
| 246 | - $this->getContext()->getRequestedVersion(), |
|
| 247 | - $this->getTimezone() |
|
| 248 | - ); |
|
| 249 | - } |
|
| 250 | - |
|
| 251 | - /** |
|
| 252 | - * Given that the array value provided was itself an array, handles finding the correct value to pass to the model. |
|
| 253 | - * @since $VID:$ |
|
| 254 | - * @return array|null |
|
| 255 | - * @throws RestException |
|
| 256 | - */ |
|
| 257 | - private function determineModelValueGivenRestInputArray() |
|
| 258 | - { |
|
| 259 | - $this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax(); |
|
| 260 | - // did they specify an operator? |
|
| 261 | - if ($this->valueIsLegacySpecifiedOperator()) { |
|
| 262 | - $query_param_value = $this->getQueryParamValue(); |
|
| 263 | - $sub_array_key = $query_param_value[0]; |
|
| 264 | - $translated_value = array($sub_array_key); |
|
| 265 | - if ($this->operatorIsNAry($sub_array_key)) { |
|
| 266 | - $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]); |
|
| 267 | - } elseif ($this->operatorIsTernary($sub_array_key)) { |
|
| 268 | - $translated_value[] = array( |
|
| 269 | - $this->prepareValuesFromJson($query_param_value[1][0]), |
|
| 270 | - $this->prepareValuesFromJson($query_param_value[1][1]) |
|
| 271 | - ); |
|
| 272 | - } elseif ($this->operatorIsLike($sub_array_key)) { |
|
| 273 | - // we want to leave this value mostly-as-is (eg don't force it to be a float |
|
| 274 | - // or a boolean or an enum value. Leave it as-is with wildcards etc) |
|
| 275 | - // but do verify it at least doesn't have any serialized data |
|
| 276 | - ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]); |
|
| 277 | - $translated_value[] = $query_param_value[1]; |
|
| 278 | - } elseif ($this->operatorIsUnary($sub_array_key)) { |
|
| 279 | - // no arguments should have been provided, so don't look for any |
|
| 280 | - } elseif ($this->operatorisBinary($sub_array_key)) { |
|
| 281 | - // it's a valid operator, but none of the exceptions. Treat it normally. |
|
| 282 | - $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]); |
|
| 283 | - } else { |
|
| 284 | - // so they provided a valid operator, but wrong number of arguments |
|
| 285 | - $this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key); |
|
| 286 | - $translated_value = null; |
|
| 287 | - } |
|
| 288 | - } else { |
|
| 289 | - // so they didn't provide a valid operator |
|
| 290 | - // if we aren't in debug mode, then just try our best to fulfill the user's request |
|
| 291 | - $this->throwInvalidOperatorExceptionIfDebugging(); |
|
| 292 | - $translated_value = null; |
|
| 293 | - } |
|
| 294 | - return $translated_value; |
|
| 295 | - } |
|
| 296 | - |
|
| 297 | - /** |
|
| 298 | - * Returns if this request is a "read" request and the value provided was an array. |
|
| 299 | - * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not. |
|
| 300 | - * @since $VID:$ |
|
| 301 | - * @return boolean |
|
| 302 | - */ |
|
| 303 | - private function valueIsArrayDuringRead() |
|
| 304 | - { |
|
| 305 | - return !$this->getContext()->isWriting() && is_array($this->getQueryParamValue()); |
|
| 306 | - } |
|
| 307 | - |
|
| 308 | - /** |
|
| 309 | - * Returns if the value provided was an associative array (we should have already verified it's an array of some |
|
| 310 | - * sort). If the value is an associative array, it had better be in the simplified specified operator structure. |
|
| 311 | - * @since $VID:$ |
|
| 312 | - * @return boolean |
|
| 313 | - */ |
|
| 314 | - private function valueIsAssociativeArray() |
|
| 315 | - { |
|
| 316 | - return !EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue()); |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - /** |
|
| 320 | - * Checks if the array value is itself an array that fits into the simplified specified operator structure |
|
| 321 | - * (eg `array('!=' => 123)`). |
|
| 322 | - * @since $VID:$ |
|
| 323 | - * @return boolean |
|
| 324 | - */ |
|
| 325 | - private function valueIsSimplifiedSpecifiedOperator() |
|
| 326 | - { |
|
| 327 | - return count($this->getQueryParamValue()) === 1 |
|
| 328 | - && array_key_exists( |
|
| 329 | - key($this->getQueryParamValue()), |
|
| 330 | - $this->getContext()->getModel()->valid_operators() |
|
| 331 | - ); |
|
| 332 | - } |
|
| 333 | - |
|
| 334 | - /** |
|
| 335 | - * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string, |
|
| 336 | - * of either comma-separated-values, or a JSON array. |
|
| 337 | - * @since $VID:$ |
|
| 338 | - * @param $sub_array_key |
|
| 339 | - * @param $sub_array_value |
|
| 340 | - * @throws RestException |
|
| 341 | - */ |
|
| 342 | - private function assertSubValueIsntArray($sub_array_key, $sub_array_value) |
|
| 343 | - { |
|
| 344 | - if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) { |
|
| 345 | - throw new RestException( |
|
| 346 | - 'csv_or_json_string_only', |
|
| 347 | - sprintf( |
|
| 348 | - /* translators: 1: variable name*/ |
|
| 349 | - esc_html__( |
|
| 350 | - 'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.', |
|
| 351 | - 'event_espresso' |
|
| 352 | - ), |
|
| 353 | - $sub_array_key |
|
| 354 | - ), |
|
| 355 | - array( |
|
| 356 | - 'status' => 400, |
|
| 357 | - ) |
|
| 358 | - ); |
|
| 359 | - } |
|
| 360 | - } |
|
| 361 | - |
|
| 362 | - /** |
|
| 363 | - * Determines if the sub-array key is an operator taking 3 or more operators. |
|
| 364 | - * @since $VID:$ |
|
| 365 | - * @param $sub_array_key |
|
| 366 | - * @return boolean |
|
| 367 | - */ |
|
| 368 | - private function subArrayKeyIsNonBinaryOperator($sub_array_key) |
|
| 369 | - { |
|
| 370 | - return array_key_exists( |
|
| 371 | - $sub_array_key, |
|
| 372 | - array_merge( |
|
| 373 | - $this->getContext()->getModel()->valid_in_style_operators(), |
|
| 374 | - $this->getContext()->getModel()->valid_between_style_operators() |
|
| 375 | - ) |
|
| 376 | - ); |
|
| 377 | - } |
|
| 378 | - |
|
| 379 | - /** |
|
| 380 | - * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument. |
|
| 381 | - * @since $VID:$ |
|
| 382 | - * @param string $sub_array_key |
|
| 383 | - * @return boolean |
|
| 384 | - */ |
|
| 385 | - private function subArrayKeyIsUnaryOperator($sub_array_key) |
|
| 386 | - { |
|
| 387 | - return array_key_exists( |
|
| 388 | - $sub_array_key, |
|
| 389 | - $this->getContext()->getModel()->valid_null_style_operators() |
|
| 390 | - ); |
|
| 391 | - } |
|
| 392 | - |
|
| 393 | - /** |
|
| 394 | - * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON |
|
| 395 | - * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`. |
|
| 396 | - * @since $VID:$ |
|
| 397 | - * @param $sub_array_value |
|
| 398 | - * @return array|mixed|object |
|
| 399 | - */ |
|
| 400 | - private function extractQuickStyleSpecifiedOperatorValue($sub_array_value) |
|
| 401 | - { |
|
| 402 | - // the value should be JSON or CSV |
|
| 403 | - $values = json_decode($sub_array_value); |
|
| 404 | - if (!is_array($values)) { |
|
| 405 | - $values = array_filter( |
|
| 406 | - array_map( |
|
| 407 | - 'trim', |
|
| 408 | - explode( |
|
| 409 | - ',', |
|
| 410 | - $sub_array_value |
|
| 411 | - ) |
|
| 412 | - ) |
|
| 413 | - ); |
|
| 414 | - } |
|
| 415 | - return $values; |
|
| 416 | - } |
|
| 417 | - |
|
| 418 | - /** |
|
| 419 | - * Throws an exception if the value isn't a simplified specified operator (only called when we expect that). |
|
| 420 | - * @since $VID:$ |
|
| 421 | - * @throws RestException |
|
| 422 | - */ |
|
| 423 | - private function assertSimplifiedSpecifiedOperator() |
|
| 424 | - { |
|
| 425 | - if (!$this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) { |
|
| 426 | - throw new RestException( |
|
| 427 | - 'numerically_indexed_array_of_values_only', |
|
| 428 | - sprintf( |
|
| 429 | - /* translators: 1: variable name*/ |
|
| 430 | - esc_html__( |
|
| 431 | - 'The array provided for the parameter "%1$s" should be numerically indexed.', |
|
| 432 | - 'event_espresso' |
|
| 433 | - ), |
|
| 434 | - $this->getQueryParamKey() |
|
| 435 | - ), |
|
| 436 | - array( |
|
| 437 | - 'status' => 400, |
|
| 438 | - ) |
|
| 439 | - ); |
|
| 440 | - } |
|
| 441 | - } |
|
| 442 | - |
|
| 443 | - /** |
|
| 444 | - * If query_param_value were in the simplified specific operator structure, change it into the legacy structure. |
|
| 445 | - * @since $VID:$ |
|
| 446 | - * @throws RestException |
|
| 447 | - */ |
|
| 448 | - private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax() |
|
| 449 | - { |
|
| 450 | - if ($this->valueIsAssociativeArray()) { |
|
| 451 | - $this->assertSimplifiedSpecifiedOperator(); |
|
| 452 | - $query_param_value = $this->getQueryParamValue(); |
|
| 453 | - $sub_array_value = reset($query_param_value); |
|
| 454 | - $sub_array_key = key($query_param_value); |
|
| 455 | - $this->assertSubValueIsntArray($sub_array_key, $sub_array_value); |
|
| 456 | - // they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5" |
|
| 457 | - if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) { |
|
| 458 | - $this->setQueryParamValue(array( |
|
| 459 | - $sub_array_key, |
|
| 460 | - $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value) |
|
| 461 | - )); |
|
| 462 | - } elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) { |
|
| 463 | - $this->setQueryParamValue(array($sub_array_key)); |
|
| 464 | - } else { |
|
| 465 | - $this->setQueryParamValue(array($sub_array_key, $sub_array_value)); |
|
| 466 | - } |
|
| 467 | - } |
|
| 468 | - } |
|
| 469 | - |
|
| 470 | - /** |
|
| 471 | - * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`. |
|
| 472 | - * @since $VID:$ |
|
| 473 | - * @return boolean |
|
| 474 | - */ |
|
| 475 | - private function valueIsLegacySpecifiedOperator() |
|
| 476 | - { |
|
| 477 | - $valid_operators = $this->getContext()->getModel()->valid_operators(); |
|
| 478 | - $query_param_value = $this->getQueryParamValue(); |
|
| 479 | - return isset($query_param_value[0]) |
|
| 480 | - && isset($valid_operators[ $query_param_value[0] ]); |
|
| 481 | - } |
|
| 482 | - |
|
| 483 | - /** |
|
| 484 | - * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN". |
|
| 485 | - * @since $VID:$ |
|
| 486 | - * @param $operator |
|
| 487 | - * @return boolean |
|
| 488 | - */ |
|
| 489 | - private function operatorIsNAry($operator) |
|
| 490 | - { |
|
| 491 | - $valueArray = $this->getQueryParamValue(); |
|
| 492 | - return array_key_exists( |
|
| 493 | - $operator, |
|
| 494 | - $this->getContext()->getModel()->valid_in_style_operators() |
|
| 495 | - ) |
|
| 496 | - && isset($valueArray[1]) |
|
| 497 | - && is_array($valueArray[1]) |
|
| 498 | - && !isset($valueArray[2]); |
|
| 499 | - } |
|
| 500 | - |
|
| 501 | - /** |
|
| 502 | - * Returns true if the operator accepts 3 arguments (eg "BETWEEN"). |
|
| 503 | - * So we're looking for a value that looks like |
|
| 504 | - * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`. |
|
| 505 | - * @since $VID:$ |
|
| 506 | - * @param $operator |
|
| 507 | - * @return boolean |
|
| 508 | - */ |
|
| 509 | - private function operatorIsTernary($operator) |
|
| 510 | - { |
|
| 511 | - $query_param_value = $this->getQueryParamValue(); |
|
| 512 | - return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators()) |
|
| 513 | - && isset($query_param_value[1]) |
|
| 514 | - && is_array($query_param_value[1]) |
|
| 515 | - && isset($query_param_value[1][0], $query_param_value[1][1]) |
|
| 516 | - && !isset($query_param_value[1][2]) |
|
| 517 | - && !isset($query_param_value[2]); |
|
| 518 | - } |
|
| 519 | - |
|
| 520 | - /** |
|
| 521 | - * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone. |
|
| 522 | - * @since $VID:$ |
|
| 523 | - * @param $operator |
|
| 524 | - * @return boolean |
|
| 525 | - */ |
|
| 526 | - private function operatorIsLike($operator) |
|
| 527 | - { |
|
| 528 | - $query_param_value = $this->getQueryParamValue(); |
|
| 529 | - return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators()) |
|
| 530 | - && isset($query_param_value[1]) |
|
| 531 | - && !isset($query_param_value[2]); |
|
| 532 | - } |
|
| 533 | - |
|
| 534 | - /** |
|
| 535 | - * Returns true if the operator only takes one argument (eg it's like `IS NULL`). |
|
| 536 | - * @since $VID:$ |
|
| 537 | - * @param $operator |
|
| 538 | - * @return boolean |
|
| 539 | - */ |
|
| 540 | - private function operatorIsUnary($operator) |
|
| 541 | - { |
|
| 542 | - $query_param_value = $this->getQueryParamValue(); |
|
| 543 | - return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators()) |
|
| 544 | - && !isset($query_param_value[1]); |
|
| 545 | - } |
|
| 546 | - |
|
| 547 | - /** |
|
| 548 | - * Returns true if the operator specified is a binary opeator (eg `=`, `!=`) |
|
| 549 | - * @since $VID:$ |
|
| 550 | - * @param $operator |
|
| 551 | - * @return boolean |
|
| 552 | - */ |
|
| 553 | - private function operatorisBinary($operator) |
|
| 554 | - { |
|
| 555 | - $query_param_value = $this->getQueryParamValue(); |
|
| 556 | - $model = $this->getContext()->getModel(); |
|
| 557 | - return isset($query_param_value[1]) |
|
| 558 | - && !isset($query_param_value[2]) |
|
| 559 | - && !array_key_exists( |
|
| 560 | - $operator, |
|
| 561 | - array_merge( |
|
| 562 | - $model->valid_in_style_operators(), |
|
| 563 | - $model->valid_null_style_operators(), |
|
| 564 | - $model->valid_like_style_operators(), |
|
| 565 | - $model->valid_between_style_operators() |
|
| 566 | - ) |
|
| 567 | - ); |
|
| 568 | - } |
|
| 569 | - |
|
| 570 | - /** |
|
| 571 | - * If we're debugging, throws an exception saying that the wrong number of arguments was provided. |
|
| 572 | - * @since $VID:$ |
|
| 573 | - * @param $operator |
|
| 574 | - * @throws RestException |
|
| 575 | - */ |
|
| 576 | - private function throwWrongNumberOfArgsExceptionIfDebugging($operator) |
|
| 577 | - { |
|
| 578 | - if (EED_Core_Rest_Api::debugMode()) { |
|
| 579 | - throw new RestException( |
|
| 580 | - 'wrong_number_of_arguments', |
|
| 581 | - sprintf( |
|
| 582 | - esc_html__( |
|
| 583 | - 'The operator you provided, "%1$s" had the wrong number of arguments', |
|
| 584 | - 'event_espresso' |
|
| 585 | - ), |
|
| 586 | - $operator |
|
| 587 | - ), |
|
| 588 | - array( |
|
| 589 | - 'status' => 400, |
|
| 590 | - ) |
|
| 591 | - ); |
|
| 592 | - } |
|
| 593 | - } |
|
| 594 | - |
|
| 595 | - /** |
|
| 596 | - * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY. |
|
| 597 | - * @since $VID:$ |
|
| 598 | - * @param $value |
|
| 599 | - * @return mixed |
|
| 600 | - * @throws RestException |
|
| 601 | - */ |
|
| 602 | - private function prepareValuesFromJson($value) |
|
| 603 | - { |
|
| 604 | - return ModelDataTranslator::prepareFieldValuesFromJson( |
|
| 605 | - $this->getField(), |
|
| 606 | - $value, |
|
| 607 | - $this->getContext()->getRequestedVersion(), |
|
| 608 | - $this->getTimezone() |
|
| 609 | - ); |
|
| 610 | - } |
|
| 611 | - |
|
| 612 | - /** |
|
| 613 | - * Throws an exception if an invalid operator was specified and we're debugging. |
|
| 614 | - * @since $VID:$ |
|
| 615 | - * @throws RestException |
|
| 616 | - */ |
|
| 617 | - private function throwInvalidOperatorExceptionIfDebugging() |
|
| 618 | - { |
|
| 619 | - // so they didn't provide a valid operator |
|
| 620 | - if (EED_Core_Rest_Api::debugMode()) { |
|
| 621 | - throw new RestException( |
|
| 622 | - 'invalid_operator', |
|
| 623 | - sprintf( |
|
| 624 | - esc_html__( |
|
| 625 | - 'You provided an invalid parameter, with key "%1$s" and value "%2$s"', |
|
| 626 | - 'event_espresso' |
|
| 627 | - ), |
|
| 628 | - $this->getQueryParamKey(), |
|
| 629 | - $this->getQueryParamValue() |
|
| 630 | - ), |
|
| 631 | - array( |
|
| 632 | - 'status' => 400, |
|
| 633 | - ) |
|
| 634 | - ); |
|
| 635 | - } |
|
| 636 | - } |
|
| 637 | - |
|
| 638 | - /** |
|
| 639 | - * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc. |
|
| 640 | - * @since $VID:$ |
|
| 641 | - * @return boolean |
|
| 642 | - */ |
|
| 643 | - private function isLogicQueryParam() |
|
| 644 | - { |
|
| 645 | - return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys()); |
|
| 646 | - } |
|
| 647 | - |
|
| 648 | - |
|
| 649 | - /** |
|
| 650 | - * If the query param isn't for a field, it must be a nested query parameter which requires different logic. |
|
| 651 | - * @since $VID:$ |
|
| 652 | - * @return array |
|
| 653 | - * @throws DomainException |
|
| 654 | - * @throws EE_Error |
|
| 655 | - * @throws RestException |
|
| 656 | - * @throws InvalidDataTypeException |
|
| 657 | - * @throws InvalidInterfaceException |
|
| 658 | - * @throws InvalidArgumentException |
|
| 659 | - */ |
|
| 660 | - public function determineNestedConditionQueryParameters() |
|
| 661 | - { |
|
| 662 | - |
|
| 663 | - // so this param doesn't correspond to a field eh? |
|
| 664 | - if ($this->getContext()->isWriting()) { |
|
| 665 | - // always tell API clients about invalid parameters when they're creating data. Otherwise, |
|
| 666 | - // they are probably going to create invalid data |
|
| 667 | - throw new RestException( |
|
| 668 | - 'invalid_field', |
|
| 669 | - sprintf( |
|
| 670 | - /* translators: 1: variable name */ |
|
| 671 | - esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'), |
|
| 672 | - $this->getQueryParamKey() |
|
| 673 | - ) |
|
| 674 | - ); |
|
| 675 | - } |
|
| 676 | - // so it's not for a field, is it a logic query param key? |
|
| 677 | - if ($this->isLogicQueryParam()) { |
|
| 678 | - return ModelDataTranslator::prepareConditionsQueryParamsForModels( |
|
| 679 | - $this->getQueryParamValue(), |
|
| 680 | - $this->getContext()->getModel(), |
|
| 681 | - $this->getContext()->getRequestedVersion() |
|
| 682 | - ); |
|
| 683 | - } |
|
| 684 | - if (EED_Core_Rest_Api::debugMode()) { |
|
| 685 | - // only tell API clients they got it wrong if we're in debug mode |
|
| 686 | - // otherwise try our best ot fulfill their request by ignoring this invalid data |
|
| 687 | - throw new RestException( |
|
| 688 | - 'invalid_parameter', |
|
| 689 | - sprintf( |
|
| 690 | - /* translators: 1: variable name */ |
|
| 691 | - esc_html__( |
|
| 692 | - 'You provided an invalid parameter, with key "%1$s"', |
|
| 693 | - 'event_espresso' |
|
| 694 | - ), |
|
| 695 | - $this->getQueryParamKey() |
|
| 696 | - ), |
|
| 697 | - array( |
|
| 698 | - 'status' => 400, |
|
| 699 | - ) |
|
| 700 | - ); |
|
| 701 | - } |
|
| 702 | - return null; |
|
| 703 | - } |
|
| 29 | + private $query_param_key; |
|
| 30 | + private $query_param_value; |
|
| 31 | + /** |
|
| 32 | + * @var RestIncomingQueryParamContext |
|
| 33 | + */ |
|
| 34 | + private $context; |
|
| 35 | + |
|
| 36 | + /** |
|
| 37 | + * @var EE_Model_Field_Base|null |
|
| 38 | + */ |
|
| 39 | + private $field; |
|
| 40 | + |
|
| 41 | + /** |
|
| 42 | + * @var string same as $query_param_key but has the * and anything after it removed |
|
| 43 | + */ |
|
| 44 | + private $query_param_key_sans_stars; |
|
| 45 | + |
|
| 46 | + /** |
|
| 47 | + * @var string for timezone or timezone offset |
|
| 48 | + */ |
|
| 49 | + private $timezone; |
|
| 50 | + |
|
| 51 | + /** |
|
| 52 | + * @var boolean if the field in $query_param_key is for a GMT field (eg `EVT_modified_gmt`) |
|
| 53 | + */ |
|
| 54 | + private $is_gmt_field = false; |
|
| 55 | + |
|
| 56 | + /** |
|
| 57 | + * RestIncomingQueryParamMetadata constructor. |
|
| 58 | + * You probably want to call |
|
| 59 | + * @param string $query_param_key |
|
| 60 | + * @param string $query_param_value |
|
| 61 | + * @param RestIncomingQueryParamContext $context |
|
| 62 | + */ |
|
| 63 | + public function __construct($query_param_key, $query_param_value, RestIncomingQueryParamContext $context) |
|
| 64 | + { |
|
| 65 | + $this->query_param_key = $query_param_key; |
|
| 66 | + $this->query_param_value = $query_param_value; |
|
| 67 | + $this->context = $context; |
|
| 68 | + $this->determineFieldAndTimezone(); |
|
| 69 | + } |
|
| 70 | + |
|
| 71 | + /** |
|
| 72 | + * Gets the query parameter key. This may have been modified (see setQueryParamValue()) |
|
| 73 | + * @return string |
|
| 74 | + */ |
|
| 75 | + public function getQueryParamKey() |
|
| 76 | + { |
|
| 77 | + return $this->query_param_key; |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + /** |
|
| 81 | + * Modifies the query parameter key passed in (Eg this is done when rewriting the simplified specified operator REST |
|
| 82 | + * query parameters into the legacy structure) |
|
| 83 | + * @param string|array|int|float $query_param_value |
|
| 84 | + */ |
|
| 85 | + private function setQueryParamValue($query_param_value) |
|
| 86 | + { |
|
| 87 | + $this->query_param_value = $query_param_value; |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + /** |
|
| 91 | + * Gets the original query parameter value passed in. |
|
| 92 | + * @return string |
|
| 93 | + */ |
|
| 94 | + public function getQueryParamValue() |
|
| 95 | + { |
|
| 96 | + return $this->query_param_value; |
|
| 97 | + } |
|
| 98 | + |
|
| 99 | + /** |
|
| 100 | + * Gets the context object. |
|
| 101 | + * @return RestIncomingQueryParamContext |
|
| 102 | + */ |
|
| 103 | + public function getContext() |
|
| 104 | + { |
|
| 105 | + return $this->context; |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + /** |
|
| 109 | + * Sets the query parameter key. This may be used to rewrite a key into its non-GMT alternative. |
|
| 110 | + * @param string $query_param_key |
|
| 111 | + */ |
|
| 112 | + private function setQueryParamKey($query_param_key) |
|
| 113 | + { |
|
| 114 | + $this->query_param_key = $query_param_key; |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + /** |
|
| 118 | + * Gets the field the query parameter key indicated. This may be null (in cases where the query parameter key |
|
| 119 | + * did not indicate a field, eg if it were `OR`). |
|
| 120 | + * @return EE_Model_Field_Base|null |
|
| 121 | + */ |
|
| 122 | + public function getField() |
|
| 123 | + { |
|
| 124 | + return $this->field; |
|
| 125 | + } |
|
| 126 | + |
|
| 127 | + /** |
|
| 128 | + * Gets the query parameter key (with the star and everything afterwards removed). |
|
| 129 | + * @return string |
|
| 130 | + */ |
|
| 131 | + public function getQueryParamKeySansStars() |
|
| 132 | + { |
|
| 133 | + return $this->query_param_key_sans_stars; |
|
| 134 | + } |
|
| 135 | + |
|
| 136 | + /** |
|
| 137 | + * Gets the timezone associated with this model (the site timezone, except for GMT datetime fields). |
|
| 138 | + * @return string |
|
| 139 | + */ |
|
| 140 | + public function getTimezone() |
|
| 141 | + { |
|
| 142 | + return $this->timezone; |
|
| 143 | + } |
|
| 144 | + |
|
| 145 | + /** |
|
| 146 | + * Returns whether or not this is a GMT field |
|
| 147 | + * @return boolean |
|
| 148 | + */ |
|
| 149 | + public function isGmtField() |
|
| 150 | + { |
|
| 151 | + return $this->is_gmt_field; |
|
| 152 | + } |
|
| 153 | + |
|
| 154 | + /** |
|
| 155 | + * Sets the field indicated by the query parameter key (might be null). |
|
| 156 | + * @param EE_Model_Field_Base|null $field |
|
| 157 | + */ |
|
| 158 | + private function setField(EE_Model_Field_Base $field = null) |
|
| 159 | + { |
|
| 160 | + $this->field = $field; |
|
| 161 | + } |
|
| 162 | + |
|
| 163 | + /** |
|
| 164 | + * Sets the query parameter key-with-stars-removed. |
|
| 165 | + * @param string $query_param_key_sans_stars |
|
| 166 | + */ |
|
| 167 | + private function setQueryParamKeySansStars($query_param_key_sans_stars) |
|
| 168 | + { |
|
| 169 | + $this->query_param_key_sans_stars = $query_param_key_sans_stars; |
|
| 170 | + } |
|
| 171 | + |
|
| 172 | + /** |
|
| 173 | + * Sets the timezone (this could be a timezeon offset string). |
|
| 174 | + * @param string $timezone |
|
| 175 | + */ |
|
| 176 | + private function setTimezone($timezone) |
|
| 177 | + { |
|
| 178 | + $this->timezone = $timezone; |
|
| 179 | + } |
|
| 180 | + |
|
| 181 | + /** |
|
| 182 | + * @param mixed $is_gmt_field |
|
| 183 | + */ |
|
| 184 | + private function setIsGmtField($is_gmt_field) |
|
| 185 | + { |
|
| 186 | + $this->is_gmt_field = $is_gmt_field; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + /** |
|
| 190 | + * Determines what field, query param name, and query param name without stars, and timezone to use. |
|
| 191 | + * @since $VID:$ |
|
| 192 | + * @type EE_Model_Field_Base $field |
|
| 193 | + * @return void { |
|
| 194 | + * @throws EE_Error |
|
| 195 | + * @throws InvalidDataTypeException |
|
| 196 | + * @throws InvalidInterfaceException |
|
| 197 | + * @throws InvalidArgumentException |
|
| 198 | + */ |
|
| 199 | + private function determineFieldAndTimezone() |
|
| 200 | + { |
|
| 201 | + $this->setQueryParamKeySansStars(ModelDataTranslator::removeStarsAndAnythingAfterFromConditionQueryParamKey( |
|
| 202 | + $this->getQueryParamKey() |
|
| 203 | + )); |
|
| 204 | + $this->setField(ModelDataTranslator::deduceFieldFromQueryParam( |
|
| 205 | + $this->getQueryParamKeySansStars(), |
|
| 206 | + $this->getContext()->getModel() |
|
| 207 | + )); |
|
| 208 | + // double-check is it a *_gmt field? |
|
| 209 | + if (!$this->getField() instanceof EE_Model_Field_Base |
|
| 210 | + && ModelDataTranslator::isGmtDateFieldName($this->getQueryParamKeySansStars()) |
|
| 211 | + ) { |
|
| 212 | + // yep, take off '_gmt', and find the field |
|
| 213 | + $this->setQueryParamKey(ModelDataTranslator::removeGmtFromFieldName($this->getQueryParamKeySansStars())); |
|
| 214 | + $this->setField(ModelDataTranslator::deduceFieldFromQueryParam( |
|
| 215 | + $this->getQueryParamKey(), |
|
| 216 | + $this->context->getModel() |
|
| 217 | + )); |
|
| 218 | + $this->setTimezone('UTC'); |
|
| 219 | + $this->setIsGmtField(true); |
|
| 220 | + } elseif ($this->getField() instanceof EE_Datetime_Field) { |
|
| 221 | + // so it's not a GMT field. Set the timezone on the model to the default |
|
| 222 | + $this->setTimezone(EEH_DTT_Helper::get_valid_timezone_string()); |
|
| 223 | + } else { |
|
| 224 | + // just keep using what's already set for the timezone |
|
| 225 | + $this->setTimezone($this->context->getModel()->get_timezone()); |
|
| 226 | + } |
|
| 227 | + } |
|
| 228 | + |
|
| 229 | + /** |
|
| 230 | + * Given a ton of input, determines the value to use for the models. |
|
| 231 | + * @since $VID:$ |
|
| 232 | + * @return array|null |
|
| 233 | + * @throws DomainException |
|
| 234 | + * @throws EE_Error |
|
| 235 | + * @throws RestException |
|
| 236 | + * @throws DomainException |
|
| 237 | + */ |
|
| 238 | + public function determineConditionsQueryParameterValue() |
|
| 239 | + { |
|
| 240 | + if ($this->valueIsArrayDuringRead()) { |
|
| 241 | + return $this->determineModelValueGivenRestInputArray(); |
|
| 242 | + } |
|
| 243 | + return ModelDataTranslator::prepareFieldValueFromJson( |
|
| 244 | + $this->getField(), |
|
| 245 | + $this->getQueryParamValue(), |
|
| 246 | + $this->getContext()->getRequestedVersion(), |
|
| 247 | + $this->getTimezone() |
|
| 248 | + ); |
|
| 249 | + } |
|
| 250 | + |
|
| 251 | + /** |
|
| 252 | + * Given that the array value provided was itself an array, handles finding the correct value to pass to the model. |
|
| 253 | + * @since $VID:$ |
|
| 254 | + * @return array|null |
|
| 255 | + * @throws RestException |
|
| 256 | + */ |
|
| 257 | + private function determineModelValueGivenRestInputArray() |
|
| 258 | + { |
|
| 259 | + $this->transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax(); |
|
| 260 | + // did they specify an operator? |
|
| 261 | + if ($this->valueIsLegacySpecifiedOperator()) { |
|
| 262 | + $query_param_value = $this->getQueryParamValue(); |
|
| 263 | + $sub_array_key = $query_param_value[0]; |
|
| 264 | + $translated_value = array($sub_array_key); |
|
| 265 | + if ($this->operatorIsNAry($sub_array_key)) { |
|
| 266 | + $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]); |
|
| 267 | + } elseif ($this->operatorIsTernary($sub_array_key)) { |
|
| 268 | + $translated_value[] = array( |
|
| 269 | + $this->prepareValuesFromJson($query_param_value[1][0]), |
|
| 270 | + $this->prepareValuesFromJson($query_param_value[1][1]) |
|
| 271 | + ); |
|
| 272 | + } elseif ($this->operatorIsLike($sub_array_key)) { |
|
| 273 | + // we want to leave this value mostly-as-is (eg don't force it to be a float |
|
| 274 | + // or a boolean or an enum value. Leave it as-is with wildcards etc) |
|
| 275 | + // but do verify it at least doesn't have any serialized data |
|
| 276 | + ModelDataTranslator::throwExceptionIfContainsSerializedData($query_param_value[1]); |
|
| 277 | + $translated_value[] = $query_param_value[1]; |
|
| 278 | + } elseif ($this->operatorIsUnary($sub_array_key)) { |
|
| 279 | + // no arguments should have been provided, so don't look for any |
|
| 280 | + } elseif ($this->operatorisBinary($sub_array_key)) { |
|
| 281 | + // it's a valid operator, but none of the exceptions. Treat it normally. |
|
| 282 | + $translated_value[] = $this->prepareValuesFromJson($query_param_value[1]); |
|
| 283 | + } else { |
|
| 284 | + // so they provided a valid operator, but wrong number of arguments |
|
| 285 | + $this->throwWrongNumberOfArgsExceptionIfDebugging($sub_array_key); |
|
| 286 | + $translated_value = null; |
|
| 287 | + } |
|
| 288 | + } else { |
|
| 289 | + // so they didn't provide a valid operator |
|
| 290 | + // if we aren't in debug mode, then just try our best to fulfill the user's request |
|
| 291 | + $this->throwInvalidOperatorExceptionIfDebugging(); |
|
| 292 | + $translated_value = null; |
|
| 293 | + } |
|
| 294 | + return $translated_value; |
|
| 295 | + } |
|
| 296 | + |
|
| 297 | + /** |
|
| 298 | + * Returns if this request is a "read" request and the value provided was an array. |
|
| 299 | + * This will indicate is such things as `array('<', 123)` and `array('IN', array(1,2,3))` are acceptable or not. |
|
| 300 | + * @since $VID:$ |
|
| 301 | + * @return boolean |
|
| 302 | + */ |
|
| 303 | + private function valueIsArrayDuringRead() |
|
| 304 | + { |
|
| 305 | + return !$this->getContext()->isWriting() && is_array($this->getQueryParamValue()); |
|
| 306 | + } |
|
| 307 | + |
|
| 308 | + /** |
|
| 309 | + * Returns if the value provided was an associative array (we should have already verified it's an array of some |
|
| 310 | + * sort). If the value is an associative array, it had better be in the simplified specified operator structure. |
|
| 311 | + * @since $VID:$ |
|
| 312 | + * @return boolean |
|
| 313 | + */ |
|
| 314 | + private function valueIsAssociativeArray() |
|
| 315 | + { |
|
| 316 | + return !EEH_Array::is_array_numerically_and_sequentially_indexed($this->getQueryParamValue()); |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + /** |
|
| 320 | + * Checks if the array value is itself an array that fits into the simplified specified operator structure |
|
| 321 | + * (eg `array('!=' => 123)`). |
|
| 322 | + * @since $VID:$ |
|
| 323 | + * @return boolean |
|
| 324 | + */ |
|
| 325 | + private function valueIsSimplifiedSpecifiedOperator() |
|
| 326 | + { |
|
| 327 | + return count($this->getQueryParamValue()) === 1 |
|
| 328 | + && array_key_exists( |
|
| 329 | + key($this->getQueryParamValue()), |
|
| 330 | + $this->getContext()->getModel()->valid_operators() |
|
| 331 | + ); |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + /** |
|
| 335 | + * Throws an exception if the sub-value is an array (eg `array('!=' => array())`). It needs to just be a string, |
|
| 336 | + * of either comma-separated-values, or a JSON array. |
|
| 337 | + * @since $VID:$ |
|
| 338 | + * @param $sub_array_key |
|
| 339 | + * @param $sub_array_value |
|
| 340 | + * @throws RestException |
|
| 341 | + */ |
|
| 342 | + private function assertSubValueIsntArray($sub_array_key, $sub_array_value) |
|
| 343 | + { |
|
| 344 | + if (is_array($sub_array_value) && EED_Core_Rest_Api::debugMode()) { |
|
| 345 | + throw new RestException( |
|
| 346 | + 'csv_or_json_string_only', |
|
| 347 | + sprintf( |
|
| 348 | + /* translators: 1: variable name*/ |
|
| 349 | + esc_html__( |
|
| 350 | + 'The value provided for the operator "%1$s" should be comma-separated value string or a JSON array.', |
|
| 351 | + 'event_espresso' |
|
| 352 | + ), |
|
| 353 | + $sub_array_key |
|
| 354 | + ), |
|
| 355 | + array( |
|
| 356 | + 'status' => 400, |
|
| 357 | + ) |
|
| 358 | + ); |
|
| 359 | + } |
|
| 360 | + } |
|
| 361 | + |
|
| 362 | + /** |
|
| 363 | + * Determines if the sub-array key is an operator taking 3 or more operators. |
|
| 364 | + * @since $VID:$ |
|
| 365 | + * @param $sub_array_key |
|
| 366 | + * @return boolean |
|
| 367 | + */ |
|
| 368 | + private function subArrayKeyIsNonBinaryOperator($sub_array_key) |
|
| 369 | + { |
|
| 370 | + return array_key_exists( |
|
| 371 | + $sub_array_key, |
|
| 372 | + array_merge( |
|
| 373 | + $this->getContext()->getModel()->valid_in_style_operators(), |
|
| 374 | + $this->getContext()->getModel()->valid_between_style_operators() |
|
| 375 | + ) |
|
| 376 | + ); |
|
| 377 | + } |
|
| 378 | + |
|
| 379 | + /** |
|
| 380 | + * Given that the $sub_array_key is a string, checks if it's an operator taking only 1 argument. |
|
| 381 | + * @since $VID:$ |
|
| 382 | + * @param string $sub_array_key |
|
| 383 | + * @return boolean |
|
| 384 | + */ |
|
| 385 | + private function subArrayKeyIsUnaryOperator($sub_array_key) |
|
| 386 | + { |
|
| 387 | + return array_key_exists( |
|
| 388 | + $sub_array_key, |
|
| 389 | + $this->getContext()->getModel()->valid_null_style_operators() |
|
| 390 | + ); |
|
| 391 | + } |
|
| 392 | + |
|
| 393 | + /** |
|
| 394 | + * Parses the $sub_array_value string into an array (given it could either be a comma-separated-list or a JSON |
|
| 395 | + * array). eg `"1,2,3"` or `"[1,2,3]"` into `array(1,2,3)`. |
|
| 396 | + * @since $VID:$ |
|
| 397 | + * @param $sub_array_value |
|
| 398 | + * @return array|mixed|object |
|
| 399 | + */ |
|
| 400 | + private function extractQuickStyleSpecifiedOperatorValue($sub_array_value) |
|
| 401 | + { |
|
| 402 | + // the value should be JSON or CSV |
|
| 403 | + $values = json_decode($sub_array_value); |
|
| 404 | + if (!is_array($values)) { |
|
| 405 | + $values = array_filter( |
|
| 406 | + array_map( |
|
| 407 | + 'trim', |
|
| 408 | + explode( |
|
| 409 | + ',', |
|
| 410 | + $sub_array_value |
|
| 411 | + ) |
|
| 412 | + ) |
|
| 413 | + ); |
|
| 414 | + } |
|
| 415 | + return $values; |
|
| 416 | + } |
|
| 417 | + |
|
| 418 | + /** |
|
| 419 | + * Throws an exception if the value isn't a simplified specified operator (only called when we expect that). |
|
| 420 | + * @since $VID:$ |
|
| 421 | + * @throws RestException |
|
| 422 | + */ |
|
| 423 | + private function assertSimplifiedSpecifiedOperator() |
|
| 424 | + { |
|
| 425 | + if (!$this->valueIsSimplifiedSpecifiedOperator() && EED_Core_Rest_Api::debugMode()) { |
|
| 426 | + throw new RestException( |
|
| 427 | + 'numerically_indexed_array_of_values_only', |
|
| 428 | + sprintf( |
|
| 429 | + /* translators: 1: variable name*/ |
|
| 430 | + esc_html__( |
|
| 431 | + 'The array provided for the parameter "%1$s" should be numerically indexed.', |
|
| 432 | + 'event_espresso' |
|
| 433 | + ), |
|
| 434 | + $this->getQueryParamKey() |
|
| 435 | + ), |
|
| 436 | + array( |
|
| 437 | + 'status' => 400, |
|
| 438 | + ) |
|
| 439 | + ); |
|
| 440 | + } |
|
| 441 | + } |
|
| 442 | + |
|
| 443 | + /** |
|
| 444 | + * If query_param_value were in the simplified specific operator structure, change it into the legacy structure. |
|
| 445 | + * @since $VID:$ |
|
| 446 | + * @throws RestException |
|
| 447 | + */ |
|
| 448 | + private function transformSimplifiedSpecifiedOperatorSyntaxIntoStandardSyntax() |
|
| 449 | + { |
|
| 450 | + if ($this->valueIsAssociativeArray()) { |
|
| 451 | + $this->assertSimplifiedSpecifiedOperator(); |
|
| 452 | + $query_param_value = $this->getQueryParamValue(); |
|
| 453 | + $sub_array_value = reset($query_param_value); |
|
| 454 | + $sub_array_key = key($query_param_value); |
|
| 455 | + $this->assertSubValueIsntArray($sub_array_key, $sub_array_value); |
|
| 456 | + // they're doing something like "&where[EVT_ID][IN]=1,2,3" or "&where[EVT_ID][>]=5" |
|
| 457 | + if ($this->subArrayKeyIsNonBinaryOperator($sub_array_key)) { |
|
| 458 | + $this->setQueryParamValue(array( |
|
| 459 | + $sub_array_key, |
|
| 460 | + $this->extractQuickStyleSpecifiedOperatorValue($sub_array_value) |
|
| 461 | + )); |
|
| 462 | + } elseif ($this->subArrayKeyIsUnaryOperator($sub_array_key)) { |
|
| 463 | + $this->setQueryParamValue(array($sub_array_key)); |
|
| 464 | + } else { |
|
| 465 | + $this->setQueryParamValue(array($sub_array_key, $sub_array_value)); |
|
| 466 | + } |
|
| 467 | + } |
|
| 468 | + } |
|
| 469 | + |
|
| 470 | + /** |
|
| 471 | + * Returns true is the value is an array using the legacy structure to specify the operator. Eg `array('!=',123)`. |
|
| 472 | + * @since $VID:$ |
|
| 473 | + * @return boolean |
|
| 474 | + */ |
|
| 475 | + private function valueIsLegacySpecifiedOperator() |
|
| 476 | + { |
|
| 477 | + $valid_operators = $this->getContext()->getModel()->valid_operators(); |
|
| 478 | + $query_param_value = $this->getQueryParamValue(); |
|
| 479 | + return isset($query_param_value[0]) |
|
| 480 | + && isset($valid_operators[ $query_param_value[0] ]); |
|
| 481 | + } |
|
| 482 | + |
|
| 483 | + /** |
|
| 484 | + * Returns true if the value specified operator accepts arbitrary number of arguments, like "IN". |
|
| 485 | + * @since $VID:$ |
|
| 486 | + * @param $operator |
|
| 487 | + * @return boolean |
|
| 488 | + */ |
|
| 489 | + private function operatorIsNAry($operator) |
|
| 490 | + { |
|
| 491 | + $valueArray = $this->getQueryParamValue(); |
|
| 492 | + return array_key_exists( |
|
| 493 | + $operator, |
|
| 494 | + $this->getContext()->getModel()->valid_in_style_operators() |
|
| 495 | + ) |
|
| 496 | + && isset($valueArray[1]) |
|
| 497 | + && is_array($valueArray[1]) |
|
| 498 | + && !isset($valueArray[2]); |
|
| 499 | + } |
|
| 500 | + |
|
| 501 | + /** |
|
| 502 | + * Returns true if the operator accepts 3 arguments (eg "BETWEEN"). |
|
| 503 | + * So we're looking for a value that looks like |
|
| 504 | + * `array('BETWEEN', array('2015-01-01T00:00:00', '2016-01-01T00:00:00'))`. |
|
| 505 | + * @since $VID:$ |
|
| 506 | + * @param $operator |
|
| 507 | + * @return boolean |
|
| 508 | + */ |
|
| 509 | + private function operatorIsTernary($operator) |
|
| 510 | + { |
|
| 511 | + $query_param_value = $this->getQueryParamValue(); |
|
| 512 | + return array_key_exists($operator, $this->getContext()->getModel()->valid_between_style_operators()) |
|
| 513 | + && isset($query_param_value[1]) |
|
| 514 | + && is_array($query_param_value[1]) |
|
| 515 | + && isset($query_param_value[1][0], $query_param_value[1][1]) |
|
| 516 | + && !isset($query_param_value[1][2]) |
|
| 517 | + && !isset($query_param_value[2]); |
|
| 518 | + } |
|
| 519 | + |
|
| 520 | + /** |
|
| 521 | + * Returns true if the operator is a similar to LIKE, indicating the value may have wildcards we should leave alone. |
|
| 522 | + * @since $VID:$ |
|
| 523 | + * @param $operator |
|
| 524 | + * @return boolean |
|
| 525 | + */ |
|
| 526 | + private function operatorIsLike($operator) |
|
| 527 | + { |
|
| 528 | + $query_param_value = $this->getQueryParamValue(); |
|
| 529 | + return array_key_exists($operator, $this->getContext()->getModel()->valid_like_style_operators()) |
|
| 530 | + && isset($query_param_value[1]) |
|
| 531 | + && !isset($query_param_value[2]); |
|
| 532 | + } |
|
| 533 | + |
|
| 534 | + /** |
|
| 535 | + * Returns true if the operator only takes one argument (eg it's like `IS NULL`). |
|
| 536 | + * @since $VID:$ |
|
| 537 | + * @param $operator |
|
| 538 | + * @return boolean |
|
| 539 | + */ |
|
| 540 | + private function operatorIsUnary($operator) |
|
| 541 | + { |
|
| 542 | + $query_param_value = $this->getQueryParamValue(); |
|
| 543 | + return array_key_exists($operator, $this->getContext()->getModel()->valid_null_style_operators()) |
|
| 544 | + && !isset($query_param_value[1]); |
|
| 545 | + } |
|
| 546 | + |
|
| 547 | + /** |
|
| 548 | + * Returns true if the operator specified is a binary opeator (eg `=`, `!=`) |
|
| 549 | + * @since $VID:$ |
|
| 550 | + * @param $operator |
|
| 551 | + * @return boolean |
|
| 552 | + */ |
|
| 553 | + private function operatorisBinary($operator) |
|
| 554 | + { |
|
| 555 | + $query_param_value = $this->getQueryParamValue(); |
|
| 556 | + $model = $this->getContext()->getModel(); |
|
| 557 | + return isset($query_param_value[1]) |
|
| 558 | + && !isset($query_param_value[2]) |
|
| 559 | + && !array_key_exists( |
|
| 560 | + $operator, |
|
| 561 | + array_merge( |
|
| 562 | + $model->valid_in_style_operators(), |
|
| 563 | + $model->valid_null_style_operators(), |
|
| 564 | + $model->valid_like_style_operators(), |
|
| 565 | + $model->valid_between_style_operators() |
|
| 566 | + ) |
|
| 567 | + ); |
|
| 568 | + } |
|
| 569 | + |
|
| 570 | + /** |
|
| 571 | + * If we're debugging, throws an exception saying that the wrong number of arguments was provided. |
|
| 572 | + * @since $VID:$ |
|
| 573 | + * @param $operator |
|
| 574 | + * @throws RestException |
|
| 575 | + */ |
|
| 576 | + private function throwWrongNumberOfArgsExceptionIfDebugging($operator) |
|
| 577 | + { |
|
| 578 | + if (EED_Core_Rest_Api::debugMode()) { |
|
| 579 | + throw new RestException( |
|
| 580 | + 'wrong_number_of_arguments', |
|
| 581 | + sprintf( |
|
| 582 | + esc_html__( |
|
| 583 | + 'The operator you provided, "%1$s" had the wrong number of arguments', |
|
| 584 | + 'event_espresso' |
|
| 585 | + ), |
|
| 586 | + $operator |
|
| 587 | + ), |
|
| 588 | + array( |
|
| 589 | + 'status' => 400, |
|
| 590 | + ) |
|
| 591 | + ); |
|
| 592 | + } |
|
| 593 | + } |
|
| 594 | + |
|
| 595 | + /** |
|
| 596 | + * Wrapper for ModelDataTranslator::prepareFieldValuesFromJson(), just a tad more DRY. |
|
| 597 | + * @since $VID:$ |
|
| 598 | + * @param $value |
|
| 599 | + * @return mixed |
|
| 600 | + * @throws RestException |
|
| 601 | + */ |
|
| 602 | + private function prepareValuesFromJson($value) |
|
| 603 | + { |
|
| 604 | + return ModelDataTranslator::prepareFieldValuesFromJson( |
|
| 605 | + $this->getField(), |
|
| 606 | + $value, |
|
| 607 | + $this->getContext()->getRequestedVersion(), |
|
| 608 | + $this->getTimezone() |
|
| 609 | + ); |
|
| 610 | + } |
|
| 611 | + |
|
| 612 | + /** |
|
| 613 | + * Throws an exception if an invalid operator was specified and we're debugging. |
|
| 614 | + * @since $VID:$ |
|
| 615 | + * @throws RestException |
|
| 616 | + */ |
|
| 617 | + private function throwInvalidOperatorExceptionIfDebugging() |
|
| 618 | + { |
|
| 619 | + // so they didn't provide a valid operator |
|
| 620 | + if (EED_Core_Rest_Api::debugMode()) { |
|
| 621 | + throw new RestException( |
|
| 622 | + 'invalid_operator', |
|
| 623 | + sprintf( |
|
| 624 | + esc_html__( |
|
| 625 | + 'You provided an invalid parameter, with key "%1$s" and value "%2$s"', |
|
| 626 | + 'event_espresso' |
|
| 627 | + ), |
|
| 628 | + $this->getQueryParamKey(), |
|
| 629 | + $this->getQueryParamValue() |
|
| 630 | + ), |
|
| 631 | + array( |
|
| 632 | + 'status' => 400, |
|
| 633 | + ) |
|
| 634 | + ); |
|
| 635 | + } |
|
| 636 | + } |
|
| 637 | + |
|
| 638 | + /** |
|
| 639 | + * Returns true if the query_param_key was a logic query parameter, eg `OR`, `AND`, `NOT`, `OR*`, etc. |
|
| 640 | + * @since $VID:$ |
|
| 641 | + * @return boolean |
|
| 642 | + */ |
|
| 643 | + private function isLogicQueryParam() |
|
| 644 | + { |
|
| 645 | + return in_array($this->getQueryParamKeySansStars(), $this->getContext()->getModel()->logic_query_param_keys()); |
|
| 646 | + } |
|
| 647 | + |
|
| 648 | + |
|
| 649 | + /** |
|
| 650 | + * If the query param isn't for a field, it must be a nested query parameter which requires different logic. |
|
| 651 | + * @since $VID:$ |
|
| 652 | + * @return array |
|
| 653 | + * @throws DomainException |
|
| 654 | + * @throws EE_Error |
|
| 655 | + * @throws RestException |
|
| 656 | + * @throws InvalidDataTypeException |
|
| 657 | + * @throws InvalidInterfaceException |
|
| 658 | + * @throws InvalidArgumentException |
|
| 659 | + */ |
|
| 660 | + public function determineNestedConditionQueryParameters() |
|
| 661 | + { |
|
| 662 | + |
|
| 663 | + // so this param doesn't correspond to a field eh? |
|
| 664 | + if ($this->getContext()->isWriting()) { |
|
| 665 | + // always tell API clients about invalid parameters when they're creating data. Otherwise, |
|
| 666 | + // they are probably going to create invalid data |
|
| 667 | + throw new RestException( |
|
| 668 | + 'invalid_field', |
|
| 669 | + sprintf( |
|
| 670 | + /* translators: 1: variable name */ |
|
| 671 | + esc_html__('You have provided an invalid parameter: "%1$s"', 'event_espresso'), |
|
| 672 | + $this->getQueryParamKey() |
|
| 673 | + ) |
|
| 674 | + ); |
|
| 675 | + } |
|
| 676 | + // so it's not for a field, is it a logic query param key? |
|
| 677 | + if ($this->isLogicQueryParam()) { |
|
| 678 | + return ModelDataTranslator::prepareConditionsQueryParamsForModels( |
|
| 679 | + $this->getQueryParamValue(), |
|
| 680 | + $this->getContext()->getModel(), |
|
| 681 | + $this->getContext()->getRequestedVersion() |
|
| 682 | + ); |
|
| 683 | + } |
|
| 684 | + if (EED_Core_Rest_Api::debugMode()) { |
|
| 685 | + // only tell API clients they got it wrong if we're in debug mode |
|
| 686 | + // otherwise try our best ot fulfill their request by ignoring this invalid data |
|
| 687 | + throw new RestException( |
|
| 688 | + 'invalid_parameter', |
|
| 689 | + sprintf( |
|
| 690 | + /* translators: 1: variable name */ |
|
| 691 | + esc_html__( |
|
| 692 | + 'You provided an invalid parameter, with key "%1$s"', |
|
| 693 | + 'event_espresso' |
|
| 694 | + ), |
|
| 695 | + $this->getQueryParamKey() |
|
| 696 | + ), |
|
| 697 | + array( |
|
| 698 | + 'status' => 400, |
|
| 699 | + ) |
|
| 700 | + ); |
|
| 701 | + } |
|
| 702 | + return null; |
|
| 703 | + } |
|
| 704 | 704 | } |
| 705 | 705 | // End of file RestQueryParamMetadata.php |
| 706 | 706 | // Location: EventEspresso\core\libraries\rest_api/RestQueryParamMetadata.php |