| @@ -254,9 +254,9 @@ | ||
| 254 | 254 | $fulltext = ''; |
| 255 | 255 | foreach ( apply_filters( 'friends_keyword_search_fields', array( 'post_title', 'post_content' ) ) as $field ) { |
| 256 | 256 | $fulltext .= PHP_EOL . $post->$field; |
| 257 | 257 | } |
| 258 | - $fulltext = wp_strip_all_tags( $fulltext ); | |
| 258 | + $fulltext = self::prepare_keyword_search_text( $fulltext ); | |
| 259 | 259 | foreach ( $keywords as $keyword ) { |
| 260 | 260 | if ( preg_match( '/' . str_replace( '/', '\\/', $keyword ) . '/ius', $fulltext ) ) { |
| 261 | 261 | $keyword_match = $keyword; |
| 262 | 262 | break; |
| @@ -480,8 +480,113 @@ | ||
| 480 | 480 | return $catch_all; |
| 481 | 481 | } |
| 482 | 482 | |
| 483 | 483 | /** |
| 484 | + * Prepares post fields for keyword notification matching. | |
| 485 | + * | |
| 486 | + * @param string $text The text to search. | |
| 487 | + * @return string The prepared text. | |
| 488 | + */ | |
| 489 | + public static function prepare_keyword_search_text( $text ) { | |
| 490 | + $text = wp_strip_all_tags( $text ); | |
| 491 | + | |
| 492 | + return preg_replace_callback( | |
| 493 | + '#https?://[^\s<>"\']+#i', | |
| 494 | + function ( $matches ) { | |
| 495 | + return self::remove_tracking_url_query_args_from_keyword_search_text( $matches[0] ); | |
| 496 | + }, | |
| 497 | + $text | |
| 498 | + ); | |
| 499 | + } | |
| 500 | + | |
| 501 | + /** | |
| 502 | + * Removes tracking query args from URLs before keyword notification matching. | |
| 503 | + * | |
| 504 | + * @param string $url The URL to normalize. | |
| 505 | + * @return string The URL without known tracking query args. | |
| 506 | + */ | |
| 507 | + private static function remove_tracking_url_query_args_from_keyword_search_text( $url ) { | |
| 508 | + $parsed_url = wp_parse_url( $url ); | |
| 509 | + if ( ! is_array( $parsed_url ) || empty( $parsed_url['host'] ) ) { | |
| 510 | + return $url; | |
| 511 | + } | |
| 512 | + | |
| 513 | + $query_args = array(); | |
| 514 | + if ( isset( $parsed_url['query'] ) ) { | |
| 515 | + wp_parse_str( html_entity_decode( $parsed_url['query'], ENT_QUOTES ), $query_args ); | |
| 516 | + } | |
| 517 | + | |
| 518 | + foreach ( array_keys( $query_args ) as $query_arg ) { | |
| 519 | + if ( self::is_tracking_query_arg( $query_arg ) ) { | |
| 520 | + unset( $query_args[ $query_arg ] ); | |
| 521 | + } | |
| 522 | + } | |
| 523 | + | |
| 524 | + $normalized_url = $parsed_url['scheme'] . '://'; | |
| 525 | + if ( isset( $parsed_url['user'] ) ) { | |
| 526 | + $normalized_url .= $parsed_url['user']; | |
| 527 | + if ( isset( $parsed_url['pass'] ) ) { | |
| 528 | + $normalized_url .= ':' . $parsed_url['pass']; | |
| 529 | + } | |
| 530 | + $normalized_url .= '@'; | |
| 531 | + } | |
| 532 | + $normalized_url .= $parsed_url['host']; | |
| 533 | + if ( isset( $parsed_url['port'] ) ) { | |
| 534 | + $normalized_url .= ':' . $parsed_url['port']; | |
| 535 | + } | |
| 536 | + if ( isset( $parsed_url['path'] ) ) { | |
| 537 | + $normalized_url .= $parsed_url['path']; | |
| 538 | + } | |
| 539 | + if ( $query_args ) { | |
| 540 | + $normalized_url .= '?' . http_build_query( $query_args, '', '&' ); | |
| 541 | + } | |
| 542 | + if ( isset( $parsed_url['fragment'] ) ) { | |
| 543 | + $normalized_url .= '#' . $parsed_url['fragment']; | |
| 544 | + } | |
| 545 | + | |
| 546 | + return $normalized_url; | |
| 547 | + } | |
| 548 | + | |
| 549 | + /** | |
| 550 | + * Checks whether a query arg is used for tracking. | |
| 551 | + * | |
| 552 | + * @param string $query_arg The query arg. | |
| 553 | + * @return bool Whether the query arg is used for tracking. | |
| 554 | + */ | |
| 555 | + private static function is_tracking_query_arg( $query_arg ) { | |
| 556 | + if ( preg_match( '/^(?:utm|mtm|hsa)_/i', $query_arg ) ) { | |
| 557 | + return true; | |
| 558 | + } | |
| 559 | + | |
| 560 | + return in_array( | |
| 561 | + strtolower( $query_arg ), | |
| 562 | + array( | |
| 563 | + '_hsenc', | |
| 564 | + '_hsmi', | |
| 565 | + 'dclid', | |
| 566 | + 'fbclid', | |
| 567 | + 'gclid', | |
| 568 | + 'gbraid', | |
| 569 | + 'igshid', | |
| 570 | + 'li_fat_id', | |
| 571 | + 'mc_cid', | |
| 572 | + 'mc_eid', | |
| 573 | + 'mkt_tok', | |
| 574 | + 'msclkid', | |
| 575 | + 'oly_anon_id', | |
| 576 | + 'oly_enc_id', | |
| 577 | + 'pk_campaign', | |
| 578 | + 'pk_kwd', | |
| 579 | + 'twclid', | |
| 580 | + 'vero_id', | |
| 581 | + 'wbraid', | |
| 582 | + 'yclid', | |
| 583 | + ), | |
| 584 | + true | |
| 585 | + ); | |
| 586 | + } | |
| 587 | + | |
| 588 | + /** | |
| 484 | 589 | * Gets the notification keywords. |
| 485 | 590 | * |
| 486 | 591 | * @return array The notification keywords. |
| 487 | 592 | */ |
| @@ -1019,8 +1124,22 @@ | ||
| 1019 | 1124 | $available_feeds[ $link_url ]['autoselect'] = true; |
| 1020 | 1125 | } |
| 1021 | 1126 | } |
| 1022 | 1127 | |
| 1128 | + // Prefer the ActivityPub actor feed over RSS when both are available. | |
| 1129 | + $activitypub_feed = null; | |
| 1130 | + foreach ( $available_feeds as $link_url => $feed ) { | |
| 1131 | + if ( 'activitypub' === $feed['parser'] ) { | |
| 1132 | + $activitypub_feed = $link_url; | |
| 1133 | + break; | |
| 1134 | + } | |
| 1135 | + } | |
| 1136 | + if ( $activitypub_feed ) { | |
| 1137 | + foreach ( $available_feeds as $link_url => $feed ) { | |
| 1138 | + $available_feeds[ $link_url ]['autoselect'] = $link_url === $activitypub_feed; | |
| 1139 | + } | |
| 1140 | + } | |
| 1141 | + | |
| 1023 | 1142 | $feed_sort_order = array( 'self', 'alternate', 'me' ); |
| 1024 | 1143 | if ( $has_friends_plugin ) { |
| 1025 | 1144 | // If we have the Friends plugin, we prefer an (augmented) RSS feed over, for example, microformats. |
| 1026 | 1145 | $feed_sort_order = array( 'alternate', 'self', 'me' ); |
| @@ -1160,14 +1279,16 @@ | ||
| 1160 | 1279 | * @param int $author_id The id of the author. |
| 1161 | 1280 | * @return int Post ID, or 0 on failure. |
| 1162 | 1281 | */ |
| 1163 | 1282 | public static function url_to_postid( $url, $author_id = false ) { |
| 1164 | - $cache_key = 'friends_url_to_postid_' . md5( $url ) . ( $author_id ? '_' . $author_id : '' ); | |
| 1283 | + $post_types = apply_filters( 'friends_frontend_post_types', array() ); | |
| 1284 | + | |
| 1285 | + // The post types are part of the cache key since they can be filtered per lookup. | |
| 1286 | + $cache_key = 'friends_url_to_postid_' . md5( $url . '|' . implode( ',', $post_types ) ) . ( $author_id ? '_' . $author_id : '' ); | |
| 1165 | 1287 | $post_id = wp_cache_get( $cache_key, 'friends' ); |
| 1166 | 1288 | if ( false !== $post_id ) { |
| 1167 | 1289 | return $post_id; |
| 1168 | 1290 | } |
| 1169 | - $post_types = apply_filters( 'friends_frontend_post_types', array() ); | |
| 1170 | 1291 | $args = $post_types; |
| 1171 | 1292 | |
| 1172 | 1293 | global $wpdb; |
| 1173 | 1294 | $sql = sprintf( |
| @@ -1190,9 +1311,11 @@ | ||
| 1190 | 1311 | $args |
| 1191 | 1312 | ) |
| 1192 | 1313 | ); |
| 1193 | 1314 | |
| 1194 | - wp_cache_set( $cache_key, $post_id, 'friends' ); | |
| 1315 | + if ( $post_id ) { | |
| 1316 | + wp_cache_set( $cache_key, $post_id, 'friends' ); | |
| 1317 | + } | |
| 1195 | 1318 | return $post_id; |
| 1196 | 1319 | } |
| 1197 | 1320 | |
| 1198 | 1321 | public function oembed_request_post_id( $post_id, $url ) { |