| 1 | <?php |
|---|
| 2 | /* |
|---|
| 3 | Plugin Name: KB Linker |
|---|
| 4 | Plugin URI: http://adambrown.info/b/widgets/kb-linker/ |
|---|
| 5 | Description: Looks for user-defined phrases in posts and automatically links them. Example: Link every occurrence of "Wordpress" to wordpress.org. |
|---|
| 6 | Author: Adam R. Brown |
|---|
| 7 | Version: 1.102 |
|---|
| 8 | Author URI: http://adambrown.info/ |
|---|
| 9 | */ |
|---|
| 10 | |
|---|
| 11 | // OPTIONAL SETTINGS |
|---|
| 12 | /*special characters for foreign languages. Add any you want to the array below. Char goes on left, HTML entity on right. The German codes are here as examples. |
|---|
| 13 | See http://www.w3schools.com/tags/ref_entities.asp for HTML entities. */ |
|---|
| 14 | $kblinker_special_chars = array( |
|---|
| 15 | 'ä' => 'ä', |
|---|
| 16 | 'Ä' => 'Ä', |
|---|
| 17 | 'ö' => 'ö', |
|---|
| 18 | 'Ö' => 'Ö', |
|---|
| 19 | 'ü' => 'ü', |
|---|
| 20 | 'Ü' => 'Ü', |
|---|
| 21 | 'ß' => 'ß' |
|---|
| 22 | ); |
|---|
| 23 | /* would you like to add title="" tags to the links with the keyword in them? true or false. */ |
|---|
| 24 | define( 'KBLINKER_USE_TITLES' , true ); |
|---|
| 25 | /* if the preceding setting is TRUE, you can customize the text before or after the keyword below. For example, if you want titles to say "More about KEYWORD.", |
|---|
| 26 | then make before = 'More about ' (note the space) and after = '.' */ |
|---|
| 27 | $kblinker_title_text = array( |
|---|
| 28 | 'before' => 'More about ', // default: 'More about ' |
|---|
| 29 | 'after' => ' »', // default: '$raquo;' (an arrow pointing to the right) |
|---|
| 30 | ); |
|---|
| 31 | // END OF SETTINGS. NO MORE EDITING REQUIRED. |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | |
|---|
| 36 | |
|---|
| 37 | /* DEVELOPMENT NOTES |
|---|
| 38 | |
|---|
| 39 | CHANGE LOG |
|---|
| 40 | 1.01 initial release |
|---|
| 41 | 1.02 quick fix to check is_array before extracting on line 105 |
|---|
| 42 | 1.03 - added support for German (or other) special characters |
|---|
| 43 | - added support for opening links in different targets |
|---|
| 44 | 1.04 add 'i' tag to the tag-detection regexes (it was already in the main replacement one, causing some errors) |
|---|
| 45 | 1.05 bugfix |
|---|
| 46 | 1.06 bugfix |
|---|
| 47 | 1.10 - puts keyword in link's "title" tags |
|---|
| 48 | - won't link the same URL more than once per post, even if multiple keywords are associated with it |
|---|
| 49 | - won't link a page to itself (imperfect) |
|---|
| 50 | 1.101 bugfix |
|---|
| 51 | 1.102 bugfix |
|---|
| 52 | |
|---|
| 53 | IMPORTANT NOTE TO ANYBODY CONSIDERING ADDING THIS PLUGIN TO A WP-MU INSTALLATION: |
|---|
| 54 | If you aren't sure whether you are using a WP-MU blog, then you aren't. Trust me. If this warning applies to you, then you will know it. |
|---|
| 55 | For WP-MU administrators: You should not use this plugin. Your users could use it to place (potentially malicious) javascript into their blogs. |
|---|
| 56 | This plugin is PERFECTLY SAFE for non-WP-MU blogs, so ignore this message if you're using regular wordpress (you probably are). |
|---|
| 57 | |
|---|
| 58 | KNOWN BUGS |
|---|
| 59 | - Pre 1.04: If a tag or attribute contained the keyword, but with different capitalization than specified in options, it was replaced. That causes problems. |
|---|
| 60 | - 1.04: Fixed that bug, but in return, you might find capitalization changing (within tags and attributes) to match that given in the linker's options. Sorry. |
|---|
| 61 | - 1.05 Fixed the capitalization bug |
|---|
| 62 | |
|---|
| 63 | GENERAL NOTE FOR DEVELOPERS/HACKERS/ETC: |
|---|
| 64 | You will notice that I've included extensive commenting in the code below. I do not have time to support this plugin. The commenting is there to make this plugin |
|---|
| 65 | easy to modify. Please try making modifications on your own before posting a support question at the plugin's URI. |
|---|
| 66 | That being said, you are welcome to post well-informed support questions on my site. |
|---|
| 67 | |
|---|
| 68 | DATABASE STRUCTURE |
|---|
| 69 | the options->KB Linker page will create a set of matching terms and URLs that gets stored as a list. |
|---|
| 70 | structure of option "kb_linker": |
|---|
| 71 | pairs => array( see below) |
|---|
| 72 | text=> same content as pairs, but in unprocessed form (for displaying in the option's form) |
|---|
| 73 | plurals => 1, 0 if 1, we should look for variants of the keywords ending in s or es |
|---|
| 74 | */ |
|---|
| 75 | |
|---|
| 76 | function kb_linker($content){ |
|---|
| 77 | global $kblinker_special_chars,$kblinker_title_text; |
|---|
| 78 | |
|---|
| 79 | $option = get_option('kb_linker'); |
|---|
| 80 | if (is_array($option)){ |
|---|
| 81 | extract($option); |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | // uncomment for testing (to override options): |
|---|
| 85 | #$pairs = array( 'contributor'=>'http://google.com', 'a'=>'http://yahoo.com/', 'scripting'=>'scripting', 'don'=>'don', 'first post'=>'firstpost.org', 'first'=>'first.org', 'wp'=>'WP.ORG'); |
|---|
| 86 | |
|---|
| 87 | // die if option isn't set yet |
|---|
| 88 | if ( !is_array($pairs) ) |
|---|
| 89 | return $content; |
|---|
| 90 | |
|---|
| 91 | // let's make use of that special chars setting. |
|---|
| 92 | if (is_array($kblinker_special_chars)){ |
|---|
| 93 | foreach ($kblinker_special_chars as $char => $code){ |
|---|
| 94 | $content = str_replace($code,$char,$content); |
|---|
| 95 | } |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | // needed below... |
|---|
| 99 | $usedUrls = array(); |
|---|
| 100 | $currentUrl = 'http://' . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; // may not work on all hosting setups. |
|---|
| 101 | |
|---|
| 102 | // most of the action is in here. |
|---|
| 103 | foreach ($pairs as $keyword => $url){ |
|---|
| 104 | if (in_array( $url, $usedUrls )) // don't link to the same URL more than once |
|---|
| 105 | continue; |
|---|
| 106 | if (strpos( $content, $url )){ // we've already used this URL, or it was manually inserted by author into post |
|---|
| 107 | $usedUrls[] = $url; |
|---|
| 108 | continue; |
|---|
| 109 | } |
|---|
| 110 | if ($url == $currentUrl){ // don't link a page to itself |
|---|
| 111 | $usedUrls[] = $url; |
|---|
| 112 | continue; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | // first, let's check whether we've got a "target" attribute specified. |
|---|
| 116 | if (false!==strpos( $url, ' ' ) ){ // Let's not waste CPU resources unless we see a ' ' in the URL: |
|---|
| 117 | $target = trim( substr( $url, strpos($url,' ') ) ); |
|---|
| 118 | $target = ' target="'.$target.'"'; |
|---|
| 119 | $url = substr( $url, 0, strpos($url,' ') ); |
|---|
| 120 | }else{ |
|---|
| 121 | $target=''; |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | // let's escape any '&' in the URL. |
|---|
| 125 | $url = str_replace( '&', '&', $url ); // this might seem unnecessary, but it prevents the next line from double-escaping the & |
|---|
| 126 | $url = str_replace( '&', '&', $url ); |
|---|
| 127 | |
|---|
| 128 | // we don't want to link the keyword if it is already linked. |
|---|
| 129 | // so let's find all instances where the keyword is in a link and precede it with &&&, which will be sufficient to avoid linking it. We use &&&, since WP would pass that |
|---|
| 130 | // to us as &&& (if it occured in a post), so it would never be in the $content on its own. |
|---|
| 131 | // this has two steps. First, look for the keyword as linked text: |
|---|
| 132 | $content = preg_replace( '|(<a[^>]+>)(.*)('.$keyword.')(.*)(</a[^>]*>)|Ui', '$1$2&&&$3$4$5', $content); |
|---|
| 133 | |
|---|
| 134 | // Next, look for the keyword inside tags. E.g. if they're linking every occurrence of "Google" manually, we don't want to find |
|---|
| 135 | // <a href="http://google.com"> and change it to <a href="http://<a href="http://www.google.com">.com"> |
|---|
| 136 | // More broadly, we don't want them linking anything that might be in a tag. (e.g. linking "strong" would screw up <strong>). |
|---|
| 137 | // if you get problems with KB linker creating links where it shouldn't, this is the regex you should tinker with, most likely. Here goes: |
|---|
| 138 | $content = preg_replace( '|(<[^>]*)('.$keyword.')(.*>)|Ui', '$1&&&$2$3', $content); |
|---|
| 139 | |
|---|
| 140 | // I'm sure a true master of regular expressions wouldn't need the previous two steps, and would simply write the replacement expression (below) better. But this works for me. |
|---|
| 141 | |
|---|
| 142 | // set the title attribute: |
|---|
| 143 | if (KBLINKER_USE_TITLES) |
|---|
| 144 | $title = ' title="'.$kblinker_title_text['before'].$keyword.$kblinker_title_text['after'].'"'; |
|---|
| 145 | |
|---|
| 146 | // now that we've taken the keyword out of any links it appears in, let's look for the keyword elsewhere. |
|---|
| 147 | if ( 1 != $plurals ){ // we do basically the same thing whether we're looking for plurals or not. Let's do non-plurals option first: |
|---|
| 148 | $content = preg_replace( '|(?<=[\s>;"\'/])('.$keyword.')(?=[\s<&.,!\'";:\-/])|i', '<a href="'.$url.'" class="kblinker"'.$target.$title.'>$1</a>', $content, 1); // that "1" at the end limits it to replacing the keyword only once per post. |
|---|
| 149 | /* some notes about that regular expression to make modifying it easier for you if you're new to these things: |
|---|
| 150 | (?<=[\s>;"\']) |
|---|
| 151 | (?<= marks it as a lookbehind assertion |
|---|
| 152 | to ensure that we are linking only complete words, we want keyword preceded by one of space, tag (>), entity (;) or certain kinds of punctuation (escaped with \ when necessary) |
|---|
| 153 | Note that '&' is NOT one of the allowed lookbehinds (or our '&&&' trick wouldn't work) |
|---|
| 154 | (?=[\s<&.,\'";:\-]) |
|---|
| 155 | (?= marks this as a lookahead assertion |
|---|
| 156 | again, we link only complete words. Must be followed by space, tag (<), entity (&), or certain kinds of punctuation. |
|---|
| 157 | Note that some of the punctuations are escaped with \ |
|---|
| 158 | */ |
|---|
| 159 | }else{ // if they want us to look for plurals too: |
|---|
| 160 | // this regex is almost identical to the non-plurals one, we just add an s? where necessary: |
|---|
| 161 | $content = preg_replace( '|(?<=[\s>;"\'/])('.$keyword.'s?)(?=[\s<&.,!\'";:\-/])|i', '<a href="'.$url.'" class="kblinker"'.$target.$title.'>$1</a>', $content, 1); // that "1" at the end limits it to replacing once per post. |
|---|
| 162 | } |
|---|
| 163 | } |
|---|
| 164 | // get rid of our '&&&' things. |
|---|
| 165 | $content = str_replace( '&&&', '', $content); |
|---|
| 166 | return $content; |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | |
|---|
| 170 | function kb_linker_options_page(){ |
|---|
| 171 | $sample = 'wordpress->http://wordpress.org/ |
|---|
| 172 | google->http://www.google.com/ _blank |
|---|
| 173 | kb linker->http://adambrown.info/b/widgets/kb-linker/ |
|---|
| 174 | knuckleheads->http://www.house.gov/'; |
|---|
| 175 | |
|---|
| 176 | if ( $_POST['kb_linker'] ){ |
|---|
| 177 | $pairs = str_replace("\r", '', $_POST['kb_linker']); |
|---|
| 178 | $pairs = explode("\n", $pairs); |
|---|
| 179 | foreach( $pairs as $pair ){ |
|---|
| 180 | $pair = trim( $pair ); // no leading or trailing spaces. Can mess with the "target" thing in function kb_linker() |
|---|
| 181 | $pair = explode( "->", $pair ); |
|---|
| 182 | if ( ( '' != $pair[0] ) && ( '' != $pair[1] ) ) |
|---|
| 183 | $new[ $pair[0] ] = $pair[1]; |
|---|
| 184 | } |
|---|
| 185 | $pairs = $new; // contains the pairs as an array for use by the filter |
|---|
| 186 | $text = $_POST['kb_linker']; // contains the pairs as entered in the form for display below |
|---|
| 187 | |
|---|
| 188 | $plurals = ( 1 == $_POST['kb_plurals'] ) ? 1 : 0; |
|---|
| 189 | $option = array( 'pairs'=>$pairs, 'text'=>$text, 'plurals'=>$plurals ); // store both versions of the option, pairs and text |
|---|
| 190 | update_option( 'kb_linker', $option ); |
|---|
| 191 | print '<div id="message" class="updated fade"><p><strong>KB Linker options updated.</strong> <a href="'.get_bloginfo('url').'">View site »</a></p></div>'; |
|---|
| 192 | }else{ |
|---|
| 193 | $option = get_option('kb_linker'); |
|---|
| 194 | if (is_array($option)){ |
|---|
| 195 | extract($option); |
|---|
| 196 | }else{ |
|---|
| 197 | $text = $sample; |
|---|
| 198 | $plurals = 0; |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | $checked = ( 1 == $plurals ) ? 'checked="checked"' : '' ; |
|---|
| 203 | |
|---|
| 204 | print ' |
|---|
| 205 | <div class="wrap"> |
|---|
| 206 | <h2>KB Linker</h2> |
|---|
| 207 | <p>KB Linker will link phrases you specify to sites you specify. For example, you could make it so that whenever "Wordpress" occurs in a post it is automatically linked to wordpress.org.</p> |
|---|
| 208 | <p>Enter your keyword-URL pairs in the box below. Each pair should appear on its own line. Separate each keyword from its respective link with "->". Look at the bottom of this page for important details. Below are a few examples to get you going. Note that the link to Google will open in a new window, since it is followed with " _blank" (note the space).</p> |
|---|
| 209 | <blockquote><pre>'.$sample.'</pre></blockquote> |
|---|
| 210 | <p>Alright, knock yourself out:</p> |
|---|
| 211 | |
|---|
| 212 | <form method="post" action="http://'.$_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'].'"> |
|---|
| 213 | <textarea id="kb_linker" name="kb_linker" rows="10" cols="45" class="widefat">'.$text.'</textarea> |
|---|
| 214 | <p><input type="checkbox" '.$checked.' name="kb_plurals" id="kb_plurals" value="1" /> Also link the keyword if it ends in <i>s</i> (i.e. plurals in certain languages)</p> |
|---|
| 215 | <p class="submit" style="width:420px;"><input type="submit" value="Submit »" /></p> |
|---|
| 216 | </form> |
|---|
| 217 | |
|---|
| 218 | <p>Considerations:</p> |
|---|
| 219 | <ul> |
|---|
| 220 | <li>URLs should be valid (i.e. begin with http://)</li> |
|---|
| 221 | <li>The same URL can appear on more than one line (i.e. with more than one keyword).</li> |
|---|
| 222 | <li>Because a word can only link to one site, a keyword should not appear on more than one line. If it does, only the last instance of the keyword will be matched to its URL.</li> |
|---|
| 223 | <li>If one of your keywords is a substring of the other--e.g. "download wordpress" and "wordpress"--then you should list the shorter one later than the first one.</li> |
|---|
| 224 | <li>Keywords are case-insensitive (e.g. "wordpress" is the same as "WoRdPrEsS").</li> |
|---|
| 225 | <li>Spaces count, so "wordpress" is not the same as "wordpress ".</li> |
|---|
| 226 | <li>Keywords will be linked only if they occur in your post as a word (or phrase), not as a partial word. So if one of your keywords is "a" (for some strange reason), it will be linked only when it occurs as the word "a"--when the letter "a" occurs within a word, it will not be linked.</li> |
|---|
| 227 | <li>You can use any valid target attribute, not just "_blank"--see <a href="http://www.w3schools.com/tags/tag_a.asp">W3C</a> for a list of valid targets.</li> |
|---|
| 228 | </ul> |
|---|
| 229 | </div> |
|---|
| 230 | '; |
|---|
| 231 | } |
|---|
| 232 | |
|---|
| 233 | function kb_linker_admin_page(){ |
|---|
| 234 | add_submenu_page('options-general.php', 'KB Linker', 'KB Linker', 5, 'kb_linker.php', 'kb_linker_options_page'); |
|---|
| 235 | } |
|---|
| 236 | |
|---|
| 237 | add_filter('the_content', 'kb_linker', 9); // run before Dagon Design Sitemap Generator (runs at priority 10) |
|---|
| 238 | add_action('admin_menu', 'kb_linker_admin_page'); |
|---|
| 239 | ?> |
|---|