http://www.mediawiki.org/wiki/Special:Code/MediaWiki/97068
Revision: 97068 Author: ashley Date: 2011-09-14 15:26:09 +0000 (Wed, 14 Sep 2011) Log Message: ----------- phase II social tools: VoteNY. Tested and built against MW 1.16.0. Can be used without the core social tools package (SocialProfile extension). Added Paths: ----------- trunk/extensions/VoteNY/ trunk/extensions/VoteNY/SpecialTopRatings.php trunk/extensions/VoteNY/Vote.css trunk/extensions/VoteNY/Vote.i18n.php trunk/extensions/VoteNY/Vote.js trunk/extensions/VoteNY/Vote.php trunk/extensions/VoteNY/VoteClass.php trunk/extensions/VoteNY/VoteHooks.php trunk/extensions/VoteNY/Vote_AjaxFunctions.php trunk/extensions/VoteNY/images/ trunk/extensions/VoteNY/images/star_half.gif trunk/extensions/VoteNY/images/star_off.gif trunk/extensions/VoteNY/images/star_on.gif trunk/extensions/VoteNY/images/star_voted.gif trunk/extensions/VoteNY/vote.sql Added: trunk/extensions/VoteNY/SpecialTopRatings.php =================================================================== --- trunk/extensions/VoteNY/SpecialTopRatings.php (rev 0) +++ trunk/extensions/VoteNY/SpecialTopRatings.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,185 @@ +<?php +/** + * A special page to display the highest rated pages on the wiki. + * + * This special page supports filtering by category and namespace, so + * {{Special:TopRatings/Adventure Games/0/10}} will show 10 ratings where the + * pages are in the "Adventure Games" category and the pages are in the main + * (0) namespace. + * + * @file + * @ingroup Extensions + * @date 21 August 2011 + * @license To the extent that it is possible, this code is in the public domain + */ +class SpecialTopRatings extends IncludableSpecialPage { + + /** + * Constructor -- set up the new special page + */ + public function __construct() { + parent::__construct( 'TopRatings' ); + } + + /** + * Show the special page + * + * @param $par Mixed: parameter passed to the special page or null + */ + public function execute( $par ) { + global $wgOut, $wgScriptPath, $wgUser; + + // Set the page title, robot policies, etc. + $this->setHeaders(); + + $categoryName = $namespace = ''; + + // Parse the parameters passed to the special page + // Make sure that the limit parameter passed to the special page is + // an integer and that it's less than 100 (performance!) + if ( isset( $par ) && is_numeric( $par ) && $par < 100 ) { + $limit = intval( $par ); + } elseif ( isset( $par ) && !is_numeric( $par ) ) { + // $par is a string...assume that we can explode() it + $exploded = explode( '/', $par ); + $categoryName = $exploded[0]; + $namespace = ( isset( $exploded[1] ) ? intval( $exploded[1] ) : $namespace ); + $limit = ( isset( $exploded[2] ) ? intval( $exploded[2] ) : 50 ); + } else { + $limit = 50; + } + + // Add JS -- needed so that users can vote on this page and so that + // their browsers' consoles won't be filled with JS errors ;-) + $wgOut->addScriptFile( $wgScriptPath . '/extensions/VoteNY/Vote.js' ); + + $ratings = array(); + $output = ''; + $sk = $wgUser->getSkin(); + + $dbr = wfGetDB( DB_SLAVE ); + $tables = $where = $joinConds = array(); + $whatToSelect = array( 'DISTINCT vote_page_id' ); + + // By default we have no category and no namespace + $tables = array( 'Vote' ); + $where = array( 'vote_page_id <> 0' ); + + // isset(), because 0 is a totally valid NS + if ( !empty( $categoryName ) && isset( $namespace ) ) { + $tables = array( 'Vote', 'page', 'categorylinks' ); + $where = array( + 'vote_page_id <> 0', + 'cl_to' => str_replace( ' ', '_', $categoryName ), + 'page_namespace' => $namespace + ); + $joinConds = array( + 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ), + 'page' => array( 'INNER JOIN', 'page_id = vote_page_id' ) + ); + } + + // Perform the SQL query with the given conditions; the basic idea is + // that we get $limit (however, 100 or less) unique page IDs from the + // Vote table. If a category and a namespace have been given, we also + // do an INNER JOIN with page and categorylinks table to get the + // correct data. + $res = $dbr->select( + $tables, + $whatToSelect, + $where, + __METHOD__, + array( 'LIMIT' => intval( $limit ) ), + $joinConds + ); + + foreach ( $res as $row ) { + // Add the results to the $ratings array and get the amount of + // votes the given page ID has + // For example: $ratings[1] = 11 = page with the page ID 1 has 11 + // votes + $ratings[$row->vote_page_id] = (int)$dbr->selectField( + 'Vote', + 'SUM(vote_value)', + array( 'vote_page_id' => $row->vote_page_id ), + __METHOD__ + ); + } + + // If we have some ratings, start building HTML output + if ( !empty( $ratings ) ) { + /* XXX dirrrrrrty hack! because when we include this page, the JS + is not included, but we want things to work still */ + if ( $this->including() ) { + $output .= '<script type="text/javascript" src="' . + $wgScriptPath . '/extensions/VoteNY/Vote.js"></script>'; + } + + // yes, array_keys() is needed + foreach ( array_keys( $ratings ) as $discardThis => $pageId ) { + $titleObj = Title::newFromId( $pageId ); + if ( !( $titleObj instanceof Title ) ) { + continue; + } + + $vote = new VoteStars( $pageId ); + $output .= '<div class="user-list-rating">' . + $sk->link( + $titleObj, + $titleObj->getPrefixedText() // prefixed, so that the namespace shows! + ) . wfMsg( 'word-separator' ) . // i18n overkill? ya betcha... + wfMsg( 'parentheses', $ratings[$pageId] ) . + '</div>'; + + $id = mt_rand(); // AFAIK these IDs are and originally were totally random... + $output .= "<div id=\"rating_stars_{$id}\">" . + $vote->displayStars( + $id, + self::getAverageRatingForPage( $pageId ), + false + ) . '</div>'; + $output .= "<div id=\"rating_{$id}\" class=\"rating-total\">" . + $vote->displayScore() . + '</div>'; + } + } else { + // Nothing? Well, display an informative error message rather than + // a blank page or somesuch. + $output .= wfMsg( 'topratings-no-pages' ); + } + + // Output everything! + $wgOut->addHTML( $output ); + } + + /** + * Static version of Vote::getAverageVote(). + * + * @param $pageId Integer: ID of the page for which we want to get the avg. + * rating + * @return Integer: average vote for the given page (ID) + */ + public static function getAverageRatingForPage( $pageId ) { + global $wgMemc; + + $key = wfMemcKey( 'vote', 'avg', $pageId ); + $data = $wgMemc->get( $key ); + $voteAvg = 0; + + if( $data ) { + wfDebug( "Loading vote avg for page {$pageId} from cache (TopRatings)\n" ); + $voteAvg = $data; + } else { + $dbr = wfGetDB( DB_SLAVE ); + $voteAvg = (int)$dbr->selectField( + 'Vote', + 'AVG(vote_value) AS VoteAvg', + array( 'vote_page_id' => $pageId ), + __METHOD__ + ); + $wgMemc->set( $key, $voteAvg ); + } + + return $voteAvg; + } +} \ No newline at end of file Property changes on: trunk/extensions/VoteNY/SpecialTopRatings.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/Vote.css =================================================================== --- trunk/extensions/VoteNY/Vote.css (rev 0) +++ trunk/extensions/VoteNY/Vote.css 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,63 @@ +/* CSS for Vote extension */ +.vote-box { + background-color: #68BD46; + height: 30px; + padding: 13px 0px 0px; + text-align: center; + width: 43px; +} + +.vote-number { + color: #FFF; + font-size: 16px; + font-weight: bold; +} + +.vote-action { + text-align: center; + width: 43px; +} + +.vote-action a { + font-weight: bold; + font-size: 11px; + text-decoration: none; +} + +.rating-score { + background-color: #68BD46; + color: #FFF; + float: left; + font-size: 14px; + font-weight: bold; + padding: 1px 8px 0px; + margin: 1px 7px 0px 0px; + text-align: center; +} + +.ratings-top { + position: absolute; + top: 37px !important; + right: 0px !important; + width: 100%; +} + +.rating-section img { + vertical-align: text-bottom; +} + +.rating-voted { + color: #666666; + line-height: 10px; + font-size: 9px; + position: absolute; + right: 0px; +} + +/* Styling for the (n votes) after rating box/stars */ +.rating-total { + color: #666; + font-weight: bold; + font-size: 11px; + margin: 3px 0px 0px 0px; +} \ No newline at end of file Property changes on: trunk/extensions/VoteNY/Vote.css ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/Vote.i18n.php =================================================================== --- trunk/extensions/VoteNY/Vote.i18n.php (rev 0) +++ trunk/extensions/VoteNY/Vote.i18n.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,84 @@ +<?php +/** + * Internationalization file for the Vote extension. + * + * @file + * @ingroup Extensions + */ + +$messages = array(); + +/** English + * @author Aaron Wright <aaron.wri...@gmail.com> + * @author David Pean <david.p...@gmail.com> + */ +$messages['en'] = array( + 'vote-link' => 'Vote', + 'vote-unvote-link' => 'unvote', + 'vote-community-score' => 'community score: $1', + 'vote-ratings' => '{{PLURAL:$1|one rating|$1 ratings}}', + 'vote-remove' => 'remove', + 'vote-gave-this' => 'you gave this a $1', + 'vote-votes' => '{{PLURAL:$1|one vote|$1 votes}}', + // Special:TopRatings + 'topratings' => 'Top rated pages', + 'topratings-no-pages' => 'No top rated pages.', + // For Special:ListGroupRights + 'right-vote' => 'Vote pages', +); + +/** Finnish (Suomi) + * @author Jack Phoenix <j...@countervandalism.net> + */ +$messages['fi'] = array( + 'vote-link' => 'Äänestä', + 'vote-unvote-link' => 'poista ääni', + 'vote-community-score' => 'yhteisön antama pistemäärä: $1', + 'vote-ratings' => '{{PLURAL:$1|yksi arvostelu|$1 arvostelua}}', + 'vote-remove' => 'poista', + 'vote-gave-this' => 'annoit tälle {{PLURAL:$1|yhden tähden|$1 tähteä}}', + 'vote-votes' => '{{PLURAL:$1|yksi ääni|$1 ääntä}}', + 'topratings' => 'Huippusivut', + 'topratings-no-pages' => 'Ei huippusivuja.', + 'right-vote' => 'Äänestää sivuja', +); + +/** French (Français) + * @author Jack Phoenix <j...@countervandalism.net> + */ +$messages['fr'] = array( + 'vote-link' => 'Voter', + 'vote-unvote-link' => 'supprimer vote', + 'vote-remove' => 'supprimer', + 'vote-votes' => '{{PLURAL:$1|un vote|$1 votes}}', + 'right-vote' => 'Voter pages', +); + +/** Dutch (Nederlands) + * @author Mitchel Corstjens + */ +$messages['nl'] = array( + 'vote-link' => 'Stem', + 'vote-unvote-link' => 'stem terugtrekken', + 'vote-community-score' => 'gemeenschap score: $1', + 'vote-remove' => 'verwijder', + 'vote-gave-this' => 'je gaf dit een $1', + 'vote-votes' => '{{PLURAL:$1|een stem|$1 stemmen}}', + 'topratings' => 'Meest gewaardeerde pagina\'s', + 'topratings-no-pages' => 'Er zijn nog geen meest gewaardeerde pagina\'s', + 'right-vote' => 'Stem paginas', +); + +/** Polish (Polski) + * @author Misiek95 + */ +$messages['pl'] = array( + 'vote-link' => 'Głosuj', + 'vote-unvote-link' => 'Anuluj', + 'vote-community-score' => 'Wynik wśród społeczności: $1', + 'vote-ratings' => '{{PLURAL:$1|1 głos|$1 głosy|$1 głosów}}', + 'vote-remove' => 'usuń', + 'vote-gave-this' => 'Oceniłeś to na $1', + 'vote-votes' => '{{PLURAL:$1|1 głos|$1 głosy|$1 głosów}}', + 'right-vote' => 'Udział w głosowaniach', +); \ No newline at end of file Property changes on: trunk/extensions/VoteNY/Vote.i18n.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/Vote.js =================================================================== --- trunk/extensions/VoteNY/Vote.js (rev 0) +++ trunk/extensions/VoteNY/Vote.js 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,136 @@ +/** + * JavaScript functions for Vote extension + * + * @file + * @ingroup Extensions + * @author Jack Phoenix <j...@countervandalism.net> + * @date 19 June 2011 + */ +var VoteNY = { + MaxRating: 5, + clearRatingTimer: '', + voted_new: [], + id: 0, + last_id: 0, + imagePath: wgScriptPath + '/extensions/VoteNY/images/', + + /** + * Called when voting through the green square voting box + * @param TheVote + * @param PageID Integer: internal ID number of the current article + * @param mk Mixed: random token + */ + clickVote: function( TheVote, PageID, mk ) { + sajax_request_type = 'POST'; + sajax_do_call( 'wfVoteClick', [ TheVote, PageID, mk ], function( request ) { + document.getElementById( 'votebox' ).style.cursor = 'default'; + document.getElementById( 'PollVotes' ).innerHTML = request.responseText; + var unvoteMessage; + if ( typeof( mediaWiki ) == 'undefined' ) { + unvoteMessage = _UNVOTE_LINK; + } else { + unvoteMessage = mediaWiki.msg( 'vote-unvote-link' ); + } + document.getElementById( 'Answer' ).innerHTML = + "<a href=javascript:VoteNY.unVote(" + PageID + ",'" + mk + + "')>" + unvoteMessage + '</a>'; + } ); + }, + + /** + * Called when removing your vote through the green square voting box + * @param PageID Integer: internal ID number of the current article + * @param mk Mixed: random token + */ + unVote: function( PageID, mk ) { + sajax_request_type = 'POST'; + sajax_do_call( 'wfVoteDelete', [ PageID, mk ], function( request ) { + document.getElementById( 'votebox' ).style.cursor = 'pointer'; + document.getElementById( 'PollVotes' ).innerHTML = request.responseText; + var voteMessage; + if ( typeof( mediaWiki ) == 'undefined' ) { + voteMessage = _VOTE_LINK; + } else { + voteMessage = mediaWiki.msg( 'vote-link' ); + } + document.getElementById( 'Answer' ).innerHTML = + '<a href=javascript:VoteNY.clickVote(1,' + PageID + ',"' + mk + + '")>' + voteMessage + '</a>'; + } ); + }, + + /** + * Called when adding a vote after a user has clicked the yellow voting stars + * @param PageID Integer: internal ID number of the current article + * @param mk Mixed: random token + * @param id Integer: ID of the current rating star + * @param action Integer: controls which AJAX function will be called + */ + clickVoteStars: function( TheVote, PageID, mk, id, action ) { + VoteNY.voted_new[id] = TheVote; + var rsfun; + if( action == 3 ) { + rsfun = 'wfVoteStars'; + } + if( action == 5 ) { + rsfun = 'wfVoteStarsMulti'; + } + + var resultElement = document.getElementById( 'rating_' + id ); + sajax_request_type = 'POST'; + sajax_do_call( rsfun, [ TheVote, PageID, mk ], resultElement ); + }, + + /** + * Called when removing your vote through the yellow voting stars + * @param PageID Integer: internal ID number of the current article + * @param mk Mixed: random token + * @param id Integer: ID of the current rating star + */ + unVoteStars: function( PageID, mk, id ) { + var resultElement = document.getElementById( 'rating_' + id ); + sajax_request_type = 'POST'; + sajax_do_call( 'wfVoteStarsDelete', [ PageID, mk ], resultElement ); + }, + + startClearRating: function( id, rating, voted ) { + VoteNY.clearRatingTimer = setTimeout( + "VoteNY.clearRating('" + id + "',0," + rating + ',' + voted + ')', + 200 + ); + }, + + clearRating: function( id, num, prev_rating, voted ) { + if( VoteNY.voted_new[id] ) { + voted = VoteNY.voted_new[id]; + } + + for( var x = 1; x <= VoteNY.MaxRating; x++ ) { + var star_on, old_rating; + if( voted ) { + star_on = 'voted'; + old_rating = voted; + } else { + star_on = 'on'; + old_rating = prev_rating; + } + var ratingElement = document.getElementById( 'rating_' + id + '_' + x ); + if( !num && old_rating >= x ) { + ratingElement.src = VoteNY.imagePath + 'star_' + star_on + '.gif'; + } else { + ratingElement.src = VoteNY.imagePath + 'star_off.gif'; + } + } + }, + + updateRating: function( id, num, prev_rating ) { + if( VoteNY.clearRatingTimer && VoteNY.last_id == id ) { + clearTimeout( VoteNY.clearRatingTimer ); + } + VoteNY.clearRating( id, num, prev_rating ); + for( var x = 1; x <= num; x++ ) { + document.getElementById( 'rating_' + id + '_' + x ).src = VoteNY.imagePath + 'star_voted.gif'; + } + VoteNY.last_id = id; + } +}; \ No newline at end of file Property changes on: trunk/extensions/VoteNY/Vote.js ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/Vote.php =================================================================== --- trunk/extensions/VoteNY/Vote.php (rev 0) +++ trunk/extensions/VoteNY/Vote.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,75 @@ +<?php +/** + * Vote extension - JavaScript-based voting with the <vote> tag + * + * @file + * @ingroup Extensions + * @version 2.3.3 + * @author Aaron Wright <aaron.wri...@gmail.com> + * @author David Pean <david.p...@gmail.com> + * @author Jack Phoenix <j...@countervandalism.net> + * @link http://www.mediawiki.org/wiki/Extension:VoteNY Documentation + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ + +/** + * Protect against register_globals vulnerabilities. + * This line must be present before any global variable is referenced. + */ +if ( !defined( 'MEDIAWIKI' ) ) { + die( "This is not a valid entry point.\n" ); +} + +// Extension credits that show up on Special:Version +$wgExtensionCredits['parserhook'][] = array( + 'name' => 'Vote', + 'version' => '2.3.3', + 'author' => array( 'Aaron Wright', 'David Pean', 'Jack Phoenix' ), + 'description' => 'JavaScript-based voting with the <tt><vote></tt> tag', + 'url' => 'http://www.mediawiki.org/wiki/Extension:VoteNY' +); + +// Path to Vote extension files +$wgVoteDirectory = "$IP/extensions/VoteNY"; + +// New user right +$wgAvailableRights[] = 'vote'; +$wgGroupPermissions['*']['vote'] = false; // Anonymous users cannot vote +$wgGroupPermissions['user']['vote'] = true; // Registered users can vote + +// AJAX functions needed by this extension +require_once( 'Vote_AjaxFunctions.php' ); + +// Autoload classes and set up i18n +$dir = dirname( __FILE__ ) . '/'; +$wgExtensionMessagesFiles['Vote'] = $dir . 'Vote.i18n.php'; +$wgAutoloadClasses['Vote'] = $dir . 'VoteClass.php'; +$wgAutoloadClasses['VoteStars'] = $dir . 'VoteClass.php'; + +// Set up the new special page, Special:TopRatings, which shows top rated pages +// based on given criteria +$wgAutoloadClasses['SpecialTopRatings'] = $dir . 'SpecialTopRatings.php'; +$wgSpecialPages['TopRatings'] = 'SpecialTopRatings'; + +// Hooked functions +$wgAutoloadClasses['VoteHooks'] = $dir . 'VoteHooks.php'; + +$wgHooks['ParserFirstCallInit'][] = 'VoteHooks::registerParserHook'; +$wgHooks['MakeGlobalVariablesScript'][] = 'VoteHooks::addJSGlobalVariables'; +$wgHooks['RenameUserSQL'][] = 'VoteHooks::onUserRename'; +// Translations for {{NUMBEROFVOTES}} +//$wgExtensionMessagesFiles['NumberOfVotes'] = $dir . 'Vote.i18n.magic.php'; +$wgHooks['LanguageGetMagic'][] = 'VoteHooks::setUpMagicWord'; +$wgHooks['ParserGetVariableValueSwitch'][] = 'VoteHooks::assignValueToMagicWord'; +$wgHooks['MagicWordwgVariableIDs'][] = 'VoteHooks::registerVariableId'; +$wgHooks['LoadExtensionSchemaUpdates'][] = 'VoteHooks::addTable'; + +// ResourceLoader support for MediaWiki 1.17+ +$wgResourceModules['ext.voteNY'] = array( + 'styles' => 'Vote.css', + 'scripts' => 'Vote.js', + 'messages' => array( 'vote-link', 'vote-unvote-link' ), + 'localBasePath' => dirname( __FILE__ ), + 'remoteExtPath' => 'VoteNY', + 'position' => 'top' // available since r85616 +); \ No newline at end of file Property changes on: trunk/extensions/VoteNY/Vote.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/VoteClass.php =================================================================== --- trunk/extensions/VoteNY/VoteClass.php (rev 0) +++ trunk/extensions/VoteNY/VoteClass.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,355 @@ +<?php +/** + * Vote class - class for handling vote-related functions (counting + * the average score of a given page, inserting/updating/removing a vote etc.) + * + * @file + * @ingroup Extensions + */ +class Vote { + var $PageID = 0; + var $Userid = 0; + var $Username = null; + + /** + * Constructor + * @param $pageID Integer: article ID number + */ + public function __construct( $pageID ) { + global $wgUser; + + $this->PageID = $pageID; + $this->Username = $wgUser->getName(); + $this->Userid = $wgUser->getID(); + } + + /** + * Counts all votes, fetching the data from memcached if available + * or from the database if memcached isn't available + * @return Integer: amount of votes + */ + function count() { + global $wgMemc; + $key = wfMemcKey( 'vote', 'count', $this->PageID ); + $data = $wgMemc->get( $key ); + + // Try cache + if( $data ) { + wfDebug( "Loading vote count for page {$this->PageID} from cache\n" ); + $vote_count = $data; + } else { + $dbr = wfGetDB( DB_SLAVE ); + $vote_count = 0; + $res = $dbr->select( + 'Vote', + 'COUNT(*) AS VoteCount', + array( 'vote_page_id' => $this->PageID ), + __METHOD__ + ); + $row = $dbr->fetchObject( $res ); + if( $row ) { + $vote_count = $row->VoteCount; + } + $wgMemc->set( $key, $vote_count ); + } + return $vote_count; + } + + /** + * Gets the average score of all votes + * @return Integer: formatted average number of votes (something like 3.50) + */ + function getAverageVote() { + global $wgMemc; + $key = wfMemcKey( 'vote', 'avg', $this->PageID ); + $data = $wgMemc->get( $key ); + + $voteAvg = 0; + if( $data ) { + wfDebug( "Loading vote avg for page {$this->PageID} from cache\n" ); + $voteAvg = $data; + } else { + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + 'Vote', + 'AVG(vote_value) AS VoteAvg', + array( 'vote_page_id' => $this->PageID ), + __METHOD__ + ); + $row = $dbr->fetchObject( $res ); + if( $row ) { + $voteAvg = $row->VoteAvg; + } + $wgMemc->set( $key, $voteAvg ); + } + return number_format( $voteAvg, 2 ); + } + + /** + * Clear caches - memcached, parser cache and Squid cache + */ + function clearCache() { + global $wgUser, $wgMemc; + + // Kill internal cache + $wgMemc->delete( wfMemcKey( 'vote', 'count', $this->PageID ) ); + $wgMemc->delete( wfMemcKey( 'vote', 'avg', $this->PageID ) ); + + // Purge squid + $page_title = Title::newFromID( $this->PageID ); + if( is_object( $page_title ) ) { + $page_title->invalidateCache(); + $page_title->purgeSquid(); + + // Kill parser cache + $article = new Article( $page_title ); + $parserCache =& ParserCache::singleton(); + $parser_key = $parserCache->getKey( $article, $wgUser ); + $wgMemc->delete( $parser_key ); + } + } + + /** + * Delete the user's vote from the DB if s/he wants to remove his/her vote + */ + function delete() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->delete( + 'Vote', + array( + 'vote_page_id' => $this->PageID, + 'username' => $this->Username + ), + __METHOD__ + ); + $dbw->commit(); + + $this->clearCache(); + + // Update social statistics if SocialProfile extension is enabled + if( class_exists( 'UserStatsTrack' ) ) { + $stats = new UserStatsTrack( $this->Userid, $this->Username ); + $stats->decStatField( 'vote' ); + } + } + + /** + * Inserts a new vote into the Vote database table + * @param $voteValue + */ + function insert( $voteValue ) { + $dbw = wfGetDB( DB_MASTER ); + wfSuppressWarnings(); // E_STRICT whining + $voteDate = date( 'Y-m-d H:i:s' ); + wfRestoreWarnings(); + if( $this->UserAlreadyVoted() == false ) { + $dbw->insert( + 'Vote', + array( + 'username' => $this->Username, + 'vote_user_id' => $this->Userid, + 'vote_page_id' => $this->PageID, + 'vote_value' => $voteValue, + 'vote_date' => $voteDate, + 'vote_ip' => wfGetIP() + ), + __METHOD__ + ); + $dbw->commit(); + + $this->clearCache(); + + // Update social statistics if SocialProfile extension is enabled + if( class_exists( 'UserStatsTrack' ) ) { + $stats = new UserStatsTrack( $this->Userid, $this->Username ); + $stats->incStatField( 'vote' ); + } + } + } + + /** + * Checks if a user has already voted + * @return Boolean: false if s/he hasn't, otherwise returns the value of + * 'vote_value' column from Vote DB table + */ + function UserAlreadyVoted() { + $dbr = wfGetDB( DB_SLAVE ); + $s = $dbr->selectRow( + 'Vote', + array( 'vote_value' ), + array( + 'vote_page_id' => $this->PageID, + 'username' => $this->Username + ), + __METHOD__ + ); + if( $s === false ) { + return false; + } else { + return $s->vote_value; + } + } + + /** + * Displays the green voting box + * @return Mixed: HTML output + */ + function display() { + global $wgUser; + + $this->votekey = md5( $this->PageID . 'pants' . $this->Username ); + $voted = $this->UserAlreadyVoted(); + + $make_vote_box_clickable = ''; + if( $voted == false ) { + $make_vote_box_clickable = ' vote-clickable'; + } + + $output = "<div class=\"vote-box{$make_vote_box_clickable}\" id=\"votebox\" onclick=\"VoteNY.clickVote(1,{$this->PageID},'{$this->votekey}')\">"; + $output .= '<span id="PollVotes" class="vote-number">' . $this->count() . '</span>'; + $output .= '</div>'; + $output .= '<div id="Answer" class="vote-action">'; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + // @todo FIXME: this is horrible. If we don't have enough + // permissions to vote, we should tell the end-user /that/, + // not require them to log in! + $login = SpecialPage::getTitleFor( 'Userlogin' ); + $output .= '<a class="votebutton" href="' . + $login->escapeFullURL() . '" rel="nofollow">' . + wfMsg( 'vote-link' ) . '</a>'; + } else { + if( !wfReadOnly() ) { + if( $voted == false ) { + $output .= "<a href=\"javascript:VoteNY.clickVote(1,{$this->PageID},'{$this->votekey}')\">" . + wfMsg( 'vote-link' ) . '</a>'; + } else { + $output .= "<a href=\"javascript:VoteNY.unVote('{$this->PageID}', '{$this->votekey}')\">" . + wfMsg( 'vote-unvote-link' ) . '</a>'; + } + } + } + $output .= '</div>'; + + return $output; + } +} + +/** + * Class for generating star rating stars. + */ +class VoteStars extends Vote { + + var $maxRating = 5; + + /** + * Displays voting stars + * @param $voted Boolean: false by default + * @return Mixed: HTML output + */ + function display( $voted = false ) { + global $wgUser; + + $overall_rating = $this->getAverageVote(); + + if( $voted ) { + $display_stars_rating = $voted; + } else { + $display_stars_rating = $this->getAverageVote(); + } + + $this->votekey = md5( $this->PageID . 'pants' . $this->Username ); + $id = ''; + + // Should probably be $this->PageID or something? + // 'cause we define $id just above as an empty string...duh + $output = '<div id="rating_' . $id . '">'; + $output .= '<div class="rating-score">'; + $output .= '<div class="voteboxrate">' . $overall_rating . '</div>'; + $output .= '</div>'; + $output .= '<div class="rating-section">'; + $output .= $this->displayStars( $id, $display_stars_rating, $voted ); + $count = $this->count(); + if( $count ) { + $output .= ' <span class="rating-total">(' . + wfMsgExt( 'vote-votes', 'parsemag', $count ) . ')</span>'; + } + $already_voted = $this->UserAlreadyVoted(); + if( $already_voted && $wgUser->isLoggedIn() ) { + $output .= '<div class="rating-voted">' . + wfMsgExt( 'vote-gave-this', 'parsemag', $already_voted ) . + " </div> + <a href=\"javascript:VoteNY.unVoteStars({$this->PageID},'{$this->votekey}','{$id}')\">(" + . wfMsg( 'vote-remove' ) . + ')</a>'; + } + $output .= '</div> + <div class="rating-clear"> + </div>'; + + $output .= '</div>'; + return $output; + } + + /** + * Displays the actual star images, depending on the state of the user's mouse + * @param $id Integer: ID of the rating (div) element + * @param $rating Integer: average rating + * @param $voted Integer + * @return Mixed: generated <img> tag + */ + function displayStars( $id, $rating, $voted ) { + global $wgScriptPath; + + if( !$rating ) { + $rating = 0; + } + $this->votekey = md5( $this->PageID . 'pants' . $this->Username ); + if( !$voted ) { + $voted = 0; + } + $output = ''; + + for( $x = 1; $x <= $this->maxRating; $x++ ) { + if( !$id ) { + $action = 3; + } else { + $action = 5; + } + $onclick = "VoteNY.clickVoteStars({$x},{$this->PageID},'{$this->votekey}','{$id}',$action);"; + $onmouseover = "VoteNY.updateRating('{$id}',{$x},{$rating});"; + $onmouseout = "VoteNY.startClearRating('{$id}','{$rating}',{$voted});"; + $output .= "<img onclick=\"javascript:{$onclick}\" onmouseover=\"javascript:{$onmouseover}\" onmouseout=\"javascript:{$onmouseout}\" id=\"rating_{$id}_{$x}\" src=\"{$wgScriptPath}/extensions/VoteNY/images/star_"; + switch( true ) { + case $rating >= $x: + if( $voted ) { + $output .= 'voted'; + } else { + $output .= 'on'; + } + break; + case( $rating > 0 && $rating < $x && $rating > ( $x - 1 ) ): + $output .= 'half'; + break; + case( $rating < $x ): + $output .= 'off'; + break; + } + + $output .= '.gif" alt="" />'; + } + + return $output; + } + + /** + * Displays the average score for the current page + * and the total amount of votes. + */ + function displayScore() { + $count = $this->count(); + return wfMsg( 'vote-community-score', '<b>' . $this->getAverageVote() . '</b>' ) . + ' (' . wfMsgExt( 'vote-ratings', 'parsemag', $count ) . ')'; + } + +} \ No newline at end of file Property changes on: trunk/extensions/VoteNY/VoteClass.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/VoteHooks.php =================================================================== --- trunk/extensions/VoteNY/VoteHooks.php (rev 0) +++ trunk/extensions/VoteNY/VoteHooks.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,189 @@ +<?php +/** + * All hooked functions used by VoteNY extension. + * + * @file + * @ingroup Extensions + */ +class VoteHooks { + + /** + * Set up the <vote> parser hook. + * + * @param $parser Parser: instance of Parser + * @return Boolean: true + */ + public static function registerParserHook( &$parser ) { + $parser->setHook( 'vote', array( 'VoteHooks', 'renderVote' ) ); + return true; + } + + /** + * Callback function for registerParserHook. + * + * @param $input String: user-supplied input, unused + * @param $args Array: user-supplied arguments, unused + * @param $parser Parser: instance of Parser, unused + * @return String: HTML + */ + public static function renderVote( $input, $args, $parser ) { + global $wgOut, $wgTitle, $wgScriptPath; + + wfProfileIn( __METHOD__ ); + + // Disable parser cache (sadly we have to do this, because the caching is + // messing stuff up; we want to show an up-to-date rating instead of old + // or totally wrong rating, i.e. another page's rating...) + $parser->disableCache(); + + // Add CSS & JS + // In order for us to do this *here* instead of having to do this in + // registerParserHook(), we must've disabled parser cache + if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) { + $wgOut->addModules( 'ext.voteNY' ); + } else { + $wgOut->addScriptFile( $wgScriptPath . '/extensions/VoteNY/Vote.js' ); + $wgOut->addExtensionStyle( $wgScriptPath . '/extensions/VoteNY/Vote.css' ); + } + + // Define variable - 0 means that we'll get that green voting box by default + $type = 0; + + // Determine what kind of a voting gadget the user wants: a box or pretty stars? + if( preg_match( "/^\s*type\s*=\s*(.*)/mi", $input, $matches ) ) { + $type = htmlspecialchars( $matches[1] ); + } elseif( !empty( $args['type'] ) ) { + $type = intval( $args['type'] ); + } + + $articleID = $wgTitle->getArticleID(); + switch( $type ) { + case 0: + $vote = new Vote( $articleID ); + break; + case 1: + $vote = new VoteStars( $articleID ); + break; + default: + $vote = new Vote( $articleID ); + } + + $output = $vote->display(); + + wfProfileOut( __METHOD__ ); + + return $output; + } + + /** + * Adds required JS variables to the HTML output. + * + * @param $vars Array: array of pre-existing JS globals + * @return Boolean: true + */ + public static function addJSGlobalVariables( $vars ) { + $vars['_VOTE_LINK'] = wfMsg( 'vote-link' ); + $vars['_UNVOTE_LINK'] = wfMsg( 'vote-unvote-link' ); + return true; + } + + /** + * For the Renameuser extension. + * + * @param $renameUserSQL + * @return Boolean: true + */ + public static function onUserRename( $renameUserSQL ) { + $renameUserSQL->tables['Vote'] = array( 'username', 'vote_user_id' ); + return true; + } + + /** + * Set up the {{NUMBEROFVOTES}} magic word. + * + * @param $magicWords Array: array of magic words + * @param $langID + * @return Boolean: true + */ + public static function setUpMagicWord( &$magicWords, $langID ) { + // tell MediaWiki that {{NUMBEROFVOTES}} and all case variants found in + // wiki text should be mapped to magic ID 'NUMBEROFVOTES' + // (0 means case-insensitive) + $magicWords['NUMBEROFVOTES'] = array( 0, 'NUMBEROFVOTES' ); + return true; + } + + /** + * Assign a value to {{NUMBEROFVOTES}}. First we try memcached and if that + * fails, we fetch it directly from the database and cache it for 24 hours. + * + * @param $parser Parser + * @param $cache + * @param $magicWordId String: magic word ID + * @param $ret Integer: return value (number of votes) + * @return Boolean: true + */ + public static function assignValueToMagicWord( &$parser, &$cache, &$magicWordId, &$ret ) { + global $wgMemc; + + if ( $magicWordId == 'NUMBEROFVOTES' ) { + $key = wfMemcKey( 'vote', 'magic-word' ); + $data = $wgMemc->get( $key ); + if ( $data != '' ) { + // We have it in cache? Oh goody, let's just use the cached value! + wfDebugLog( + 'VoteNY', + 'Got the amount of votes from memcached' + ); + // return value + $ret = $data; + } else { + // Not cached → have to fetch it from the database + $dbr = wfGetDB( DB_SLAVE ); + $voteCount = (int)$dbr->selectField( + 'Vote', + 'COUNT(*) AS count', + array(), + __METHOD__ + ); + wfDebugLog( 'VoteNY', 'Got the amount of votes from DB' ); + // Store the count in cache... + // (86400 = seconds in a day) + $wgMemc->set( $key, $voteCount, 86400 ); + // ...and return the value to the user + $ret = $voteCount; + } + } + return true; + } + + /** + * Register the magic word ID for {{NUMBEROFVOTES}}. + * + * @param $variableIds Array: array of pre-existing variable IDs + * @return Boolean: true + */ + public static function registerVariableId( &$variableIds ) { + $variableIds[] = 'NUMBEROFVOTES'; + return true; + } + + /** + * Creates the necessary database table when the user runs + * maintenance/update.php. + * + * @param $updater Object: instance of DatabaseUpdater + * @return Boolean: true + */ + public static function addTable( $updater = null ) { + $dir = dirname( __FILE__ ); + $file = "$dir/vote.sql"; + if ( $updater === null ) { + global $wgExtNewTables; + $wgExtNewTables[] = array( 'Vote', $file ); + } else { + $updater->addExtensionUpdate( array( 'addTable', 'Vote', $file, true ) ); + } + return true; + } +} \ No newline at end of file Property changes on: trunk/extensions/VoteNY/VoteHooks.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/Vote_AjaxFunctions.php =================================================================== --- trunk/extensions/VoteNY/Vote_AjaxFunctions.php (rev 0) +++ trunk/extensions/VoteNY/Vote_AjaxFunctions.php 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,88 @@ +<?php +/** + * AJAX functions used by Vote extension. + */ +$wgAjaxExportList[] = 'wfVoteClick'; + +function wfVoteClick( $voteValue, $pageId, $mk ) { + global $wgUser; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + return ''; + } + + if( is_numeric( $pageId ) && ( is_numeric( $voteValue ) ) ) { + $vote = new Vote( $pageId ); + $vote->insert( $voteValue ); + + return $vote->count( 1 ); + } else { + return 'error'; + } +} + +$wgAjaxExportList[] = 'wfVoteDelete'; +function wfVoteDelete( $pageId, $mk ) { + global $wgUser; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + return ''; + } + + if( is_numeric( $pageId ) ) { + $vote = new Vote( $pageId ); + $vote->delete(); + + return $vote->count( 1 ); + } else { + return 'error'; + } +} + +$wgAjaxExportList[] = 'wfVoteStars'; +function wfVoteStars( $voteValue, $pageId, $mk ) { + global $wgUser; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + return ''; + } + + $vote = new VoteStars( $pageId ); + if( $vote->UserAlreadyVoted() ) { + $vote->delete(); + } + $vote->insert( $voteValue ); + + return $vote->display( $voteValue ); +} + +$wgAjaxExportList[] = 'wfVoteStarsMulti'; +function wfVoteStarsMulti( $voteValue, $pageId, $mk ) { + global $wgUser; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + return ''; + } + + $vote = new VoteStars( $pageId ); + if( $vote->UserAlreadyVoted() ) { + $vote->delete(); + } + $vote->insert( $voteValue ); + + return $vote->displayScore(); +} + +$wgAjaxExportList[] = 'wfVoteStarsDelete'; +function wfVoteStarsDelete( $pageId ) { + global $wgUser; + + if ( !$wgUser->isAllowed( 'vote' ) ) { + return ''; + } + + $vote = new VoteStars( $pageId ); + $vote->delete(); + + return $vote->display(); +} \ No newline at end of file Property changes on: trunk/extensions/VoteNY/Vote_AjaxFunctions.php ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/VoteNY/images/star_half.gif =================================================================== (Binary files differ) Property changes on: trunk/extensions/VoteNY/images/star_half.gif ___________________________________________________________________ Added: svn:mime-type + image/gif Added: trunk/extensions/VoteNY/images/star_off.gif =================================================================== (Binary files differ) Property changes on: trunk/extensions/VoteNY/images/star_off.gif ___________________________________________________________________ Added: svn:mime-type + image/gif Added: trunk/extensions/VoteNY/images/star_on.gif =================================================================== (Binary files differ) Property changes on: trunk/extensions/VoteNY/images/star_on.gif ___________________________________________________________________ Added: svn:mime-type + image/gif Added: trunk/extensions/VoteNY/images/star_voted.gif =================================================================== (Binary files differ) Property changes on: trunk/extensions/VoteNY/images/star_voted.gif ___________________________________________________________________ Added: svn:mime-type + image/gif Added: trunk/extensions/VoteNY/vote.sql =================================================================== --- trunk/extensions/VoteNY/vote.sql (rev 0) +++ trunk/extensions/VoteNY/vote.sql 2011-09-14 15:26:09 UTC (rev 97068) @@ -0,0 +1,21 @@ +CREATE TABLE /*_*/Vote ( + -- Internal ID to identify between different vote tags on different pages + `vote_id` int(11) NOT NULL auto_increment PRIMARY KEY, + -- Username (if any) of the person who voted + `username` varchar(255) NOT NULL default '0', + -- User ID of the person who voted + `vote_user_id` int(11) NOT NULL default '0', + -- ID of the page where the vote tag is in + `vote_page_id` int(11) NOT NULL default '0', + -- Value of the vote (ranging from 1 to 5) + `vote_value` char(1) character set latin1 collate latin1_bin NOT NULL default '', + -- Timestamp when the vote was cast + `vote_date` datetime NOT NULL default '0000-00-00 00:00:00', + -- IP address of the user who voted + `vote_ip` varchar(45) NOT NULL default '' +) /*$wgDBTableOptions*/; + +CREATE INDEX vote_page_id_index ON /*_*/Vote (vote_page_id); +CREATE INDEX valueidx ON /*_*/Vote (vote_value); +CREATE INDEX usernameidx ON /*_*/Vote (username); +CREATE INDEX vote_date ON /*_*/Vote (vote_date); \ No newline at end of file Property changes on: trunk/extensions/VoteNY/vote.sql ___________________________________________________________________ Added: svn:eol-style + native _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs