| @@ -125,8 +125,78 @@ | ||
| 125 | 125 | 'rewrite' => false, |
| 126 | 126 | '_pll' => true, |
| 127 | 127 | ) |
| 128 | 128 | ); |
| 129 | + | |
| 130 | + $this->add_sanitization_hooks( $this->tax_language ); | |
| 131 | + } | |
| 132 | + | |
| 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 ); | |
| 129 | 199 | } |
| 130 | 200 | |
| 131 | 201 | /** |
| 132 | 202 | * Returns the language taxonomy name. |