Making WordPress.org

Changeset 13640


Ignore:
Timestamp:
05/01/2024 02:37:31 AM (10 months ago)
Author:
dd32
Message:

Plugin Directory: Search: Reformat the search logic.

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php

    r13636 r13640  
    992992
    993993            $wp_query->set( 's', $s );
     994
     995            // If the search is in the block directory, require that.
     996            if ( $wp_query->get( 'block_search' ) ) {
     997                $wp_query->query_vars['tax_query']['plugin_section'][] = array(
     998                    'taxonomy' => 'plugin_section',
     999                    'field'    => 'slug',
     1000                    'terms'    => 'block',
     1001                );
     1002            }
    9941003        }
    9951004
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-search.php

    r13332 r13640  
    135135
    136136        return $module;
     137    }
     138
     139
     140    /**
     141     * Localise the ES fields searched for localised queries.
     142     */
     143    public function localise_es_fields( $fields ) {
     144        $localised_prefixes = [
     145            'all_content',
     146            'title',
     147            'excerpt',
     148            'description',
     149        ];
     150
     151        $localised_fields = array();
     152
     153        foreach ( (array) $fields as $field ) {
     154            // title.ngram
     155            list( $field, $type ) = explode( '.', $field . '.' );
     156            if ( $type ) {
     157                $type = ".{$type}";
     158            }
     159
     160            if ( ! in_array( $field, $localised_prefixes ) ) {
     161                $localised_fields[] = $field . $type;
     162                continue;
     163            }
     164
     165            if ( $this->is_english ) {
     166                $localised_fields[] = $field . '_en' . $type;
     167                continue;
     168            }
     169
     170            $boost    = '';
     171            $en_boost = '^' . $this->en_boost;
     172            if ( 'description' === $field ) {
     173                $boost = '^' . $this->desc_boost;
     174                $en_boost = '^' . $this->desc_en_boost;
     175            }
     176
     177            $localised_fields[] = $field . '_' . $this->locale . $type . $boost;
     178            $localised_fields[] = $field . '_en' . $type . $en_boost;
     179        }
     180
     181        return $localised_fields;
    137182    }
    138183
     
    164209        $this->is_english = ( ! $this->locale || str_starts_with( $this->locale, 'en_' ) );
    165210
    166         if ( $this->is_english ) {
    167             $matching_fields = array(
    168                 'all_content_en',
    169             );
    170         } else {
    171             $matching_fields = array(
    172                 'all_content_' . $this->locale,
    173                 'all_content_en^' . $this->en_boost,
    174             );
    175         }
    176 
    177         $args['query_fields'] = $matching_fields;
     211        $args['query_fields'] = $this->localise_es_fields( 'all_content' );
    178212
    179213        return $args;
     
    205239        }
    206240
    207         // Set boost on the match query
    208 
     241        // The should match is where we add the fields to be searched in, and the weighting of them (boost).
     242        $should_match = [];
     243        if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ] ) ) {
     244            $should_match = & $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ];
     245        }
     246
     247        $search_phrase = $should_match[0][ 'multi_match' ][ 'query' ] ?? '';
     248
     249        // The function score is where calculations on fields occur.
     250        $function_score = [];
     251        if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'functions' ] ) ) {
     252            $function_score = & $es_query_args[ 'query' ][ 'function_score' ][ 'functions' ];
     253        }
     254
     255        // Set boost on the match query, from jetpack_search_es_wp_query_args.
    209256        if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'must' ][0][ 'multi_match' ] ) ) {
    210257            $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'must' ][0][ 'multi_match' ][ 'boost' ] = 0.1;
    211258        }
    212259
    213         // Old version had one less level here. Probably unimportant but this makes the unit tests pass.
    214         if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'must' ][0] ) ) {
    215             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'must' ] = $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'must' ][0];
    216         }
    217 
    218         // Not sure if this matters, but again it's in the tests
    219         if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ][ 'operator' ] ) ) {
    220             unset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ][ 'operator' ] );
    221         }
    222 
    223         // Some extra fields here
    224         if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ] ) ) {
    225             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ][ 'boost' ] = 2;
    226             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ][ 'fields' ] = ( $this->is_english ? [
    227                 0 => 'title_en',
    228                 1 => 'excerpt_en',
    229                 2 => 'description_en^1',
    230                 3 => 'taxonomy.plugin_tags.name',
    231             ] : [
    232                 'title_' . $this->locale,
    233                 'excerpt_' . $this->locale,
    234                 'description_' . $this->locale . '^' . $this->desc_boost,
    235                 'title_en^' . $this->en_boost,
    236                 'excerpt_en^' . $this->en_boost,
    237                 'description_en^' . $this->desc_en_boost,
     260        // This extends the search to additionally search in the title, excerpt, description and plugin_tags.
     261        if ( isset( $should_match[0][ 'multi_match' ] ) ) {
     262            $should_match[0][ 'multi_match' ][ 'boost' ]  = 2;
     263            $should_match[0][ 'multi_match' ][ 'fields' ] = $this->localise_es_fields( [
     264                'title',
     265                'excerpt',
     266                'description',
    238267                'taxonomy.plugin_tags.name',
    239268            ] );
    240269        }
    241270
    242         // And some more fancy bits here
    243         if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ] ) && 1 === count( $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ] ) ) {
    244             $search_phrase = $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][0][ 'multi_match' ][ 'query' ];
    245 
    246             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][] = [
    247                 'multi_match' => [
    248                 'query' => $search_phrase,
    249                 'fields' => ( $this->is_english ? [
    250                     0 => 'title_en.ngram',
    251                 ] : [
    252                     'title_' . $this->locale . '.ngram',
    253                     'title_en.ngram^' . $this->en_boost,
     271        // Setup the boosting for various fields.
     272        $should_match[] = [
     273            'multi_match' => [
     274                'query'  => $search_phrase,
     275                'fields' => $this->localise_es_fields( [ 'title.ngram' ] ),
     276                'type'   => 'phrase',
     277                'boost'  => 2,
     278            ],
     279        ];
     280
     281        // A direct slug match
     282        $should_match[] = [
     283            'multi_match' => [
     284                'query'  => $search_phrase,
     285                'fields' => $this->localise_es_fields( 'title', 'slug_text' ),
     286                'type'   => 'most_fields',
     287                'boost'  => 5,
     288            ],
     289        ];
     290
     291        $should_match[] = [
     292            'multi_match' => [
     293                'query'  => $search_phrase,
     294                'fields' => $this->localise_es_fields( [
     295                    'excerpt',
     296                    'description',
     297                    'taxonomy.plugin_tags.name',
    254298                ] ),
    255                 'type' => 'phrase',
    256                 'boost' => 2,
    257                 ],
    258             ];
    259 
    260             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][] = [
    261                 'multi_match' => [
    262                   'query' => $search_phrase,
    263                   'fields' => ( $this->is_english ? [
    264                     0 => 'title_en',
    265                     1 => 'slug_text',
    266                   ] : [
    267                     'title_' . $this->locale,
    268                     'title_en^' . $this->en_boost,
    269                     'slug_text',
    270                   ] ),
    271                   'type' => 'most_fields',
    272                   'boost' => 5,
    273                 ],
    274             ];
    275 
    276             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][] = [
    277                 'multi_match' => [
    278                   'query' => $search_phrase,
    279                   'fields' => ( $this->is_english ? [
    280                     0 => 'excerpt_en',
    281                     1 => 'description_en^1',
    282                     2 => 'taxonomy.plugin_tags.name',
    283                   ] : [
    284                     'excerpt_' . $this->locale,
    285                     'description_' . $this->locale . '^' . $this->desc_boost,
    286                     'excerpt_en^' . $this->en_boost,
    287                     'description_en^' . $this->desc_en_boost,
    288                     'taxonomy.plugin_tags.name',
    289                   ] ),
    290                   'type' => 'best_fields',
    291                   'boost' => 2,
    292                 ],
    293             ];
    294 
    295             $es_query_args[ 'query' ][ 'function_score' ][ 'query' ][ 'bool' ][ 'should' ][] = [
    296                 'multi_match' => [
    297                   'query' => $search_phrase,
    298                   'fields' => [
    299                     0 => 'author',
    300                     1 => 'contributors',
    301                   ],
    302                   'type' => 'best_fields',
    303                   'boost' => 2,
    304                 ],
    305             ];
    306         }
    307 
    308         if ( isset( $es_query_args[ 'query' ][ 'function_score' ][ 'functions' ] ) ) {
    309             $es_query_args[ 'query' ][ 'function_score' ][ 'functions' ] = [
    310                 0 => [
    311                   'exp' => [
     299                'type'   => 'best_fields',
     300                'boost'  => 2,
     301            ],
     302        ];
     303
     304        $should_match[] = [
     305            'multi_match' => [
     306                'query'  => $search_phrase,
     307                'fields' => $this->localise_es_fields( [
     308                    'author',
     309                    'contributor'
     310                ] ),
     311                'type'   => 'best_fields',
     312                'boost'  => 3,
     313            ],
     314        ];
     315
     316        // We'll overwrite the default Jetpack Search function scoring with our own.
     317        $function_score = [
     318            [
     319                // The more recent a plugin was updated, the more relevant it is.
     320                'exp' => [
    312321                    'plugin_modified' => [
    313                       'origin' => date('Y-m-d'),
    314                       'offset' => '180d',
    315                       'scale' => '360d',
    316                       'decay' => 0.5,
     322                        'origin' => date('Y-m-d'),
     323                        'offset' => '180d',
     324                        'scale' => '360d',
     325                        'decay' => 0.5,
    317326                    ],
    318                   ],
    319                 ],
    320                 1 => [
    321                   'exp' => [
     327                ]
     328            ],
     329            [
     330                // The older a plugins tested-up-to is, the less likely it's relevant.
     331                'exp' => [
    322332                    'tested' => [
    323                       'origin' => '5.0',
    324                       'offset' => 0.1,
    325                       'scale' => 0.4,
    326                       'decay' => 0.6,
     333                        'origin' => '5.0',
     334                        'offset' => 0.1,
     335                        'scale' => 0.4,
     336                        'decay' => 0.6,
    327337                    ],
    328                   ],
    329                 ],
    330                 2 => [
    331                   'field_value_factor' => [
    332                     'field' => 'active_installs',
    333                     'factor' => 0.375,
     338                ],
     339            ],
     340            [
     341                // A higher install base is a sign that the plugin will be relevant to the searcher.
     342                'field_value_factor' => [
     343                    'field'    => 'active_installs',
     344                    'factor'   => 0.375,
    334345                    'modifier' => 'log2p',
    335                     'missing' => 1,
    336                   ],
    337                 ],
    338                 3 => [
    339                   'filter' => [
     346                    'missing'  => 1,
     347                ],
     348            ],
     349            [
     350                // For plugins with less than 1 million installs, we need to adjust their scores a bit more.
     351                'filter' => [
    340352                    'range' => [
    341                       'active_installs' => [
    342                         'lte' => 1000000,
    343                       ],
     353                            'active_installs' => [
     354                                'lte' => 1000000,
     355                            ],
     356                        ],
    344357                    ],
    345                   ],
    346                   'exp' => [
     358                'exp' => [
    347359                    'active_installs' => [
    348                       'origin' => 1000000,
    349                       'offset' => 0,
    350                       'scale' => 900000,
    351                       'decay' => 0.75,
     360                        'origin' => 1000000,
     361                        'offset' => 0,
     362                        'scale' => 900000,
     363                        'decay' => 0.75,
    352364                    ],
    353                   ],
    354                 ],
    355                 4 => [
    356                   'field_value_factor' => [
    357                     'field' => 'support_threads_resolved',
    358                     'factor' => 0.25,
     365                ],
     366            ],
     367            [
     368                // The more resolved support threads (as a percentage) a plugin has, the more responsive the developer is, and the better experience the end-user will have.
     369                'field_value_factor' => [
     370                    'field'    => 'support_threads_resolved',
     371                    'factor'   => 0.25,
    359372                    'modifier' => 'log2p',
    360                     'missing' => 0.5,
    361                   ],
    362                 ],
    363                 5 => [
    364                   'field_value_factor' => [
    365                     'field' => 'rating',
    366                     'factor' => 0.25,
     373                    'missing'  => 0.5,
     374                ],
     375            ],
     376            [
     377                // A higher rated plugin is more likely to be preferred.
     378                'field_value_factor' => [
     379                    'field'    => 'rating',
     380                    'factor'   => 0.25,
    367381                    'modifier' => 'sqrt',
    368                     'missing' => 2.5,
    369                   ],
    370                 ],
    371             ];
    372         }
    373 
    374         // Old version didn't have these
     382                    'missing'  => 2.5,
     383                ],
     384            ],
     385        ];
     386
    375387        unset( $es_query_args[ 'query' ][ 'function_score' ][ 'score_mode' ] );
    376         unset( $es_query_args[ 'query' ][ 'function_score' ][ 'max_boost' ] );
    377         unset( $es_query_args[ 'aggregations' ] );
     388        unset( $es_query_args[ 'query' ][ 'function_score' ][ 'boost_mode' ] );
    378389
    379390        // Couple of extra fields wanted in the response, mainly for debugging
    380391        $es_query_args[ 'fields' ] = [
    381             0 => 'slug',
    382             1 => 'post_id',
    383             2 => 'blog_id',
    384         ];
    385 
    386         // Old version had things wrapped in an extra query => filtered layer.
    387         $es_query_args[ 'query' ] = [
    388             'filtered' => [
    389                 'query' => $es_query_args[ 'query' ]
    390             ]
     392            'slug',
     393            'post_id',
    391394        ];
    392395
Note: See TracChangeset for help on using the changeset viewer.