Property changes on: trunk/extensions/CommunityVoice/CLI/Initialize.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 1 | + native |
Index: trunk/extensions/CommunityVoice/Modules/Ratings.php |
— | — | @@ -1,489 +1,489 @@ |
2 | | -<?php
|
3 | | -
|
4 | | -abstract class CommunityVoiceRatings {
|
5 | | -
|
6 | | - /* Private Static Functions */
|
7 | | -
|
8 | | - private static function getScaleFraction( $rating, $star ) {
|
9 | | - if ( floor( $rating ) > $star ) {
|
10 | | - return 6;
|
11 | | - } else if ( floor( $rating ) < $star ) {
|
12 | | - return 0;
|
13 | | - } else {
|
14 | | - return round( ( 6 / 10 ) * ( ( $rating - floor( $rating ) ) * 10 ) );
|
15 | | - }
|
16 | | - }
|
17 | | -
|
18 | | - private static function getArticlesUsing( $category, $title ) {
|
19 | | - $dbr = wfGetDB( DB_SLAVE );
|
20 | | - $result = $dbr->select(
|
21 | | - 'cv_ratings_usage',
|
22 | | - 'usg_article',
|
23 | | - array(
|
24 | | - 'usg_category' => $category,
|
25 | | - 'usg_title' => $title,
|
26 | | - )
|
27 | | - );
|
28 | | - $articles = array();
|
29 | | - while ( $row = $result->fetchRow() ) {
|
30 | | - $articles[] = (string)$row['usg_article'];
|
31 | | - }
|
32 | | - return $articles;
|
33 | | - }
|
34 | | -
|
35 | | - private static function getCategories() {
|
36 | | - $dbr = wfGetDB( DB_SLAVE );
|
37 | | - $result = $dbr->select(
|
38 | | - 'cv_ratings_votes',
|
39 | | - 'DISTINCT vot_category'
|
40 | | - );
|
41 | | - $categories = array();
|
42 | | - while ( $row = $result->fetchRow() ) {
|
43 | | - $categories[] = (string)$row['vot_category'];
|
44 | | - }
|
45 | | - return $categories;
|
46 | | - }
|
47 | | -
|
48 | | - private static function getTitles( $category ) {
|
49 | | - $dbr = wfGetDB( DB_SLAVE );
|
50 | | - $result = $dbr->select(
|
51 | | - 'cv_ratings_votes',
|
52 | | - 'DISTINCT vot_title',
|
53 | | - array( 'vot_category' => $category )
|
54 | | - );
|
55 | | - $titles = array();
|
56 | | - while ( $row = $result->fetchRow() ) {
|
57 | | - $titles[] = (string)$row['vot_title'];
|
58 | | - }
|
59 | | - return $titles;
|
60 | | - }
|
61 | | -
|
62 | | - private static function getTotalVotes( $category, $title ) {
|
63 | | - $dbr = wfGetDB( DB_SLAVE );
|
64 | | - return (integer)$dbr->selectField(
|
65 | | - 'cv_ratings_votes',
|
66 | | - 'COUNT(*)',
|
67 | | - array(
|
68 | | - 'vot_category' => $category,
|
69 | | - 'vot_title' => $title,
|
70 | | - )
|
71 | | - );
|
72 | | - }
|
73 | | -
|
74 | | - private static function getUserVoted( $category, $title ) {
|
75 | | - global $wgUser;
|
76 | | - $dbr = wfGetDB( DB_SLAVE );
|
77 | | - return (bool)$dbr->selectField(
|
78 | | - 'cv_ratings_votes',
|
79 | | - 'COUNT(*)',
|
80 | | - array(
|
81 | | - 'vot_category' => $category,
|
82 | | - 'vot_title' => $title,
|
83 | | - 'vot_user' => $wgUser->getId(),
|
84 | | - )
|
85 | | - );
|
86 | | - }
|
87 | | -
|
88 | | - private static function getAverageRating( $category, $title ) {
|
89 | | - $dbr = wfGetDB( DB_SLAVE );
|
90 | | - return (float)$dbr->selectField(
|
91 | | - 'cv_ratings_votes',
|
92 | | - 'AVG(vot_rating)',
|
93 | | - array(
|
94 | | - 'vot_category' => $category,
|
95 | | - 'vot_title' => $title,
|
96 | | - )
|
97 | | - );
|
98 | | - }
|
99 | | -
|
100 | | - private static function addVote( $category, $title, $rating ) {
|
101 | | - global $wgUser;
|
102 | | - // Checks if...
|
103 | | - if (
|
104 | | - // User is logged in
|
105 | | - $wgUser->isLoggedIn() &&
|
106 | | - // User has not yet voted
|
107 | | - !self::getUserVoted( $category, $title )
|
108 | | - ) {
|
109 | | - // Get database connection
|
110 | | - $dbw = wfGetDB( DB_MASTER );
|
111 | | - // Insert new vote for user
|
112 | | - $dbw->insert(
|
113 | | - 'cv_ratings_votes',
|
114 | | - array(
|
115 | | - 'vot_category' => $category,
|
116 | | - 'vot_title' => $title,
|
117 | | - 'vot_user' => $wgUser->getId(),
|
118 | | - 'vot_rating' => $rating,
|
119 | | - )
|
120 | | - );
|
121 | | - $dbw->commit();
|
122 | | - return true;
|
123 | | - }
|
124 | | - return false;
|
125 | | - }
|
126 | | -
|
127 | | - /* Static Functions */
|
128 | | -
|
129 | | - public static function register() {
|
130 | | - global $wgParser, $wgAjaxExportList, $wgHooks;
|
131 | | - // Register the hook with the parser
|
132 | | - $wgParser->setHook( 'ratings:scale', array( __CLASS__, 'renderScale' ) );
|
133 | | - // Register ajax response hook
|
134 | | - $wgAjaxExportList[] = __CLASS__ . '::handleScaleVoteCall';
|
135 | | - // Register article save hook
|
136 | | - $wgHooks['ArticleSave'][] = __CLASS__ . '::updateArticleUsage';
|
137 | | -
|
138 | | - }
|
139 | | -
|
140 | | - public static function updateArticleUsage( $article, $user, $text, $summary, $minor, $watch, $sectionanchor, $flags ) {
|
141 | | - $usedRatings = array();
|
142 | | - // Extract all ratings:scale tags
|
143 | | - preg_match_all(
|
144 | | - "/<ratings:scale[^>]*[\\/]*>/i", $text, $matches, PREG_PATTERN_ORDER
|
145 | | - );
|
146 | | - // Loop over each match
|
147 | | - foreach ( $matches[0] as $match ) {
|
148 | | - $rating = array();
|
149 | | - foreach ( array( 'category', 'title' ) as $attribute ) {
|
150 | | - // Extract value of attribute
|
151 | | - preg_match(
|
152 | | - "/{$attribute}=['\"]*(?<value>[^'\"]*)['\"]*/i",
|
153 | | - $match,
|
154 | | - $values
|
155 | | - );
|
156 | | - if ( isset( $values['value'] ) ) {
|
157 | | - $rating[$attribute] = $values['value'];
|
158 | | - }
|
159 | | - }
|
160 | | - if ( isset( $rating['category'], $rating['title'] ) ) {
|
161 | | - $usedRatings[] = $rating;
|
162 | | - }
|
163 | | - }
|
164 | | - // Gets name of article
|
165 | | - $articleDbKey = $article->getTitle()->getPrefixedDBkey();
|
166 | | - // Get database connection
|
167 | | - $dbw = wfGetDB( DB_MASTER );
|
168 | | - // Remove all usage for this article
|
169 | | - $dbw->delete(
|
170 | | - 'cv_ratings_usage',
|
171 | | - array( 'usg_article' => $articleDbKey )
|
172 | | - );
|
173 | | - // Loop over each rating
|
174 | | - foreach ( $usedRatings as $rating ) {
|
175 | | - // Add usage for rating for this article
|
176 | | - $dbw->insert(
|
177 | | - 'cv_ratings_usage',
|
178 | | - array(
|
179 | | - 'usg_category' => $rating['category'],
|
180 | | - 'usg_title' => $rating['title'],
|
181 | | - 'usg_article' => $articleDbKey,
|
182 | | - )
|
183 | | - );
|
184 | | - }
|
185 | | - return true;
|
186 | | - }
|
187 | | -
|
188 | | - public static function renderScale( $input, $args, $parser ) {
|
189 | | - global $wgUser, $wgTitle;
|
190 | | - global $egCommunityVoiceResourcesPath;
|
191 | | - // Validate and sanitize incoming arguments
|
192 | | - $errors = array();
|
193 | | - $error = false;
|
194 | | - foreach ( array( 'category', 'title' ) as $argument ) {
|
195 | | - if ( isset( $args[$argument] ) ) {
|
196 | | - $args[$argument] = htmlspecialchars( $args[$argument] );
|
197 | | - } else {
|
198 | | - $error = true;
|
199 | | - if ( $parser->getOptions()->getIsPreview() ) {
|
200 | | - $errors[] = CommunityVoice::getMessage(
|
201 | | - 'ratings', 'error-missing-argument', $argument
|
202 | | - );
|
203 | | - }
|
204 | | - }
|
205 | | - }
|
206 | | - // Checks if an error ocurred
|
207 | | - if ( $error ) {
|
208 | | - // Checks if there are any error messages to return
|
209 | | - if ( count( $errors ) ) {
|
210 | | - return Html::div(
|
211 | | - array( 'class' => 'error' ), implode( ' ', $errors )
|
212 | | - );
|
213 | | - }
|
214 | | - // Continues without rendering
|
215 | | - return true;
|
216 | | - }
|
217 | | - // Collects data
|
218 | | - $totalVotes = self::getTotalVotes( $args['category'], $args['title'] );
|
219 | | - $rating = self::getAverageRating( $args['category'], $args['title'] );
|
220 | | - $userVoted = self::getUserVoted( $args['category'], $args['title'] );
|
221 | | - // Builds sanitized HTML id with prepended module naming
|
222 | | - $id = Html::toId(
|
223 | | - 'cv_ratings_scale_' . $args['category'] . '_' . $args['title']
|
224 | | - );
|
225 | | - // Gets stats message
|
226 | | - $stats = CommunityVoice::getMessage(
|
227 | | - 'ratings', 'scale-stats', array( round( $rating, 1 ), $totalVotes )
|
228 | | - );
|
229 | | - // Begins rating scale
|
230 | | - $htmlOut = Html::open(
|
231 | | - 'div',
|
232 | | - array( 'class' => 'communityvoice-ratings-scale', 'id' => $id )
|
233 | | - );
|
234 | | - // Checks for input
|
235 | | - if ( $input != '' ) {
|
236 | | - // Adds content of tag as parsed wiki-text
|
237 | | - $htmlOut .= $parser->recursiveTagParse( $input );
|
238 | | - }
|
239 | | - // Checks if the user has not voted yet and is logged in
|
240 | | - if ( !$userVoted && $wgUser->isLoggedIn() ) {
|
241 | | -
|
242 | | - /* Ajax Interaction */
|
243 | | -
|
244 | | - // Adds scale script
|
245 | | - $htmlOut .= Html::script(
|
246 | | - Js::callFunction(
|
247 | | - 'communityVoice.ratings.scales.add',
|
248 | | - Js::buildInstance(
|
249 | | - 'CommunityVoiceRatingsScale',
|
250 | | - array(
|
251 | | - Js::toScalar( $id ),
|
252 | | - Js::toScalar( $args['category'] ),
|
253 | | - Js::toScalar( $args['title'] ),
|
254 | | - Js::toScalar( $rating ),
|
255 | | - Js::toObject(
|
256 | | - array(
|
257 | | - 'stats' => $stats,
|
258 | | - 'status' => array(
|
259 | | - 'null' => ' ',
|
260 | | - 'sending' => CommunityVoice::getMessage(
|
261 | | - 'ratings', 'scale-status-sending'
|
262 | | - ),
|
263 | | - 'error' => CommunityVoice::getMessage(
|
264 | | - 'ratings', 'scale-status-error'
|
265 | | - ),
|
266 | | - 'thanks' => CommunityVoice::getMessage(
|
267 | | - 'ratings', 'scale-status-thanks'
|
268 | | - ),
|
269 | | - )
|
270 | | - )
|
271 | | - ),
|
272 | | - Js::toScalar( $wgTitle->getPrefixedText() )
|
273 | | - )
|
274 | | - )
|
275 | | - )
|
276 | | - );
|
277 | | -
|
278 | | - /* HTML Form Interaction */
|
279 | | -
|
280 | | - // Begins non-javascript fallback
|
281 | | - $htmlOut .= Html::open( 'noscript' );
|
282 | | - // Begins form
|
283 | | - $specialPageTitle = Title::newFromText( 'Special:CommunityVoice' );
|
284 | | - $htmlOut .= Html::open(
|
285 | | - 'form',
|
286 | | - array(
|
287 | | - 'action' => $specialPageTitle->getFullUrl(),
|
288 | | - 'method' => 'post',
|
289 | | - )
|
290 | | - );
|
291 | | - // Builds list of hidden fields
|
292 | | - $hiddenFields = array(
|
293 | | - 'token' => $wgUser->editToken(),
|
294 | | - 'module' => 'Ratings',
|
295 | | - 'action' => 'ScaleVoteSubmission',
|
296 | | - 'scale[article]' => $wgTitle->getPrefixedText(),
|
297 | | - 'scale[category]' => $args['category'],
|
298 | | - 'scale[title]' => $args['title'],
|
299 | | - );
|
300 | | - // Loops over each field
|
301 | | - foreach ( $hiddenFields as $name => $value ) {
|
302 | | - // Adds hidden field
|
303 | | - $htmlOut .= Html::input(
|
304 | | - array(
|
305 | | - 'type' => 'hidden', 'name' => $name, 'value' => $value
|
306 | | - )
|
307 | | - );
|
308 | | - }
|
309 | | - // Loops 5 times (once per star)
|
310 | | - for ( $i = 0; $i < 5; $i++ ) {
|
311 | | - // Adds star as image input
|
312 | | - $htmlOut .= Html::input(
|
313 | | - array(
|
314 | | - 'type' => 'image',
|
315 | | - 'name' => 'scale[rating_' . $i . ']',
|
316 | | - 'src' => sprintf(
|
317 | | - '%s/Icons/star-%d.png',
|
318 | | - $egCommunityVoiceResourcesPath,
|
319 | | - self::getScaleFraction( $rating, $i )
|
320 | | - ),
|
321 | | - 'border' => 0,
|
322 | | - 'alt' => '',
|
323 | | - 'class' => 'star',
|
324 | | - 'align' => 'absmiddle',
|
325 | | - )
|
326 | | - );
|
327 | | - }
|
328 | | - // Adds stats message
|
329 | | - $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats );
|
330 | | - // Ends form
|
331 | | - $htmlOut .= Html::close( 'form' );
|
332 | | - // Ends non-javascript fallback
|
333 | | - $htmlOut .= Html::close( 'noscript' );
|
334 | | - } else {
|
335 | | -
|
336 | | - /* No Interaction */
|
337 | | -
|
338 | | - // Loops 5 times (once per star)
|
339 | | - for ( $i = 0; $i < 5; $i++ ) {
|
340 | | - // Adds star as image
|
341 | | - $htmlOut .= Html::tag(
|
342 | | - 'img',
|
343 | | - array(
|
344 | | - 'src' => sprintf(
|
345 | | - '%s/Icons/star-%d.png',
|
346 | | - $egCommunityVoiceResourcesPath,
|
347 | | - self::getScaleFraction( $rating, $i )
|
348 | | - ),
|
349 | | - 'border' => 0,
|
350 | | - 'alt' => '',
|
351 | | - 'class' => 'star',
|
352 | | - 'align' => 'absmiddle',
|
353 | | - )
|
354 | | - );
|
355 | | - }
|
356 | | - // Adds stats message
|
357 | | - $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats );
|
358 | | - }
|
359 | | - // Ends scale
|
360 | | - $htmlOut .= Xml::closeElement( 'div' );
|
361 | | - // Returns output
|
362 | | - return $htmlOut;
|
363 | | - }
|
364 | | -
|
365 | | - /* Processing Functions */
|
366 | | -
|
367 | | - /**
|
368 | | - * Hanlder for ratings scale vote via ajax call
|
369 | | - */
|
370 | | - public static function handleScaleVoteCall( $category, $title, $rating, $article ) {
|
371 | | - global $wgUser;
|
372 | | - // Adds vote and checks for success
|
373 | | - if ( self::addVote( $category, $title, $rating ) ) {
|
374 | | - // Gets new rating data
|
375 | | - $rating = self::getAverageRating( $category, $title );
|
376 | | - // Builds result
|
377 | | - $result = array(
|
378 | | - 'rating' => $rating,
|
379 | | - 'stats' => CommunityVoice::getMessage(
|
380 | | - 'ratings',
|
381 | | - 'scale-stats',
|
382 | | - array(
|
383 | | - round( $rating, 1 ),
|
384 | | - self::getTotalVotes( $category, $title )
|
385 | | - )
|
386 | | - ),
|
387 | | - );
|
388 | | - // Gets articles that use this rating
|
389 | | - $articles = self::getArticlesUsing( $category, $title );
|
390 | | - // Loops over each article
|
391 | | - foreach ( $articles as $article ) {
|
392 | | - // Invalidates the cache of the article
|
393 | | - CommunityVoice::touchArticle( $article );
|
394 | | - }
|
395 | | - // Ensure database commits take place (since this is an ajax call)
|
396 | | - $dbw = wfGetDB( DB_MASTER );
|
397 | | - $dbw->commit();
|
398 | | - // Returns result
|
399 | | - return Js::toObject( $result );
|
400 | | - }
|
401 | | - // Returns error information
|
402 | | - return Js::toObject( array( 'rating' => - 1, 'stats' => null ) );
|
403 | | - }
|
404 | | -
|
405 | | - /**
|
406 | | - * Hanlder for ratings scale vote via HTML form submission
|
407 | | - */
|
408 | | - public static function handleScaleVoteSubmission() {
|
409 | | - global $wgOut, $wgRequest;
|
410 | | - // Gets scale data
|
411 | | - $scale = $wgRequest->getArray( 'scale' );
|
412 | | - // Checks if an article was given
|
413 | | - if ( isset( $scale['article'], $scale['title'], $scale['category'] ) ) {
|
414 | | - // Looks for rating value
|
415 | | - foreach ( $scale as $key => $value ) {
|
416 | | - // Breaks key into parts
|
417 | | - $parts = explode( '_', $key );
|
418 | | - // Checks if...
|
419 | | - if (
|
420 | | - // There's at least 2 parts
|
421 | | - ( count( $parts ) > 1 ) &&
|
422 | | - // The first part is 'rating'
|
423 | | - ( $parts[0] == 'rating' ) &&
|
424 | | - // The second part is a number
|
425 | | - ( is_numeric( $parts[1] ) )
|
426 | | - ) {
|
427 | | - // Uses number as rating
|
428 | | - $rating = $parts[1];
|
429 | | - // Finishes loop
|
430 | | - break;
|
431 | | - }
|
432 | | - }
|
433 | | - // Checks if a rating was found
|
434 | | - if ( isset( $rating ) ) {
|
435 | | - // Adds vote and checks for success
|
436 | | - if (
|
437 | | - self::addVote(
|
438 | | - $scale['category'], $scale['title'], $rating
|
439 | | - )
|
440 | | - ) {
|
441 | | - // Gets articles that use this rating
|
442 | | - $articles = self::getArticlesUsing(
|
443 | | - $scale['category'], $scale['title']
|
444 | | - );
|
445 | | - // Loops over each article
|
446 | | - foreach ( $articles as $article ) {
|
447 | | - // Invalidates the cache of the article
|
448 | | - CommunityVoice::touchArticle( $article );
|
449 | | - }
|
450 | | - // Redirects user back to article
|
451 | | - $wgOut->redirect(
|
452 | | - Title::newFromText( $scale['article'] )->getFullUrl()
|
453 | | - );
|
454 | | - } else {
|
455 | | - throw new MWException( 'Voting failed!' );
|
456 | | - }
|
457 | | - } else {
|
458 | | - throw new MWException( 'No rating parameter!' );
|
459 | | - }
|
460 | | - } else {
|
461 | | - throw new MWException( 'Missing parameters!' );
|
462 | | - }
|
463 | | - }
|
464 | | -
|
465 | | - /* UI Functions */
|
466 | | -
|
467 | | - /**
|
468 | | - * Outputs a summary UI for the module
|
469 | | - */
|
470 | | - public static function showSummary() {
|
471 | | - global $wgOut;
|
472 | | - //
|
473 | | - $wgOut->addWikiText( '==== Categories ====' );
|
474 | | - $xmlCategories = Html::open( 'ul' );
|
475 | | - foreach ( self::getCategories() as $category ) {
|
476 | | - $xmlCategories .= Html::tag( 'li', $category );
|
477 | | - }
|
478 | | - $xmlCategories .= Html::close( 'ul' );
|
479 | | - $wgOut->addHtml( $xmlCategories );
|
480 | | - }
|
481 | | -
|
482 | | - /**
|
483 | | - * Outputs main UI for module
|
484 | | - */
|
485 | | - public static function showMain( $path ) {
|
486 | | - global $wgOut;
|
487 | | - //
|
488 | | - $wgOut->addWikiText( '==== Detailed Information ====' );
|
489 | | - }
|
490 | | -}
|
| 2 | +<?php |
| 3 | + |
| 4 | +abstract class CommunityVoiceRatings { |
| 5 | + |
| 6 | + /* Private Static Functions */ |
| 7 | + |
| 8 | + private static function getScaleFraction( $rating, $star ) { |
| 9 | + if ( floor( $rating ) > $star ) { |
| 10 | + return 6; |
| 11 | + } else if ( floor( $rating ) < $star ) { |
| 12 | + return 0; |
| 13 | + } else { |
| 14 | + return round( ( 6 / 10 ) * ( ( $rating - floor( $rating ) ) * 10 ) ); |
| 15 | + } |
| 16 | + } |
| 17 | + |
| 18 | + private static function getArticlesUsing( $category, $title ) { |
| 19 | + $dbr = wfGetDB( DB_SLAVE ); |
| 20 | + $result = $dbr->select( |
| 21 | + 'cv_ratings_usage', |
| 22 | + 'usg_article', |
| 23 | + array( |
| 24 | + 'usg_category' => $category, |
| 25 | + 'usg_title' => $title, |
| 26 | + ) |
| 27 | + ); |
| 28 | + $articles = array(); |
| 29 | + while ( $row = $result->fetchRow() ) { |
| 30 | + $articles[] = (string)$row['usg_article']; |
| 31 | + } |
| 32 | + return $articles; |
| 33 | + } |
| 34 | + |
| 35 | + private static function getCategories() { |
| 36 | + $dbr = wfGetDB( DB_SLAVE ); |
| 37 | + $result = $dbr->select( |
| 38 | + 'cv_ratings_votes', |
| 39 | + 'DISTINCT vot_category' |
| 40 | + ); |
| 41 | + $categories = array(); |
| 42 | + while ( $row = $result->fetchRow() ) { |
| 43 | + $categories[] = (string)$row['vot_category']; |
| 44 | + } |
| 45 | + return $categories; |
| 46 | + } |
| 47 | + |
| 48 | + private static function getTitles( $category ) { |
| 49 | + $dbr = wfGetDB( DB_SLAVE ); |
| 50 | + $result = $dbr->select( |
| 51 | + 'cv_ratings_votes', |
| 52 | + 'DISTINCT vot_title', |
| 53 | + array( 'vot_category' => $category ) |
| 54 | + ); |
| 55 | + $titles = array(); |
| 56 | + while ( $row = $result->fetchRow() ) { |
| 57 | + $titles[] = (string)$row['vot_title']; |
| 58 | + } |
| 59 | + return $titles; |
| 60 | + } |
| 61 | + |
| 62 | + private static function getTotalVotes( $category, $title ) { |
| 63 | + $dbr = wfGetDB( DB_SLAVE ); |
| 64 | + return (integer)$dbr->selectField( |
| 65 | + 'cv_ratings_votes', |
| 66 | + 'COUNT(*)', |
| 67 | + array( |
| 68 | + 'vot_category' => $category, |
| 69 | + 'vot_title' => $title, |
| 70 | + ) |
| 71 | + ); |
| 72 | + } |
| 73 | + |
| 74 | + private static function getUserVoted( $category, $title ) { |
| 75 | + global $wgUser; |
| 76 | + $dbr = wfGetDB( DB_SLAVE ); |
| 77 | + return (bool)$dbr->selectField( |
| 78 | + 'cv_ratings_votes', |
| 79 | + 'COUNT(*)', |
| 80 | + array( |
| 81 | + 'vot_category' => $category, |
| 82 | + 'vot_title' => $title, |
| 83 | + 'vot_user' => $wgUser->getId(), |
| 84 | + ) |
| 85 | + ); |
| 86 | + } |
| 87 | + |
| 88 | + private static function getAverageRating( $category, $title ) { |
| 89 | + $dbr = wfGetDB( DB_SLAVE ); |
| 90 | + return (float)$dbr->selectField( |
| 91 | + 'cv_ratings_votes', |
| 92 | + 'AVG(vot_rating)', |
| 93 | + array( |
| 94 | + 'vot_category' => $category, |
| 95 | + 'vot_title' => $title, |
| 96 | + ) |
| 97 | + ); |
| 98 | + } |
| 99 | + |
| 100 | + private static function addVote( $category, $title, $rating ) { |
| 101 | + global $wgUser; |
| 102 | + // Checks if... |
| 103 | + if ( |
| 104 | + // User is logged in |
| 105 | + $wgUser->isLoggedIn() && |
| 106 | + // User has not yet voted |
| 107 | + !self::getUserVoted( $category, $title ) |
| 108 | + ) { |
| 109 | + // Get database connection |
| 110 | + $dbw = wfGetDB( DB_MASTER ); |
| 111 | + // Insert new vote for user |
| 112 | + $dbw->insert( |
| 113 | + 'cv_ratings_votes', |
| 114 | + array( |
| 115 | + 'vot_category' => $category, |
| 116 | + 'vot_title' => $title, |
| 117 | + 'vot_user' => $wgUser->getId(), |
| 118 | + 'vot_rating' => $rating, |
| 119 | + ) |
| 120 | + ); |
| 121 | + $dbw->commit(); |
| 122 | + return true; |
| 123 | + } |
| 124 | + return false; |
| 125 | + } |
| 126 | + |
| 127 | + /* Static Functions */ |
| 128 | + |
| 129 | + public static function register() { |
| 130 | + global $wgParser, $wgAjaxExportList, $wgHooks; |
| 131 | + // Register the hook with the parser |
| 132 | + $wgParser->setHook( 'ratings:scale', array( __CLASS__, 'renderScale' ) ); |
| 133 | + // Register ajax response hook |
| 134 | + $wgAjaxExportList[] = __CLASS__ . '::handleScaleVoteCall'; |
| 135 | + // Register article save hook |
| 136 | + $wgHooks['ArticleSave'][] = __CLASS__ . '::updateArticleUsage'; |
| 137 | + |
| 138 | + } |
| 139 | + |
| 140 | + public static function updateArticleUsage( $article, $user, $text, $summary, $minor, $watch, $sectionanchor, $flags ) { |
| 141 | + $usedRatings = array(); |
| 142 | + // Extract all ratings:scale tags |
| 143 | + preg_match_all( |
| 144 | + "/<ratings:scale[^>]*[\\/]*>/i", $text, $matches, PREG_PATTERN_ORDER |
| 145 | + ); |
| 146 | + // Loop over each match |
| 147 | + foreach ( $matches[0] as $match ) { |
| 148 | + $rating = array(); |
| 149 | + foreach ( array( 'category', 'title' ) as $attribute ) { |
| 150 | + // Extract value of attribute |
| 151 | + preg_match( |
| 152 | + "/{$attribute}=['\"]*(?<value>[^'\"]*)['\"]*/i", |
| 153 | + $match, |
| 154 | + $values |
| 155 | + ); |
| 156 | + if ( isset( $values['value'] ) ) { |
| 157 | + $rating[$attribute] = $values['value']; |
| 158 | + } |
| 159 | + } |
| 160 | + if ( isset( $rating['category'], $rating['title'] ) ) { |
| 161 | + $usedRatings[] = $rating; |
| 162 | + } |
| 163 | + } |
| 164 | + // Gets name of article |
| 165 | + $articleDbKey = $article->getTitle()->getPrefixedDBkey(); |
| 166 | + // Get database connection |
| 167 | + $dbw = wfGetDB( DB_MASTER ); |
| 168 | + // Remove all usage for this article |
| 169 | + $dbw->delete( |
| 170 | + 'cv_ratings_usage', |
| 171 | + array( 'usg_article' => $articleDbKey ) |
| 172 | + ); |
| 173 | + // Loop over each rating |
| 174 | + foreach ( $usedRatings as $rating ) { |
| 175 | + // Add usage for rating for this article |
| 176 | + $dbw->insert( |
| 177 | + 'cv_ratings_usage', |
| 178 | + array( |
| 179 | + 'usg_category' => $rating['category'], |
| 180 | + 'usg_title' => $rating['title'], |
| 181 | + 'usg_article' => $articleDbKey, |
| 182 | + ) |
| 183 | + ); |
| 184 | + } |
| 185 | + return true; |
| 186 | + } |
| 187 | + |
| 188 | + public static function renderScale( $input, $args, $parser ) { |
| 189 | + global $wgUser, $wgTitle; |
| 190 | + global $egCommunityVoiceResourcesPath; |
| 191 | + // Validate and sanitize incoming arguments |
| 192 | + $errors = array(); |
| 193 | + $error = false; |
| 194 | + foreach ( array( 'category', 'title' ) as $argument ) { |
| 195 | + if ( isset( $args[$argument] ) ) { |
| 196 | + $args[$argument] = htmlspecialchars( $args[$argument] ); |
| 197 | + } else { |
| 198 | + $error = true; |
| 199 | + if ( $parser->getOptions()->getIsPreview() ) { |
| 200 | + $errors[] = CommunityVoice::getMessage( |
| 201 | + 'ratings', 'error-missing-argument', $argument |
| 202 | + ); |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + // Checks if an error ocurred |
| 207 | + if ( $error ) { |
| 208 | + // Checks if there are any error messages to return |
| 209 | + if ( count( $errors ) ) { |
| 210 | + return Html::div( |
| 211 | + array( 'class' => 'error' ), implode( ' ', $errors ) |
| 212 | + ); |
| 213 | + } |
| 214 | + // Continues without rendering |
| 215 | + return true; |
| 216 | + } |
| 217 | + // Collects data |
| 218 | + $totalVotes = self::getTotalVotes( $args['category'], $args['title'] ); |
| 219 | + $rating = self::getAverageRating( $args['category'], $args['title'] ); |
| 220 | + $userVoted = self::getUserVoted( $args['category'], $args['title'] ); |
| 221 | + // Builds sanitized HTML id with prepended module naming |
| 222 | + $id = Html::toId( |
| 223 | + 'cv_ratings_scale_' . $args['category'] . '_' . $args['title'] |
| 224 | + ); |
| 225 | + // Gets stats message |
| 226 | + $stats = CommunityVoice::getMessage( |
| 227 | + 'ratings', 'scale-stats', array( round( $rating, 1 ), $totalVotes ) |
| 228 | + ); |
| 229 | + // Begins rating scale |
| 230 | + $htmlOut = Html::open( |
| 231 | + 'div', |
| 232 | + array( 'class' => 'communityvoice-ratings-scale', 'id' => $id ) |
| 233 | + ); |
| 234 | + // Checks for input |
| 235 | + if ( $input != '' ) { |
| 236 | + // Adds content of tag as parsed wiki-text |
| 237 | + $htmlOut .= $parser->recursiveTagParse( $input ); |
| 238 | + } |
| 239 | + // Checks if the user has not voted yet and is logged in |
| 240 | + if ( !$userVoted && $wgUser->isLoggedIn() ) { |
| 241 | + |
| 242 | + /* Ajax Interaction */ |
| 243 | + |
| 244 | + // Adds scale script |
| 245 | + $htmlOut .= Html::script( |
| 246 | + Js::callFunction( |
| 247 | + 'communityVoice.ratings.scales.add', |
| 248 | + Js::buildInstance( |
| 249 | + 'CommunityVoiceRatingsScale', |
| 250 | + array( |
| 251 | + Js::toScalar( $id ), |
| 252 | + Js::toScalar( $args['category'] ), |
| 253 | + Js::toScalar( $args['title'] ), |
| 254 | + Js::toScalar( $rating ), |
| 255 | + Js::toObject( |
| 256 | + array( |
| 257 | + 'stats' => $stats, |
| 258 | + 'status' => array( |
| 259 | + 'null' => ' ', |
| 260 | + 'sending' => CommunityVoice::getMessage( |
| 261 | + 'ratings', 'scale-status-sending' |
| 262 | + ), |
| 263 | + 'error' => CommunityVoice::getMessage( |
| 264 | + 'ratings', 'scale-status-error' |
| 265 | + ), |
| 266 | + 'thanks' => CommunityVoice::getMessage( |
| 267 | + 'ratings', 'scale-status-thanks' |
| 268 | + ), |
| 269 | + ) |
| 270 | + ) |
| 271 | + ), |
| 272 | + Js::toScalar( $wgTitle->getPrefixedText() ) |
| 273 | + ) |
| 274 | + ) |
| 275 | + ) |
| 276 | + ); |
| 277 | + |
| 278 | + /* HTML Form Interaction */ |
| 279 | + |
| 280 | + // Begins non-javascript fallback |
| 281 | + $htmlOut .= Html::open( 'noscript' ); |
| 282 | + // Begins form |
| 283 | + $specialPageTitle = Title::newFromText( 'Special:CommunityVoice' ); |
| 284 | + $htmlOut .= Html::open( |
| 285 | + 'form', |
| 286 | + array( |
| 287 | + 'action' => $specialPageTitle->getFullUrl(), |
| 288 | + 'method' => 'post', |
| 289 | + ) |
| 290 | + ); |
| 291 | + // Builds list of hidden fields |
| 292 | + $hiddenFields = array( |
| 293 | + 'token' => $wgUser->editToken(), |
| 294 | + 'module' => 'Ratings', |
| 295 | + 'action' => 'ScaleVoteSubmission', |
| 296 | + 'scale[article]' => $wgTitle->getPrefixedText(), |
| 297 | + 'scale[category]' => $args['category'], |
| 298 | + 'scale[title]' => $args['title'], |
| 299 | + ); |
| 300 | + // Loops over each field |
| 301 | + foreach ( $hiddenFields as $name => $value ) { |
| 302 | + // Adds hidden field |
| 303 | + $htmlOut .= Html::input( |
| 304 | + array( |
| 305 | + 'type' => 'hidden', 'name' => $name, 'value' => $value |
| 306 | + ) |
| 307 | + ); |
| 308 | + } |
| 309 | + // Loops 5 times (once per star) |
| 310 | + for ( $i = 0; $i < 5; $i++ ) { |
| 311 | + // Adds star as image input |
| 312 | + $htmlOut .= Html::input( |
| 313 | + array( |
| 314 | + 'type' => 'image', |
| 315 | + 'name' => 'scale[rating_' . $i . ']', |
| 316 | + 'src' => sprintf( |
| 317 | + '%s/Icons/star-%d.png', |
| 318 | + $egCommunityVoiceResourcesPath, |
| 319 | + self::getScaleFraction( $rating, $i ) |
| 320 | + ), |
| 321 | + 'border' => 0, |
| 322 | + 'alt' => '', |
| 323 | + 'class' => 'star', |
| 324 | + 'align' => 'absmiddle', |
| 325 | + ) |
| 326 | + ); |
| 327 | + } |
| 328 | + // Adds stats message |
| 329 | + $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats ); |
| 330 | + // Ends form |
| 331 | + $htmlOut .= Html::close( 'form' ); |
| 332 | + // Ends non-javascript fallback |
| 333 | + $htmlOut .= Html::close( 'noscript' ); |
| 334 | + } else { |
| 335 | + |
| 336 | + /* No Interaction */ |
| 337 | + |
| 338 | + // Loops 5 times (once per star) |
| 339 | + for ( $i = 0; $i < 5; $i++ ) { |
| 340 | + // Adds star as image |
| 341 | + $htmlOut .= Html::tag( |
| 342 | + 'img', |
| 343 | + array( |
| 344 | + 'src' => sprintf( |
| 345 | + '%s/Icons/star-%d.png', |
| 346 | + $egCommunityVoiceResourcesPath, |
| 347 | + self::getScaleFraction( $rating, $i ) |
| 348 | + ), |
| 349 | + 'border' => 0, |
| 350 | + 'alt' => '', |
| 351 | + 'class' => 'star', |
| 352 | + 'align' => 'absmiddle', |
| 353 | + ) |
| 354 | + ); |
| 355 | + } |
| 356 | + // Adds stats message |
| 357 | + $htmlOut .= Html::tag( 'span', array( 'class' => 'stats' ), $stats ); |
| 358 | + } |
| 359 | + // Ends scale |
| 360 | + $htmlOut .= Xml::closeElement( 'div' ); |
| 361 | + // Returns output |
| 362 | + return $htmlOut; |
| 363 | + } |
| 364 | + |
| 365 | + /* Processing Functions */ |
| 366 | + |
| 367 | + /** |
| 368 | + * Hanlder for ratings scale vote via ajax call |
| 369 | + */ |
| 370 | + public static function handleScaleVoteCall( $category, $title, $rating, $article ) { |
| 371 | + global $wgUser; |
| 372 | + // Adds vote and checks for success |
| 373 | + if ( self::addVote( $category, $title, $rating ) ) { |
| 374 | + // Gets new rating data |
| 375 | + $rating = self::getAverageRating( $category, $title ); |
| 376 | + // Builds result |
| 377 | + $result = array( |
| 378 | + 'rating' => $rating, |
| 379 | + 'stats' => CommunityVoice::getMessage( |
| 380 | + 'ratings', |
| 381 | + 'scale-stats', |
| 382 | + array( |
| 383 | + round( $rating, 1 ), |
| 384 | + self::getTotalVotes( $category, $title ) |
| 385 | + ) |
| 386 | + ), |
| 387 | + ); |
| 388 | + // Gets articles that use this rating |
| 389 | + $articles = self::getArticlesUsing( $category, $title ); |
| 390 | + // Loops over each article |
| 391 | + foreach ( $articles as $article ) { |
| 392 | + // Invalidates the cache of the article |
| 393 | + CommunityVoice::touchArticle( $article ); |
| 394 | + } |
| 395 | + // Ensure database commits take place (since this is an ajax call) |
| 396 | + $dbw = wfGetDB( DB_MASTER ); |
| 397 | + $dbw->commit(); |
| 398 | + // Returns result |
| 399 | + return Js::toObject( $result ); |
| 400 | + } |
| 401 | + // Returns error information |
| 402 | + return Js::toObject( array( 'rating' => - 1, 'stats' => null ) ); |
| 403 | + } |
| 404 | + |
| 405 | + /** |
| 406 | + * Hanlder for ratings scale vote via HTML form submission |
| 407 | + */ |
| 408 | + public static function handleScaleVoteSubmission() { |
| 409 | + global $wgOut, $wgRequest; |
| 410 | + // Gets scale data |
| 411 | + $scale = $wgRequest->getArray( 'scale' ); |
| 412 | + // Checks if an article was given |
| 413 | + if ( isset( $scale['article'], $scale['title'], $scale['category'] ) ) { |
| 414 | + // Looks for rating value |
| 415 | + foreach ( $scale as $key => $value ) { |
| 416 | + // Breaks key into parts |
| 417 | + $parts = explode( '_', $key ); |
| 418 | + // Checks if... |
| 419 | + if ( |
| 420 | + // There's at least 2 parts |
| 421 | + ( count( $parts ) > 1 ) && |
| 422 | + // The first part is 'rating' |
| 423 | + ( $parts[0] == 'rating' ) && |
| 424 | + // The second part is a number |
| 425 | + ( is_numeric( $parts[1] ) ) |
| 426 | + ) { |
| 427 | + // Uses number as rating |
| 428 | + $rating = $parts[1]; |
| 429 | + // Finishes loop |
| 430 | + break; |
| 431 | + } |
| 432 | + } |
| 433 | + // Checks if a rating was found |
| 434 | + if ( isset( $rating ) ) { |
| 435 | + // Adds vote and checks for success |
| 436 | + if ( |
| 437 | + self::addVote( |
| 438 | + $scale['category'], $scale['title'], $rating |
| 439 | + ) |
| 440 | + ) { |
| 441 | + // Gets articles that use this rating |
| 442 | + $articles = self::getArticlesUsing( |
| 443 | + $scale['category'], $scale['title'] |
| 444 | + ); |
| 445 | + // Loops over each article |
| 446 | + foreach ( $articles as $article ) { |
| 447 | + // Invalidates the cache of the article |
| 448 | + CommunityVoice::touchArticle( $article ); |
| 449 | + } |
| 450 | + // Redirects user back to article |
| 451 | + $wgOut->redirect( |
| 452 | + Title::newFromText( $scale['article'] )->getFullUrl() |
| 453 | + ); |
| 454 | + } else { |
| 455 | + throw new MWException( 'Voting failed!' ); |
| 456 | + } |
| 457 | + } else { |
| 458 | + throw new MWException( 'No rating parameter!' ); |
| 459 | + } |
| 460 | + } else { |
| 461 | + throw new MWException( 'Missing parameters!' ); |
| 462 | + } |
| 463 | + } |
| 464 | + |
| 465 | + /* UI Functions */ |
| 466 | + |
| 467 | + /** |
| 468 | + * Outputs a summary UI for the module |
| 469 | + */ |
| 470 | + public static function showSummary() { |
| 471 | + global $wgOut; |
| 472 | + // |
| 473 | + $wgOut->addWikiText( '==== Categories ====' ); |
| 474 | + $xmlCategories = Html::open( 'ul' ); |
| 475 | + foreach ( self::getCategories() as $category ) { |
| 476 | + $xmlCategories .= Html::tag( 'li', $category ); |
| 477 | + } |
| 478 | + $xmlCategories .= Html::close( 'ul' ); |
| 479 | + $wgOut->addHtml( $xmlCategories ); |
| 480 | + } |
| 481 | + |
| 482 | + /** |
| 483 | + * Outputs main UI for module |
| 484 | + */ |
| 485 | + public static function showMain( $path ) { |
| 486 | + global $wgOut; |
| 487 | + // |
| 488 | + $wgOut->addWikiText( '==== Detailed Information ====' ); |
| 489 | + } |
| 490 | +} |
Property changes on: trunk/extensions/CommunityVoice/Modules/Ratings.php |
___________________________________________________________________ |
Name: svn:eol-style |
491 | 491 | + native |
Property changes on: trunk/extensions/ParserFunctions/exprTests.txt |
___________________________________________________________________ |
Name: svn:eol-style |
492 | 492 | + native |
Property changes on: trunk/extensions/ParserFunctions/testExpr.php |
___________________________________________________________________ |
Name: svn:eol-style |
493 | 493 | + native |
Property changes on: trunk/extensions/MetavidWiki/skins/mv_embed/libAddMedia/searchLibs/solrArchiveSearch.js |
___________________________________________________________________ |
Name: svn:eol-style |
494 | 494 | + native |
Property changes on: trunk/extensions/MetavidWiki/skins/mv_embed/libAddMedia/searchLibs/mediaWikiSearch.js |
___________________________________________________________________ |
Name: svn:eol-style |
495 | 495 | + native |
Property changes on: trunk/extensions/MetavidWiki/skins/mv_embed/libAddMedia/searchLibs/metavidSearch.js |
___________________________________________________________________ |
Name: svn:eol-style |
496 | 496 | + native |
Property changes on: trunk/extensions/MetavidWiki/skins/mv_embed/libAddMedia/seqRemoteSearchDriver.js |
___________________________________________________________________ |
Name: svn:eol-style |
497 | 497 | + native |
Property changes on: trunk/extensions/ClientSide/ClientSide.php |
___________________________________________________________________ |
Name: svn:eol-style |
498 | 498 | + native |
Property changes on: trunk/extensions/DeleteQueue/Views/DeleteQueueView.template.php |
___________________________________________________________________ |
Name: svn:eol-style |
499 | 499 | + native |
Property changes on: trunk/extensions/DeleteQueue/Views/DeleteQueueViewNominate.php |
___________________________________________________________________ |
Name: svn:eol-style |
500 | 500 | + native |
Property changes on: trunk/extensions/DeleteQueue/Views/DeleteQueueViewCase.php |
___________________________________________________________________ |
Name: svn:eol-style |
501 | 501 | + native |
Property changes on: trunk/extensions/DeleteQueue/Views/DeleteQueueViewVote.php |
___________________________________________________________________ |
Name: svn:eol-style |
502 | 502 | + native |
Property changes on: trunk/extensions/DeleteQueue/Views/DeleteQueueView.php |
___________________________________________________________________ |
Name: svn:eol-style |
503 | 503 | + native |
Property changes on: trunk/extensions/DeleteQueue/deletequeue.css |
___________________________________________________________________ |
Name: svn:eol-style |
504 | 504 | + native |