Making WordPress.org

Ticket #174: 174.patch

File 174.patch, 17.3 KB (added by keesiemeijer, 8 years ago)

Add title keywords algorithm and display keywords in single posts pages

  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/functions.php

     
    8181require __DIR__ . '/inc/search.php';
    8282
    8383/**
     84 * Title keywords algorithm for related posts
     85 */
     86require __DIR__ . '/inc/title-keywords.php';
     87
     88/**
     89 * PHP5 Implementation of the Porter Stemmer algorithm
     90 */
     91require __DIR__ . '/inc/porterstemmer.php';
     92
     93/**
    8494 * Set the content width based on the theme's design and stylesheet.
    8595 */
    8696if ( ! isset( $content_width ) ) {
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/inc/porterstemmer.php

     
     1<?php
     2namespace DevHub;
     3
     4
     5/**
     6 * Copyright (c) 2005 Richard Heyes (http://www.phpguru.org/)
     7 *
     8 * All rights reserved.
     9 *
     10 * This script is free software.
     11 */
     12
     13/**
     14 * PHP5 Implementation of the Porter Stemmer algorithm. Certain elements
     15 * were borrowed from the (broken) implementation by Jon Abernathy.
     16 *
     17 * Usage:
     18 *
     19 *  $stem = PorterStemmer::Stem($word);
     20 *
     21 * How easy is that?
     22 */
     23
     24class PorterStemmer {
     25    /**
     26     * Regex for matching a consonant
     27     *
     28     * @var string
     29     */
     30    private static $regex_consonant = '(?:[bcdfghjklmnpqrstvwxz]|(?<=[aeiou])y|^y)';
     31
     32
     33    /**
     34     * Regex for matching a vowel
     35     *
     36     * @var string
     37     */
     38    private static $regex_vowel = '(?:[aeiou]|(?<![aeiou])y)';
     39
     40
     41    /**
     42     * Stems a word. Simple huh?
     43     *
     44     * @param string  $word Word to stem
     45     * @return string       Stemmed word
     46     */
     47    public static function Stem( $word ) {
     48        if ( strlen( $word ) <= 2 ) {
     49            return $word;
     50        }
     51
     52        $word = self::step1ab( $word );
     53        $word = self::step1c( $word );
     54        $word = self::step2( $word );
     55        $word = self::step3( $word );
     56        $word = self::step4( $word );
     57        $word = self::step5( $word );
     58
     59        return $word;
     60    }
     61
     62
     63    /**
     64     * Step 1
     65     */
     66    private static function step1ab( $word ) {
     67        // Part a
     68        if ( substr( $word, -1 ) == 's' ) {
     69
     70            self::replace( $word, 'sses', 'ss' )
     71                or self::replace( $word, 'ies', 'i' )
     72                or self::replace( $word, 'ss', 'ss' )
     73                or self::replace( $word, 's', '' );
     74        }
     75
     76        // Part b
     77        if ( substr( $word, -2, 1 ) != 'e' or !self::replace( $word, 'eed', 'ee', 0 ) ) { // First rule
     78            $v = self::$regex_vowel;
     79
     80            // ing and ed
     81            if (   preg_match( "#$v+#", substr( $word, 0, -3 ) ) && self::replace( $word, 'ing', '' )
     82                or preg_match( "#$v+#", substr( $word, 0, -2 ) ) && self::replace( $word, 'ed', '' ) ) { // Note use of && and OR, for precedence reasons
     83
     84                // If one of above two test successful
     85                if (    !self::replace( $word, 'at', 'ate' )
     86                    and !self::replace( $word, 'bl', 'ble' )
     87                    and !self::replace( $word, 'iz', 'ize' ) ) {
     88
     89                    // Double consonant ending
     90                    if (    self::doubleConsonant( $word )
     91                        and substr( $word, -2 ) != 'll'
     92                        and substr( $word, -2 ) != 'ss'
     93                        and substr( $word, -2 ) != 'zz' ) {
     94
     95                        $word = substr( $word, 0, -1 );
     96
     97                    } else if ( self::m( $word ) == 1 and self::cvc( $word ) ) {
     98                            $word .= 'e';
     99                        }
     100                }
     101            }
     102        }
     103
     104        return $word;
     105    }
     106
     107
     108    /**
     109     * Step 1c
     110     *
     111     * @param string  $word Word to stem
     112     */
     113    private static function step1c( $word ) {
     114        $v = self::$regex_vowel;
     115
     116        if ( substr( $word, -1 ) == 'y' && preg_match( "#$v+#", substr( $word, 0, -1 ) ) ) {
     117            self::replace( $word, 'y', 'i' );
     118        }
     119
     120        return $word;
     121    }
     122
     123
     124    /**
     125     * Step 2
     126     *
     127     * @param string  $word Word to stem
     128     */
     129    private static function step2( $word ) {
     130        switch ( substr( $word, -2, 1 ) ) {
     131        case 'a':
     132            self::replace( $word, 'ational', 'ate', 0 )
     133                or self::replace( $word, 'tional', 'tion', 0 );
     134            break;
     135
     136        case 'c':
     137            self::replace( $word, 'enci', 'ence', 0 )
     138                or self::replace( $word, 'anci', 'ance', 0 );
     139            break;
     140
     141        case 'e':
     142            self::replace( $word, 'izer', 'ize', 0 );
     143            break;
     144
     145        case 'g':
     146            self::replace( $word, 'logi', 'log', 0 );
     147            break;
     148
     149        case 'l':
     150            self::replace( $word, 'entli', 'ent', 0 )
     151                or self::replace( $word, 'ousli', 'ous', 0 )
     152                or self::replace( $word, 'alli', 'al', 0 )
     153                or self::replace( $word, 'bli', 'ble', 0 )
     154                or self::replace( $word, 'eli', 'e', 0 );
     155            break;
     156
     157        case 'o':
     158            self::replace( $word, 'ization', 'ize', 0 )
     159                or self::replace( $word, 'ation', 'ate', 0 )
     160                or self::replace( $word, 'ator', 'ate', 0 );
     161            break;
     162
     163        case 's':
     164            self::replace( $word, 'iveness', 'ive', 0 )
     165                or self::replace( $word, 'fulness', 'ful', 0 )
     166                or self::replace( $word, 'ousness', 'ous', 0 )
     167                or self::replace( $word, 'alism', 'al', 0 );
     168            break;
     169
     170        case 't':
     171            self::replace( $word, 'biliti', 'ble', 0 )
     172                or self::replace( $word, 'aliti', 'al', 0 )
     173                or self::replace( $word, 'iviti', 'ive', 0 );
     174            break;
     175        }
     176
     177        return $word;
     178    }
     179
     180
     181    /**
     182     * Step 3
     183     *
     184     * @param string  $word String to stem
     185     */
     186    private static function step3( $word ) {
     187        switch ( substr( $word, -2, 1 ) ) {
     188        case 'a':
     189            self::replace( $word, 'ical', 'ic', 0 );
     190            break;
     191
     192        case 's':
     193            self::replace( $word, 'ness', '', 0 );
     194            break;
     195
     196        case 't':
     197            self::replace( $word, 'icate', 'ic', 0 )
     198                or self::replace( $word, 'iciti', 'ic', 0 );
     199            break;
     200
     201        case 'u':
     202            self::replace( $word, 'ful', '', 0 );
     203            break;
     204
     205        case 'v':
     206            self::replace( $word, 'ative', '', 0 );
     207            break;
     208
     209        case 'z':
     210            self::replace( $word, 'alize', 'al', 0 );
     211            break;
     212        }
     213
     214        return $word;
     215    }
     216
     217
     218    /**
     219     * Step 4
     220     *
     221     * @param string  $word Word to stem
     222     */
     223    private static function step4( $word ) {
     224        switch ( substr( $word, -2, 1 ) ) {
     225        case 'a':
     226            self::replace( $word, 'al', '', 1 );
     227            break;
     228
     229        case 'c':
     230            self::replace( $word, 'ance', '', 1 )
     231                or self::replace( $word, 'ence', '', 1 );
     232            break;
     233
     234        case 'e':
     235            self::replace( $word, 'er', '', 1 );
     236            break;
     237
     238        case 'i':
     239            self::replace( $word, 'ic', '', 1 );
     240            break;
     241
     242        case 'l':
     243            self::replace( $word, 'able', '', 1 )
     244                or self::replace( $word, 'ible', '', 1 );
     245            break;
     246
     247        case 'n':
     248            self::replace( $word, 'ant', '', 1 )
     249                or self::replace( $word, 'ement', '', 1 )
     250                or self::replace( $word, 'ment', '', 1 )
     251                or self::replace( $word, 'ent', '', 1 );
     252            break;
     253
     254        case 'o':
     255            if ( substr( $word, -4 ) == 'tion' or substr( $word, -4 ) == 'sion' ) {
     256                self::replace( $word, 'ion', '', 1 );
     257            } else {
     258                self::replace( $word, 'ou', '', 1 );
     259            }
     260            break;
     261
     262        case 's':
     263            self::replace( $word, 'ism', '', 1 );
     264            break;
     265
     266        case 't':
     267            self::replace( $word, 'ate', '', 1 )
     268                or self::replace( $word, 'iti', '', 1 );
     269            break;
     270
     271        case 'u':
     272            self::replace( $word, 'ous', '', 1 );
     273            break;
     274
     275        case 'v':
     276            self::replace( $word, 'ive', '', 1 );
     277            break;
     278
     279        case 'z':
     280            self::replace( $word, 'ize', '', 1 );
     281            break;
     282        }
     283
     284        return $word;
     285    }
     286
     287
     288    /**
     289     * Step 5
     290     *
     291     * @param string  $word Word to stem
     292     */
     293    private static function step5( $word ) {
     294        // Part a
     295        if ( substr( $word, -1 ) == 'e' ) {
     296            if ( self::m( substr( $word, 0, -1 ) ) > 1 ) {
     297                self::replace( $word, 'e', '' );
     298
     299            } else if ( self::m( substr( $word, 0, -1 ) ) == 1 ) {
     300
     301                    if ( !self::cvc( substr( $word, 0, -1 ) ) ) {
     302                        self::replace( $word, 'e', '' );
     303                    }
     304                }
     305        }
     306
     307        // Part b
     308        if ( self::m( $word ) > 1 and self::doubleConsonant( $word ) and substr( $word, -1 ) == 'l' ) {
     309            $word = substr( $word, 0, -1 );
     310        }
     311
     312        return $word;
     313    }
     314
     315
     316    /**
     317     * Replaces the first string with the second, at the end of the string. If third
     318     * arg is given, then the preceding string must match that m count at least.
     319     *
     320     * @param string  $str   String to check
     321     * @param string  $check Ending to check for
     322     * @param string  $repl  Replacement string
     323     * @param int     $m     Optional minimum number of m() to meet
     324     * @return bool          Whether the $check string was at the end
     325     *                       of the $str string. True does not necessarily mean
     326     *                       that it was replaced.
     327     */
     328    private static function replace( &$str, $check, $repl, $m = null ) {
     329        $len = 0 - strlen( $check );
     330
     331        if ( substr( $str, $len ) == $check ) {
     332            $substr = substr( $str, 0, $len );
     333            if ( is_null( $m ) or self::m( $substr ) > $m ) {
     334                $str = $substr . $repl;
     335            }
     336
     337            return true;
     338        }
     339
     340        return false;
     341    }
     342
     343
     344    /**
     345     * What, you mean it's not obvious from the name?
     346     *
     347     * m() measures the number of consonant sequences in $str. if c is
     348     * a consonant sequence and v a vowel sequence, and <..> indicates arbitrary
     349     * presence,
     350     *
     351     * <c><v>       gives 0
     352     * <c>vc<v>     gives 1
     353     * <c>vcvc<v>   gives 2
     354     * <c>vcvcvc<v> gives 3
     355     *
     356     * @param string  $str The string to return the m count for
     357     * @return int         The m count
     358     */
     359    private static function m( $str ) {
     360        $c = self::$regex_consonant;
     361        $v = self::$regex_vowel;
     362
     363        $str = preg_replace( "#^$c+#", '', $str );
     364        $str = preg_replace( "#$v+$#", '', $str );
     365
     366        preg_match_all( "#($v+$c+)#", $str, $matches );
     367
     368        return count( $matches[1] );
     369    }
     370
     371
     372    /**
     373     * Returns true/false as to whether the given string contains two
     374     * of the same consonant next to each other at the end of the string.
     375     *
     376     * @param string  $str String to check
     377     * @return bool        Result
     378     */
     379    private static function doubleConsonant( $str ) {
     380        $c = self::$regex_consonant;
     381
     382        return preg_match( "#$c{2}$#", $str, $matches ) and $matches[0]{0} == $matches[0]{1};
     383    }
     384
     385
     386    /**
     387     * Checks for ending CVC sequence where second C is not W, X or Y
     388     *
     389     * @param string  $str String to check
     390     * @return bool        Result
     391     */
     392    private static function cvc( $str ) {
     393        $c = self::$regex_consonant;
     394        $v = self::$regex_vowel;
     395
     396        return     preg_match( "#($c$v$c)$#", $str, $matches )
     397            and strlen( $matches[1] ) == 3
     398            and $matches[1]{2} != 'w'
     399            and $matches[1]{2} != 'x'
     400            and $matches[1]{2} != 'y';
     401    }
     402}
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/inc/template-tags.php

     
    14371437                // Order dictates order of display.
    14381438                $templates = array(
    14391439                        'description',
     1440                        'title-keywords',
    14401441                        'params',
    14411442                        'return',
    14421443                        'source',
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/inc/title-keywords.php

     
     1<?php
     2namespace DevHub;
     3
     4/**
     5 * Returns array with restricted title words.
     6 *
     7 * @return array Array with restricted title words
     8 */
     9function get_restricted_words() {
     10        return apply_filters( 'devhub_restricted_words', array(
     11                        'a', 'an', 'are', 'as', 'at', 'be', 'by', 'for', 'from',
     12                        'get', 'how', 'in', 'is', 'it', 'of', 'on', 'or', 'that', 'the',
     13                        'this', 'to', 'with', 'wp'
     14                ) );
     15}
     16
     17
     18/**
     19 * Returns array with allowed first words in a title.
     20 *
     21 * @return array Array with restricted words
     22 */
     23function get_allowed_first_words() {
     24        return apply_filters( 'devhub_allowed_first_words', array(
     25                        'is',  // is_* functions
     26                        'get', // get_* functions
     27                ) );
     28}
     29
     30/**
     31 * Returns allowed keywords and their stems from a title.
     32 * Restricted words are excluded. See get_restricted_words()
     33 *
     34 * @param string  $title Title to get words from.
     35 * @return array Array with Words and word stems.
     36 */
     37function get_title_keywords( $title ) {
     38        $words = $stemmed_words = array();
     39
     40        if ( empty( $title ) ) {
     41                return array();
     42        }
     43
     44        // Allow 'wp' as a single title word.
     45        if ( 'wp' === strtolower( $title ) ) {
     46                return array( 'wp' );
     47        }
     48
     49        $restricted    = get_restricted_words();
     50        $allowed_first = get_allowed_first_words();
     51        $words         = get_words( $title );
     52
     53        // Get all wp-words and their stems.
     54        $wp_words = get_wp_words( $words );
     55
     56        // Remove 'wp' from the words array.
     57        $words = array_values( array_diff( $words, array( 'wp' ) ) );
     58
     59        foreach ( $words as $key => $word ) {
     60
     61                // Keep allowed first words
     62                if ( !$key && in_array( $word, $allowed_first ) ) {
     63                        continue;
     64                }
     65
     66                // Remove restricted words.
     67                if ( in_array( $word, $restricted ) ) {
     68                        unset( $words[ $key ] );
     69                }
     70        }
     71
     72        // Stem words
     73        $stemmed_words = array_map( 'DevHub\PorterStemmer::Stem', $words );
     74
     75        // Merge all words
     76        $words = array_merge( $words, $wp_words, $stemmed_words );
     77
     78        return array_values( array_unique( array_filter( $words ) ) );
     79}
     80
     81/**
     82 * Returns all words from a title.
     83 *
     84 * @param string  $title Title.
     85 * @return string Array with words.
     86 */
     87function get_words( $title ) {
     88        $title = strtolower( $title );
     89        // remove all non letter characters to underscores
     90        $title = preg_replace( '/[^a-z0-9]/', '_', $title );
     91        // replace multiple underscores with a single underscore
     92        $title = preg_replace( '/_+/', '_', $title );
     93
     94        return explode( '_', trim( $title, '_' ) );
     95}
     96
     97/**
     98 * Returns wp-words if 'wp' is found as a word in the title.
     99 * wp-words are: wp-{second_word}, second word and second word stem.
     100 * e.g. wp-words for the function wp_create_term() are: wp-create, create and creat.
     101 *
     102 * @param string  $title Title to get wp words from.
     103 * @return array wp words
     104 */
     105function get_wp_words( $words ) {
     106        $wp_words = array();
     107
     108        if ( empty( $words ) || !is_array( $words ) ) {
     109                return array();
     110        }
     111
     112        foreach ( $words as $key => $word ) {
     113
     114                // Check if word is 'wp'
     115                if ( 'wp' !== $word ) {
     116                        continue;
     117                }
     118
     119                // Check if there is a next word
     120                if ( !isset( $words[ $key+1 ] ) ) {
     121                        continue;
     122                }
     123
     124                // Create wp-words
     125                $wp_words[] = 'wp-' . $words[ $key + 1 ];
     126                $wp_words[] = $words[ $key+1 ];
     127                $wp_words[] = PorterStemmer::Stem( $words[ $key + 1 ] );
     128        }
     129
     130        return array_values( array_unique( $wp_words ) );
     131}
  • sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-developer/reference/template-title-keywords.php

     
     1<?php
     2/**
     3 * Reference Template: Title Keywords
     4 *
     5 * @package wporg-developer
     6 * @subpackage Reference
     7 */
     8
     9namespace DevHub;
     10?>
     11
     12<hr />
     13<section class="description">
     14        <h2><?php _e( 'Title keywords', 'wporg' ); ?></h2>
     15        <?php
     16                $title = get_the_title();
     17                if ( $title ) {
     18                        echo implode( ', ' , get_title_keywords( $title ) );
     19                }
     20        ?>
     21</section>