/** * Get tag dropdown select * @param int $selected, selected level * @return string */ public static function getTagMenu( $selected = '' ) { $s = "<label for='wpRatingTag'>" . wfMsgHtml('readerfeedback-tagfilter') . "</label> "; $s .= Xml::openElement( 'select', array('name' => 'ratingtag', 'id' => 'wpRatingTag') ); foreach( ReaderFeedback::getFeedbackTags() as $tag => $weight ) { $s .= Xml::option( wfMsg( "readerfeedback-$tag" ), $tag, $selected===$tag ); } $s .= Xml::closeElement('select')."\n"; return $s; }
protected function showPageList() { global $wgOut; $tags = ReaderFeedback::getFeedbackTags(); $pager = new RatedPagesPager( $this, array(), $this->namespace, $this->tag, $this->tier ); if( isset($tags[$this->tag]) && $pager->getNumRows() ) { $wgOut->addHTML( wfMsgExt('ratedpages-list', array('parse') ) ); $wgOut->addHTML( $pager->getNavigationBar() ); $wgOut->addHTML( $pager->getBody() ); $wgOut->addHTML( $pager->getNavigationBar() ); } elseif( $this->tag ) { // must select first... $wgOut->addHTML( wfMsgExt('ratedpages-none', array('parse') ) ); } }
/** * Get a table of the vote totals for a page * @param Title $page * @param int $period, number of days back * @param array $add, optional vote to add on (used to visually avoid lag) * @param string $cache, optional param to not use cache * @return string HTML table */ public static function getVoteAggregates( $page, $period, $add = array(), $cache = 'useCache' ) { global $wgLang, $wgMemc; $votes = null; $now = time(); $key = wfMemcKey( 'feedback', 'ratingtally', $page->getArticleId(), $period ); // Check cache if( $cache == 'useCache' ) { $set = $wgMemc->get($key); // Cutoff is at the 24 hour mark due to the way the aggregate // schema groups ratings by date for graphs. $cache_cutoff = $now - ($now % 86400); if( is_array($set) && count($set) == 2 ) { list($val,$time) = $set; $touched = wfTimestamp( TS_UNIX, RatingHistory::getTouched($page) ); if( $time > $cache_cutoff && $time > $touched ) { $votes = $val; } } } // Do query, cache miss if( !isset($votes) ) { // Set cutoff time for period $dbr = wfGetDB( DB_SLAVE ); $cutoff_unixtime = $now - ($period * 24 * 3600); // Use integral number of days to be consistent with graphs $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); $cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) ); // Get the first revision possibly voted on in the range $firstRevTS = $dbr->selectField( 'revision', 'rev_timestamp', array( 'rev_page' => $page->getArticleId(), "rev_timestamp <= $cutoff" ), __METHOD__, array( 'ORDER BY' => 'rev_timestamp DESC' ) ); // Find average, median... $res = $dbr->select( array( 'revision', 'reader_feedback' ), array( 'rfb_ratings' ), array( 'rev_page' => $page->getArticleId(), "rev_id = rfb_rev_id", "rfb_timestamp >= $cutoff", // Trigger INDEX usage "rev_timestamp >= ".$dbr->addQuotes($firstRevTS) ), __METHOD__, array( 'USE INDEX' => array('revision' => 'page_timestamp') ) ); $votes = array(); foreach( ReaderFeedback::getFeedbackTags() as $tag => $w ) { $votes[$tag] = array( 0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0 ); } // Read votes and tally the numbers foreach ( $res as $row ) { $dims = ReaderFeedback::expandRatings( $row->rfb_ratings ); foreach( $dims as $tag => $val ) { if( isset($votes[$tag]) && isset($votes[$tag][$val]) ) { $votes[$tag][$val]++; } } } // Tack on $add for display (used to avoid cache/lag) foreach( $add as $tag => $val ) { if( isset($votes[$tag]) && isset($votes[$tag][$val]) ) { $votes[$tag][$val]++; } } $wgMemc->set( $key, array( $votes, $now ), 24*3600 ); } // Output multi-column list $html = "<table class='rfb-reader_feedback_table' cellspacing='0'><tr>"; foreach( ReaderFeedback::getFeedbackTags() as $tag => $w ) { // Get tag average... $dist = isset($votes[$tag]) ? $votes[$tag] : array(); $count = array_sum($dist); if( $count ) { $ave = ($dist[0] + 2*$dist[1] + 3*$dist[2] + 4*$dist[3] + 5*$dist[4])/$count; $ave = round($ave,1); } else { $ave = '-'; // DIV by zero } $html .= '<td align="center"><b>'.wfMsgHtml("readerfeedback-$tag").'</b>  '. '<sup>('.wfMsgHtml('ratinghistory-ave',$wgLang->formatNum($ave)).')</sup></td>'; } $html .= '</tr><tr>'; foreach( $votes as $dist ) { $html .= '<td><table>'; $html .= '<tr><th align="left">'.wfMsgHtml('ratinghistory-table-rating').'</th>'; for( $i = 1; $i <= 5; $i++ ) { $html .= "<td align='center' class='rfb-rating-option-".($i-1)."'>$i</td>"; } $html .= '</tr><tr>'; $html .= '<th align="left">'.wfMsgHtml("ratinghistory-table-votes").'</th>'; $html .= '<td align="center">'.$dist[0].'</td>'; $html .= '<td align="center">'.$dist[1].'</td>'; $html .= '<td align="center">'.$dist[2].'</td>'; $html .= '<td align="center">'.$dist[3].'</td>'; $html .= '<td align="center">'.$dist[4].'</td>'; $html .= "</tr></table></td>\n"; } $html .= '</tr></table>'; return $html; }
/** * Adds a brief feedback form to a page. * @param OutputPage $out * @param bool $top, should this form always go on top? * @param Title $title */ protected static function addQuickFeedback( &$data, $top = false, $title ) { global $wgOut, $wgUser, $wgFeedbackTags; # Are there any reader input tags? if( empty($wgFeedbackTags) ) { return false; } # Revision being displayed $id = $wgOut->getRevisionId(); # Load required messages $reviewTitle = SpecialPage::getTitleFor( 'ReaderFeedback' ); $action = $reviewTitle->getLocalUrl( 'action=submit' ); $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-feedbackform' ) ); $form .= Xml::openElement( 'fieldset', array('class' => 'feedback_reviewform noprint') ); $form .= "<legend><strong>" . wfMsgHtml( 'readerfeedback' ) . "</strong></legend>\n"; # Avoid clutter if( !$wgUser->isAllowed('review') ) { $form .= wfMsgExt( 'readerfeedback-text', array('parse') ); } $form .= Xml::openElement( 'span', array('id' => 'mw-feedbackselects') ); # Loop through all different flag types foreach( ReaderFeedback::getFeedbackTags() as $quality => $levels ) { $selected = ( isset($flags[$quality]) && $flags[$quality] > 0 ) ? $flags[$quality] : -1; $form .= "<b>" . Xml::label( wfMsgHtml("readerfeedback-$quality"), "wp$quality" ) . ":</b>"; $attribs = array( 'name' => "wp$quality", 'id' => "wp$quality", 'onchange' => "updateFeedbackForm()" ); $form .= ' ' . Xml::openElement( 'select', $attribs ); $levels = array_reverse($levels,true); foreach( $levels as $i => $name ) { $optionClass = array( 'class' => "rfb-rating-option-$i" ); $form .= Xml::option( wfMsg("readerfeedback-level-$i"), $i, ($i == $selected), $optionClass ) ."\n"; } $form .= Xml::option( wfMsg("readerfeedback-level-none"), -1, (-1 == $selected) ) ."\n"; $form .= Xml::closeElement( 'select' )."\n"; } $form .= Xml::closeElement( 'span' ); $form .= Xml::submitButton( wfMsg('readerfeedback-submit'), array('id' => 'submitfeedback','accesskey' => wfMsg('readerfeedback-ak-review'), 'title' => wfMsg('readerfeedback-tt-review').' ['.wfMsg('readerfeedback-ak-review').']' ) ); # Hidden params $form .= Html::hidden( 'title', $reviewTitle->getPrefixedText() ) . "\n"; $form .= Html::hidden( 'target', $title->getPrefixedDBKey() ) . "\n"; $form .= Html::hidden( 'oldid', $id ) . "\n"; $form .= Html::hidden( 'validatedParams', ReaderFeedbackPage::validationKey( $id, $wgUser->getId() ) ); $form .= Html::hidden( 'action', 'submit') . "\n"; $form .= Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n"; # Honeypot input $form .= Xml::input( 'commentary', 12, '', array('style' => 'display:none;') ) . "\n"; $form .= Xml::closeElement( 'fieldset' ); $form .= Xml::closeElement( 'form' ); if( $top ) { $wgOut->prependHTML( $form ); } else { $data .= $form; } return true; }
public static function AjaxReview( /*$args...*/ ) { global $wgUser; $args = func_get_args(); // Basic permission check if( $wgUser->isAllowed( 'feedback' ) ) { if( $wgUser->isBlocked() ) { return '<err#><h2>' . wfMsgHtml('blockedtitle') . '</h2>' . wfMsg('badaccess-group0'); } } else { return '<err#><strong>' . wfMsg('badaccess-group0') . '</<strong>'; } if( wfReadOnly() ) { return '<err#><strong>' . wfMsg('formerror') . '</<strong>'; } $tags = ReaderFeedback::getFeedbackTags(); // Make review interface object $form = new ReaderFeedbackPage(); $form->dims = array(); $unsureCount = 0; $bot = false; // Each ajax url argument is of the form param|val. // This means that there is no ugly order dependance. foreach( $args as $arg ) { $set = explode('|',$arg,2); if( count($set) != 2 ) { return '<err#>' . wfMsg('formerror'); } list($par,$val) = $set; switch( $par ) { case "target": $form->page = Title::newFromURL( $val ); if( is_null($form->page) || !ReaderFeedback::isPageRateable( $form->page ) ) { return '<err#>' . wfMsg('formerror'); } break; case "oldid": $form->oldid = intval( $val ); if( !$form->oldid ) { return '<err#>' . wfMsg('formerror'); } break; case "validatedParams": $form->validatedParams = $val; break; case "wpEditToken": if( !$wgUser->matchEditToken( $val ) ) { return '<err#>' . wfMsg('formerror'); } break; case "commentary": // honeypot value if( $val ) $bot = true; break; default: $p = preg_replace( '/^wp/', '', $par ); // kill any "wp" prefix if( array_key_exists( $p, $tags ) ) { $form->dims[$p] = intval($val); if( $form->dims[$p] === null ) { // nothing sent at all :( return '<err#>' . wfMsg('formerror'); // bad range } elseif( $form->dims[$p] === -1 ) { $unsureCount++; } } break; } } // Missing params? if( count($form->dims) != count($tags) || $unsureCount >= count($form->dims) ) { return '<err#>' . wfMsg('formerror'); } // Doesn't match up? if( $form->validatedParams != self::validationKey( $form->oldid, $wgUser->getId() ) ) { return '<err#>' . wfMsg('formerror'); } $rhist = SpecialPage::getTitleFor( 'RatingHistory' ); $graphLink = $rhist->getFullUrl( 'target='.$form->page->getPrefixedUrl() ); $talk = $form->page->getTalkPage(); $tallyTable = ReaderFeedback::getVoteAggregates( $form->page, 31, $form->dims ); $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); if( $bot ) { $ok = self::REVIEW_ERROR; // don't submit for mindless drones } else { $ok = $form->submit(); } $dbw->commit(); switch( $ok ) { case self::REVIEW_OK: return '<suc#>' . "<div class='plainlinks'>" . wfMsgExt( 'readerfeedback-success', array('parseinline'), $form->page->getPrefixedText(), $graphLink, $talk->getFullUrl( 'action=edit§ion=new' ) ) . '<h4>'.wfMsgHtml('ratinghistory-table')."</h4>\n$tallyTable" . "</div>"; case self::REVIEW_DUP: return '<err#>' . "<div class='plainlinks'>" . wfMsgExt( 'readerfeedback-voted', array('parseinline'), $form->page->getPrefixedText(), $graphLink, $talk->getFullUrl( 'action=edit§ion=new' ) ) . "</div>"; default: return '<err#>' . "<div class='plainlinks'>" . wfMsgExt( 'readerfeedback-error', array('parseinline'), $form->page->getPrefixedText(), $graphLink, $talk->getFullUrl( 'action=edit§ion=new' ) ) . "</div>"; } }
public function purgePage() { global $wgUploadDirectory; foreach( ReaderFeedback::getFeedbackTags() as $tag => $weight ) { $dir = "{$wgUploadDirectory}/graphs/".$this->page->getArticleId()."/{$tag}/"; if( is_dir( $dir ) ) { $handle = opendir( $dir ); if( $handle ) { while( false !== ( $file = readdir($handle) ) ) { @unlink("$dir/$file"); } closedir( $handle ); } } @rmdir( $dir ); } return true; }