| @@ -125,11 +125,81 @@ | ||
| 125 | 125 | 'rewrite' => false, |
| 126 | 126 | '_pll' => true, |
| 127 | 127 | ) |
| 128 | 128 | ); |
| 129 | + | |
| 130 | + $this->add_sanitization_hooks( $this->tax_language ); | |
| 129 | 131 | } |
| 130 | 132 | |
| 131 | 133 | /** |
| 134 | + * Hooks sanitization for a Polylang taxonomy that stores serialized data in term descriptions. | |
| 135 | + * | |
| 136 | + * @since 3.8.10 | |
| 137 | + * | |
| 138 | + * @param string $taxonomy Taxonomy name. | |
| 139 | + * @return void | |
| 140 | + * | |
| 141 | + * @phpstan-param non-empty-string $taxonomy | |
| 142 | + */ | |
| 143 | + protected function add_sanitization_hooks( string $taxonomy ): void { | |
| 144 | + add_filter( "pre_{$taxonomy}_description", array( $this, 'sanitize_description' ), 0 ); | |
| 145 | + add_filter( "get_{$taxonomy}", array( $this, 'sanitize_term' ), 0 ); | |
| 146 | + } | |
| 147 | + | |
| 148 | + /** | |
| 149 | + * Empties the description of a term hydrated from the database when it holds a disallowed serialized type. | |
| 150 | + * | |
| 151 | + * `get_{$taxonomy}` is fired by `get_term()`, whatever the sanitization context, and thus covers the | |
| 152 | + * terms hydrated by `get_terms()` and `wp_get_object_terms()` too. Only the returned object is modified, | |
| 153 | + * the stored value is left untouched. | |
| 154 | + * | |
| 155 | + * @since 3.8.10 | |
| 156 | + * | |
| 157 | + * @param mixed $term Term object, may be anything another callback returned. | |
| 158 | + * @return mixed The term, with a sanitized description. | |
| 159 | + */ | |
| 160 | + public function sanitize_term( $term ) { | |
| 161 | + if ( $term instanceof WP_Term && $this->has_disallowed_type( $term->description ) ) { | |
| 162 | + $term->description = ''; | |
| 163 | + } | |
| 164 | + | |
| 165 | + return $term; | |
| 166 | + } | |
| 167 | + | |
| 168 | + /** | |
| 169 | + * Drops serialized values that contain a disallowed PHP type. | |
| 170 | + * | |
| 171 | + * @since 3.8.10 | |
| 172 | + * | |
| 173 | + * @param mixed $description Term description. | |
| 174 | + * @return string Empty string for a non-string value or when a disallowed type is found, unchanged otherwise. | |
| 175 | + */ | |
| 176 | + public function sanitize_description( $description ) { | |
| 177 | + if ( ! is_string( $description ) || '' === $description ) { | |
| 178 | + return ''; | |
| 179 | + } | |
| 180 | + | |
| 181 | + return $this->has_disallowed_type( $description ) ? '' : $description; | |
| 182 | + } | |
| 183 | + | |
| 184 | + /** | |
| 185 | + * Tells if a serialized value contains a disallowed PHP type. | |
| 186 | + * | |
| 187 | + * The regex does not parse string payloads: a string value containing `{O:` or `";O:` | |
| 188 | + * is treated as a disallowed type. | |
| 189 | + * | |
| 190 | + * Allowed serialized types: array, string, int, and bool. | |
| 191 | + * | |
| 192 | + * @since 3.8.10 | |
| 193 | + * | |
| 194 | + * @param string $description Term description. | |
| 195 | + * @return bool | |
| 196 | + */ | |
| 197 | + private function has_disallowed_type( string $description ): bool { | |
| 198 | + return 0 !== preg_match( '#(?:^|[;{])(?:[OCEdrR]:|N;)#', $description ); | |
| 199 | + } | |
| 200 | + | |
| 201 | + /** | |
| 132 | 202 | * Returns the language taxonomy name. |
| 133 | 203 | * |
| 134 | 204 | * @since 3.4 |
| 135 | 205 | * |
| @@ -175,9 +245,9 @@ | ||
| 175 | 245 | * @return bool True when successfully assigned. False otherwise (or if the given language is already assigned to |
| 176 | 246 | * the object). |
| 177 | 247 | */ |
| 178 | 248 | public function set_language( $id, $lang ) { |
| 179 | - $id = $this->sanitize_int_id( $id ); | |
| 249 | + $id = pll_sanitize_id( $id ); | |
| 180 | 250 | |
| 181 | 251 | if ( empty( $id ) ) { |
| 182 | 252 | return false; |
| 183 | 253 | } |
| @@ -209,9 +279,9 @@ | ||
| 209 | 279 | * @return PLL_Language|false A `PLL_Language` object. `false` if no language is associated to that object or if the |
| 210 | 280 | * ID is invalid. |
| 211 | 281 | */ |
| 212 | 282 | public function get_language( $id ) { |
| 213 | - $id = $this->sanitize_int_id( $id ); | |
| 283 | + $id = pll_sanitize_id( $id ); | |
| 214 | 284 | |
| 215 | 285 | if ( empty( $id ) ) { |
| 216 | 286 | return false; |
| 217 | 287 | } |
| @@ -234,9 +304,9 @@ | ||
| 234 | 304 | * @param int $id Term ID. |
| 235 | 305 | * @return void |
| 236 | 306 | */ |
| 237 | 307 | public function delete_language( $id ) { |
| 238 | - $id = $this->sanitize_int_id( $id ); | |
| 308 | + $id = pll_sanitize_id( $id ); | |
| 239 | 309 | |
| 240 | 310 | if ( empty( $id ) ) { |
| 241 | 311 | return; |
| 242 | 312 | } |
| @@ -253,9 +323,9 @@ | ||
| 253 | 323 | * @param string $taxonomy Taxonomy name. |
| 254 | 324 | * @return array<int,WP_Term> Array of terms with object ID as key. |
| 255 | 325 | */ |
| 256 | 326 | protected function get_object_terms( array $object_ids, string $taxonomy ) { |
| 257 | - $object_ids = $this->sanitize_int_ids_list( $object_ids ); | |
| 327 | + $object_ids = pll_sanitize_ids( $object_ids ); | |
| 258 | 328 | if ( empty( $object_ids ) ) { |
| 259 | 329 | return array(); |
| 260 | 330 | } |
| 261 | 331 | |
| @@ -464,9 +534,9 @@ | ||
| 464 | 534 | } |
| 465 | 535 | |
| 466 | 536 | $object_ids = $this->query_objects_with_no_lang( $language_ids, $limit, $args ); |
| 467 | 537 | |
| 468 | - return array_values( $this->sanitize_int_ids_list( $object_ids ) ); | |
| 538 | + return array_values( pll_sanitize_ids( $object_ids ) ); | |
| 469 | 539 | } |
| 470 | 540 | |
| 471 | 541 | /** |
| 472 | 542 | * Returns object IDs without language. |
| @@ -496,44 +566,8 @@ | ||
| 496 | 566 | $object_ids = $this->get_raw_objects_with_no_lang( $language_ids, $limit, $args ); |
| 497 | 567 | $this->set_to_cache( $key, $object_ids ); |
| 498 | 568 | |
| 499 | 569 | return $object_ids; |
| 500 | - } | |
| 501 | - | |
| 502 | - /** | |
| 503 | - * Sanitizes an ID as positive integer. | |
| 504 | - * Kind of similar to `absint()`, but rejects negative integers instead of making them positive. | |
| 505 | - * | |
| 506 | - * @since 3.2 | |
| 507 | - * | |
| 508 | - * @param mixed $id A supposedly numeric ID. | |
| 509 | - * @return int A positive integer. `0` for non numeric values and negative integers. | |
| 510 | - * | |
| 511 | - * @phpstan-return int<0,max> | |
| 512 | - */ | |
| 513 | - public function sanitize_int_id( $id ) { | |
| 514 | - return is_numeric( $id ) && $id >= 1 ? abs( (int) $id ) : 0; | |
| 515 | - } | |
| 516 | - | |
| 517 | - /** | |
| 518 | - * Sanitizes an array of IDs as positive integers. | |
| 519 | - * `0` values are removed. | |
| 520 | - * | |
| 521 | - * @since 3.2 | |
| 522 | - * | |
| 523 | - * @param mixed $ids An array of numeric IDs. | |
| 524 | - * @return int[] | |
| 525 | - * | |
| 526 | - * @phpstan-return array<positive-int> | |
| 527 | - */ | |
| 528 | - public function sanitize_int_ids_list( $ids ) { | |
| 529 | - if ( empty( $ids ) || ! is_array( $ids ) ) { | |
| 530 | - return array(); | |
| 531 | - } | |
| 532 | - | |
| 533 | - $ids = array_map( array( $this, 'sanitize_int_id' ), $ids ); | |
| 534 | - | |
| 535 | - return array_filter( $ids ); | |
| 536 | 570 | } |
| 537 | 571 | |
| 538 | 572 | /** |
| 539 | 573 | * Fetches the IDs of the objects without language. |