r47249 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r47248‎ | r47249 | r47250 >
Date:01:54, 14 February 2009
Author:tparscal
Status:deferred
Tags:
Comment:
Initial import.
Modified paths:
  • /trunk/extensions/CommunityVoice (modified) (history)
  • /trunk/extensions/CommunityVoice/CLI (added) (history)
  • /trunk/extensions/CommunityVoice/CLI/Initialize.php (added) (history)
  • /trunk/extensions/CommunityVoice/CommunityVoice.i18n.php (added) (history)
  • /trunk/extensions/CommunityVoice/CommunityVoice.page.php (added) (history)
  • /trunk/extensions/CommunityVoice/CommunityVoice.php (added) (history)
  • /trunk/extensions/CommunityVoice/CommunityVoice.sql (added) (history)
  • /trunk/extensions/CommunityVoice/Modules (added) (history)
  • /trunk/extensions/CommunityVoice/Modules/Ratings.php (added) (history)
  • /trunk/extensions/CommunityVoice/Resources (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/CommunityVoice.css (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/CommunityVoice.js (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-0-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-0.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-1-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-1.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-2-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-2.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-3-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-3.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-4-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-4.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-5-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-5.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-6-hover.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star-6.png (added) (history)
  • /trunk/extensions/CommunityVoice/Resources/Icons/star.svg (added) (history)

Diff [purge]

Index: trunk/extensions/CommunityVoice/CLI/Initialize.php
@@ -0,0 +1,20 @@
 2+<?php
 3+
 4+require_once dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) .
 5+ "/maintenance/commandLine.inc";
 6+
 7+if ( isset( $options['help'] ) ) {
 8+ echo "Rebuilds database structure for CommunityVoice.\n";
 9+ echo "Usage:\n";
 10+ echo "\tphp extensions/CommunityVoice/CLI/Initialize.php --confirm=yes \n";
 11+} else {
 12+ if ( isset( $options['confirm'] ) && $options['confirm'] == 'yes' ) {
 13+ echo "Rebuilding database structure for CommunityVoice...\n";
 14+ // Get a connection
 15+ $dbw = wfGetDB( DB_MASTER );
 16+ // Runs initialization
 17+ $dbw->sourceFile( dirname( dirname( __FILE__ ) ) . '/CommunityVoice.sql' );
 18+ } else {
 19+ echo "Nothing was changed. Run with --help for usage information.\n";
 20+ }
 21+}
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/CommunityVoice.sql
@@ -0,0 +1,33 @@
 2+--
 3+-- SQL for CommunityVoice Extension
 4+--
 5+-- Table for ratings
 6+DROP TABLE IF EXISTS /*$wgDBprefix*/cv_ratings_votes;
 7+CREATE TABLE /*$wgDBPrefix*/cv_ratings_votes (
 8+ -- Category of item being rated
 9+ vot_category VARBINARY(255) NOT NULL default '',
 10+ -- Title of item being rated
 11+ vot_title VARBINARY(255) NOT NULL default '',
 12+ -- User who made the rating, 0 for anons (however it shoudn't be allowed)
 13+ vot_user INTEGER NOT NULL default 0,
 14+ -- Value of rating
 15+ vot_rating INTEGER NOT NULL default 0,
 16+ --
 17+ INDEX vot_category_title ( vot_category, vot_title ),
 18+ INDEX vot_category_title_user ( vot_category, vot_title, vot_user ),
 19+ INDEX vot_category_title_rating ( vot_category, vot_title, vot_rating )
 20+) /*$wgDBTableOptions*/;
 21+--
 22+-- Table for articles which include ratings
 23+DROP TABLE IF EXISTS /*$wgDBprefix*/cv_ratings_usage;
 24+CREATE TABLE /*$wgDBPrefix*/cv_ratings_usage (
 25+ -- Category of item being rated
 26+ usg_category VARBINARY(255) NOT NULL default '',
 27+ -- Title of item being rated
 28+ usg_title VARBINARY(255) NOT NULL default '',
 29+ -- Title of article which includes the rating
 30+ usg_article VARBINARY(255) NOT NULL default '',
 31+ --
 32+ INDEX rat_category_title ( usg_category, usg_title ),
 33+ INDEX rat_category_title_article ( usg_category, usg_title, usg_article )
 34+) /*$wgDBTableOptions*/;
Index: trunk/extensions/CommunityVoice/Modules/Ratings.php
@@ -0,0 +1,530 @@
 2+<?php
 3+
 4+abstract class CommunityVoiceRatings {
 5+
 6+ /* Private Static Functions */
 7+
 8+ private static function getScaleFraction(
 9+ $rating,
 10+ $star
 11+ ) {
 12+ if ( floor( $rating ) > $star ) {
 13+ return 6;
 14+ } else if ( floor( $rating ) < $star ) {
 15+ return 0;
 16+ } else {
 17+ return round( ( 6 / 10 ) * ( ( $rating - floor( $rating ) ) * 10 ) );
 18+ }
 19+ }
 20+
 21+ private static function getArticlesUsing(
 22+ $category,
 23+ $title
 24+ ) {
 25+ $dbr = wfGetDB( DB_SLAVE );
 26+ $result = $dbr->select(
 27+ 'cv_ratings_usage',
 28+ 'usg_article',
 29+ array(
 30+ 'usg_category' => $category,
 31+ 'usg_title' => $title,
 32+ )
 33+ );
 34+ $articles = array();
 35+ while ( $row = $result->fetchRow() ) {
 36+ $articles[] = (string)$row['usg_article'];
 37+ }
 38+ return $articles;
 39+ }
 40+
 41+ private static function getCategories() {
 42+ $dbr = wfGetDB( DB_SLAVE );
 43+ $result = $dbr->select(
 44+ 'cv_ratings_votes',
 45+ 'DISTINCT vot_category'
 46+ );
 47+ $categories = array();
 48+ while ( $row = $result->fetchRow() ) {
 49+ $categories[] = (string)$row['vot_category'];
 50+ }
 51+ return $categories;
 52+ }
 53+
 54+ private static function getTitles(
 55+ $category
 56+ ) {
 57+ $dbr = wfGetDB( DB_SLAVE );
 58+ $result = $dbr->select(
 59+ 'cv_ratings_votes',
 60+ 'DISTINCT vot_title',
 61+ array( 'vot_category' => $category )
 62+ );
 63+ $titles = array();
 64+ while ( $row = $result->fetchRow() ) {
 65+ $titles[] = (string)$row['vot_title'];
 66+ }
 67+ return $titles;
 68+ }
 69+
 70+ private static function getTotalVotes(
 71+ $category,
 72+ $title
 73+ ) {
 74+ $dbr = wfGetDB( DB_SLAVE );
 75+ return (integer)$dbr->selectField(
 76+ 'cv_ratings_votes',
 77+ 'COUNT(*)',
 78+ array(
 79+ 'vot_category' => $category,
 80+ 'vot_title' => $title,
 81+ )
 82+ );
 83+ }
 84+
 85+ private static function getUserVoted(
 86+ $category,
 87+ $title
 88+ ) {
 89+ global $wgUser;
 90+ $dbr = wfGetDB( DB_SLAVE );
 91+ return (bool)$dbr->selectField(
 92+ 'cv_ratings_votes',
 93+ 'COUNT(*)',
 94+ array(
 95+ 'vot_category' => $category,
 96+ 'vot_title' => $title,
 97+ 'vot_user' => $wgUser->getId(),
 98+ )
 99+ );
 100+ }
 101+
 102+ private static function getAverageRating(
 103+ $category,
 104+ $title
 105+ ) {
 106+ $dbr = wfGetDB( DB_SLAVE );
 107+ return (float)$dbr->selectField(
 108+ 'cv_ratings_votes',
 109+ 'AVG(vot_rating)',
 110+ array(
 111+ 'vot_category' => $category,
 112+ 'vot_title' => $title,
 113+ )
 114+ );
 115+ }
 116+
 117+ private static function addVote(
 118+ $category,
 119+ $title,
 120+ $rating
 121+ ) {
 122+ global $wgUser;
 123+ // Checks if...
 124+ if (
 125+ // User is logged in
 126+ $wgUser->isLoggedIn() &&
 127+ // User has not yet voted
 128+ !self::getUserVoted( $category, $title )
 129+ ) {
 130+ // Get database connection
 131+ $dbw = wfGetDB( DB_MASTER );
 132+ // Insert new vote for user
 133+ $dbw->insert(
 134+ 'cv_ratings_votes',
 135+ array(
 136+ 'vot_category' => $category,
 137+ 'vot_title' => $title,
 138+ 'vot_user' => $wgUser->getId(),
 139+ 'vot_rating' => $rating,
 140+ )
 141+ );
 142+ $dbw->commit();
 143+ return true;
 144+ }
 145+ return false;
 146+ }
 147+
 148+ /* Static Functions */
 149+
 150+ public static function register() {
 151+ global $wgParser, $wgAjaxExportList, $wgHooks;
 152+ // Register the hook with the parser
 153+ $wgParser->setHook( 'ratings:scale', array( __CLASS__, 'renderScale' ) );
 154+ // Register ajax response hook
 155+ $wgAjaxExportList[] = __CLASS__ . '::handleScaleVoteCall';
 156+ // Register article save hook
 157+ $wgHooks['ArticleSave'][] = __CLASS__ . '::updateArticleUsage';
 158+
 159+ }
 160+
 161+ public static function updateArticleUsage(
 162+ $article,
 163+ $user,
 164+ $text,
 165+ $summary,
 166+ $minor,
 167+ $watch,
 168+ $sectionanchor,
 169+ $flags
 170+ ) {
 171+ $usedRatings = array();
 172+ // Extract all ratings:scale tags
 173+ preg_match_all(
 174+ "/<ratings:scale[^>]*[\\/]*>/i", $text, $matches, PREG_PATTERN_ORDER
 175+ );
 176+ // Loop over each match
 177+ foreach( $matches[0] as $match ) {
 178+ $rating = array();
 179+ foreach ( array( 'category', 'title' ) as $attribute ) {
 180+ // Extract value of attribute
 181+ preg_match(
 182+ "/{$attribute}=['\"]*(?<value>[^'\"]*)['\"]*/i",
 183+ $match,
 184+ $values
 185+ );
 186+ if ( isset( $values['value'] ) ) {
 187+ $rating[$attribute] = $values['value'];
 188+ }
 189+ }
 190+ if ( isset( $rating['category'], $rating['title'] ) ) {
 191+ $usedRatings[] = $rating;
 192+ }
 193+ }
 194+ // Gets name of article
 195+ $articleDbKey = $article->getTitle()->getPrefixedDBkey();
 196+ // Get database connection
 197+ $dbw = wfGetDB( DB_MASTER );
 198+ // Remove all usage for this article
 199+ $dbw->delete(
 200+ 'cv_ratings_usage',
 201+ array( 'usg_article' => $articleDbKey )
 202+ );
 203+ // Loop over each rating
 204+ foreach ( $usedRatings as $rating ) {
 205+ // Add usage for rating for this article
 206+ $dbw->insert(
 207+ 'cv_ratings_usage',
 208+ array(
 209+ 'usg_category' => $rating['category'],
 210+ 'usg_title' => $rating['title'],
 211+ 'usg_article' => $articleDbKey,
 212+ )
 213+ );
 214+ }
 215+ return true;
 216+ }
 217+
 218+ public static function renderScale(
 219+ $input,
 220+ $args,
 221+ $parser
 222+ ) {
 223+ global $wgUser, $wgTitle;
 224+ global $egCommunityVoiceResourcesPath;
 225+ // Validate and sanitize incoming arguments
 226+ $errors = array();
 227+ $error = false;
 228+ foreach ( array( 'category', 'title' ) as $argument ) {
 229+ if ( isset( $args[$argument] ) ) {
 230+ $args[$argument] = htmlspecialchars( $args[$argument] );
 231+ } else {
 232+ $error = true;
 233+ if ( $parser->getOptions()->getIsPreview() ) {
 234+ $errors[] = CommunityVoice::getMessage(
 235+ 'ratings', 'error-missing-argument', $argument
 236+ );
 237+ }
 238+ }
 239+ }
 240+ // Checks if an error ocurred
 241+ if ( $error ) {
 242+ // Checks if there are any error messages to return
 243+ if ( count( $errors ) ) {
 244+ return Html::div(
 245+ array( 'class' => 'error' ), implode( ' ', $errors )
 246+ );
 247+ }
 248+ // Continues without rendering
 249+ return true;
 250+ }
 251+ // Collects data
 252+ $totalVotes = self::getTotalVotes( $args['category'], $args['title'] );
 253+ $rating = self::getAverageRating( $args['category'], $args['title'] );
 254+ $userVoted = self::getUserVoted( $args['category'], $args['title'] );
 255+ // Builds sanitized HTML id with prepended module naming
 256+ $id = Html::toId(
 257+ 'cv_ratings_scale_' . $args['category'] . '_' . $args['title']
 258+ );
 259+ // Gets stats message
 260+ $stats = CommunityVoice::getMessage(
 261+ 'ratings', 'scale-stats', array( round( $rating, 1 ), $totalVotes )
 262+ );
 263+ // Begins rating scale
 264+ $htmlOut = Html::open(
 265+ 'div',
 266+ array( 'class' => 'communityvoice-ratings-scale', 'id' => $id )
 267+ );
 268+ // Checks for input
 269+ if ( $input != '' ) {
 270+ // Adds content of tag as parsed wiki-text
 271+ $htmlOut .= $parser->recursiveTagParse( $input );
 272+ }
 273+ // Checks if the user has not voted yet and is logged in
 274+ if ( !$userVoted && $wgUser->isLoggedIn() ) {
 275+
 276+ /* Ajax Interaction */
 277+
 278+ // Adds scale script
 279+ $htmlOut .= Html::script(
 280+ Js::callFunction(
 281+ 'communityVoice.ratings.scales.add',
 282+ Js::buildInstance(
 283+ 'CommunityVoiceRatingsScale',
 284+ array(
 285+ Js::toScalar( $id ),
 286+ Js::toScalar( $args['category'] ),
 287+ Js::toScalar( $args['title'] ),
 288+ Js::toScalar( $rating ),
 289+ Js::toObject(
 290+ array(
 291+ 'stats' => $stats,
 292+ 'status' => array(
 293+ 'null' => '&nbsp;',
 294+ 'sending' => CommunityVoice::getMessage(
 295+ 'ratings', 'scale-status-sending'
 296+ ),
 297+ 'error' => CommunityVoice::getMessage(
 298+ 'ratings', 'scale-status-error'
 299+ ),
 300+ 'thanks' => CommunityVoice::getMessage(
 301+ 'ratings', 'scale-status-thanks'
 302+ ),
 303+ )
 304+ )
 305+ ),
 306+ Js::toScalar( $wgTitle->getPrefixedText() )
 307+ )
 308+ )
 309+ )
 310+ );
 311+
 312+ /* HTML Form Interaction */
 313+
 314+ // Begins non-javascript fallback
 315+ $htmlOut .= Html::open( 'noscript' );
 316+ // Begins form
 317+ $specialPageTitle = Title::newFromText( 'Special:CommunityVoice' );
 318+ $htmlOut .= Html::open(
 319+ 'form',
 320+ array(
 321+ 'action' => $specialPageTitle->getFullUrl(),
 322+ 'method' => 'post',
 323+ )
 324+ );
 325+ // Builds list of hidden fields
 326+ $hiddenFields = array(
 327+ 'token' => $wgUser->editToken(),
 328+ 'module' => 'Ratings',
 329+ 'action' => 'ScaleVoteSubmission',
 330+ 'scale[article]' => $wgTitle->getPrefixedText(),
 331+ 'scale[category]' => $args['category'],
 332+ 'scale[title]' => $args['title'],
 333+ );
 334+ // Loops over each field
 335+ foreach ( $hiddenFields as $name => $value ) {
 336+ // Adds hidden field
 337+ $htmlOut .= Html::input(
 338+ array(
 339+ 'type' => 'hidden', 'name' => $name, 'value' => $value
 340+ )
 341+ );
 342+ }
 343+ // Loops 5 times (once per star)
 344+ for ( $i = 0; $i < 5; $i++ ) {
 345+ // Adds star as image input
 346+ $htmlOut .= Html::input(
 347+ array(
 348+ 'type' => 'image',
 349+ 'name' => 'scale[rating_' . $i . ']',
 350+ 'src' => sprintf(
 351+ '%s/Icons/star-%d.png',
 352+ $egCommunityVoiceResourcesPath,
 353+ self::getScaleFraction( $rating, $i )
 354+ ),
 355+ 'border' => 0,
 356+ 'alt' => '',
 357+ 'class' => 'star',
 358+ 'align' => 'absmiddle',
 359+ )
 360+ );
 361+ }
 362+ // Adds stats message
 363+ $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats );
 364+ // Ends form
 365+ $htmlOut .= Html::close( 'form' );
 366+ // Ends non-javascript fallback
 367+ $htmlOut .= Html::close( 'noscript' );
 368+ } else {
 369+
 370+ /* No Interaction */
 371+
 372+ // Loops 5 times (once per star)
 373+ for ( $i = 0; $i < 5; $i++ ) {
 374+ // Adds star as image
 375+ $htmlOut .= Html::tag(
 376+ 'img',
 377+ array(
 378+ 'src' => sprintf(
 379+ '%s/Icons/star-%d.png',
 380+ $egCommunityVoiceResourcesPath,
 381+ self::getScaleFraction( $rating, $i )
 382+ ),
 383+ 'border' => 0,
 384+ 'alt' => '',
 385+ 'class' => 'star',
 386+ 'align' => 'absmiddle',
 387+ )
 388+ );
 389+ }
 390+ // Adds stats message
 391+ $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats );
 392+ }
 393+ // Ends scale
 394+ $htmlOut .= Xml::closeElement( 'div' );
 395+ // Returns output
 396+ return $htmlOut;
 397+ }
 398+
 399+ /* Processing Functions */
 400+
 401+ /**
 402+ * Hanlder for ratings scale vote via ajax call
 403+ */
 404+ public static function handleScaleVoteCall(
 405+ $category,
 406+ $title,
 407+ $rating,
 408+ $article
 409+ ) {
 410+ global $wgUser;
 411+ // Adds vote and checks for success
 412+ if ( self::addVote( $category, $title, $rating ) ) {
 413+ // Gets new rating data
 414+ $rating = self::getAverageRating( $category, $title );
 415+ // Builds result
 416+ $result = array(
 417+ 'rating' => $rating,
 418+ 'stats' => CommunityVoice::getMessage(
 419+ 'ratings',
 420+ 'scale-stats',
 421+ array(
 422+ round( $rating, 1 ),
 423+ self::getTotalVotes( $category, $title )
 424+ )
 425+ ),
 426+ );
 427+ // Gets articles that use this rating
 428+ $articles = self::getArticlesUsing( $category, $title );
 429+ // Loops over each article
 430+ foreach($articles as $article ) {
 431+ // Invalidates the cache of the article
 432+ CommunityVoice::touchArticle( $article );
 433+ }
 434+ // Ensure database commits take place (since this is an ajax call)
 435+ $dbw = wfGetDB( DB_MASTER );
 436+ $dbw->commit();
 437+ // Returns result
 438+ return Js::toObject( $result );
 439+ }
 440+ // Returns error information
 441+ return Js::toObject( array( 'rating' => -1, 'stats' => null ) );
 442+ }
 443+
 444+ /**
 445+ * Hanlder for ratings scale vote via HTML form submission
 446+ */
 447+ public static function handleScaleVoteSubmission() {
 448+ global $wgOut, $wgRequest;
 449+ // Gets scale data
 450+ $scale = $wgRequest->getArray( 'scale' );
 451+ // Checks if an article was given
 452+ if ( isset( $scale['article'], $scale['title'], $scale['category'] ) ) {
 453+ // Looks for rating value
 454+ foreach ( $scale as $key => $value ) {
 455+ // Breaks key into parts
 456+ $parts = explode( '_', $key );
 457+ // Checks if...
 458+ if (
 459+ // There's at least 2 parts
 460+ ( count( $parts ) > 1 ) &&
 461+ // The first part is 'rating'
 462+ ( $parts[0] == 'rating' ) &&
 463+ // The second part is a number
 464+ ( is_numeric( $parts[1] ) )
 465+ ) {
 466+ // Uses number as rating
 467+ $rating = $parts[1];
 468+ // Finishes loop
 469+ break;
 470+ }
 471+ }
 472+ // Checks if a rating was found
 473+ if ( isset( $rating ) ) {
 474+ // Adds vote and checks for success
 475+ if (
 476+ self::addVote(
 477+ $scale['category'], $scale['title'], $rating
 478+ )
 479+ ) {
 480+ // Gets articles that use this rating
 481+ $articles = self::getArticlesUsing(
 482+ $scale['category'], $scale['title']
 483+ );
 484+ // Loops over each article
 485+ foreach($articles as $article ) {
 486+ // Invalidates the cache of the article
 487+ CommunityVoice::touchArticle( $article );
 488+ }
 489+ // Redirects user back to article
 490+ $wgOut->redirect(
 491+ Title::newFromText( $scale['article'] )->getFullUrl()
 492+ );
 493+ } else {
 494+ throw new MWException( 'Voting failed!' );
 495+ }
 496+ } else {
 497+ throw new MWException( 'No rating parameter!' );
 498+ }
 499+ } else {
 500+ throw new MWException( 'Missing parameters!' );
 501+ }
 502+ }
 503+
 504+ /* UI Functions */
 505+
 506+ /**
 507+ * Outputs a summary UI for the module
 508+ */
 509+ public static function showSummary() {
 510+ global $wgOut;
 511+ //
 512+ $wgOut->addWikiText( '==== Categories ====' );
 513+ $xmlCategories = Html::open( 'ul' );
 514+ foreach( self::getCategories() as $category ) {
 515+ $xmlCategories .= Html::tag( 'li', $category );
 516+ }
 517+ $xmlCategories .= Html::close( 'ul' );
 518+ $wgOut->addHtml( $xmlCategories );
 519+ }
 520+
 521+ /**
 522+ * Outputs main UI for module
 523+ */
 524+ public static function showMain(
 525+ $path
 526+ ) {
 527+ global $wgOut;
 528+ //
 529+ $wgOut->addWikiText( '==== Detailed Information ====' );
 530+ }
 531+}
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/Resources/CommunityVoice.js
@@ -0,0 +1,243 @@
 2+/* JavaScript Document */
 3+
 4+/* Prototypes */
 5+
 6+/**
 7+ * Generic global access system
 8+ */
 9+function CommunityVoicePool() {
 10+
 11+ /* Private Members */
 12+
 13+ var self = this;
 14+ var objects = {};
 15+
 16+ /* Public Functions */
 17+
 18+ /**
 19+ * Adds an object to pool, and returns it's unique ID
 20+ * @param object Object reference to add
 21+ */
 22+ this.add = function(
 23+ object
 24+ ) {
 25+ if ( typeof( object.getId ) == 'function' ) {
 26+ objects[object.getId()] = object;
 27+ return true;
 28+ }
 29+ return false;
 30+ }
 31+ /**
 32+ * Removes an object form pool
 33+ * @param id ID number of object to remove
 34+ */
 35+ this.remove = function(
 36+ id
 37+ ) {
 38+ if ( objects[id] !== undefined ) {
 39+ delete objects[id];
 40+ return true;
 41+ }
 42+ return false;
 43+ }
 44+ /**
 45+ * Gets an object from pool
 46+ * @param id ID number of object to get
 47+ */
 48+ this.get = function(
 49+ id
 50+ ) {
 51+ if ( objects[id] !== undefined ) {
 52+ return objects[id];
 53+ }
 54+ return null;
 55+ }
 56+}
 57+
 58+/**
 59+ * Ratings Scale Object
 60+ */
 61+function CommunityVoiceRatingsScale(
 62+ id,
 63+ category,
 64+ title,
 65+ rating,
 66+ messages,
 67+ article
 68+) {
 69+ /* Members */
 70+
 71+ var self = this;
 72+ // Gets object references
 73+ var element = document.getElementById( id );
 74+ var stars = [];
 75+ var labels = {};
 76+ // Sets state
 77+ var status = null;
 78+ var locked = true;
 79+
 80+ /* Functions */
 81+
 82+ this.getId = function() {
 83+ return id;
 84+ }
 85+
 86+ this.rate = function(
 87+ newRating
 88+ ) {
 89+ // Checks if a save is already taking place
 90+ if ( locked ) {
 91+ // Exits function immediately
 92+ return;
 93+ }
 94+ // Sets message to sending
 95+ status = 'sending';
 96+ // Updates UI
 97+ self.update();
 98+ // Don't allow any more requests
 99+ self.lock();
 100+ // Saves current request type
 101+ var oldRequestType = sajax_request_type;
 102+ // Changes request type to post
 103+ sajax_request_type = "POST";
 104+ // Performs asynchronous save on server
 105+ sajax_do_call(
 106+ "CommunityVoiceRatings::handleScaleVoteCall",
 107+ [ category, title, newRating, article ],
 108+ new Function(
 109+ "request",
 110+ "communityVoice.ratings.scales." +
 111+ "get( '" + id + "' ).respond( request )"
 112+ )
 113+ );
 114+ // Restores current request type
 115+ sajax_request_type = oldRequestType;
 116+ }
 117+
 118+ this.respond = function(
 119+ request
 120+ ) {
 121+ /*
 122+ * If errors are happening, this will help with debugging:
 123+ * alert( request.responseText );
 124+ */
 125+ if ( request.responseText.substr( 0, 1 ) !== '{' ) {
 126+ // Something VERY wrong just happened
 127+ // Changes message to error
 128+ status = 'error';
 129+ // Updates UI
 130+ self.update();
 131+ return;
 132+ }
 133+ // Parse JSON response
 134+ eval( 'var response = ' + request.responseText );
 135+ // Checks that an error did not occur
 136+ if ( response.rating !== undefined && response.stats !== undefined ) {
 137+ if ( response.rating >= 0 ) {
 138+ // Changes message to received
 139+ status = 'thanks';
 140+ // Uses response data
 141+ messages.stats = response.stats;
 142+ rating = response.rating;
 143+ } else {
 144+ // Alerts user of error
 145+ status = 'error';
 146+ }
 147+ } else {
 148+ // Alerts user of error
 149+ status = 'error';
 150+ }
 151+ // Updates UI
 152+ self.update();
 153+ }
 154+
 155+ this.lock = function() {
 156+ locked = true;
 157+ for ( star in stars ) {
 158+ stars[star].style.cursor = 'default';
 159+ }
 160+ }
 161+
 162+ this.unlock = function() {
 163+ locked = false;
 164+ for ( star in stars ) {
 165+ stars[star].style.cursor = 'pointer';
 166+ }
 167+ }
 168+
 169+ this.update = function(
 170+ hoveredStar
 171+ ) {
 172+ var dir = egCommunityVoiceResourcesPath + '/Icons';
 173+ var fraction = 0;
 174+ // Change UI accordingly
 175+ for ( star in stars ) {
 176+ if ( Math.floor( rating ) > star ) {
 177+ fraction = 6;
 178+ } else if ( Math.floor( rating ) < star ) {
 179+ fraction = 0;
 180+ } else {
 181+ fraction = Math.round(
 182+ ( 6 / 10 ) * ( ( rating - Math.floor( rating ) ) * 10 )
 183+ );
 184+ }
 185+ if ( hoveredStar !== undefined && hoveredStar >= star && !locked ) {
 186+ stars[star].src = dir + '/star-' + fraction + '-hover.png'
 187+ } else {
 188+ stars[star].src = dir + '/star-' + fraction + '.png'
 189+ }
 190+ }
 191+ labels.stats.innerHTML = messages.stats;
 192+ labels.stats.className = 'stats';
 193+ labels.status.innerHTML = messages.status[status];
 194+ labels.status.className = status;
 195+ }
 196+
 197+ // Loops 5 times (once per star)
 198+ for ( var i = 0; i < 5; i++ ) {
 199+ // Creates a new image
 200+ stars[i] = document.createElement( 'img' );
 201+ stars[i].style.borderWidth = '0px';
 202+ stars[i].className = 'star';
 203+ // Adds handlers to image
 204+ addHandler(
 205+ stars[i],
 206+ 'click',
 207+ new Function(
 208+ "communityVoice.ratings.scales." +
 209+ "get( '" + id + "' ).rate( " + ( i + 1 ) + " )"
 210+ )
 211+ );
 212+ addHandler(
 213+ stars[i],
 214+ 'mouseover',
 215+ new Function(
 216+ "communityVoice.ratings.scales." +
 217+ "get( '" + id + "' ).update( " + i + " )"
 218+ )
 219+ );
 220+ addHandler(
 221+ stars[i],
 222+ 'mouseout',
 223+ new Function(
 224+ "communityVoice.ratings.scales." +
 225+ "get( '" + id + "' ).update()"
 226+ )
 227+ );
 228+ // Inserts image into element
 229+ element.appendChild( stars[i] );
 230+ }
 231+ // Adds labels
 232+ labels.stats = document.createElement( 'span' );
 233+ element.appendChild( labels.stats );
 234+ labels.status = document.createElement( 'span' );
 235+ element.appendChild( labels.status );
 236+ this.unlock();
 237+ this.update();
 238+}
 239+
 240+/* Globals */
 241+
 242+var communityVoice = {};
 243+communityVoice.ratings = {};
 244+communityVoice.ratings.scales = new CommunityVoicePool();
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-0.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-0.png
___________________________________________________________________
Added: svn:mime-type
1245 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-0-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-0-hover.png
___________________________________________________________________
Added: svn:mime-type
2246 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-1.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-1.png
___________________________________________________________________
Added: svn:mime-type
3247 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-1-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-1-hover.png
___________________________________________________________________
Added: svn:mime-type
4248 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-2.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-2.png
___________________________________________________________________
Added: svn:mime-type
5249 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-2-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-2-hover.png
___________________________________________________________________
Added: svn:mime-type
6250 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-3.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-3.png
___________________________________________________________________
Added: svn:mime-type
7251 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-3-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-3-hover.png
___________________________________________________________________
Added: svn:mime-type
8252 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-4.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-4.png
___________________________________________________________________
Added: svn:mime-type
9253 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-4-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-4-hover.png
___________________________________________________________________
Added: svn:mime-type
10254 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-5.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-5.png
___________________________________________________________________
Added: svn:mime-type
11255 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-5-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-5-hover.png
___________________________________________________________________
Added: svn:mime-type
12256 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-6.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-6.png
___________________________________________________________________
Added: svn:mime-type
13257 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star-6-hover.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: trunk/extensions/CommunityVoice/Resources/Icons/star-6-hover.png
___________________________________________________________________
Added: svn:mime-type
14258 + application/octet-stream
Index: trunk/extensions/CommunityVoice/Resources/Icons/star.svg
@@ -0,0 +1,307 @@
 2+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 3+<!-- Created with Inkscape (http://www.inkscape.org/) -->
 4+<svg
 5+ xmlns:dc="http://purl.org/dc/elements/1.1/"
 6+ xmlns:cc="http://creativecommons.org/ns#"
 7+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 8+ xmlns:svg="http://www.w3.org/2000/svg"
 9+ xmlns="http://www.w3.org/2000/svg"
 10+ xmlns:xlink="http://www.w3.org/1999/xlink"
 11+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 12+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 13+ version="1.0"
 14+ width="48"
 15+ height="48"
 16+ id="svg0"
 17+ sodipodi:version="0.32"
 18+ inkscape:version="0.46"
 19+ sodipodi:docname="star.svg"
 20+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
 21+ style="display:inline"
 22+ inkscape:export-filename="/Users/tparscal/Sites/wiki/extensions/CommunityVoice/Resources/Icons/star-0-hover.png"
 23+ inkscape:export-xdpi="47.228661"
 24+ inkscape:export-ydpi="47.228661">
 25+ <metadata
 26+ id="metadata2465">
 27+ <rdf:RDF>
 28+ <cc:Work
 29+ rdf:about="">
 30+ <dc:format>image/svg+xml</dc:format>
 31+ <dc:type
 32+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 33+ </cc:Work>
 34+ </rdf:RDF>
 35+ </metadata>
 36+ <sodipodi:namedview
 37+ inkscape:window-height="786"
 38+ inkscape:window-width="1440"
 39+ inkscape:pageshadow="2"
 40+ inkscape:pageopacity="0.0"
 41+ guidetolerance="10.0"
 42+ gridtolerance="10.0"
 43+ objecttolerance="10.0"
 44+ borderopacity="1.0"
 45+ bordercolor="#666666"
 46+ pagecolor="#ffffff"
 47+ id="base"
 48+ showgrid="false"
 49+ showguides="true"
 50+ inkscape:guide-bbox="true"
 51+ inkscape:zoom="10.375"
 52+ inkscape:cx="21.809994"
 53+ inkscape:cy="24"
 54+ inkscape:window-x="0"
 55+ inkscape:window-y="22"
 56+ inkscape:current-layer="layer9" />
 57+ <defs
 58+ id="defs">
 59+ <linearGradient
 60+ id="linearGradient3295">
 61+ <stop
 62+ offset="0"
 63+ style="stop-color: rgb(252, 249, 251); stop-opacity: 1;"
 64+ id="stop3297" />
 65+ <stop
 66+ offset="1"
 67+ style="stop-color:rgb(252, 249, 251);stop-opacity:0.46698113"
 68+ id="stop3299" />
 69+ </linearGradient>
 70+ <linearGradient
 71+ id="linearGradient3287">
 72+ <stop
 73+ offset="0"
 74+ style="stop-color: rgb(252, 249, 251); stop-opacity: 1;"
 75+ id="stop3289" />
 76+ <stop
 77+ offset="1"
 78+ style="stop-color: rgb(252, 249, 251); stop-opacity: 0;"
 79+ id="stop3291" />
 80+ </linearGradient>
 81+ <inkscape:perspective
 82+ sodipodi:type="inkscape:persp3d"
 83+ inkscape:vp_x="0 : 24 : 1"
 84+ inkscape:vp_y="0 : 1000 : 0"
 85+ inkscape:vp_z="48 : 24 : 1"
 86+ inkscape:persp3d-origin="24 : 16 : 1"
 87+ id="perspective2467" />
 88+ <linearGradient
 89+ id="G0">
 90+ <stop
 91+ id="s1"
 92+ style="stop-color: rgb(230, 207, 0); stop-opacity: 1;"
 93+ offset="0" />
 94+ <stop
 95+ id="s2"
 96+ style="stop-color: rgb(253, 233, 74); stop-opacity: 1;"
 97+ offset="1" />
 98+ </linearGradient>
 99+ <linearGradient
 100+ id="G1">
 101+ <stop
 102+ id="s3"
 103+ style="stop-color: rgb(252, 249, 251); stop-opacity: 1;"
 104+ offset="0" />
 105+ <stop
 106+ id="s4"
 107+ style="stop-color: rgb(252, 249, 251); stop-opacity: 0;"
 108+ offset="1" />
 109+ </linearGradient>
 110+ <linearGradient
 111+ id="G2">
 112+ <stop
 113+ id="s5"
 114+ style="stop-color: rgb(0, 0, 0); stop-opacity: 0.63;"
 115+ offset="0" />
 116+ <stop
 117+ id="s6"
 118+ style="stop-color: rgb(0, 0, 0); stop-opacity: 0;"
 119+ offset="1" />
 120+ </linearGradient>
 121+ <linearGradient
 122+ x1="14.660452"
 123+ y1="7.0243196"
 124+ x2="24.030643"
 125+ y2="34.826122"
 126+ id="G3"
 127+ xlink:href="#G1"
 128+ gradientUnits="userSpaceOnUse" />
 129+ <radialGradient
 130+ cx="24"
 131+ cy="22"
 132+ r="22"
 133+ fx="24"
 134+ fy="22"
 135+ id="R0"
 136+ xlink:href="#G0"
 137+ gradientUnits="userSpaceOnUse"
 138+ gradientTransform="matrix(1,-0.2,0.2,1,-3.8,6.8)" />
 139+ <radialGradient
 140+ cx="17.3125"
 141+ cy="25.53125"
 142+ r="9.6875"
 143+ fx="17.3125"
 144+ fy="25.53125"
 145+ id="R1"
 146+ xlink:href="#G2"
 147+ gradientUnits="userSpaceOnUse"
 148+ gradientTransform="matrix(2.4,0,0,0.67,-17.1,24.13494)" />
 149+ <clipPath
 150+ clipPathUnits="userSpaceOnUse"
 151+ id="clipPath3351">
 152+ <rect
 153+ style="opacity:0.46698118;fill:#fdfbed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"
 154+ id="rect3353"
 155+ width="11.180724"
 156+ height="47.903614"
 157+ x="36.915661"
 158+ y="0" />
 159+ </clipPath>
 160+ <clipPath
 161+ clipPathUnits="userSpaceOnUse"
 162+ id="clipPath3358">
 163+ <rect
 164+ style="opacity:0.46698118;fill:#fdfbed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 165+ id="rect3360"
 166+ width="36.819279"
 167+ height="47.903614"
 168+ x="11.277108"
 169+ y="0" />
 170+ </clipPath>
 171+ <clipPath
 172+ clipPathUnits="userSpaceOnUse"
 173+ id="clipPath3365">
 174+ <rect
 175+ y="0"
 176+ x="18.409639"
 177+ height="47.903614"
 178+ width="29.590363"
 179+ id="rect3367"
 180+ style="opacity:0.46698118;fill:#fdfbed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
 181+ </clipPath>
 182+ <clipPath
 183+ clipPathUnits="userSpaceOnUse"
 184+ id="clipPath3370">
 185+ <rect
 186+ style="opacity:0.46698118;fill:#fdfbed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"
 187+ id="rect3372"
 188+ width="23.903616"
 189+ height="47.903614"
 190+ x="24"
 191+ y="0" />
 192+ </clipPath>
 193+ <clipPath
 194+ clipPathUnits="userSpaceOnUse"
 195+ id="clipPath3377">
 196+ <rect
 197+ y="0"
 198+ x="30.26506"
 199+ height="47.903614"
 200+ width="17.734943"
 201+ id="rect3379"
 202+ style="opacity:0.46698118;fill:#fdfbed;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline" />
 203+ </clipPath>
 204+ </defs>
 205+ <g
 206+ inkscape:groupmode="layer"
 207+ id="layer5"
 208+ inkscape:label="6"
 209+ style="display:inline">
 210+ <path
 211+ d="M 47,41.23494 C 47,44.82461 36.702552,47.73494 24,47.73494 C 11.297449,47.73494 1,44.82461 1,41.23494 C 1,37.64494 11.297449,33.73494 24,33.73494 C 36.702552,33.73494 47,37.644909 47,41.23494 z"
 212+ id="s"
 213+ style="opacity:0.18446601;fill:url(#R1)" />
 214+ <path
 215+ d="M 37.310531,41.5 L 24.030644,34.826121 L 10.735101,41.471448 L 13.289408,27.369086 L 2.551164,17.368344 L 17.409702,15.326301 L 24.068641,2.5 L 30.697416,15.340488 L 45.551111,17.414249 L 34.789382,27.392039 L 37.310531,41.5 z"
 216+ id="star"
 217+ style="fill:url(#R0);stroke:#c4a000" />
 218+ <path
 219+ d="M 35.973752,39.712833 L 24.028333,33.709386 L 12.082531,39.678326 L 14.362768,27.008543 L 4.7776396,18.070249 L 18.056076,16.247921 L 24.062792,4.6783526 L 30.045232,16.255741 L 43.322959,18.11306 L 33.713703,27.034785 L 35.973752,39.712833 z"
 220+ id="ol"
 221+ style="opacity:0.68999999;fill:none;stroke:#ffffff" />
 222+ </g>
 223+ <g
 224+ inkscape:groupmode="layer"
 225+ id="layer8"
 226+ inkscape:label="5"
 227+ style="display:none">
 228+ <path
 229+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline"
 230+ id="path3341"
 231+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 232+ clip-path="url(#clipPath3351)" />
 233+ </g>
 234+ <g
 235+ inkscape:groupmode="layer"
 236+ id="layer3"
 237+ inkscape:label="4"
 238+ style="display:none">
 239+ <path
 240+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline"
 241+ id="path3307"
 242+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 243+ clip-path="url(#clipPath3377)" />
 244+ </g>
 245+ <g
 246+ inkscape:groupmode="layer"
 247+ id="layer2"
 248+ inkscape:label="3"
 249+ style="display:none">
 250+ <path
 251+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 252+ id="path3304"
 253+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline"
 254+ clip-path="url(#clipPath3370)" />
 255+ </g>
 256+ <g
 257+ inkscape:groupmode="layer"
 258+ id="layer1"
 259+ inkscape:label="2"
 260+ style="display:none">
 261+ <path
 262+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline"
 263+ id="path3301"
 264+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 265+ clip-path="url(#clipPath3365)" />
 266+ </g>
 267+ <g
 268+ inkscape:groupmode="layer"
 269+ id="layer7"
 270+ inkscape:label="1"
 271+ style="display:none">
 272+ <path
 273+ clip-path="url(#clipPath3358)"
 274+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 275+ id="path3331"
 276+ style="fill:#ffffff;fill-opacity:1;stroke:none;display:inline" />
 277+ </g>
 278+ <g
 279+ inkscape:groupmode="layer"
 280+ id="layer4"
 281+ inkscape:label="0"
 282+ style="display:inline">
 283+ <path
 284+ d="M 36.635246,40.704819 L 23.98047,34.345093 L 11.310776,40.677611 L 13.744847,27.239076 L 3.5120747,17.70909 L 17.67119,15.76317 L 24.016679,3.54063 L 30.333424,15.776689 L 44.487925,17.752834 L 34.232772,27.260949 L 36.635246,40.704819 z"
 285+ id="path2469"
 286+ style="fill:#ffffff;fill-opacity:1;stroke:none" />
 287+ </g>
 288+ <g
 289+ inkscape:groupmode="layer"
 290+ id="layer6"
 291+ inkscape:label="Shine"
 292+ style="display:inline">
 293+ <path
 294+ d="M 17.731857,15.79089 L 24.06374,3.58682 L 30.369452,15.794804 L 44.440148,17.761498 L 40.820395,21.121507 C 24.382895,17.434007 31.36502,28.341981 13.251748,30.364721 L 13.819799,27.184324 L 3.660452,17.718688 C 3.660452,17.718688 17.731857,15.79089 17.731857,15.79089 z"
 295+ id="hl"
 296+ style="opacity:0.8;fill:url(#G3);stroke:none;display:inline" />
 297+ </g>
 298+ <g
 299+ inkscape:groupmode="layer"
 300+ id="layer9"
 301+ inkscape:label="Hover"
 302+ style="display:inline">
 303+ <path
 304+ style="fill:#ffffff;fill-opacity:1;stroke:#c4a000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline"
 305+ id="path2434"
 306+ d="M 31.718345,34.290786 L 23.98807,30.405895 L 16.248682,34.274167 L 17.735554,26.065127 L 11.484781,20.243655 L 20.133994,19.054974 L 24.010188,11.588733 L 27.868824,19.063232 L 36.515219,20.270377 L 30.250774,26.078487 L 31.718345,34.290786 z" />
 307+ </g>
 308+</svg>
Index: trunk/extensions/CommunityVoice/Resources/CommunityVoice.css
@@ -0,0 +1,36 @@
 2+/* CSS Document */
 3+
 4+div.communityvoice-ratings-scale {
 5+ padding: 2px;
 6+}
 7+div.communityvoice-ratings-scale form {
 8+ margin: 0px;
 9+ padding: 0px;
 10+}
 11+div.communityvoice-ratings-scale p {
 12+ margin: 0px;
 13+ padding: 0px;
 14+}
 15+div.communityvoice-ratings-scale img.star {
 16+ border: 0px;
 17+}
 18+div.communityvoice-ratings-scale span.stats {
 19+ padding-left: 10px;
 20+ color: gray;
 21+ font-size: smaller;
 22+}
 23+div.communityvoice-ratings-scale span.sending {
 24+ padding-left: 10px;
 25+ color: blue;
 26+ font-size: smaller;
 27+}
 28+div.communityvoice-ratings-scale span.error {
 29+ padding-left: 10px;
 30+ color: red;
 31+ font-size: smaller;
 32+}
 33+div.communityvoice-ratings-scale span.thanks {
 34+ padding-left: 10px;
 35+ color: green;
 36+ font-size: smaller;
 37+}
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/CommunityVoice.page.php
@@ -0,0 +1,87 @@
 2+<?php
 3+/**
 4+ * Special Page Class for CommunityVoice extension
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+class CommunityVoicePage extends SpecialPage {
 11+
 12+ /* Functions */
 13+
 14+ public function __construct() {
 15+
 16+ /* Initialization */
 17+
 18+ // Initializes special page
 19+ parent::__construct( 'CommunityVoice' );
 20+ // Loads extension messages
 21+ wfLoadExtensionMessages( 'CommunityVoice' );
 22+ }
 23+
 24+ public function execute(
 25+ $sub
 26+ ) {
 27+ global $wgOut, $wgRequest, $wgUser;
 28+
 29+ /* Control */
 30+
 31+ // Gets edit token
 32+ $token = $wgRequest->getText( 'token' );
 33+ // Checks if a token was given
 34+ if ( $token ) {
 35+ // Validates edit token
 36+ if ( $wgUser->editToken() == $token ) {
 37+ // Gets module
 38+ $module = $wgRequest->getText( 'module' );
 39+ // Gets action
 40+ $action = $wgRequest->getText( 'action' );
 41+ // Checks that module and action were given
 42+ if ( $module and $action ) {
 43+ // Calls action on module
 44+ CommunityVoice::callModuleAction(
 45+ $module, 'handle', $action
 46+ );
 47+ // Finishes page
 48+ return true;
 49+ } else {
 50+ throw new MWException(
 51+ $module . ' module or ' . $action . ' action ' .
 52+ ' not found or are not callable!'
 53+ );
 54+ }
 55+ } else {
 56+ throw new MWException( 'Invalid edit token!' );
 57+ }
 58+ }
 59+
 60+ /* View */
 61+
 62+ // Begins output
 63+ $this->setHeaders();
 64+ // Breaks sub into path steps
 65+ $path = explode( '/', $sub );
 66+ // Checks if a specific module was given
 67+ if ( count( $path ) > 0 ) {
 68+ // Calls specific action on module
 69+ CommunityVoice::callModuleAction(
 70+ $path[0], 'show', count( $path ) >= 2 ? $path[1] : 'Main'
 71+ );
 72+ // Finishes page
 73+ return true;
 74+ }
 75+ // Modules summary view
 76+ foreach( CommunityVoice::getModules() as $module ) {
 77+ // Adds heading
 78+ $wgOut->addWikiText(
 79+ '== ' . wfMsg( 'communityvoice-' . $module ) . ' =='
 80+ );
 81+ // Calls summary action on module
 82+ self::callModuleAction( $module, 'show', 'Summary' );
 83+ }
 84+ // Finishes page
 85+ return true;
 86+ }
 87+
 88+}
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/CommunityVoice.i18n.php
@@ -0,0 +1,27 @@
 2+<?php
 3+/**
 4+ * Internationalization for CommunityVoice extension
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ */
 9+
 10+$messages = array();
 11+
 12+/** English
 13+ * @author Trevor Parscal
 14+ */
 15+$messages['en'] = array(
 16+ // Page
 17+ 'communityvoice' => 'Community Voice',
 18+ // Description
 19+ 'communityvoice-desc' => 'Community participation tools',
 20+ // Ratings Module
 21+ 'communityvoice-ratings' => 'Ratings',
 22+ 'communityvoice-ratings-scale-status-sending' => 'Sending...',
 23+ 'communityvoice-ratings-scale-status-error' => 'Error sending!',
 24+ 'communityvoice-ratings-scale-status-thanks' => 'Thanks for voting!',
 25+ 'communityvoice-ratings-scale-stats' =>'$1 / 5 ($2 {{PLURAL:$2|vote|votes}} cast)',
 26+ 'communityvoice-ratings-error-no-category' => 'Category attribute missing in rating tag.',
 27+ 'communityvoice-ratings-error-no-title' => 'Title attribute missing in rating tag.',
 28+);
\ No newline at end of file
Index: trunk/extensions/CommunityVoice/CommunityVoice.php
@@ -0,0 +1,189 @@
 2+<?php
 3+/**
 4+ * CommunityVoice extension
 5+ *
 6+ * @file
 7+ * @ingroup Extensions
 8+ *
 9+ * This is the main include file for the CommunityVoice extension.
 10+ *
 11+ * Installation: Add the following line in LocalSettings.php:
 12+ * require_once( "$IP/extensions/CommunityVoice/CommunityVoice.php" );
 13+ *
 14+ * This extension depends on the ClientSide extension, which provides functions
 15+ * for generating code in client-side formats such as HTML, CSS and JavaScript
 16+ *
 17+ * @author Trevor Parscal <tparscal@wikimedia.org>
 18+ * @license GPL v2
 19+ * @version 0.1.0
 20+ */
 21+
 22+// Check environment
 23+if ( !defined( 'MEDIAWIKI' ) ) {
 24+ echo ( "This is a MediaWiki extension and cannot be run standalone.\n" );
 25+ die ( 1 );
 26+}
 27+
 28+/* Configuration */
 29+
 30+// Web-accessable resource path
 31+$egCommunityVoiceResourcesPath = $wgScriptPath .
 32+ '/extensions/CommunityVoice/Resources';
 33+
 34+/* MediaWiki Integration */
 35+
 36+// Credits
 37+$wgExtensionCredits['other'][] = array(
 38+ 'name' => 'CommunityVoice',
 39+ 'author' => 'Trevor Parscal',
 40+ 'url' => 'http://www.mediawiki.org/wiki/Extension:CommunityVoice',
 41+ 'description-msg' => 'communityvoice-desc',
 42+);
 43+// Shortcut to this extension directory
 44+$dir = dirname( __FILE__ ) . '/';
 45+// Internationalization
 46+$wgExtensionMessagesFiles['CommunityVoice'] = $dir . 'CommunityVoice.i18n.php';
 47+// Class Autoloading
 48+$wgAutoloadClasses['CommunityVoice'] = $dir . 'CommunityVoice.php';
 49+$wgAutoloadClasses['CommunityVoicePage'] = $dir . 'CommunityVoice.page.php';
 50+$wgAutoloadClasses['CommunityVoiceRatings'] = $dir . 'Modules/Ratings.php';
 51+// Spacial Pages
 52+$wgSpecialPages['CommunityVoice'] = 'CommunityVoicePage';
 53+// Setup Hooks
 54+$wgExtensionFunctions[] = 'CommunityVoice::registerModules';
 55+$wgHooks['AjaxAddScript'][] = 'CommunityVoice::addScripts';
 56+$wgHooks['BeforePageDisplay'][] = 'CommunityVoice::addStyles';
 57+
 58+/* Classes */
 59+
 60+abstract class CommunityVoice {
 61+
 62+ /* Static Members */
 63+
 64+ static private $modules = array(
 65+ 'Ratings' => array( 'class' => 'CommunityVoiceRatings' )
 66+ );
 67+ static private $messagesLoaded = false;
 68+
 69+ /* Static Functions */
 70+
 71+ public static function getModules() {
 72+ return array_keys( self::$modules );
 73+ }
 74+
 75+ public static function callModuleAction(
 76+ $module,
 77+ $type,
 78+ $action = ''
 79+ ) {
 80+ // Checks for class
 81+ if ( isset( self::$modules[$module] ) ) {
 82+ if ( class_exists( self::$modules[$module]['class'] ) ) {
 83+ // Builds function
 84+ $function = array(
 85+ self::$modules[$module]['class'], $type . $action
 86+ );
 87+ // Checks callability
 88+ if ( is_callable( $function ) ) {
 89+ // Calls function on class
 90+ return call_user_func( $function );
 91+ } else {
 92+ // Throws unfound/uncallable function exception
 93+ throw new MWException(
 94+ implode( '::', $function ) .
 95+ ' was not found or is not callable!'
 96+ );
 97+ }
 98+ } else {
 99+ // Throws non-existant class exception
 100+ throw new MWException(
 101+ self::$modules[$module]['class'] . ' is not a class!'
 102+ );
 103+ }
 104+ } else {
 105+ // Throws non-existant module exception
 106+ throw new MWException( $module . ' is not a module!' );
 107+ }
 108+ }
 109+
 110+ /**
 111+ * Registers modules with MediaWiki
 112+ */
 113+ public static function registerModules() {
 114+ // Loops over each module
 115+ foreach( self::getModules() as $module ) {
 116+ self::callModuleAction( $module, 'register' );
 117+ }
 118+ return true;
 119+ }
 120+
 121+ public static function getMessage(
 122+ $module,
 123+ $message,
 124+ $parameter = null
 125+ ) {
 126+ // Checks if extension messages have been loaded already
 127+ if ( !self::$messagesLoaded ) {
 128+ // Loads extension messages
 129+ wfLoadExtensionMessages( 'CommunityVoice' );
 130+ self::$messagesLoaded = true;
 131+ }
 132+ // Returns message
 133+ return wfMsg( 'communityvoice-' . $module . '-' . $message, $parameter );
 134+ }
 135+
 136+ public static function touchArticle(
 137+ $article
 138+ ) {
 139+ // Gets the title of the article which included the scale
 140+ $articleTitle = Title::newFromText( $article );
 141+ // Invalidates the cache of the article
 142+ $articleTitle->invalidateCache();
 143+ }
 144+
 145+ /**
 146+ * Adds scripts to document
 147+ */
 148+ public static function addScripts(
 149+ $out
 150+ ) {
 151+ global $wgJsMimeType;
 152+ global $egCommunityVoiceResourcesPath;
 153+ $out->addInlineScript(
 154+ sprintf(
 155+ "var egCommunityVoiceResourcesPath = '%s';\n" ,
 156+ Xml::escapeJsString( $egCommunityVoiceResourcesPath )
 157+ )
 158+ );
 159+ $out->addScript(
 160+ Xml::element(
 161+ 'script',
 162+ array(
 163+ 'type' => $wgJsMimeType,
 164+ 'src' => $egCommunityVoiceResourcesPath .
 165+ '/CommunityVoice.js'
 166+ ),
 167+ '',
 168+ false
 169+ )
 170+ );
 171+ return true;
 172+ }
 173+
 174+ /**
 175+ * Adds styles to document
 176+ */
 177+ public static function addStyles(
 178+ $out
 179+ ) {
 180+ global $egCommunityVoiceResourcesPath;
 181+ $out->addLink(
 182+ array(
 183+ 'rel' => 'stylesheet',
 184+ 'type' => 'text/css',
 185+ 'href' => $egCommunityVoiceResourcesPath . '/CommunityVoice.css'
 186+ )
 187+ );
 188+ return true;
 189+ }
 190+}
\ No newline at end of file
Property changes on: trunk/extensions/CommunityVoice
___________________________________________________________________
Added: svn:ignore
1191 + .project

Status & tagging log