public function execute( $par ) {
		global $wgRequest, $wgUser, $wgOut;
		$this->setHeaders();
		if( $wgUser->isAllowed( 'feedback' ) ) {
			if( $wgUser->isBlocked() ) {
				$wgOut->blockedPage();
				return;
			}
		} else {
			$wgOut->permissionRequired( 'feedback' );
			return;
		}
		if( wfReadOnly() ) {
			$wgOut->readOnlyPage();
			return;
		}
		$this->skin = $wgUser->getSkin();
		$this->doPurge = ('purge' === $wgRequest->getVal( 'action' ) && $wgUser->isAllowed('purge'));
		# Our target page
		$this->target = $wgRequest->getText( 'target' );
		$this->page = Title::newFromURL( $this->target );
		# We need a page...
		if( is_null($this->page) ) {
			$wgOut->showErrorPage( 'notargettitle', 'notargettext' );
			return;
		} elseif( !ReaderFeedback::isPageRateable( $this->page ) ) {
			$wgOut->addHTML( wfMsgExt('readerfeedback-main',array('parse')) );
			return;
		}
		# Thank people who voted...
		if( ReaderFeedbackPage::userAlreadyVoted( $this->page ) ) {
			$wgOut->setSubtitle( wfMsgExt('ratinghistory-thanks','parseinline') );
		}
		$period = $wgRequest->getInt( 'period' );
		$validPeriods = array(31,93,365,1095);
		if( !in_array($period,$validPeriods) ) {
			$period = 31; // default
		}
		$this->period = $period;
		$this->dScale = 20;

		$this->now = time(); // one time for whole request

		$this->showForm();
		$this->showTable();
		/*
		 * Allow client caching.
		 */
		if( !$this->doPurge && $wgOut->checkLastModified( self::getTouched($this->page) ) ) {
			return; // Client cache fresh and headers sent, nothing more to do
		} else {
			$wgOut->enableClientCache( false ); // don't show stale graphs
			if( $this->doPurge ) $this->purgePage();
		}
		$this->showGraphs();
	}
	 /**
	 * 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 .= '&#160;' . 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&section=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&section=new' ) ) .
					"</div>";
			default:
				return '<err#>' .
					"<div class='plainlinks'>" .
						wfMsgExt( 'readerfeedback-error', array('parseinline'), 
							$form->page->getPrefixedText(), $graphLink,
							$talk->getFullUrl( 'action=edit&section=new' ) ) .
					"</div>";
		}
	}