Jamesmontalvo3 has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/161169

Change subject: Add file approval and fine-tuned permissions; bump to v1.0
......................................................................

Add file approval and fine-tuned permissions; bump to v1.0

This commit drastically changes the functionality of Approved Revs.
It is now possible to define specifically who can approve certain
namespaces, categories and pages. Additionally, it is possible to
define the approver as the person who created the page, the user
who "owns" the page (in the User namespace), and if Semantic
MediaWiki is installed, by properties set in pages.

Additionally, it is now possible to approve files. This affects
which version of the file is shown in the page.

ApprovedRevs.hooks.php:
* Added several hooks to handle file approvals
* Fixed cache purge issue which wasn't clearing history page cache on approval
  * This causes a bug in SMW 1.9.0 only. Not in 1.9.1+
* Made ApprovedRevs_Files.sql get called in update.php

ApprovedRevs_body.php:
* significant changes to entire file, including:
  * modified pageIsApprovable() to use new $egApprovedRevsPermissions
  * added mediaIsApprovable() to handle files
  * modified userCanApproved() to use new $egApprovedRevsPermissions
  * Added several methods

specials/
* Moved SpecialApprovedRevs.php to specials/SpecialApprovedPages.php
* QueryPage broken out into specials/SpecialApprovedPagesQueryPage.php
* Created specials/SpecialApprovedFiles.php:
  * Largely a clone of SpecialApprovedPages.php
* Created specials/SpecialApprovedFilesQueryPage.php
  * New file to handle Special:ApprovedFiles queries

README:
* bump version to 1.0

ApprovedRevs.alias.php:
* new aliases to account for new special page Special:ApprovedFiles

maintenance/
* ApprovedRevs_Files.sql: New file for file approvals SQL
* ApprovedRevs.sql: moved file from /ApprovedRevs.sql

i18n/
* en.json: Added messages to support new features
* qqq.json: Added message descriptions

Change-Id: I81746a7a0671d23f938cc3c2479544eadc59768e

cleanup

Change-Id: I109c3ebb6e7391d5c0de9092c9bd903e14ae5f40

fix query page issues

Change-Id: Icfde940596ad7eecc33233e6c5681e3f7bf9212e
---
M .gitignore
M ApprovedRevs.alias.php
M ApprovedRevs.hooks.php
M ApprovedRevs.php
M ApprovedRevs_body.php
M README
D SpecialApprovedRevs.php
M i18n/en.json
M i18n/qqq.json
R maintenance/ApprovedRevs.sql
A maintenance/ApprovedRevs_Files.sql
A specials/SpecialApprovedFiles.php
A specials/SpecialApprovedFilesQueryPage.php
A specials/SpecialApprovedPages.php
A specials/SpecialApprovedPagesQueryPage.php
15 files changed, 1,508 insertions(+), 396 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ApprovedRevs 
refs/changes/69/161169/1

diff --git a/.gitignore b/.gitignore
index 98b092a..9b23c67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 *~
 *.kate-swp
 .*.swp
+Thumbs.db
+.DS_Store
\ No newline at end of file
diff --git a/ApprovedRevs.alias.php b/ApprovedRevs.alias.php
index ec23cab..4a0692c 100644
--- a/ApprovedRevs.alias.php
+++ b/ApprovedRevs.alias.php
@@ -9,7 +9,8 @@
 
 /** English (English) */
 $specialPageAliases['en'] = array(
-       'ApprovedRevs' => array( 'ApprovedRevs', 'ApprovedPages', 
'UnapprovedPages' ),
+       'ApprovedPages' => array( 'ApprovedPages', 'ApprovedRevs', 
'UnapprovedPages' ),
+       'ApprovedFiles' => array( 'ApprovedFiles' ),
 );
 
 /** Arabic (العربية) */
diff --git a/ApprovedRevs.hooks.php b/ApprovedRevs.hooks.php
index e04baa3..21fb6a9 100644
--- a/ApprovedRevs.hooks.php
+++ b/ApprovedRevs.hooks.php
@@ -557,6 +557,8 @@
                $approvedRevID = ApprovedRevs::getApprovedRevID( 
$article->getTitle() );
                $article->getTitle()->approvedRevID = $approvedRevID;
 
+               ApprovedRevs::addCSS();
+
                return true;
        }
 
@@ -567,7 +569,7 @@
         * revision. If it's the approved revision also add on a "star"
         * icon, regardless of the user.
         */
-       static function addApprovalLink( $historyPage, &$row , &$s ) {
+       static function addApprovalLink( $historyPage, &$row , &$s, &$classes ) 
{
                $title = $historyPage->getTitle();
                if ( ! ApprovedRevs::pageIsApprovable( $title ) ) {
                        return true;
@@ -578,7 +580,13 @@
                // stored earlier
                $approvedRevID = $title->approvedRevID;
                if ( $row->rev_id == $approvedRevID ) {
-                       $s .= ' ★';
+                       if ( is_array( $classes ) ) {
+                               $classes[] = "approved-revision";
+                       }
+                       else {
+                               $classes = array( "approved-revision" );
+                       }
+                       $s = wfMessage( 'approvedrevs-historylabel' )->text() . 
'<br />' .  $s;
                }
                if ( ApprovedRevs::userCanApprove( $title ) ) {
                        if ( $row->rev_id == $approvedRevID ) {
@@ -637,14 +645,13 @@
                        array( 'style' => 'clear: both' )
                ) . "\n" );
 
-               // Show the revision, instead of the history page.
-               if ( defined( 'SMW_VERSION' ) && version_compare( SMW_VERSION, 
'1.9', '<' ) ) {
-                       // Call this only for SMW < 1.9 - it causes semantic
-                       // data to not be set when using SMW 1.9 (a bug fixed
-                       // in SMW 1.9.1), but thankfully it doesn't seem to be
-                       // needed, in any case.
-                       $article->doPurge();
-               }
+               // doPurge() causes semantic data to not be set when using SMW 
1.9.0
+               // due to a bug in SMW. This was fixed in SMW 1.9.1. Approved 
Revs
+               // accounted for this bug in versions prior to v1.0.0 (see 
commits
+               // e80ac09f and c5370dd4), but doing so caused cache issues: the
+               // history page would not show updated approvals without a hard
+               // refresh. *** Approved Revs now DOES NOT support SMW 1.9.0 ***
+               $article->doPurge();
                $article->view();
 
                return false;
@@ -748,23 +755,27 @@
                        $general_section->addRow( $extensions_row );
                }
                $extensions_row->addItem( ALItem::newFromSpecialPage( 
'ApprovedRevs' ) );
+               $extensions_row->addItem( ALItem::newFromSpecialPage( 
'ApprovedFiles' ) );
                return true;
        }
 
        public static function describeDBSchema( $updater = null ) {
-               $dir = dirname( __FILE__ );
+               global $egApprovedRevsIP;
+               $dir = "$egApprovedRevsIP/maintenance";
 
                // DB updates
                // For now, there's just a single SQL file for all DB types.
                if ( $updater === null ) {
                        global $wgExtNewTables, $wgDBtype;
-                       //if ( $wgDBtype == 'mysql' ) {
+                       // if ( $wgDBtype == 'mysql' ) {
                                $wgExtNewTables[] = array( 'approved_revs', 
"$dir/ApprovedRevs.sql" );
-                       //}
+                               $wgExtNewTables[] = array( 
'approved_revs_files', "$dir/ApprovedRevs_Files.sql" );
+                       // }
                } else {
-                       //if ( $updater->getDB()->getType() == 'mysql' ) {
+                       // if ( $updater->getDB()->getType() == 'mysql' ) {
                                $updater->addExtensionUpdate( array( 
'addTable', 'approved_revs', "$dir/ApprovedRevs.sql", true ) );
-                       //}
+                               $updater->addExtensionUpdate( array( 
'addTable', 'approved_revs_files', "$dir/ApprovedRevs_Files.sql", true ) );
+                       // }
                }
                return true;
        }
@@ -886,4 +897,278 @@
                return true;
        }
 
+       /**
+        *  On image pages (pages in NS_FILE), modify each line in the file 
history
+        *  (file history, not history of wikitext on file page). Add
+        *  "approved-revision" class to the appropriate row. For users with
+        *  approve permissions on this page add "approve" and "unapprove" 
links as
+        *  required.
+        **/
+       public static function onImagePageFileHistoryLine ( $hist, $file, &$s, 
&$rowClass ) {
+
+               $fileTitle = $file->getTitle();
+
+               if ( ! ApprovedRevs::fileIsApprovable( $fileTitle ) ) {
+                       return true;
+               }
+
+               $rowTimestamp = $file->getTimestamp();
+               $rowSha1 = $file->getSha1();
+
+               list( $approvedRevTimestamp, $approvedRevSha1 ) = 
ApprovedRevs::getApprovedFileInfo( $file->getTitle() );
+
+               ApprovedRevs::addCSS();
+
+               // Apply class to row of approved revision
+               // Note: both here and below in the "userCanApprove" section, 
if the
+               // timestamp condition is removed then all rows with the same 
sha1 as
+               // the approved rev will be given the class 
"approved-revision", and
+               // highlighted. Only the actual approved rev will be given the 
message
+               // approvedrevs-historylabel, though.
+               if ( $rowSha1 == $approvedRevSha1 && $rowTimestamp == 
$approvedRevTimestamp ) {
+                       if ( $rowClass ) {
+                               $rowClass .= ' ';
+                       }
+                       $rowClass .= "approved-revision";
+
+                       $pattern = "/<td[^>]+filehistory-selected+[^>]+>/";
+                       $replace = "$0" . wfMessage( 
'approvedrevs-historylabel' )->text() . "<br />";
+                       $s = preg_replace( $pattern, $replace, $s );
+               }
+
+               if ( ApprovedRevs::userCanApprove( $fileTitle ) ) {
+                       if ( $rowSha1 == $approvedRevSha1 && $rowTimestamp == 
$approvedRevTimestamp ) {
+                               $url = $fileTitle->getLocalUrl(
+                                       array( 'action' => 'unapprovefile' )
+                               );
+                               $msg = wfMessage( 'approvedrevs-unapprove' 
)->text();
+                       } else {
+                               $url = $fileTitle->getLocalUrl(
+                                       array( 'action' => 'approvefile', 'ts' 
=> $rowTimestamp, 'sha1' => $rowSha1 )
+                               );
+                               $msg = wfMessage( 'approvedrevs-approve' 
)->text();
+                       }
+                       $s .= '<td>' . Xml::element(
+                               'a',
+                               array( 'href' => $url ),
+                               $msg
+                       ) . '</td>';
+               }
+               return true;
+
+       }
+
+       /**
+        *  Called on BeforeParserFetchFileAndTitle hook
+        *  Changes links and thumbnails of files to point to the approved 
revision in all cases except
+        *  the primary file on file pages (e.g. the big image in the top left 
on File:My File.png). To
+        *  modify that image see self::onImagePageFindFile()
+        **/
+       public static function modifyFileLinks ( $parser, Title $fileTitle, 
&$options, &$query ) {
+
+               if ( $fileTitle->getNamespace() == NS_MEDIA ) {
+                       $fileTitle = Title::makeTitle( NS_FILE, 
$fileTitle->getDBkey() );
+                       $fileTitle->resetArticleId( $fileTitle->getArticleID() 
); // avoid extra queries
+
+                       // Media link redirects don't get caught by the normal 
redirect check, so this
+                       // extra check is required
+                       if ( $temp = WikiPage::newFromID( 
$fileTitle->getArticleID() )->getRedirectTarget() ) {
+                               $fileTitle = $temp;
+                               unset( $temp );
+                       }
+               }
+
+               if ( $fileTitle->isRedirect() ) {
+                       $page = WikiPage::newFromID( $fileTitle->getArticleID() 
);
+                       $fileTitle = $page->getRedirectTarget();
+                       $fileTitle->resetArticleId( $fileTitle->getArticleID() 
); // avoid extra queries
+               }
+
+               # Tell Parser what file version to use
+               list( $approvedRevTimestamp, $approvedRevSha1 ) = 
ApprovedRevs::getApprovedFileInfo( $fileTitle );
+
+               // no valid approved timestamp or sha1, so don't modify image 
or image link
+               if ( ( ! $approvedRevTimestamp ) || ( ! $approvedRevSha1 ) ) {
+                       return true;
+               }
+
+               $options['time'] = wfTimestampOrNull( TS_MW, 
$approvedRevTimestamp );
+               $options['sha1'] = $approvedRevSha1;
+
+               // $options['broken'] = true; // breaks the link? was in 
FlaggedRevs...why would we want to do this?
+
+               # Stabilize the file link
+               if ( $query != '' ) {
+                       $query .= '&';
+               }
+               $query .= "filetimestamp=" . urlencode( wfTimestamp( TS_MW, 
$approvedRevTimestamp ) );
+
+               return true;
+       }
+
+       /**
+        *  Applicable on image pages only, this changes the primary image on 
the page
+        *  from the most recent to the approved revision.
+        **/
+       public static function onImagePageFindFile ( $imagePage, &$normalFile, 
&$displayFile ) {
+
+               list( $approvedRevTimestamp, $approvedRevSha1 ) = 
ApprovedRevs::getApprovedFileInfo( $imagePage->getFile()->getTitle() );
+               if ( ( ! $approvedRevTimestamp ) || ( ! $approvedRevSha1 ) )
+                       return true;
+
+               $title = $imagePage->getTitle();
+
+               $displayFile = wfFindFile( $title, array( 'time' => 
$approvedRevTimestamp ) );
+               # If none found, try current
+               if ( !$displayFile ) {
+                       wfDebug( __METHOD__ . ": {$title->getPrefixedDBkey()}: 
$approvedRevTimestamp not found, using current\n" );
+                       $displayFile = wfFindFile( $title );
+                       # If none found, use a valid local placeholder
+                       if ( !$displayFile ) {
+                               $displayFile = wfLocalFile( $title ); // 
fallback to current
+                       }
+                       $normalFile = $displayFile;
+               # If found, set $normalFile
+               } else {
+                       wfDebug( __METHOD__ . ": {$title->getPrefixedDBkey()}: 
using timestamp $approvedRevTimestamp\n" );
+                       $normalFile = wfFindFile( $title );
+               }
+
+               return true;
+       }
+
+
+       /**
+        * Handle the 'approvefile' action, defined for ApprovedRevs -
+        * mark the revision as approved, log it, and show a message to
+        * the user.
+        */
+       public static function setFileAsApproved( $action, $article ) {
+               // Return "true" if the call failed (meaning, pass on handling
+               // of the hook to others), and "false" otherwise.
+               if ( $action != 'approvefile' ) {
+                       return true;
+               }
+               $title = $article->getTitle();
+               if ( ! ApprovedRevs::fileIsApprovable( $title ) ) {
+                       return true;
+               }
+               if ( ! ApprovedRevs::userCanApprove( $title ) ) {
+                       return true;
+               }
+               global $wgRequest;
+               if ( ( ! $wgRequest->getCheck( 'ts' ) ) || ( ! 
$wgRequest->getCheck( 'sha1' ) ) ) {
+                       die( 'check query string' ); return true;
+               }
+               $revisionID = $wgRequest->getVal( 'ts' );
+               ApprovedRevs::setApprovedFileInDB(
+                       $title, $wgRequest->getVal( 'ts' ), $wgRequest->getVal( 
'sha1' ) );
+
+               global $wgOut;
+               $wgOut->addHTML( "\t\t" . Xml::element(
+                       'div',
+                       array( 'class' => 'successbox' ),
+                       wfMessage( 'approvedrevs-approvesuccess' )->text()
+               ) . "\n" );
+               $wgOut->addHTML( "\t\t" . Xml::element(
+                       'p',
+                       array( 'style' => 'clear: both' )
+               ) . "\n" );
+
+               // show the revision, instead of the history page
+               $article->doPurge();
+               $article->view();
+
+               return false;
+       }
+
+       /**
+        * Handle the 'unapprovefile' action, defined for ApprovedRevs -
+        * unset the previously-approved revision, log the change, and show
+        * a message to the user.
+        */
+       public static function unsetFileAsApproved( $action, $article ) {
+               // return "true" if the call failed (meaning, pass on handling
+               // of the hook to others), and "false" otherwise
+               if ( $action != 'unapprovefile' ) {
+                       return true;
+               }
+               $title = $article->getTitle();
+               if ( ! ApprovedRevs::userCanApprove( $title ) ) {
+                       return true;
+               }
+
+               ApprovedRevs::unsetApprovedFileInDB( $title );
+
+               // the message depends on whether the page should display
+               // a blank right now or not
+               global $egApprovedRevsBlankIfUnapproved;
+               if ( $egApprovedRevsBlankIfUnapproved ) {
+                       $successMsg = wfMessage( 
'approvedrevs-unapprovesuccess2' )->text();
+               } else {
+                       $successMsg = wfMessage( 
'approvedrevs-unapprovesuccess' )->text();
+               }
+
+               global $wgOut;
+               $wgOut->addHTML( "\t\t" . Xml::element(
+                       'div',
+                       array( 'class' => 'successbox' ),
+                       $successMsg
+               ) . "\n" );
+               $wgOut->addHTML( "\t\t" . Xml::element(
+                       'p',
+                       array( 'style' => 'clear: both' )
+               ) . "\n" );
+
+               // show the revision, instead of the history page
+               $article->doPurge();
+               $article->view();
+
+               return false;
+       }
+
+       /**
+        *      If a file is deleted, check if the sha1 (and timestamp?) exist 
in the
+        *  approved_revs_files table, and delete that row accordingly. A 
deleted
+        *  version of a file should not be the approved version!
+        **/
+       public static function onFileDeleteComplete ( File $file, $oldimage, 
$article, $user, $reason ) {
+
+               $dbr = wfGetDB( DB_SLAVE );
+               // check if this file has an approved revision
+               $approvedFile = $dbr->selectRow(
+                       'approved_revs_files',
+                       array( 'approved_timestamp', 'approved_sha1' ),
+                       array( 'file_title' => $file->getTitle()->getDBkey() )
+               );
+
+               // If an approved revision exists, loop through all files in 
history.
+               // Since this hook happens AFTER deletion (there is no hook 
before deletion), check to see
+               // if the sha1 of the approved revision is NOT in the history. 
If it is not in the history,
+               // then it has no business being in the approved_revs_files 
table, and should be deleted.
+               if ( $approvedFile ) {
+
+                       $revs = array();
+                       $approvedExists = false;
+
+                       $hist = $file->getHistory();
+                       foreach ( $hist as $OldLocalFile ) {
+                               // need to check both sha1 and timestamp, since 
reverted files can have the same
+                               // sha1, but different timestamps
+                               if ( $OldLocalFile->getTimestamp() == 
$approvedFile->approved_timestamp
+                                       && $OldLocalFile->getSha1() == 
$approvedFile->approved_sha1 )
+                               {
+                                       $approvedExists = true;
+                               }
+
+                       }
+
+                       if ( ! $approvedExists )
+                               ApprovedRevs::unsetApprovedFileInDB( 
$file->getTitle() );
+
+               }
+
+               return true;
+       }
+
 }
diff --git a/ApprovedRevs.php b/ApprovedRevs.php
index d50c28f..f51fede 100644
--- a/ApprovedRevs.php
+++ b/ApprovedRevs.php
@@ -9,7 +9,7 @@
  * @author Yaron Koren
  */
 
-define( 'APPROVED_REVS_VERSION', '0.7' );
+define( 'APPROVED_REVS_VERSION', '1.0.0' );
 
 // credits
 $wgExtensionCredits['other'][] = array(
@@ -22,26 +22,57 @@
 );
 
 // global variables
-$egApprovedRevsIP = dirname( __FILE__ ) . '/';
-$egApprovedRevsNamespaces = array( NS_MAIN, NS_USER, NS_PROJECT, NS_TEMPLATE, 
NS_HELP );
-$egApprovedRevsSelfOwnedNamespaces = array();
+$egApprovedRevsIP = __DIR__ . '/';
 $egApprovedRevsBlankIfUnapproved = false;
 $egApprovedRevsAutomaticApprovals = true;
 $egApprovedRevsShowApproveLatest = false;
 $egApprovedRevsShowNotApprovedMessage = false;
 
+// default permissions:
+//   * Group:sysop can approve anything approvable
+//   * Namespaces Main, User, Template, Help and Project are approvable
+//     with no additional approvers
+//   * No categories or pages are approvable (unless they're within one
+//     of the above namespaces)
+$egApprovedRevsPermissions = array (
+
+    'All Pages' => array( 'group' => 'sysop' ),
+
+    'Namespace Permissions' => array (
+        NS_MAIN => array(),
+        NS_USER => array(),
+        NS_TEMPLATE => array(),
+        NS_HELP => array(),
+        NS_PROJECT => array(),
+    ),
+
+    'Category Permissions' => array (),
+
+    'Page Permissions' => array ()
+
+);
+
+
+
 // internationalization
-$wgMessagesDirs['ApprovedRevs'] = __DIR__ . '/i18n';
+$wgMessagesDirs['ApprovedRevs'] = $egApprovedRevsIP . 'i18n';
 $wgExtensionMessagesFiles['ApprovedRevs'] = $egApprovedRevsIP . 
'ApprovedRevs.i18n.php';
 $wgExtensionMessagesFiles['ApprovedRevsAlias'] = $egApprovedRevsIP . 
'ApprovedRevs.alias.php';
 $wgExtensionMessagesFiles['ApprovedRevsMagic'] = $egApprovedRevsIP . 
'ApprovedRevs.i18n.magic.php';
 
-// register all classes
+// autoload classes
 $wgAutoloadClasses['ApprovedRevs'] = $egApprovedRevsIP . 
'ApprovedRevs_body.php';
 $wgAutoloadClasses['ApprovedRevsHooks'] = $egApprovedRevsIP . 
'ApprovedRevs.hooks.php';
-$wgSpecialPages['ApprovedRevs'] = 'SpecialApprovedRevs';
-$wgAutoloadClasses['SpecialApprovedRevs'] = $egApprovedRevsIP . 
'SpecialApprovedRevs.php';
-$wgSpecialPageGroups['ApprovedRevs'] = 'pages';
+$wgAutoloadClasses['SpecialApprovedPages'] = $egApprovedRevsIP . 
'specials/SpecialApprovedPages.php';
+$wgAutoloadClasses['SpecialApprovedFiles'] = $egApprovedRevsIP . 
'specials/SpecialApprovedFiles.php';
+$wgAutoloadClasses['SpecialApprovedPagesQueryPage'] = $egApprovedRevsIP . 
'specials/SpecialApprovedPagesQueryPage.php';
+$wgAutoloadClasses['SpecialApprovedFilesQueryPage'] = $egApprovedRevsIP . 
'specials/SpecialApprovedFilesQueryPage.php';
+
+// special pages
+$wgSpecialPages['ApprovedPages'] = 'SpecialApprovedPages';
+$wgSpecialPages['ApprovedFiles'] = 'SpecialApprovedFiles';
+$wgSpecialPageGroups['ApprovedPages'] = 'pages';
+$wgSpecialPageGroups['ApprovedFiles'] = 'pages';
 
 // hooks
 $wgHooks['ArticleEditUpdates'][] = 'ApprovedRevsHooks::updateLinksAfterEdit';
@@ -73,6 +104,15 @@
 $wgHooks['ArticleViewHeader'][] = 'ApprovedRevsHooks::setArticleHeader';
 $wgHooks['ArticleViewHeader'][] = 
'ApprovedRevsHooks::displayNotApprovedHeader';
 
+// Approved File Revisions
+$wgHooks['UnknownAction'][] = 'ApprovedRevsHooks::setFileAsApproved';
+$wgHooks['UnknownAction'][] = 'ApprovedRevsHooks::unsetFileAsApproved';
+$wgHooks['ImagePageFileHistoryLine'][] = 
'ApprovedRevsHooks::onImagePageFileHistoryLine';
+$wgHooks['BeforeParserFetchFileAndTitle'][] = 
'ApprovedRevsHooks::modifyFileLinks';
+$wgHooks['ImagePageFindFile'][] = 'ApprovedRevsHooks::onImagePageFindFile';
+$wgHooks['FileDeleteComplete'][] = 'ApprovedRevsHooks::onFileDeleteComplete';
+
+
 // logging
 $wgLogTypes['approval'] = 'approval';
 $wgLogNames['approval'] = 'approvedrevs-logname';
@@ -81,8 +121,8 @@
 $wgLogActions['approval/unapprove'] = 'approvedrevs-unapproveaction';
 
 // user rights
-$wgAvailableRights[] = 'approverevisions';
-$wgGroupPermissions['sysop']['approverevisions'] = true;
+$wgAvailableRights[] = 'approverevisions'; // jamesmontalvo3: do we remove 
this or leave it behind even though it's not being used anymore?
+$wgGroupPermissions['sysop']['approverevisions'] = true; // jamesmontalvo3: do 
we remove this or leave it behind even though it's not being used anymore?
 $wgAvailableRights[] = 'viewlinktolatest';
 $wgGroupPermissions['*']['viewlinktolatest'] = true;
 
diff --git a/ApprovedRevs_body.php b/ApprovedRevs_body.php
index 604ac23..3fa5941 100644
--- a/ApprovedRevs_body.php
+++ b/ApprovedRevs_body.php
@@ -13,8 +13,14 @@
        // Static arrays to prevent querying the database more than necessary.
        static $mApprovedContentForPage = array();
        static $mApprovedRevIDForPage = array();
+       static $mApprovedFileInfo = array();
        static $mUserCanApprove = null;
-       
+       static $permissions = null;
+       static $mUserGroups = null;
+       static $currentTitle = null;
+       static $currentUser = null;
+       static $bannedNamespaceIds = array( NS_FILE, NS_MEDIAWIKI, NS_CATEGORY 
);
+
        /**
         * Gets the approved revision ID for this page, or null if there isn't
         * one.
@@ -29,18 +35,24 @@
                        return null;
                }
 
+               return self::getApprovedRevIDfromDB( $pageID );
+       }
+
+       public static function getApprovedRevIDfromDB ( $pageID ) {
+
                $dbr = wfGetDB( DB_SLAVE );
                $revID = $dbr->selectField( 'approved_revs', 'rev_id', array( 
'page_id' => $pageID ) );
-               self::$mApprovedRevIDForPage[$pageID] = $revID;
-               return $revID;
+               return self::$mApprovedRevIDForPage[$pageID] = $revID;
+
        }
+
 
        /**
         * Returns whether or not this page has a revision ID.
         */
        public static function hasApprovedRevision( $title ) {
-               $revision_id = self::getApprovedRevID( $title );
-               return ( ! empty( $revision_id ) );
+               $revisionId = self::getApprovedRevID( $title );
+               return ( ! empty( $revisionId ) );
        }
 
        /**
@@ -89,7 +101,6 @@
                        return false;
                }
                // check if it's an action other than viewing
-               global $wgRequest;
                if ( $wgRequest->getCheck( 'action' ) &&
                        $wgRequest->getVal( 'action' ) != 'view' &&
                        $wgRequest->getVal( 'action' ) != 'purge' &&
@@ -106,15 +117,15 @@
         * object, to speed up processing if it's called more than once.
         */
        public static function pageIsApprovable( Title $title ) {
-               // If this function was already called for this page, the value
-               // should have been stored as a field in the $title object.
+               // if this function was already called for this page, the
+               // value should have been stored as a field in the $title object
                if ( isset( $title->isApprovable ) ) {
                        return $title->isApprovable;
                }
 
-               if ( !$title->exists() ) {
+               if ( ! $title->exists() ) {
                        $title->isApprovable = false;
-                       return $title->isApprovable;                    
+                       return false;
                }
 
                // Allow custom setting of whether the page is approvable.
@@ -123,12 +134,112 @@
                        return $title->isApprovable;
                }
 
-               // Check the namespace.
-               global $egApprovedRevsNamespaces;
-               if ( in_array( $title->getNamespace(), 
$egApprovedRevsNamespaces ) ) {
-                       $title->isApprovable = true;
-                       return $title->isApprovable;
+               // Even if File, Category, etc are in 
ApprovedRevs::$permissions, those
+               // pages* cannot be approved. isApprovable = false if in banned 
NS.
+               //   * File pages cannot be approved, but the media itself can
+               if ( in_array( $title->getNamespace(), 
self::$bannedNamespaceIds ) ) {
+                       $title->isApprovable = false;
+                       return false;
                }
+
+               // Check if ApprovedRevs::$permissions defines approvals for 
$title
+               if ( self::titleInApprovedRevsPermissions( $title ) ) {
+                       $title->isApprovable = true;
+                       return true;
+               }
+
+
+
+               // @deprecated: in v1.0.0+ per-page permissions should not be 
handled
+               // using the __APPROVEDREVS__ magic word. Instead add a 
category like
+               // [[Category:Requires approval]]. In a future version this 
should be
+               // removed.
+               // 
------------------------------------------------------------------
+               // Page doesn't satisfy ApprovedRevs::$permissions, so next
+               // check for the page property - for some reason, calling the 
standard
+               // getProperty() function doesn't work, so we just do a DB 
query on
+               // the page_props table
+               if ( self::pageHasMagicWord( $title ) )
+                       return $title->isApprovable = true;
+
+               // if a page already has an approval, it must be considered 
approvable 
+               // in order for the user to be able to view/modify approvals. 
Though 
+               // this wasn't the case in versions of ApprovedRevs before 
v1.0, it is
+               // necessary now since approvability can change much more easily
+               if ( self::getApprovedRevIDfromDB( $title->getArticleID() ) ) {
+                       $title->isApprovable = true;
+                       return true;
+               }
+               else {
+                       $title->isApprovable = false;
+                       return false;
+               }
+
+       }
+
+       public static function titleInApprovedRevsPermissions ( Title $title ) {
+
+               $perms = self::getPermissions();
+
+               // title in namespace permissions
+               if ( in_array( $title->getNamespace() , array_keys( 
$perms['Namespace Permissions'] ) ) ) {
+                       return true;
+               }
+
+               $titleApprovableCategories = array_intersect( 
self::getCategoryList( $title ), array_keys( $perms['Category Permissions'] ) );
+
+               // title in category permissions
+               if ( count( $titleApprovableCategories ) > 0 ) {
+                       return true;
+               }
+
+               // title in page permissions and is in main namespace
+               if ( in_array( $title->getText(), array_keys( $perms['Page 
Permissions'] ) ) ) {
+                       return true;
+               }
+
+               // title in page permissions in another namespace
+               else if ( in_array( $title->getNsText() . ':' . 
$title->getText(), array_keys( $perms['Page Permissions'] ) ) ) {
+                       return true;
+               }
+
+               return false;
+
+       }
+
+       public static function fileIsApprovable ( Title $title ) {
+
+               // title doesn't exist, not approvable
+               if ( ! $title->exists() ) {
+                       return $title->isApprovable = false;
+               }
+
+               // Check if ApprovedRevs::$permissions defines approvals for 
$title
+               if ( self::titleInApprovedRevsPermissions( $title ) ) {
+                       $title->isApprovable = true;
+                       return true;
+               }
+
+               // if a file already has an approval, it must be considered 
approvable 
+               // in order for the user to be able to view/modify approvals. 
Though 
+               // this wasn't the case on versions of ApprovedRevs before 
v1.0, it is
+               // necessary now since approvability can change much more easily
+               list( $timestamp, $sha1 ) = self::getApprovedFileInfo( $title 
); // if title in approved_revs_files table
+               if ( $timestamp !== false ) {
+                       // only approvable because it already has an approved 
rev, not 
+                       // because it is in ApprovedRevs::$permissions
+                       $title->isApprovable = true;
+                       return true;
+               }
+               else {
+                       $title->isApprovable = false;
+                       return false;
+               }
+
+       }
+
+       // check if page has __APPROVEDREVS__
+       public static function pageHasMagicWord ( $title ) {
 
                // It's not in an included namespace, so check for the page
                // property - for some reason, calling the standard
@@ -143,64 +254,64 @@
                        )
                );
                $row = $dbr->fetchRow( $res );
-               $isApprovable = ( $row[0] == '1' );
-               $title->isApprovable = $isApprovable;
-               return $isApprovable;
+               if ( $row[0] == '1' ) {
+                       return true;
+               }
+               else {
+                       return false;
+               }
        }
 
-       public static function userCanApprove( $title ) {
-               global $egApprovedRevsSelfOwnedNamespaces;
+       public static function userCanApprove ( $title, $user=false ) {
 
-               // $mUserCanApprove is a static variable used for
-               // "caching" the result of this function, so that
-               // it only has to be called once.
-               if ( self::$mUserCanApprove ) {
-                       return true;
-               } elseif ( self::$mUserCanApprove === false ) {
-                       return false;
-               } elseif ( $title->userCan( 'approverevisions' ) ) {
-                       self::$mUserCanApprove = true;
-                       return true;
-               } else {
-                       // If the user doesn't have the 'approverevisions'
-                       // permission, they still might be able to approve
-                       // revisions - it depends on whether the current
-                       // namespace is within the admin-defined
-                       // $egApprovedRevsSelfOwnedNamespaces array.
+               // set title and user for permissions checks
+               self::$currentTitle = $title;
+               if ( ! $user ) {
                        global $wgUser;
-                       $namespace = $title->getNamespace();
-                       if ( in_array( $namespace, 
$egApprovedRevsSelfOwnedNamespaces ) ) {
-                               if ( $namespace == NS_USER ) {
-                                       // If the page is in the 'User:'
-                                       // namespace, this user can approve
-                                       // revisions if it's their user page.
-                                       if ( $title->getText() == 
$wgUser->getName() ) {
-                                               self::$mUserCanApprove = true;
-                                               return true;
-                                       }
-                               } else {
-                                       // Otherwise, they can approve revisions
-                                       // if they created the page.
-                                       // We get that information via a SQL
-                                       // query - is there an easier way?
-                                       $dbr = wfGetDB( DB_SLAVE );
-                                       $row = $dbr->selectRow(
-                                               array( 'revision', 'page' ),
-                                               'revision.rev_user_text',
-                                               array( 'page.page_title' => 
$title->getDBkey() ),
-                                               null,
-                                               array( 'ORDER BY' => 
'revision.rev_id ASC' ),
-                                               array( 'revision' => array( 
'JOIN', 'revision.rev_page = page.page_id' ) )
-                                       );
-                                       if ( $row->rev_user_text == 
$wgUser->getName() ) {
-                                               self::$mUserCanApprove = true;
-                                               return true;
-                                       }
-                               }
+                       self::$currentUser = $wgUser;
+               }
+               else {
+                       self::$currentUser = $user;
+               }
+
+               // $mUserCanApprove is a static variable used for "caching" the 
result
+               // of this function, so the logic only has to be executed once.
+               if ( isset( self::$mUserCanApprove ) )
+                       return self::$mUserCanApprove;
+
+               $pageNamespace  = self::$currentTitle->getNamespace();
+               $pageCategories = self::getCategoryList( self::$currentTitle );
+               $pageFullName   = self::$currentTitle->getText();
+
+               $permissions = self::getPermissions();
+
+               if ( self::checkIfUserInPerms( $permissions['All Pages'] ) ) {
+                       return self::$mUserCanApprove;
+               }
+
+               foreach ( $permissions['Namespace Permissions'] as $namespace 
=> $whoCanApprove ) {
+                       if ( $namespace === $pageNamespace ) {
+                               self::checkIfUserInPerms( $whoCanApprove );
                        }
                }
-               self::$mUserCanApprove = false;
-               return false;
+
+               foreach ( $permissions['Category Permissions'] as $category => 
$whoCanApprove ) {
+                       if ( in_array( $category, $pageCategories ) ) {
+                               self::checkIfUserInPerms( $whoCanApprove );
+                       }
+               }
+
+               foreach ( $permissions['Page Permissions'] as $page => 
$whoCanApprove ) {
+                       if ( $page == $pageFullName ) {
+                               self::checkIfUserInPerms( $whoCanApprove );
+                       }
+               }
+
+               if ( self::usernameIsBasePageName( self::$currentUser, 
self::$currentTitle ) ) {
+                       self::$mUserCanApprove = true;
+               }
+
+               return self::$mUserCanApprove;
        }
 
        public static function saveApprovedRevIDInDB( $title, $rev_id ) {
@@ -304,4 +415,345 @@
                global $wgOut;
                $wgOut->addModuleStyles( 'ext.ApprovedRevs' );
        }
+
+       // setup permissions and fill in any defaults
+       public static function getPermissions () {
+
+               if ( self::$permissions ) {
+                       return self::$permissions;
+               }
+
+               global $egApprovedRevsPermissions;
+               self::$permissions = $egApprovedRevsPermissions;
+
+               $permissionsZones = array( 'Namespace Permissions', 'Category 
Permissions', 'Page Permissions' );
+               foreach ( $permissionsZones as $zone ) {
+
+                       // for each subzone, such as NS_MAIN within namespaces, 
or
+                       // "Category:Approval Required" within categories, or 
"Main Page"
+                       // within pages...for each of these format the 
permissions for
+                       // simpler logic later
+                       foreach ( self::$permissions[$zone] as $subzone => 
$subzonePerms ) {
+                               self::$permissions[$zone][$subzone] = 
self::formatPermissionTypes( $subzonePerms );
+                       }
+               }
+
+               // perform the same formatting on the All Pages permissions
+               self::$permissions['All Pages'] = self::formatPermissionTypes( 
self::$permissions['All Pages'] );
+
+               return self::$permissions;
+
+       }
+
+       public static function formatPermissionTypes ( $permArray ) {
+
+               $permissionsTypes = array( 'group', 'user', 'property' ); // 
'creator' not included since it's bool
+
+               foreach ( $permissionsTypes as $type ) {
+
+                       // if zone-type additional approvers set to null, 
convert to
+                       // empty array
+                       if ( ! isset( $permArray[$type] ) ) {
+                               $permArray[$type] = array(); // this just makes 
the logic easier later
+                       }
+
+                       // if set to string, wrap that string in an array
+                       else if ( is_string( $permArray[$type] ) ) {
+                               $permArray[$type] = array(
+                                       $permArray[$type]
+                               );
+                       }
+
+               }
+
+               if ( ! isset( $permArray['creator'] ) ) {
+                       $permArray['creator'] = false;
+               }
+
+               // default is category permissions override namespace, page 
override
+               // category. Specifically, anything later in 
$egApprovedRevsPermissions
+               // overrides prior entries (unless override is explicitly set 
to false)
+               if ( ! isset( $permArray['override'] ) ) {
+                       $permArray['override'] = true;
+               }
+
+               return $permArray;
+
+       }
+
+       /**
+        * @since 1.0.0
+        *
+        * @param array $whoCanApprove array of who can approve,
+        * @return boolean: Whether or not the user has permission to approve 
the page
+        *
+        * $whoCanApprove is like:
+        * array(
+        *              'group' => array('editors', 'management'),
+        *              'user' => 'John',
+        *              'creator' => true,
+        *              'property' => 'Subject matter expert',
+        *              'override' => false // <-- this is irrelevant within 
this function
+        * )
+        */
+       public static function checkIfUserInPerms( $whoCanApprove ) {
+
+               // $whoCanApprove['override'] determines whether or not this 
pass
+               // through checkIfUserInPerms() will override previous passes. 
If this
+               // isn't going to overwrite other permissions, and other 
permissions
+               // say the user can approve, no need to check further.
+               if ( $whoCanApprove['override'] == false && 
self::$mUserCanApprove == true ) {
+                       return self::$mUserCanApprove;
+               }
+
+               $userGroups = array_map( 'strtolower' , 
self::$currentUser->getGroups() );
+
+               // check if user is the page creator
+               if ( $whoCanApprove['creator'] === true && 
self::isPageCreator() ) {
+                       self::$mUserCanApprove = true;
+                       return self::$mUserCanApprove;
+               }
+
+               // check if the user is in any of the listed groups
+               foreach ( $whoCanApprove['group'] as $group ) {
+                       if ( in_array( strtolower( $group ), $userGroups ) ) {
+                               self::$mUserCanApprove = true;
+                               return self::$mUserCanApprove;
+                       }
+               }
+
+               // check if the user is in the list of users
+               foreach ( $whoCanApprove['user'] as $user ) {
+                       if ( strtolower( $user ) === strtolower( 
self::$currentUser->getName() ) ) {
+                               self::$mUserCanApprove = true;
+                               return self::$mUserCanApprove;
+                       }
+               }
+
+               // check if the user is set as the value of any SMW properties
+               // (if SMW enabled)
+               foreach ( $whoCanApprove['property'] as $property ) {
+                       if ( self::smwPropertyEqualsCurrentUser( $property ) ) {
+                               self::$mUserCanApprove = true;
+                               return self::$mUserCanApprove;
+                       }
+               }
+
+               // At this point self::$mUserCanApprove was not set to TRUE in 
this
+               // call to this method, and thus from the perspective of just 
this call
+               // to this method FALSE should be returned. Previous calls to 
this
+               // method are irrelevant because if self::$mUserCanApprove was 
TRUE
+               // and $whoCanApprove['override'] was FALSE this call to this 
method
+               // would already have returned TRUE in the first if-block at 
the top.
+               // This could be overridden in subsequent calls to this method.
+               self::$mUserCanApprove = false;
+               return self::$mUserCanApprove;
+
+       }
+
+       // return true if self::$currentUser created self::$currentTitle
+       public static function isPageCreator () {
+               $dbr = wfGetDB( DB_SLAVE );
+               $row = $dbr->selectRow(
+                       array( 'revision', 'page' ),
+                       'revision.rev_user_text',
+                       array( 'page.page_title' => 
self::$currentTitle->getDBkey() ),
+                       null,
+                       array( 'ORDER BY' => 'revision.rev_id ASC' ),
+                       array( 'revision' => array( 'JOIN', 'revision.rev_page 
= page.page_id' ) )
+               );
+               return $row->rev_user_text == self::$currentUser->getName();
+       }
+
+       // Determines if username is the base pagename, e.g. if user is
+       // User:Jamesmontalvo3 then this returns true for pages named
+       // User:Jamesmontalvo3, User:Jamesmontalvo3/Subpage, etc
+       // This is for use when the User or User_talk namespaces are used in
+       // $egApprovedRevsPermissions. 
+       public static function usernameIsBasePageName () {
+
+               if ( self::$currentTitle->getNamespace() == NS_USER || 
self::$currentTitle->getNamespace() == NS_USER_TALK ) {
+
+                       // explode on slash to just get the first part (if it 
is a subpage)
+                       // as far as I know usernames cannot have slashes in 
them, so this
+                       // should be okay
+                       $titleSubpageParts = explode( '/', 
self::$currentTitle->getText() );
+
+                       return $titleSubpageParts[0] == 
self::$currentUser->getName();
+
+               }
+               return false;
+       }
+
+       public static function getCategoryList ( $title ) {
+               $catTree = $title->getParentCategoryTree();
+               return array_unique( self::getCategoryListHelper( $catTree ) );
+       }
+
+       public static function getCategoryListHelper ( $catTree ) {
+
+               $categoryNames = array(); // array of categories w/o tree 
structure
+               foreach ( $catTree as $cat => $parentCats ) {
+                       $catParts = explode( ':', $cat, 2 ); // best var name 
ever!
+                       $categoryNames[] = str_replace( '_', ' ', $catParts[1] 
); // @todo: anything besides _ need to be replaced?
+                       if ( count( $parentCats ) > 0 ) {
+                               array_merge( $categoryNames, 
self::getCategoryListHelper( $parentCats ) );
+                       }
+               }
+               return $categoryNames;
+
+       }
+
+       public static function smwPropertyEqualsCurrentUser ( $userProperty ) {
+
+               if ( ! class_exists( 'SMWHooks' ) ) {
+                       return false; // SMW not installed, return false to 
ignore
+               }
+               else {
+                       $valueDis = smwfGetStore()->getPropertyValues(
+                               new SMWDIWikiPage( 
self::$currentTitle->getDBkey(), self::$currentTitle->getNamespace(), '' ),
+                               new SMWDIProperty( 
SMWPropertyValue::makeUserProperty( $userProperty )->getDBkey() ) );   // 
trim($userProperty)
+
+                       foreach ( $valueDis as $valueDI ) {
+                               if ( ! $valueDI instanceof SMWDIWikiPage ) {
+                                       throw new Exception( 'ApprovedRevs 
"Property" permissions must use Semantic MediaWiki properties of type "Page"' );
+                               }
+                               if ( $valueDI->getTitle()->getText() == 
self::$currentUser->getUserPage()->getText() ) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+
+
+       public static function setApprovedFileInDB ( $title, $timestamp, $sha1 
) {
+
+               $parser = new Parser();
+               $parser->setTitle( $title );
+
+               $dbr = wfGetDB( DB_MASTER );
+               $fileTitle = $title->getDBkey();
+               $oldFileTitle = $dbr->selectField( 'approved_revs_files', 
'file_title', array( 'file_title' => $fileTitle ) );
+               if ( $oldFileTitle ) {
+                       $dbr->update( 'approved_revs_files',
+                               array( 'approved_timestamp' => $timestamp, 
'approved_sha1' => $sha1 ), // update fields
+                               array( 'file_title' => $fileTitle )
+                       );
+               } else {
+                       $dbr->insert( 'approved_revs_files',
+                               array( 'file_title' => $fileTitle, 
'approved_timestamp' => $timestamp, 'approved_sha1' => $sha1 )
+                       );
+               }
+               // Update "cache" in memory
+               self::$mApprovedFileInfo[$fileTitle] = array( $timestamp, $sha1 
);
+
+               $log = new LogPage( 'approval' );
+
+               $imagepage = ImagePage::newFromID( $title->getArticleID() );
+               $displayedFileUrl = 
$imagepage->getDisplayedFile()->getFullURL();
+
+               $revisionAnchorTag = Xml::element(
+                       'a',
+                       array( 'href' => $displayedFileUrl, 'title' => 'unique 
identifier: ' . $sha1 ),
+                       substr( $sha1, 0, 8 ) // show first 6 characters of sha1
+               );
+               $logParams = array( $revisionAnchorTag );
+               $log->addEntry(
+                       'approve',
+                       $title,
+                       '',
+                       $logParams
+               );
+
+               wfRunHooks( 'ApprovedRevsFileRevisionApproved', array( $parser, 
$title, $timestamp, $sha1 ) );
+
+       }
+
+       public static function unsetApprovedFileInDB ( $title ) {
+
+               $parser = new Parser();
+               $parser->setTitle( $title );
+
+               $fileTitle = $title->getDBkey();
+
+               $dbr = wfGetDB( DB_MASTER );
+               $dbr->delete( 'approved_revs_files', array( 'file_title' => 
$fileTitle ) );
+               // the unapprove page method had LinksUpdate and Parser objects 
here, but the page text has
+               // not changed at all with a file approval, so I don't think 
those are necessary.
+
+               $log = new LogPage( 'approval' );
+               $log->addEntry(
+                       'unapprove',
+                       $title,
+                       ''
+               );
+
+               wfRunHooks( 'ApprovedRevsFileRevisionUnapproved', array( 
$parser, $title ) );
+
+       }
+
+       /**
+        *  Pulls from DB table approved_revs_files which revision of a file, 
if any
+        *  besides most recent, should be used as the approved revision.
+        **/
+       public static function getApprovedFileInfo ( $fileTitle ) {
+
+               if ( isset( self::$mApprovedFileInfo[ $fileTitle->getDBkey() ] 
) ) {
+                       return self::$mApprovedFileInfo[ $fileTitle->getDBkey() 
];
+               }
+
+               $dbr = wfGetDB( DB_SLAVE );
+               $row = $dbr->selectRow(
+                       'approved_revs_files', // select from table
+                       array( 'approved_timestamp', 'approved_sha1' ),
+                       array( 'file_title' => $fileTitle->getDBkey() )
+               );
+               if ( $row ) {
+                       $return = array( $row->approved_timestamp, 
$row->approved_sha1 );
+               }
+               else {
+                       $return = array( false, false );
+               }
+               
+               self::$mApprovedFileInfo[ $fileTitle->getDBkey() ] = $return;
+               return $return;
+
+       }
+
+       public static function getApprovabilityStringsForDB () {
+
+               $perms = self::getPermissions();
+
+               $approvableNamespaceIdString = implode( ',' , array_keys( 
$perms['Namespace Permissions'] ) );
+
+
+               $dbSafeCategoryNames = array();
+               foreach ( array_keys( $perms['Category Permissions'] ) as 
$category ) {
+                       $mwCategoryObject = Category::newFromName( $category );
+
+                       // cannot use category IDs like with pages and 
namespaces. Instead
+                       // need to create database-safe SQL column names. 
Columns in same
+                       // form as categorylinks.cl_to
+                       $dbSafeCategoryNames[] = "'" . 
mysql_real_escape_string( $mwCategoryObject->getName() ) . "'";
+               }
+               $dbSafeCategoryNamesString = implode( ',', $dbSafeCategoryNames 
);
+
+
+               $approvablePageIds = array();
+               foreach ( array_keys( $perms['Page Permissions'] ) as $page ) {
+                       $title = Title::newFromText( $page );
+                       $approvablePageIds[] = $title->getArticleID();
+               }
+               $approvablePageIdString = implode( ',', $approvablePageIds );
+
+               return array(
+                       $approvableNamespaceIdString,
+                       $dbSafeCategoryNamesString,
+                       $approvablePageIdString,
+               );
+
+       }
+
 }
diff --git a/README b/README
index db994de..de53ef8 100644
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
 Approved Revs Extension
 
-        Version 0.7
+        Version 1.0.0
         Yaron Koren
 
 This is free software licensed under the GNU General Public License. Please
diff --git a/SpecialApprovedRevs.php b/SpecialApprovedRevs.php
deleted file mode 100644
index f1a1a64..0000000
--- a/SpecialApprovedRevs.php
+++ /dev/null
@@ -1,292 +0,0 @@
-<?php
-
-/**
- * Special page that displays various lists of pages that either do or do
- * not have an approved revision.
- *
- * @author Yaron Koren
- */
-class SpecialApprovedRevs extends SpecialPage {
-
-       /**
-        * Constructor
-        */
-       function __construct() {
-               parent::__construct( 'ApprovedRevs' );
-       }
-
-       function execute( $query ) {
-               global $wgRequest;
-
-               ApprovedRevs::addCSS();
-               $this->setHeaders();
-               list( $limit, $offset ) = wfCheckLimits();
-
-               $mode = $wgRequest->getVal( 'show' );
-               $rep = new SpecialApprovedRevsPage( $mode );
-
-               if ( method_exists( $rep, 'execute' ) ) {
-                       return $rep->execute( $query );
-               } else {
-                       return $rep->doQuery( $offset, $limit );
-               }
-       }
-
-}
-
-class SpecialApprovedRevsPage extends QueryPage {
-
-       protected $mMode;
-
-       public function __construct( $mode ) {
-               if ( $this instanceof SpecialPage ) {
-                       parent::__construct( 'ApprovedRevs' );
-               }
-               $this->mMode = $mode;
-       }
-
-       function getName() {
-               return 'ApprovedRevs';
-       }
-
-       function isExpensive() { return false; }
-
-       function isSyndicated() { return false; }
-
-       function getPageHeader() {
-               // show the names of the three lists of pages, with the one
-               // corresponding to the current "mode" not being linked
-               $approvedPagesTitle = SpecialPage::getTitleFor( 'ApprovedRevs' 
);
-               $navLine = wfMessage( 'approvedrevs-view' )->parse() . ' ';
-
-               if ( $this->mMode == '' ) {
-                       $navLine .= Xml::element( 'strong',
-                               null,
-                               wfMessage( 'approvedrevs-approvedpages' 
)->text()
-                       );
-               } else {
-                       $navLine .= Xml::element( 'a',
-                               array( 'href' => 
$approvedPagesTitle->getLocalURL() ),
-                               wfMessage( 'approvedrevs-approvedpages' 
)->text()
-                       );
-               }
-
-               $navLine .= ' | ';
-
-               if ( $this->mMode == 'notlatest' ) {
-                       $navLine .= Xml::element( 'strong',
-                               null,
-                               wfMessage( 'approvedrevs-notlatestpages' 
)->text()
-                       );
-               } else {
-                       $navLine .= Xml::element( 'a',
-                               array( 'href' => 
$approvedPagesTitle->getLocalURL( array( 'show' => 'notlatest' ) ) ),
-                               wfMessage( 'approvedrevs-notlatestpages' 
)->text()
-                       );
-               }
-
-               $navLine .= ' | ';
-
-               if ( $this->mMode == 'unapproved' ) {
-                       $navLine .= Xml::element( 'strong',
-                               null,
-                               wfMessage( 'approvedrevs-unapprovedpages' 
)->text()
-                       );
-               } else {
-                       $navLine .= Xml::element( 'a',
-                               array( 'href' => 
$approvedPagesTitle->getLocalURL( array( 'show' => 'unapproved' ) ) ),
-                               wfMessage( 'approvedrevs-unapprovedpages' 
)->text()
-                       );
-               }
-
-               return Xml::tags( 'p', null, $navLine ) . "\n";
-       }
-
-       /**
-        * Set parameters for standard navigation links.
-        */
-       function linkParameters() {
-               $params = array();
-
-               if ( $this->mMode == 'notlatest' ) {
-                       $params['show'] = 'notlatest';
-               } elseif ( $this->mMode == 'unapproved' ) {
-                       $params['show'] = 'unapproved';
-               } else { // all approved pages
-               }
-
-               return $params;
-       }
-
-       function getPageFooter() {
-       }
-
-       public static function getNsConditionPart( $ns ) {
-               return 'p.page_namespace = ' . $ns;
-       }
-
-       /**
-        * (non-PHPdoc)
-        * @see QueryPage::getSQL()
-        */
-       function getQueryInfo() {
-               global $egApprovedRevsNamespaces;
-
-               $namespacesString = '(' . implode( ',', 
$egApprovedRevsNamespaces ) . ')';
-               if ( $this->mMode == 'notlatest' ) {
-                       return array(
-                               'tables' => array(
-                                       'ar' => 'approved_revs',
-                                       'p' => 'page',
-                                       'pp' => 'page_props',
-                               ),
-                               'fields' => array(
-                                       'p.page_id AS id',
-                                       'ar.rev_id AS rev_id',
-                                       'p.page_latest AS latest_id',
-                               ),
-                               'join_conds' => array(
-                                       'p' => array(
-                                               'JOIN', 'ar.page_id=p.page_id'
-                                       ),
-                                       'pp' => array(
-                                               'LEFT OUTER JOIN', 
'ar.page_id=pp_page'
-                                       ),
-                               ),
-                               'conds' => "p.page_latest != ar.rev_id AND 
((p.page_namespace IN $namespacesString) OR (pp_propname = 'approvedrevs' AND 
pp_value = 'y'))",
-                       );
-               } elseif ( $this->mMode == 'unapproved' ) {
-                       return array(
-                               'tables' => array(
-                                       'ar' => 'approved_revs',
-                                       'p' => 'page',
-                                       'pp' => 'page_props',
-                               ),
-                               'fields' => array(
-                                       'p.page_id AS id',
-                                       'p.page_latest AS latest_id'
-                               ),
-                               'join_conds' => array(
-                                       'ar' => array(
-                                               'LEFT OUTER JOIN', 
'p.page_id=ar.page_id'
-                                       ),
-                                       'pp' => array(
-                                               'LEFT OUTER JOIN', 
'ar.page_id=pp_page'
-                                       ),
-                               ),
-                               'conds' => "ar.page_id IS NULL AND 
((p.page_namespace IN $namespacesString) OR (pp_propname = 'approvedrevs' AND 
pp_value = 'y'))",
-                       );
-               } else { // all approved pages
-                       return array(
-                               'tables' => array(
-                                       'ar' => 'approved_revs',
-                                       'p' => 'page',
-                                       'pp' => 'page_props',
-                               ),
-                               'fields' => array(
-                                       'p.page_id AS id',
-                                       'ar.rev_id AS rev_id',
-                                       'p.page_latest AS latest_id',
-                               ),
-                               'join_conds' => array(
-                                       'p' => array(
-                                               'JOIN', 'ar.page_id=p.page_id',
-                                       ),
-                                       'pp' => array(
-                                               'LEFT OUTER JOIN', 
'ar.page_id=pp_page'
-                                       ),
-                               ),
-                               'conds' => "(p.page_namespace IN 
$namespacesString) OR (pp_propname = 'approvedrevs' AND pp_value = 'y')",
-                       );
-               }
-       }
-
-       function getOrder() {
-               return ' ORDER BY p.page_namespace, p.page_title ASC';
-       }
-
-       function getOrderFields() {
-               return array( 'p.page_namespace', 'p.page_title' );
-       }
-
-       function sortDescending() {
-               return false;
-       }
-
-       function formatResult( $skin, $result ) {
-               $title = Title::newFromId( $result->id );
-
-               if( !ApprovedRevs::pageIsApprovable( $title ) ) {
-                       return false;
-               }
-
-               $pageLink = Linker::link( $title );
-
-               if ( $this->mMode == 'unapproved' ) {
-                       global $egApprovedRevsShowApproveLatest;
-
-                       $line = $pageLink;
-                       if ( $egApprovedRevsShowApproveLatest &&
-                               $title->userCan( 'approverevisions' ) ) {
-                               $line .= ' (' . Xml::element( 'a',
-                                       array( 'href' => $title->getLocalUrl(
-                                               array(
-                                                       'action' => 'approve',
-                                                       'oldid' => 
$result->latest_id
-                                               )
-                                       ) ),
-                                       wfMessage( 'approvedrevs-approvelatest' 
)->text()
-                               ) . ')';
-                       }
-
-                       return $line;
-               } elseif ( $this->mMode == 'notlatest' ) {
-                       $diffLink = Xml::element( 'a',
-                               array( 'href' => $title->getLocalUrl(
-                                       array(
-                                               'diff' => $result->latest_id,
-                                               'oldid' => $result->rev_id
-                                       )
-                               ) ),
-                               wfMessage( 'approvedrevs-difffromlatest' 
)->text()
-                       );
-
-                       return "$pageLink ($diffLink)";
-               } else { // main mode (pages with an approved revision)
-                       global $wgUser, $wgOut, $wgLang;
-
-                       $additionalInfo = Xml::element( 'span',
-                               array (
-                                       'class' => $result->rev_id == 
$result->latest_id ? 'approvedRevIsLatest' : 'approvedRevNotLatest'
-                               ),
-                               wfMessage( 'approvedrevs-revisionnumber', 
$result->rev_id )->text()
-                       );
-
-                       // Get data on the most recent approval from the
-                       // 'approval' log, and display it if it's there.
-                       $loglist = new LogEventsList( $wgOut->getSkin(), $wgOut 
);
-                       $pager = new LogPager( $loglist, 'approval', '', 
$title->getText() );
-                       $pager->mLimit = 1;
-                       $pager->doQuery();
-                       $row = $pager->mResult->fetchObject();
-
-                       if ( !empty( $row ) ) {
-                               $timestamp = $wgLang->timeanddate( wfTimestamp( 
TS_MW, $row->log_timestamp ), true );
-                               $date = $wgLang->date( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
-                               $time = $wgLang->time( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
-                               $userLink = Linker::userLink( $row->log_user, 
$row->user_name );
-                               $additionalInfo .= ', ' . wfMessage(
-                                       'approvedrevs-approvedby',
-                                       $userLink,
-                                       $timestamp,
-                                       $row->user_name,
-                                       $date,
-                                       $time
-                               )->text();
-                       }
-
-                       return "$pageLink ($additionalInfo)";
-               }
-       }
-
-}
diff --git a/i18n/en.json b/i18n/en.json
index 170970c..3d73562 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -23,7 +23,6 @@
     "approvedrevs-approvedpages": "All pages with an approved revision",
     "approvedrevs-notlatestpages": "Pages whose approved revision is not their 
latest",
     "approvedrevs-unapprovedpages": "Unapproved pages",
-    "approvedrevs-view": "View:",
     "approvedrevs-revisionnumber": "revision $1",
     "approvedrevs-approvedby": "approved by {{GENDER:$3|$1}} on $4 at $5",
     "approvedrevs-difffromlatest": "diff from latest",
@@ -32,6 +31,19 @@
     "approvedrevs-viewlatestrev": "View the most recent revision.",
     "approvedrevs-revision-nav": "($1) $2{{int:pipe-separator}}$3 
($4){{int:pipe-separator}}$5 ($6){{int:pipe-separator}}$7 ($8)",
     "approvedrevs-approvedrevision": "Approved revision",
-    "right-approverevisions": "Set a certain revision of a wiki page as 
approved",
+    "approvedrevs-invalid-pages": "Pages with invalid approvals",
+    "approvedrevs-invalid-files": "Files with invalid approvals",
+    "approvedrevs-invalid-description": "The items below have approved 
revisions despite the fact that they are not considered approvable per this 
wiki's approval permissions. Pages should either have approvals removed or be 
added to the <code>$egApprovedRevsPermissions</code> variable in 
<code>LocalSettings.php</code>.",
+    "approvedrevs-approvedfiles": "All files with an approved revision",
+    "approvedrevs-notlatestfiles": "Files whose approved revision is not their 
latest",
+    "approvedrevs-unapprovedfiles": "Unapproved files",
+    "approvedrevs-view" : "View list of:",
+    "approvedrevs-seealso" : "See also",
+    "approvedrevs-historylabel": "&#9733; Approved revision",
+    "approvedrevs-approvedfile": "Approved file",
+    "approvedrevs-latestfile": "Latest file",
+    "approvedpages": "Approved pages",
+    "approvedfiles": "Approved files",
+    "right-approverevisions": "Set a certain revision of a wiki page as 
approved (deprecated)",
     "right-viewlinktolatest": "View explanatory text at the top of pages that 
have an approved revision"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 0256321..f1279a9 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -26,18 +26,30 @@
        "approvedrevs-noapprovedrevision": "Used if this page has no approved 
revision.\n\nPreceded by subtitle + \"<code><nowiki><br /></nowiki></code>\".",
        "approvedrevs-editwarning": "Used as warning shown in '''bold'''.",
        "approvedrevs": "{{doc-special|ApprovedRevs}}\n{{Identical|Approved 
revision}}",
-       "approvedrevs-approvedpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedRevs]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}.\n\nThis message is followed by a string \" | \" 
and {{msg-mw|Approvedrevs-notlatestpages}}.",
-       "approvedrevs-notlatestpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedRevs]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}, {{msg-mw|Approvedrevs-approvedpages}} and \" | 
\".",
-       "approvedrevs-unapprovedpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedRevs]].\n\nThis message follows 
{{msg-mw|Approvedrevs-notlatestpages}} and \" | \".\n\nSee also:\n* 
{{msg-mw|Approvedrevs-view}}",
-       "approvedrevs-view": "Used as label in 
[[Special:ApprovedRevs]].\n\nThis message is followed by the following 
messages:\n* {{msg-mw|Approvedrevs-approvedpages}}\n* \" | \"\n* 
{{msg-mw|Approvedrevs-notlatestpages}}\n* \" | \"\n* 
{{msg-mw|Approvedrevs-unapprovedpages}}\n{{Identical|View}}",
-       "approvedrevs-revisionnumber": "Used in 
[[Special:ApprovedRevs]].\n\nParameters:\n* $1 - revision 
ID\n{{Identical|Revision}}",
-       "approvedrevs-approvedby": "Used in [[Special:ApprovedRevs]]. 
Parameters:\n* $1 - link to the user's user page, or link to the IP user's 
contributions page\n* $2 - (Unused) time and date\n* $3 - username\n* $4 - 
date\n* $5 - time",
+    "approvedpages": "{{doc-special|ApprovedPages}}\n{{Identical|Approved 
pages}}",
+    "approvedfiles": "{{doc-special|ApprovedFiles}}\n{{Identical|Approved 
files}}",
+       "approvedrevs-approvedpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedPages]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}.\n\nThis message is followed by a string \" | \" 
and {{msg-mw|Approvedrevs-notlatestpages}}.",
+       "approvedrevs-notlatestpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedPages]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}, {{msg-mw|Approvedrevs-approvedpages}} and \" | 
\".",
+       "approvedrevs-unapprovedpages": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedPages]].\n\nThis message follows 
{{msg-mw|Approvedrevs-notlatestpages}} and \" | \".\n\nSee also:\n* 
{{msg-mw|Approvedrevs-view}}",
+       "approvedrevs-view": "Used as label in [[Special:ApprovedPages]] and 
[[Special:ApprovedFiles]].\n\nThis message is followed by the following 
messages:\n* {{msg-mw|Approvedrevs-approvedpages}}\n* \" | \"\n* 
{{msg-mw|Approvedrevs-notlatestpages}}\n* \" | \"\n* 
{{msg-mw|Approvedrevs-unapprovedpages}}\n{{Identical|View}}",
+       "approvedrevs-revisionnumber": "Used in [[Special:ApprovedPages]] and 
[[Special:ApprovedFiles]].\n\nParameters:\n* $1 - revision 
ID\n{{Identical|Revision}}",
+       "approvedrevs-approvedby": "Used in [[Special:ApprovedPages]] and 
[[Special:ApprovedFiles]]. Parameters:\n* $1 - link to the user's user page, or 
link to the IP user's contributions page\n* $2 - (Unused) time and date\n* $3 - 
username\n* $4 - date\n* $5 - time",
        "approvedrevs-difffromlatest": "Used as link text in 
[[Special:ApprovedRevs]].\n\nThe link points to the \"Diff\" page.\n\nThe link 
follows a link which points to the page.",
        "approvedrevs-approvelatest": "Used as link text in 
[[Special:ApprovedRevs]].\n\nThis message means \"approve the latest 
revision\".\n\nThe link follows a link to the page.",
        "approvedrevs-approvethisrev": "Used as link text.\n\nSee also:\n* 
{{msg-mw|Approvedrevs-blankpageshown}}\n* 
{{msg-mw|Approvedrevs-viewlatestrev}}",
        "approvedrevs-viewlatestrev": "Used as link text.\n\nThis message 
follows any one of the messages:\n* {{msg-mw|Approvedrevs-notlatest}}\n* 
{{msg-mw|Approvedrevs-blankpageshown}}",
        "approvedrevs-revision-nav": "{{Optional}}\nAn expansion of the core 
MediaWiki message {{msg-mw|Revision-nav}}.\n\nParameters:\n* $1 - prev diff. 
link text is {{msg-mw|Diff}}\n* $2 - prev link. link text is 
{{msg-mw|Previousrevision}}\n* $3 - approved link. link text is 
{{msg-mw|Approvedrevs-approvedrevision}}\n* $4 - approved diff. link text is 
{{msg-mw|Diff}}\n* $5 - link. link text is {{msg-mw|Currentrevisionlink}}\n* $6 
- cur diff. link text is {{msg-mw|Diff}}\n* $7 - next link. link text is 
{{msg-mw|Nextrevision}}\n* $8 - next diff. link text is {{msg-mw|Diff}}",
        "approvedrevs-approvedrevision": "Used to link directly to the approved 
revision.\n{{Identical|Approved revision}}",
+    "approvedrevs-historylabel": "Used as a marker for the current approved 
revision",
+    "approvedrevs-unapprovedfiles": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedFiles]].\n\nThis message follows 
{{msg-mw|Approvedrevs-notlatestfiles}} and \" | \".\n\nSee also:\n* 
{{msg-mw|Approvedrevs-view}}",
+    "approvedrevs-approvedfile": "Used to link directly to the approved file.",
+    "approvedrevs-approvedfiles": "Used as a '''bold''' message or a link text 
in [[Special:ApprovedFiles]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}.\n\nThis message is followed by a string \" | \" 
and {{msg-mw|Approvedrevs-notlatestfiles}}.",
+    "approvedrevs-notlatestfiles": "Used as a '''bold''' message or a link 
text in [[Special:ApprovedFiles]].\n\nThis message follows 
{{msg-mw|Approvedrevs-view}}, {{msg-mw|Approvedrevs-approvedfiles}} and \" | 
\".",
+    "approvedrevs-latestfile": "Used to show a link to the most recently 
uploaded file",
+    "approvedrevs-seealso" : "Used to show a \"see also\" link to the 
Special:ApprovedPages when on Special:ApprovedFiles, and vice versa.",
+    "approvedrevs-invalid-pages": "Used as a '''bold''' message or a link text 
in [[Special:ApprovedPages]].\n\nSee also:\n* {{msg-mw|Approvedrevs-view}}",
+    "approvedrevs-invalid-files": "Used as a '''bold''' message or a link text 
in [[Special:ApprovedFiles]].\n\nSee also:\n* {{msg-mw|Approvedrevs-view}}",
+    "approvedrevs-invalid-description": "Used as a message to describe the 
purpose of the invalid pages and files list",
        "right-approverevisions": "{{doc-right|approverevisions}}",
        "right-viewlinktolatest": "{{doc-right|viewlinktolatest}}"
-}
+}
\ No newline at end of file
diff --git a/ApprovedRevs.sql b/maintenance/ApprovedRevs.sql
similarity index 100%
rename from ApprovedRevs.sql
rename to maintenance/ApprovedRevs.sql
diff --git a/maintenance/ApprovedRevs_Files.sql 
b/maintenance/ApprovedRevs_Files.sql
new file mode 100644
index 0000000..7ecb3cf
--- /dev/null
+++ b/maintenance/ApprovedRevs_Files.sql
@@ -0,0 +1,7 @@
+CREATE TABLE /*_*/approved_revs_files (
+       file_title varbinary(255) NOT NULL,
+       approved_timestamp binary(14) NOT NULL,
+       approved_sha1 varbinary(32) NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX approved_revs_file_title ON /*_*/approved_revs_files 
(file_title);
diff --git a/specials/SpecialApprovedFiles.php 
b/specials/SpecialApprovedFiles.php
new file mode 100644
index 0000000..04dfd47
--- /dev/null
+++ b/specials/SpecialApprovedFiles.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Special page that displays various lists of files that either do or do
+ * not have an approved revision.
+ *
+ * @author James Montalvo
+ */
+class SpecialApprovedFiles extends SpecialPage {
+
+       /**
+        * Constructor
+        */
+       function __construct() {
+               parent::__construct( 'ApprovedFiles' );
+       }
+
+       function execute( $query ) {
+
+               ApprovedRevs::addCSS();
+               $this->setHeaders();
+
+               $rep = new SpecialApprovedFilesQueryPage( 
$this->getRequest()->getVal( 'show' ) );
+
+               return $rep->execute( $query );
+
+       }
+
+}
diff --git a/specials/SpecialApprovedFilesQueryPage.php 
b/specials/SpecialApprovedFilesQueryPage.php
new file mode 100644
index 0000000..25674e1
--- /dev/null
+++ b/specials/SpecialApprovedFilesQueryPage.php
@@ -0,0 +1,247 @@
+<?php
+
+class SpecialApprovedFilesQueryPage extends SpecialApprovedPagesQueryPage {
+
+       static $repo = null;
+       protected $specialpage = 'ApprovedFiles';
+       protected $header_links = array( // files
+               'approvedrevs-notlatestfiles'     => '', // was 
'notlatestfiles', but is now default
+               'approvedrevs-unapprovedfiles'    => 'unapproved',
+               'approvedrevs-approvedfiles'      => 'allapproved',
+               'approvedrevs-invalid-files' => 'invalid',
+       );
+       protected $other_special_page = 'ApprovedPages';
+
+       #
+       #       FILE QUERY
+       #
+       function getQueryInfo() {
+
+               $tables = array(
+                       'ar' => 'approved_revs_files',
+                       'im' => 'image',
+                       'p' => 'page',
+               );
+
+               $fields = array(
+                       'im.img_name AS title',
+                       'ar.approved_sha1 AS approved_sha1',
+                       'ar.approved_timestamp AS approved_ts',
+                       'im.img_sha1 AS latest_sha1',
+                       'im.img_timestamp AS latest_ts',
+               );
+
+               $conds = array();
+
+               $join_conds = array(
+                       'im' => array( 'LEFT OUTER JOIN', 
'ar.file_title=im.img_name' ),
+                       'p'  => array( 'LEFT OUTER JOIN', 
'im.img_name=p.page_title' ),
+               );
+
+               #
+               #       ALLFILES: list all approved pages
+               #   also includes $this->mMode == 'invalid', see formatResult()
+               #
+               if ( $this->mMode == 'allapproved' ) {
+
+                       $conds['p.page_namespace'] = NS_FILE; // get everything 
from approved_revs table
+
+               #
+               #       UNAPPROVED
+               #
+               } elseif ( $this->mMode == 'unapproved' ) {
+
+                       $tables['c'] = 'categorylinks';
+                       $join_conds['c'] = array( 'LEFT OUTER JOIN', 
'p.page_id=c.cl_from' );
+
+                       $join_conds['im'] = array( 'RIGHT OUTER JOIN', 
'ar.file_title=im.img_name' );
+
+                       $perms = ApprovedRevs::getPermissions();
+
+                       // if all files are not approvable then need to find 
files matching
+                       // page and category permissions        
+                       if ( ! in_array( NS_FILE, array_keys( $perms['Namespace 
Permissions'] ) ) ) {
+
+                               list( $ns, $cat, $pg ) = 
ApprovedRevs::getApprovabilityStringsForDB();
+
+                               $pageCatConditions = array();
+                               if ( $cat !== '' ) {
+                                       $pageCatConditions[] = "c.cl_to IN 
($cat)";
+                               }
+                               if ( $pg  !== '' ) {
+                                       $pageCatConditions[] = "p.page_id IN 
($pg)";
+                               }
+
+                               // if there were any page or category 
conditions, add to $conds
+                               if ( count( $pageCatConditions ) > 0 ) {
+                                       $conds[] = '(' . implode( ' OR ', 
$pageCatConditions ) . ')';
+                               }
+
+                       }
+
+                       $conds['ar.file_title'] = null;
+                       $conds['p.page_namespace'] = NS_FILE;
+
+               #
+               #       INVALID PERMISSIONS
+               #
+               } elseif ( $this->mMode == 'invalid' ) {
+
+                       $tables['c'] = 'categorylinks';
+                       $join_conds['c'] = array( 'LEFT OUTER JOIN', 
'p.page_id=c.cl_from' );
+                       $join_conds['im'] = array( 'LEFT OUTER JOIN', 
'ar.file_title=im.img_name' );
+
+                       $perms = ApprovedRevs::getPermissions();
+                       if ( in_array( NS_FILE, array_keys( $perms['Namespace 
Permissions'] ) ) ) {
+                               // if all files approvable should break out of 
this...since none can be
+                               // invalid if they're all approvable
+                               $conds[] = 'p.page_namespace=1 AND 
p.page_namespace=2'; // impossible condition, hack
+                       }
+                       else {
+                               list( $ns, $cat, $pg ) = 
ApprovedRevs::getApprovabilityStringsForDB();
+
+                               if ( $cat !== '' ) {
+                                       $conds[] = "p.page_id NOT IN (SELECT 
DISTINCT cl_from FROM categorylinks WHERE cl_to IN ($cat))";
+                               }
+                               if ( $pg  !== '' ) {
+                                       $conds[] = "p.page_id NOT IN ($pg)";
+                               }
+
+                       }
+
+                       $conds['p.page_namespace'] = NS_FILE;
+
+               #
+               #       NOTLATEST
+               #
+               } else {
+
+                       // Name/Title both exist, sha1's don't match OR 
timestamps don't match
+                       $conds['p.page_namespace'] = NS_FILE;
+                       $conds[] = "(ar.approved_sha1!=im.img_sha1 OR 
ar.approved_timestamp!=im.img_timestamp)";
+
+               }
+
+               return array(
+                       'tables' => $tables,
+                       'fields' => $fields,
+                       'join_conds' => $join_conds,
+                       'conds' => $conds,
+                       'options' => array( 'DISTINCT' ),
+               );
+
+       }
+
+       function formatResult( $skin, $result ) {
+
+               $title = Title::makeTitle( NS_FILE, $result->title );
+
+               if ( ! self::$repo ) {
+                       self::$repo = RepoGroup::singleton();
+               }
+               
+               $pageLink = Linker::link( $title );
+
+
+               #
+               #       Unapproved Files and invalid Files
+               #
+               if ( $this->mMode == 'unapproved' || $this->mMode == 'invalid' 
) {
+                       global $egApprovedRevsShowApproveLatest;
+
+                       if ( $this->mMode == 'invalid' && ! 
ApprovedRevs::fileIsApprovable( $title ) ) {
+                               // if showing invalid files only, don't show 
files that have real approvability
+                               return '';
+                       }
+
+                       if ( $egApprovedRevsShowApproveLatest && 
ApprovedRevs::userCanApprove( $title ) ) {
+                               $approveLink = ' (' . Xml::element(
+                                       'a',
+                                       array(
+                                               'href' => $title->getLocalUrl(
+                                                       array(
+                                                               'action' => 
'approvefile',
+                                                               'ts' => 
$result->latest_ts,
+                                                               'sha1' => 
$result->latest_sha1
+                                                       )
+                                               )
+                                       ),
+                                       wfMessage( 'approvedrevs-approve' 
)->text()
+                               ) . ')';
+                       }
+                       else {
+                               $approveLink = '';
+                       }
+
+                       return "$pageLink$approveLink";
+
+               #
+               # Not Latest Files:
+               # [[My File.jpg]] (revision 2ba82e7f approved; revision 
6ac914dc latest)
+               } elseif ( $this->mMode == 'notlatestfiles' ) {
+
+                       $approved_file = self::$repo->findFileFromKey(
+                               $result->approved_sha1,
+                               array( 'time' => $result->approved_ts )
+                       );
+                       $latest_file = self::$repo->findFileFromKey(
+                               $result->latest_sha1,
+                               array( 'time' => $result->latest_ts )
+                       );
+
+                       $approvedLink = Xml::element( 'a',
+                               array( 'href' => $approved_file->getUrl() ),
+                               wfMessage( 'approvedrevs-approvedfile' )->text()
+                       );
+                       $latestLink = Xml::element( 'a',
+                               array( 'href' => $latest_file->getUrl() ),
+                               wfMessage( 'approvedrevs-latestfile' )->text()
+                       );
+
+                       return "$pageLink ($approvedLink | $latestLink)";
+
+               #
+               #       All Files with an approved revision
+               #
+               } else { // main mode (pages with an approved revision)
+                       global $wgUser, $wgOut, $wgLang;
+
+                       $additionalInfo = Xml::element( 'span',
+                               array (
+                                       'class' =>
+                                               ( $result->approved_sha1 == 
$result->latest_sha1 && $result->approved_ts == $result->latest_ts ) ? 
'approvedRevIsLatest' : 'approvedRevNotLatest'
+                               ),
+                               wfMessage( 'approvedrevs-revisionnumber', 
substr( $result->approved_sha1, 0, 8 ) )->parse()
+                       );
+
+                       // Get data on the most recent approval from the
+                       // 'approval' log, and display it if it's there.
+                       $sk = $wgUser->getSkin();
+                       $loglist = new LogEventsList( $sk, $wgOut );
+                       $pager = new LogPager( $loglist, 'approval', '', $title 
);
+                       $pager->mLimit = 1;
+                       $pager->doQuery();
+
+                       $result = $pager->getResult();
+                       $row = $result->fetchObject();
+
+
+                       if ( !empty( $row ) ) {
+                               $timestamp = $wgLang->timeanddate( wfTimestamp( 
TS_MW, $row->log_timestamp ), true );
+                               $date = $wgLang->date( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
+                               $time = $wgLang->time( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
+                               $userLink = $sk->userLink( $row->log_user, 
$row->user_name );
+                               $additionalInfo .= ', ' . wfMessage(
+                                       'approvedrevs-approvedby',
+                                       $userLink,
+                                       $timestamp,
+                                       $row->user_name,
+                                       $date,
+                                       $time
+                               )->text();
+                       }
+
+                       return "$pageLink ($additionalInfo)";
+               }
+       }
+}
diff --git a/specials/SpecialApprovedPages.php 
b/specials/SpecialApprovedPages.php
new file mode 100644
index 0000000..c169b4f
--- /dev/null
+++ b/specials/SpecialApprovedPages.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * Special page that displays various lists of pages that either do or do
+ * not have an approved revision.
+ *
+ * @author Yaron Koren
+ */
+class SpecialApprovedPages extends SpecialPage {
+
+       /**
+        * Constructor
+        */
+       function __construct() {
+               parent::__construct( 'ApprovedPages' );
+       }
+
+       function execute( $query ) {
+
+               ApprovedRevs::addCSS();
+               $this->setHeaders();
+
+               $rep = new SpecialApprovedPagesQueryPage( 
$this->getRequest()->getVal( 'show' ) );
+
+               return $rep->execute( $query );
+
+       }
+
+}
\ No newline at end of file
diff --git a/specials/SpecialApprovedPagesQueryPage.php 
b/specials/SpecialApprovedPagesQueryPage.php
new file mode 100644
index 0000000..a5dcc3a
--- /dev/null
+++ b/specials/SpecialApprovedPagesQueryPage.php
@@ -0,0 +1,288 @@
+<?php
+
+class SpecialApprovedPagesQueryPage extends QueryPage {
+
+       protected $mMode;
+       protected $specialpage = 'ApprovedPages';
+       protected $header_links = array(
+               'approvedrevs-notlatestpages'     => '', // was 'notlatest'
+               'approvedrevs-unapprovedpages'    => 'unapproved',
+               'approvedrevs-approvedpages'      => 'all', // was '' (empty 
string)
+               'approvedrevs-invalid-pages' => 'invalid',
+       );
+       protected $other_special_page = 'ApprovedFiles';
+
+       public function __construct( $mode ) {
+               if ( $this instanceof SpecialPage ) {
+                       parent::__construct( $this->specialpage );
+               }
+               $this->mMode = $mode;
+       }
+
+       function getName() {
+               return $this->specialpage;
+       }
+
+       function isExpensive() { return false; }
+
+       function isSyndicated() { return false; }
+
+       // When MW requires PHP 5.4 in the future, move this into a
+       // trait so both special page classes are sourced from it.
+       function getPageHeader() {
+
+               // show the names of the four lists of pages, with the one
+               // corresponding to the current "mode" not being linked
+               $navLinks = '';
+               foreach ( $this->header_links as $msg => $query_param ) {
+                       $navLinks .= '<li>' . $this->createHeaderLink( $msg, 
$query_param ) . '</li>';
+               }
+
+               $header = wfMessage( 'approvedrevs-view' )->text() . ' ';
+               $header .= Xml::tags( 'ul', null, $navLinks ) . "\n";
+
+
+               if ( $this->mMode == 'invalid' ) {
+                       $header .= Xml::tags(
+                               'p', array( 'style' => 'font-style:italic;' ), 
wfMessage( 'approvedrevs-invalid-description' )->parse() );
+               }
+
+               $out = Xml::tags( 'div', array( 'class' => 
'specialapprovedrevs-header' ), $header );
+
+
+               return '<small>' . wfMessage( 'approvedrevs-seealso' )->text() 
. ': ' . Xml::element( 'a',
+                               array( 'href' => SpecialPage::getTitleFor( 
$this->other_special_page )->getLocalURL() ),
+                               wfMessage( strtolower( 
$this->other_special_page ) )->text()
+                       ) . '</small>' . $out;
+
+
+       }
+
+       function createHeaderLink( $msg, $query_param ) {
+
+               $approvedPagesTitle = SpecialPage::getTitleFor( 
$this->getName() );
+
+               if ( $this->mMode == $query_param ) {
+                       return Xml::element( 'strong',
+                               null,
+                               wfMessage( $msg )->text()
+                       );
+               } else {
+                       $show = ( $query_param == '' ) ? array() : array( 
'show' => $query_param );
+                       return Xml::element( 'a',
+                               array( 'href' => 
$approvedPagesTitle->getLocalURL( $show ) ),
+                               wfMessage( $msg )->text()
+                       );
+               }
+
+       }
+
+       /**
+        * Set parameters for standard navigation links.
+        */
+       function linkParameters() {
+               $params = array();
+
+               if ( $this->mMode == 'notlatest' ) {
+                       $params['show'] = 'notlatest';
+               } elseif ( $this->mMode == 'unapproved' ) {
+                       $params['show'] = 'unapproved';
+               } else { // all approved pages
+               }
+
+               return $params;
+       }
+
+       function getPageFooter() {
+       }
+
+       public static function getNsConditionPart( $ns ) {
+               return 'p.page_namespace = ' . $ns;
+       }
+
+       /**
+        * (non-PHPdoc)
+        * @see QueryPage::getSQL()
+        */
+       function getQueryInfo() {
+
+               $tables = array(
+                       'ar' => 'approved_revs',
+                       'p' => 'page',
+                       'pp' => 'page_props',
+               );
+
+               $fields = array(
+                       'p.page_id AS id', // required for all all
+                       'ar.rev_id AS rev_id', // not required for 
"unapproved", but won't hurt anything
+                       'p.page_latest AS latest_id', // required for all
+               );
+
+               $join_conds = array(
+                       'p' => array(
+                               'JOIN', 'ar.page_id=p.page_id'
+                       ),
+                       'pp' => array(
+                               'LEFT OUTER JOIN', 'ar.page_id=pp_page'
+                       ),
+               );
+
+               $bannedNSCheck = '(p.page_namespace NOT IN ('
+                       . implode( ',' , ApprovedRevs::$bannedNamespaceIds )
+                       . '))';
+
+               #
+               #       ALLPAGES: all approved pages
+               #       also includes $this->mMode == 'invalid', see 
formatResult()
+               #
+               if ( $this->mMode == 'all' ) {
+
+                       $conds = $bannedNSCheck; // get everything from 
approved_revs table
+                       // keep default: $conds = "$namespacesString 
(pp_propname = 'approvedrevs' AND pp_value = 'y')";
+
+               #
+               #       UNAPPROVED
+               #
+               } elseif ( $this->mMode == 'unapproved' ) {
+
+                       $tables['c'] = 'categorylinks';
+                       $join_conds['p'] = array( 'RIGHT OUTER JOIN', 
'ar.page_id=p.page_id' ); // override
+                       $join_conds['c'] = array( 'LEFT OUTER JOIN', 
'p.page_id=cl_from' );
+
+                       list( $ns, $cat, $pg ) = 
ApprovedRevs::getApprovabilityStringsForDB();
+                       $conds  = ( $ns === '' )  ? '' : "(p.page_namespace IN 
($ns)) OR ";
+                       $conds .= ( $cat === '' ) ? '' : "(c.cl_to IN ($cat)) 
OR ";
+                       $conds .= ( $pg === '' )  ? '' : "(p.page_id IN ($pg)) 
OR ";
+                       $conds .= "(pp_propname = 'approvedrevs' AND pp_value = 
'y')";
+                       $conds  = "ar.page_id IS NULL AND ($conds) AND 
$bannedNSCheck";
+
+               #
+               #       INVALID PERMISSIONS
+               #
+               } else if ( $this->mMode == 'invalid' ) {
+
+                       $tables['c'] = 'categorylinks';
+                       $join_conds['p'] = array( 'LEFT OUTER JOIN', 
'ar.page_id=p.page_id' );  // override
+                       $join_conds['c'] = array( 'LEFT OUTER JOIN', 
'p.page_id=cl_from' );
+
+                       list( $ns, $cat, $pg ) = 
ApprovedRevs::getApprovabilityStringsForDB();
+                       $conds = "";
+                       $conds .= ( $ns === '' )  ? '' : "(p.page_namespace NOT 
IN ($ns)) AND ";
+                       $conds .= ( $cat === '' ) ? '' : "(p.page_id NOT IN 
(SELECT DISTINCT cl_from FROM categorylinks WHERE cl_to IN ($cat))) AND ";
+                       $conds .= ( $pg === '' )  ? '' : "(p.page_id NOT IN 
($pg)) AND ";
+                       $conds .= "(pp_propname IS NULL OR NOT (pp_propname = 
'approvedrevs' AND pp_value = 'y'))";
+
+                       $options = array( 'DISTINCT' => true );
+
+               #
+               #       NOTLATEST
+               #
+               } else {
+
+                       // gets pages in approved_revs table that
+                       //   (a) are not the latest rev
+                       //   (b) satisfy ApprovedRevs::$permissions
+                       // $tables['c'] = 'categorylinks';
+                       // $join_conds['c'] = array( 'LEFT OUTER JOIN', 
'p.page_id=cl_from' );
+                       // $conds = "p.page_latest != ar.rev_id AND ($conds)";
+                       $conds = "p.page_latest != ar.rev_id AND 
$bannedNSCheck"; // gets everything in the approved_revs table that is not 
latest rev
+
+               }
+
+               $return = array(
+                       'tables' => $tables,
+                       'fields' => $fields,
+                       'join_conds' => $join_conds,
+                       'conds' => $conds,
+                       'options' => array( 'DISTINCT' ),
+               );
+
+               return $return;
+       }
+
+       function getOrder() {
+               return ' ORDER BY p.page_namespace, p.page_title ASC';
+       }
+
+       function getOrderFields() {
+               return array( 'p.page_namespace', 'p.page_title' );
+       }
+
+       function sortDescending() {
+               return false;
+       }
+
+       function formatResult( $skin, $result ) {
+               $title = Title::newFromId( $result->id );
+
+               $pageLink = Linker::link( $title );
+
+               if ( $this->mMode == 'unapproved' || $this->mMode == 'invalid' 
) {
+
+                       global $egApprovedRevsShowApproveLatest;
+
+                       $line = $pageLink;
+                       if ( $egApprovedRevsShowApproveLatest &&
+                               ApprovedRevs::userCanApprove( $title ) ) {
+                               $line .= ' (' . Xml::element( 'a',
+                                       array( 'href' => $title->getLocalUrl(
+                                               array(
+                                                       'action' => 'approve',
+                                                       'oldid' => 
$result->latest_id
+                                               )
+                                       ) ),
+                                       wfMessage( 'approvedrevs-approvelatest' 
)->text()
+                               ) . ')';
+                       }
+
+                       return $line;
+               } elseif ( $this->mMode == 'notlatest' ) {
+                       $diffLink = Xml::element( 'a',
+                               array( 'href' => $title->getLocalUrl(
+                                       array(
+                                               'diff' => $result->latest_id,
+                                               'oldid' => $result->rev_id
+                                       )
+                               ) ),
+                               wfMessage( 'approvedrevs-difffromlatest' 
)->text()
+                       );
+
+                       return "$pageLink ($diffLink)";
+               } else { // main mode (pages with an approved revision)
+                       global $wgUser, $wgOut, $wgLang;
+
+                       $additionalInfo = Xml::element( 'span',
+                               array (
+                                       'class' => $result->rev_id == 
$result->latest_id ? 'approvedRevIsLatest' : 'approvedRevNotLatest'
+                               ),
+                               wfMessage( 'approvedrevs-revisionnumber', 
$result->rev_id )->text()
+                       );
+
+                       // Get data on the most recent approval from the
+                       // 'approval' log, and display it if it's there.
+                       $loglist = new LogEventsList( $wgOut->getSkin(), $wgOut 
);
+                       $pager = new LogPager( $loglist, 'approval', '', 
$title->getText() );
+                       $pager->mLimit = 1;
+                       $pager->doQuery();
+                       $row = $pager->mResult->fetchObject();
+
+                       if ( !empty( $row ) ) {
+                               $timestamp = $wgLang->timeanddate( wfTimestamp( 
TS_MW, $row->log_timestamp ), true );
+                               $date = $wgLang->date( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
+                               $time = $wgLang->time( wfTimestamp( TS_MW, 
$row->log_timestamp ), true );
+                               $userLink = Linker::userLink( $row->log_user, 
$row->user_name );
+                               $additionalInfo .= ', ' . wfMessage(
+                                       'approvedrevs-approvedby',
+                                       $userLink,
+                                       $timestamp,
+                                       $row->user_name,
+                                       $date,
+                                       $time
+                               )->text();
+                       }
+
+                       return "$pageLink ($additionalInfo)";
+               }
+       }
+
+}
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/161169
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Icfde940596ad7eecc33233e6c5681e3f7bf9212e
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/ApprovedRevs
Gerrit-Branch: master
Gerrit-Owner: Jamesmontalvo3 <jamesmontal...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to