r35862 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r35861‎ | r35862 | r35863 >
Date:14:30, 4 June 2008
Author:bartek
Status:old
Tags:
Comment:

bringing in a new EditSimilar extension from Wikia
Modified paths:
  • /trunk/extensions/EditSimilar (added) (history)
  • /trunk/extensions/EditSimilar/EditSimilar.i18n.php (added) (history)
  • /trunk/extensions/EditSimilar/EditSimilar.php (added) (history)

Diff [purge]

Index: trunk/extensions/EditSimilar/EditSimilar.i18n.php
@@ -0,0 +1,21 @@
 2+<?php
 3+$messages = array();
 4+$messages['en'] = array(
 5+ 'editsimilar-thanks' => 'Thanks for your edit. Check out these related articles: $1.' ,
 6+ 'editsimilar-thanks-singleresult' => 'Thanks for your edit. Check out this related article: $1.' ,
 7+ 'editsimilar-thanks-notsimilar' => 'Thank you for your edit. These articles could also use your help: $1.',
 8+ 'editsimilar-thanks-notsimilar-singleresult' => 'Thank you for your edit. This article could also use your help: $1.',
 9+ 'editsimilar-thankyou' => 'Thanks for your edit, $1!' ,
 10+ 'editsimilar-link-disable' => 'set preferences' ,
 11+ 'tog-edit-similar' => 'Enable similar articles suggestions' ,
 12+) ;
 13+
 14+$messages['pl'] = array(
 15+ 'editsimilar-thanks' => 'Dzięki za edycję! Zerknij też na podobne artykuły: $1.' ,
 16+ 'editsimilar-thanks-singleresult' => 'Dzięki za edycję! Zerknij też na ten podobny artykuł: $1.' ,
 17+ 'editsimilar-thanks-notsimilar' => 'Dzięki za edycję! Te artykuły również mogą potrzebować twojej pomocy: $1.',
 18+ 'editsimilar-thanks-notsimilar-singleresult' => 'Dzięki za edycję! Ten artykuł również może potrzebować twojej pomocy: $1.',
 19+ 'editsimilar-thankyou' => 'Dzięki za Twoją edycję, $1!' ,
 20+ 'editsimilar-link-disable' => 'zmień ustawienia' ,
 21+ 'tog-edit-similar'=> 'Włącz sugestie edycji podobnych artykułów' ,
 22+) ;
Property changes on: trunk/extensions/EditSimilar/EditSimilar.i18n.php
___________________________________________________________________
Added: svn:eol-style
123 + native
Index: trunk/extensions/EditSimilar/EditSimilar.php
@@ -0,0 +1,413 @@
 2+<?php
 3+
 4+/**
 5+* Extension that suggest editing of similar articles upon saving an article
 6+*
 7+* @package MediaWiki
 8+* @subpackage Extensions
 9+*
 10+* @author Bartek Łapiński <bartek@wikia.com>
 11+* @copyright Copyright (C) 2008, Wikia Inc.
 12+* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 13+*/
 14+
 15+if ( defined( 'MEDIAWIKI' ) ) {
 16+
 17+global $wgExtensionFunctions, $wgGroupPermissions;
 18+
 19+$dir = dirname(__FILE__);
 20+$wgExtensionMessagesFiles ['EditSimilar'] = $dir . '/EditSimilar.i18n.php' ;
 21+
 22+if (empty ($wgEditSimilarMaxResultsPool ) ) {
 23+ // maximum number of results to choose from
 24+ $wgEditSimilarMaxResultsPool = 50 ;
 25+}
 26+
 27+if (empty ($wgEditSimilarMaxResultsToDisplay)) {
 28+ // maximum number of results to display in text
 29+ $wgEditSimilarMaxResultsToDisplay = 3 ;
 30+}
 31+
 32+if (empty ($wgEditSimilarCounterValue)) {
 33+ // show message per specified number of edits
 34+ $wgEditSimilarCounterValue = 1 ;
 35+}
 36+
 37+$wgExtensionFunctions [] = 'wfEditSimilarSetup' ;
 38+
 39+$wgExtensionCredits['other'][] = array(
 40+ 'name' => 'EditSimilar',
 41+ 'version' => 1.16 ,
 42+ 'author' => 'Bartek Łapiński, [http://inside.wikia.com/wiki/User:TOR Łukasz \'TOR\' Garczewski]',
 43+ 'url' => 'http://help.wikia.com/wiki/Help:EditSimilar',
 44+ 'description' => 'Encourages users to edit an article similar (by categories) to the one they just had edited.',
 45+);
 46+
 47+/*
 48+ How this extension works:
 49+ - upon save, the script searches for articles that are similar
 50+ right now, I have assumed the following criteria:
 51+ * articles that need attention
 52+ * articles similar in category to the one we edited
 53+ * if no similar articles were found, we're taking results straight from categories that need attention
 54+ * number of articles in result is limited
 55+
 56+ IMPORTANT NOTE: This extension REQUIRES the article MediaWiki:EditSimilar-Categories to exist on your
 57+ wiki in order to run. If this article is nonexistant, the extension will disable itself.
 58+ Format of the article is as follows:
 59+ * Chosen Stub Category 1
 60+ * Chosen Stub Category 2
 61+ etc. (separated by stars)
 62+
 63+ insert '-' if you want to disable the extension without blanking the commanding article
 64+
 65+*/
 66+
 67+// base class for this extension
 68+class EditSimilar {
 69+ var $mBaseArticle ; // the article from which we hail in our quest for similiarities, this is its title
 70+ var $mMarkerType ; // how do we mark articles that need attention? currently, by category only
 71+ var $mAttentionMarkers ; // the marker array (for now it contains categories)
 72+ var $mMatchType ; // how do we match articles as a secondary
 73+ var $mPoolLimit ; // limit up the pool of 'stubs' to choose from
 74+ var $mBaseCategories ; // extracted categories that this saved article is in
 75+ var $mSimilarArticles ; // to differentiate between really similar results or just needing attention
 76+
 77+ // constructor
 78+ function __construct ($article, $markertype = 'category') {
 79+ global $wgEditSimilarMaxResultsPool ;
 80+ $this->mBaseArticle = $article ;
 81+ $this->mMarkerType = $markertype ;
 82+ $this->mAttentionMarkers = $this->getStubCategories () ;
 83+ $this->mPoolLimit = $wgEditSimilarMaxResultsPool ;
 84+ $this->mBaseCategories = $this->getBaseCategories () ;
 85+ $this->mSimilarArticles = true ;
 86+ }
 87+
 88+ // fetch categories marked as 'stub categories'
 89+ function getStubCategories () {
 90+ $stub_categories = wfMsgForContent ('EditSimilar-Categories') ;
 91+ if ( ('&lt;EditSimilar-Categories&gt;' == $stub_categories) || ('' == $stub_categories) || ('-' == $stub_categories) ) {
 92+ return false ;
 93+ } else {
 94+ $lines = preg_split ("/\*/", $stub_categories) ;
 95+ $normalised_lines = array () ;
 96+ array_shift ($lines) ;
 97+ foreach ($lines as $line) {
 98+ $normalised_lines [] = str_replace(" ", "_", trim ($line)) ;
 99+ }
 100+ return $normalised_lines ;
 101+ }
 102+ }
 103+
 104+ // this is the main function that returns articles we deem similar or worth showing
 105+ function getSimilarArticles () {
 106+ global $wgUser, $wgEditSimilarMarker ;
 107+ global $wgEditSimilarMaxResultsToDisplay ;
 108+
 109+ if (empty ($this->mAttentionMarkers) || !$this->mAttentionMarkers) {
 110+ return false ;
 111+ }
 112+ $text = '' ;
 113+ $articles = array () ;
 114+ $x = 0 ;
 115+
 116+ while ( (count ($articles) < $wgEditSimilarMaxResultsToDisplay) && ($x < count ($this->mAttentionMarkers)) ) {
 117+ $articles = array_merge ($articles, $this->getResults ($this->mAttentionMarkers [$x]) ) ;
 118+ if (!empty ($articles)) {
 119+ $articles = array_unique ($articles) ;
 120+ }
 121+ $x++ ;
 122+ }
 123+
 124+ if (empty ($articles) ) {
 125+ $articles = $this->getAdditionalCheck () ;
 126+ // second check to make sure we have anything to display
 127+ if (empty ($articles) ) {
 128+ return false;
 129+ }
 130+ $articles = array_unique ($articles) ;
 131+ $this->mSimilarArticles = false ;
 132+ }
 133+
 134+ if (1 == count ($articles)) { // in this case, array_rand returns a single element, not an array
 135+ $rand_articles = array (0) ;
 136+ } else {
 137+ $rand_articles = array_rand ($articles, min ($wgEditSimilarMaxResultsToDisplay, count ($articles)) ) ;
 138+ }
 139+ $sk = $wgUser->getSkin () ;
 140+ $skinname = get_class ($sk) ;
 141+ $skinname = strtolower ( substr ($skinname, 4) ) ;
 142+ $real_rand_values = array () ;
 143+ if (empty ($rand_articles)) {
 144+ return false ;
 145+ }
 146+
 147+ $translated_titles = array () ;
 148+ foreach ($rand_articles as $r_key => $rand_article_key) {
 149+ $translated_titles [] = $articles [$rand_article_key] ;
 150+ }
 151+ $translated_titles = $this->idsToTitles ($translated_titles) ;
 152+
 153+ foreach ($translated_titles as $link_title) {
 154+ $article_link = $sk->makeKnownLinkObj ($link_title) ;
 155+ $real_rand_values [] = $article_link ;
 156+ }
 157+
 158+ return $real_rand_values;
 159+ }
 160+
 161+ // extract all categories our base article is in
 162+ function getBaseCategories () {
 163+ global $wgEditSimilarMaxResultsToDisplay ;
 164+ if (empty ($this->mAttentionMarkers) || !$this->mAttentionMarkers) {
 165+ return false ;
 166+ }
 167+
 168+ $dbr = wfGetDB( DB_SLAVE );
 169+ $result_array = array () ;
 170+ $res = $dbr->select (
 171+ array ('categorylinks') ,
 172+ array ('cl_to') ,
 173+ array ( 'cl_from' => $this->mBaseArticle ) ,
 174+ __METHOD__ ,
 175+ array ( 'ORDER_BY' => 'cl_from' ,
 176+ 'USE_INDEX' => 'cl_from'
 177+ )
 178+ ) ;
 179+ while( $x = $dbr->fetchObject ( $res ) ) {
 180+ if (!in_array ($x->cl_to, $this->mAttentionMarkers) ) {
 181+ $result_array [] = $x->cl_to ;
 182+ }
 183+ }
 184+
 185+ if (!empty ($result_array) ) {
 186+ return $result_array ;
 187+ } else {
 188+ return false ;
 189+ }
 190+ }
 191+
 192+ /*
 193+ latest addition: if we got no results at all (indicating that:
 194+ A - the article had no categories,
 195+ B - the article had no relevant results for its categories)
 196+
 197+ this is to ensure we can get always (well, almost - if "marker" categories get no results, it's dead in the water anyway)
 198+ some results
 199+ */
 200+ function getAdditionalCheck () {
 201+ $dbr = wfGetDB( DB_SLAVE );
 202+
 203+ $query = "SELECT cl_from
 204+ FROM categorylinks
 205+ WHERE cl_to IN (" ;
 206+
 207+ $fixed_names = array () ;
 208+ foreach ($this->mAttentionMarkers as $category) {
 209+ $fixed_names [] = $dbr->addQuotes ($category) ;
 210+ }
 211+ $stringed_names = implode (",", $fixed_names) ;
 212+ $query .= $stringed_names . ")" ;
 213+
 214+ $res = $dbr->query ($query, __METHOD__) ;
 215+ $result_array = array () ;
 216+ while( $x = $dbr->fetchObject ( $res ) ) {
 217+ if ($this->mBaseArticle != $x->cl_from) {
 218+ $result_array [] = $x->cl_from ;
 219+ }
 220+ }
 221+ $dbr->freeResult( $res );
 222+
 223+ return $result_array ;
 224+
 225+ }
 226+
 227+ // one function to turn result ids into titles in one query rather than multiple ones
 228+ function idsToTitles ($id_array) {
 229+ global $wgContentNamespaces ;
 230+ $dbr = wfGetDB( DB_SLAVE );
 231+ $query = "SELECT page_namespace, page_title
 232+ FROM page
 233+ WHERE page_id IN (" ;
 234+
 235+ $stringed_names = implode (",", $id_array) ;
 236+ $query .= $stringed_names . ")" ;
 237+
 238+ $res = $dbr->query ($query, __METHOD__) ;
 239+ $result_array = array () ;
 240+
 241+ // so for now, to speed things up, just discard results from other namespaces (and subpages)
 242+ while( ($x = $dbr->fetchObject ( $res ))
 243+ && (in_array ($x->page_namespace, $wgContentNamespaces))
 244+ && false === strpos ($x->page_title, "/") ) {
 245+ $result_array [] = Title::makeTitle ($x->page_namespace, $x->page_title ) ;
 246+ }
 247+
 248+ $dbr->freeResult( $res );
 249+ return $result_array ;
 250+ }
 251+
 252+ // get categories from the 'stub' or 'attention needed' category
 253+ function getResults ($marker_category) {
 254+ $dbr = wfGetDB( DB_SLAVE );
 255+ $title = Title::makeTitle (NS_CATEGORY, $marker_category );
 256+ $result_array = array () ;
 257+
 258+ if (empty ($this->mBaseCategories)) {
 259+ return $result_array ;
 260+ }
 261+
 262+ $query = "SELECT c1.cl_from
 263+ FROM categorylinks as c1, categorylinks as c2
 264+ WHERE c1.cl_from = c2.cl_from
 265+ AND c1.cl_to = " .$dbr->addQuotes ($title->getDBKey ()) . "
 266+ AND c2.cl_to IN (" ;
 267+
 268+ $fixed_names = array () ;
 269+ foreach ($this->mBaseCategories as $category) {
 270+ $fixed_names [] = $dbr->addQuotes ($category) ;
 271+ }
 272+ $stringed_names = implode (",", $fixed_names) ;
 273+ $query .= $stringed_names . ")" ;
 274+
 275+ $res = $dbr->query ($query, __METHOD__) ;
 276+ while( $x = $dbr->fetchObject ( $res ) ) {
 277+ if ($this->mBaseArticle != $x->cl_from) {
 278+ $result_array [] = $x->cl_from ;
 279+ }
 280+ }
 281+ $dbr->freeResult( $res );
 282+
 283+ return $result_array ;
 284+ }
 285+
 286+ // message box wrapper
 287+ static public function showMessage ($text) {
 288+ global $wgOut, $wgUser, $wgScript ;
 289+ $wgOut->addHTML ("
 290+ <style type=\"text/css\">
 291+ .editsimilar {
 292+ background-color: #c0fec0;
 293+ border: solid 1px #006400;
 294+ }
 295+
 296+ .editsimilar_dismiss {
 297+ float:right;
 298+ font-size:0.9em;
 299+ }
 300+ </style>
 301+ ") ;
 302+ if ($wgUser->isLoggedIn () ) {
 303+ $link = "<div class=\"editsimilar_dismiss\">[<span class=\"plainlinks\"><a href=\"" . $wgScript . "?title=Special:Preferences#prefsection-4\" id=\"editsimilar_preferences\">" . wfMsg('editsimilar-link-disable') . "</a></span>]</div><div style=\"display:block\">&nbsp;</div>" ;
 304+ } else {
 305+ $link = '' ;
 306+ }
 307+ $wgOut->addHTML ("<div id=\"editsimilar_links\" class=\"usermessage editsimilar\"><div>" . $text . "</div>" . $link . "</div>") ;
 308+ }
 309+
 310+
 311+ // this is for determining whether to display the message or not
 312+ static public function checkCounter () {
 313+ global $wgEditSimilarCounterValue ;
 314+ if (isset ($_SESSION ['ES_counter'])) {
 315+ $_SESSION ['ES_counter'] -- ;
 316+ if ($_SESSION ['ES_counter'] > 0) {
 317+ return false ;
 318+ } else {
 319+ $_SESSION ['ES_counter'] = $wgEditSimilarCounterValue ;
 320+ return true ;
 321+ }
 322+ } else {
 323+ $_SESSION ['ES_counter'] = $wgEditSimilarCounterValue ;
 324+ return true ;
 325+ }
 326+ }
 327+}
 328+
 329+function wfEditSimilarSetup () {
 330+ global $wgHooks, $wgMessageCache, $wgUser ;
 331+ $wgHooks ['ArticleSaveComplete'][] = 'wfEditSimilarCheck' ;
 332+ $wgHooks ['OutputPageBeforeHTML'][] = 'wfEditSimilarViewMesg' ;
 333+ if ( $wgUser->isLoggedIn ()) {
 334+ $wgHooks ['getEditingPreferencesCustomHtml'][] = 'wfEditSimilarPrefCustomHtml' ;
 335+ $wgHooks ['UserToggles'][] = 'wfEditSimilarToggle' ;
 336+ }
 337+}
 338+
 339+// check if we had the extension enabled at all and if this is in a content namespace
 340+function wfEditSimilarCheck ($article) {
 341+ global $wgOut, $wgUser, $wgContentNamespaces ;
 342+
 343+ $namespace = $article->getTitle()->getNamespace() ;
 344+ if ( (1 == $wgUser->getOption ('edit-similar', 1)) && (in_array ($namespace, $wgContentNamespaces)) ) {
 345+ $_SESSION ['ES_saved'] = 'yes' ;
 346+ }
 347+ return true ;
 348+}
 349+
 350+//view message depending on settings and the relevancy of the results
 351+function wfEditSimilarViewMesg (&$out) {
 352+ global $wgTitle, $wgUser, $wgEditSimilarAlwaysShowThanks ;
 353+ wfLoadExtensionMessages ('EditSimilar') ;
 354+ if ( !empty ($_SESSION ['ES_saved']) && (1 == $wgUser->getOption ('edit-similar', 1) ) && $out->isArticle ()) {
 355+ if (EditSimilar::checkCounter ()) {
 356+ $message_text = '' ;
 357+ $article_title = $wgTitle->getText() ;
 358+ // here we'll populate the similar articles and links
 359+ $SInstance = new EditSimilar ($wgTitle->getArticleId(), 'category') ;
 360+ $similarities = $SInstance->getSimilarArticles () ;
 361+ if (!empty($similarities)) {
 362+ if ($SInstance->mSimilarArticles) {
 363+ if (count($similarities) > 1) {
 364+ $message_text = wfMsg ('editsimilar-thanks', implode (", ", $similarities));
 365+ } else {
 366+ $message_text = wfMsg ('editsimilar-thanks-singleresult', implode (", ", $similarities));
 367+ }
 368+ } else { // the articles we found were rather just articles needing attention
 369+ if (count($similarities) > 1) {
 370+ $message_text = wfMsg ('editsimilar-thanks-notsimilar', implode (", ", $similarities));
 371+ } else {
 372+ $message_text = wfMsg ('editsimilar-thanks-notsimilar-singleresult', implode (", ", $similarities));
 373+ }
 374+ }
 375+ } else {
 376+ if ($wgUser->isLoggedIn () && !empty ($wgEditSimilarAlwaysShowThanks)) {
 377+ $message_text = wfMsg ('editsimilar-thankyou', $wgUser->getName ()) ;
 378+ }
 379+ }
 380+
 381+ if ('' != $message_text) {
 382+ EditSimilar::showMessage ($message_text, $article_title) ;
 383+ }
 384+ }
 385+ //display that only once
 386+ $_SESSION ['ES_saved'] = '' ;
 387+ }
 388+ return true ;
 389+}
 390+
 391+// a customized version of getToggle from SpecialPreferences
 392+// this one uses getOption with a default - so we can have it checked if unset
 393+function wfEditSimilarPrefCustomHtml ($prefsForm) {
 394+ wfLoadExtensionMessages ('EditSimilar') ;
 395+ global $wgOut, $wgUser, $wgLang ;
 396+ $tname = 'edit-similar' ;
 397+ $prefsForm->mUsedToggles [$tname] = true ;
 398+ $ttext = $wgLang->getUserToggle ($tname) ;
 399+ // the catch lies here
 400+ $checked = $wgUser->getOption ($tname, 1) == 1 ? ' checked="checked"' : '';
 401+
 402+ $wgOut->addHTML ("<div class='toggle'><input type='checkbox' value='1' id=\"$tname\" name=\"wpOp$tname\"$checked />" .
 403+ " <span class='toggletext'><label for=\"$tname\">$ttext</label></span></div>\n") ;
 404+ return true ;
 405+}
 406+
 407+function wfEditSimilarToggle ($toggles) {
 408+ wfLoadExtensionMessages ('EditSimilar') ;
 409+ $toggles ['edit-similar'] = 'edit-similar' ;
 410+ return true ;
 411+}
 412+
 413+}
 414+?>
Property changes on: trunk/extensions/EditSimilar/EditSimilar.php
___________________________________________________________________
Added: svn:eol-style
1415 + native

Status & tagging log