Merge "Implemented hasRules()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 30 Dec 2014 00:17:18 +0000 (00:17 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 30 Dec 2014 00:17:18 +0000 (00:17 +0000)
24 files changed:
RELEASE-NOTES-1.25
autoload.php
includes/User.php
includes/api/ApiMain.php
includes/api/ApiQueryAllImages.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/libs/composer/ComposerJson.php [new file with mode: 0644]
includes/libs/composer/ComposerLock.php [new file with mode: 0644]
includes/logging/LogFormatter.php
includes/logging/MergeLogFormatter.php
includes/parser/Parser.php
includes/specials/SpecialListgrouprights.php
includes/specials/SpecialUserrights.php
includes/specials/SpecialVersion.php
languages/Language.php
languages/i18n/en.json
languages/i18n/qqq.json
maintenance/checkComposerLockUpToDate.php [new file with mode: 0644]
tests/phpunit/data/composer/composer.json [new file with mode: 0644]
tests/phpunit/data/composer/composer.lock [new file with mode: 0644]
tests/phpunit/data/composer/new-composer.json [new file with mode: 0644]
tests/phpunit/includes/libs/composer/ComposerJsonTest.php [new file with mode: 0644]
tests/phpunit/includes/libs/composer/ComposerLockTest.php [new file with mode: 0644]

index 77dc55b..065a5d7 100644 (file)
@@ -161,6 +161,8 @@ production.
 * (T76052) list=tags can now indicate whether a tag is defined.
 * (T75522) list=prefixsearch now supports continuation
 * (T78737) action=expandtemplates can now return page properties.
+* (T78690) list=allimages now accepts multiple pipe-separated values
+  for the 'aimime' parameter.
 
 === Action API internal changes in 1.25 ===
 * ApiHelp has been rewritten to support i18n and paginated HTML output.
index 6cefa13..b36153a 100644 (file)
@@ -202,6 +202,7 @@ $wgAutoloadLocalClasses = array(
        'ChangesListSpecialPage' => __DIR__ . '/includes/specialpage/ChangesListSpecialPage.php',
        'ChannelFeed' => __DIR__ . '/includes/Feed.php',
        'CheckBadRedirects' => __DIR__ . '/maintenance/checkBadRedirects.php',
+       'CheckComposerLockUpToDate' => __DIR__ . '/maintenance/checkComposerLockUpToDate.php',
        'CheckExtensionsCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc',
        'CheckImages' => __DIR__ . '/maintenance/checkImages.php',
        'CheckLanguageCLI' => __DIR__ . '/maintenance/language/checkLanguage.inc',
@@ -229,6 +230,8 @@ $wgAutoloadLocalClasses = array(
        'CompareParserCache' => __DIR__ . '/maintenance/compareParserCache.php',
        'CompareParsers' => __DIR__ . '/maintenance/compareParsers.php',
        'ComposerHookHandler' => __DIR__ . '/includes/composer/ComposerHookHandler.php',
+       'ComposerJson' => __DIR__ . '/includes/libs/composer/ComposerJson.php',
+       'ComposerLock' => __DIR__ . '/includes/libs/composer/ComposerLock.php',
        'ComposerPackageModifier' => __DIR__ . '/includes/composer/ComposerPackageModifier.php',
        'ComposerVersionNormalizer' => __DIR__ . '/includes/composer/ComposerVersionNormalizer.php',
        'CompressOld' => __DIR__ . '/maintenance/storage/compressOld.php',
index 34af4c5..88004dc 100644 (file)
@@ -4471,7 +4471,7 @@ class User implements IDBAccessObject {
                if ( $title ) {
                        return Linker::link( $title, htmlspecialchars( $text ) );
                } else {
-                       return $text;
+                       return htmlspecialchars( $text );
                }
        }
 
index a5287b6..1544189 100644 (file)
@@ -710,12 +710,14 @@ class ApiMain extends ApiBase {
 
                        $errMessage = array(
                                'code' => 'internal_api_error_' . get_class( $e ),
-                               'info' => $info,
-                       );
-                       ApiResult::setContent(
-                               $errMessage,
-                               $config->get( 'ShowExceptionDetails' ) ? "\n\n{$e->getTraceAsString()}\n\n" : ''
+                               'info' => '[' . MWExceptionHandler::getLogId( $e ) . '] ' . $info,
                        );
+                       if ( $config->get( 'ShowExceptionDetails' ) ) {
+                               ApiResult::setContent(
+                                       $errMessage,
+                                       MWExceptionHandler::getRedactedTraceAsString( $e )
+                               );
+                       }
                }
 
                // Remember all the warnings to re-add them later
index 20e9f5e..6c962cd 100644 (file)
@@ -232,10 +232,25 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                                $this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
                        }
 
-                       list( $major, $minor ) = File::splitMime( $params['mime'] );
-
-                       $this->addWhereFld( 'img_major_mime', $major );
-                       $this->addWhereFld( 'img_minor_mime', $minor );
+                       $mimeConds = array();
+                       foreach ( $params['mime'] as $mime ) {
+                               list( $major, $minor ) = File::splitMime( $mime );
+                               $mimeConds[] = $db->makeList(
+                                       array(
+                                               'img_major_mime' => $major,
+                                               'img_minor_mime' => $minor,
+                                       ),
+                                       LIST_AND
+                               );
+                       }
+                       // safeguard against internal_api_error_DBQueryError
+                       if ( count( $mimeConds ) > 0 ) {
+                               $this->addWhere( $db->makeList( $mimeConds, LIST_OR ) );
+                       } else {
+                               // no MIME types, no files
+                               $this->getResult()->addValue( 'query', $this->getModuleName(), array() );
+                               return;
+                       }
                }
 
                $limit = $params['limit'];
@@ -359,6 +374,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                        ),
                        'mime' => array(
                                ApiBase::PARAM_DFLT => null,
+                               ApiBase::PARAM_ISMULTI => true,
                        ),
                        'limit' => array(
                                ApiBase::PARAM_DFLT => 10,
@@ -385,6 +401,8 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                        'action=query&list=allimages&aiprop=user|timestamp|url&' .
                                'aisort=timestamp&aidir=older'
                                => 'apihelp-query+allimages-example-recent',
+                       'action=query&list=allimages&aimime=image/png|image/gif'
+                               => 'apihelp-query+allimages-example-mimetypes',
                        'action=query&generator=allimages&gailimit=4&' .
                                'gaifrom=T&prop=imageinfo'
                                => 'apihelp-query+allimages-example-generator',
index 61de86c..9286424 100644 (file)
        "apihelp-query+allimages-param-sha1base36": "SHA1 hash of image in base 36 (used in MediaWiki).",
        "apihelp-query+allimages-param-user": "Only return files uploaded by this user. Can only be used with $1sort=timestamp. Cannot be used together with $1filterbots.",
        "apihelp-query+allimages-param-filterbots": "How to filter files uploaded by bots. Can only be used with $1sort=timestamp. Cannot be used together with $1user.",
-       "apihelp-query+allimages-param-mime": "What MIME type to search for. e.g. image/jpeg.",
+       "apihelp-query+allimages-param-mime": "What MIME types to search for, e.g. <kbd>image/jpeg</kbd>.",
        "apihelp-query+allimages-param-limit": "How many images in total to return.",
        "apihelp-query+allimages-example-B": "Show a list of files starting at the letter \"B\"",
        "apihelp-query+allimages-example-recent": "Show a list of recently uploaded files similar to [[Special:NewFiles]]",
+       "apihelp-query+allimages-example-mimetypes": "Show a list of files with MIME type <kbd>image/png</kbd> or <kbd>image/gif</kbd>",
        "apihelp-query+allimages-example-generator": "Show info about 4 files starting at the letter \"T\"",
 
        "apihelp-query+alllinks-description": "Enumerate all links that point to a given namespace.",
index c2696d1..19ec6cb 100644 (file)
        "apihelp-query+allimages-param-limit": "{{doc-apihelp-param|query+allimages|limit}}",
        "apihelp-query+allimages-example-B": "{{doc-apihelp-example|query+allimages}}",
        "apihelp-query+allimages-example-recent": "{{doc-apihelp-example|query+allimages}}",
+       "apihelp-query+allimages-example-mimetypes": "{{doc-apihelp-example|query+allimages}}",
        "apihelp-query+allimages-example-generator": "{{doc-apihelp-example|query+allimages}}",
        "apihelp-query+alllinks-description": "{{doc-apihelp-description|query+alllinks}}",
        "apihelp-query+alllinks-param-from": "{{doc-apihelp-param|query+alllinks|from}}",
diff --git a/includes/libs/composer/ComposerJson.php b/includes/libs/composer/ComposerJson.php
new file mode 100644 (file)
index 0000000..4786165
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Reads a composer.json file and provides accessors to get
+ * its hash and the required dependencies
+ *
+ * @since 1.25
+ */
+class ComposerJson {
+
+       /**
+        * @param string $location
+        */
+       public function __construct( $location ) {
+               $this->hash = md5_file( $location );
+               $this->contents = json_decode( file_get_contents( $location ), true );
+       }
+
+       public function getHash() {
+               return $this->hash;
+       }
+
+       /**
+        * Dependencies as specified by composer.json
+        *
+        * @return array
+        */
+       public function getRequiredDependencies() {
+               $deps = array();
+               foreach ( $this->contents['require'] as $package => $version ) {
+                       if ( $package !== "php" ) {
+                               $deps[$package] = self::normalizeVersion( $version );
+                       }
+               }
+
+               return $deps;
+       }
+
+       /**
+        * Strip a leading "v" from the version name
+        *
+        * @param string $version
+        * @return string
+        */
+       public static function normalizeVersion( $version ) {
+               if ( strpos( $version, 'v' ) === 0 ) {
+                       // Composer auto-strips the "v" in front of the tag name
+                       $version = ltrim( $version, 'v' );
+               }
+
+               return $version;
+       }
+
+}
\ No newline at end of file
diff --git a/includes/libs/composer/ComposerLock.php b/includes/libs/composer/ComposerLock.php
new file mode 100644 (file)
index 0000000..d2b0e8e
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Reads a composer.lock file and provides accessors to get
+ * its hash and what is installed
+ *
+ * @since 1.25
+ */
+class ComposerLock {
+
+       /**
+        * @param string $location
+        */
+       public function __construct( $location ) {
+               $this->contents = json_decode( file_get_contents( $location ), true );
+       }
+
+       public function getHash() {
+               return $this->contents['hash'];
+       }
+
+       /**
+        * Dependencies currently installed according to composer.lock
+        *
+        * @return array
+        */
+       public function getInstalledDependencies() {
+               $deps = array();
+               foreach ( $this->contents['packages'] as $installed ) {
+                       $deps[$installed['name']] = ComposerJson::normalizeVersion( $installed['version'] );
+               }
+
+               return $deps;
+       }
+}
index 464b723..c6fcdb0 100644 (file)
@@ -355,8 +355,10 @@ class LogFormatter {
                                $element = $this->styleRestricedElement( $element );
                        }
                } else {
-                       $performer = $this->getPerformerElement() . $this->msg( 'word-separator' )->text();
-                       $element = $performer . $this->getRestrictedElement( 'rev-deleted-event' );
+                       $sep = $this->msg( 'word-separator' );
+                       $sep = $this->plaintext ? $sep->text() : $sep->escaped();
+                       $performer = $this->getPerformerElement();
+                       $element = $performer . $sep . $this->getRestrictedElement( 'rev-deleted-event' );
                }
 
                return $element;
@@ -731,7 +733,9 @@ class LegacyLogFormatter extends LogFormatter {
 
                $performer = $this->getPerformerElement();
                if ( !$this->irctext ) {
-                       $action = $performer . $this->msg( 'word-separator' )->text() . $action;
+                       $sep = $this->msg( 'word-separator' );
+                       $sep = $this->plaintext ? $sep->text() : $sep->escaped();
+                       $action = $performer . $sep . $action;
                }
 
                return $action;
index 263d37a..6763dbd 100644 (file)
@@ -40,7 +40,8 @@ class MergeLogFormatter extends LogFormatter {
                $newname = $this->makePageLink( Title::newFromText( $params[3] ) );
                $params[2] = Message::rawParam( $oldname );
                $params[3] = Message::rawParam( $newname );
-               $params[4] = $this->context->getLanguage()->timeanddate( $params[4] );
+               $params[4] = $this->context->getLanguage()
+                       ->userTimeAndDate( $params[4], $this->context->getUser() );
                return $params;
        }
 
index 6b2444e..b7f8cf2 100644 (file)
@@ -146,7 +146,8 @@ class Parser {
         * @var MagicWordArray
         */
        public $mSubstWords;
-       public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
+       # Initialised in constructor
+       public $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols;
 
        # Cleared with clearState():
        /**
@@ -633,7 +634,9 @@ class Parser {
         * @param bool|PPFrame $frame
         * @return mixed|string
         */
-       public function preprocess( $text, Title $title = null, ParserOptions $options, $revid = null, $frame = false ) {
+       public function preprocess( $text, Title $title = null,
+               ParserOptions $options, $revid = null, $frame = false
+       ) {
                wfProfileIn( __METHOD__ );
                $magicScopeVariable = $this->lock();
                $this->startParse( $title, $options, self::OT_PREPROCESS, true );
index 8b9a0ee..828a93b 100644 (file)
@@ -86,13 +86,14 @@ class SpecialListGroupRights extends SpecialPage {
                        $grouppageLocalized = !$msg->isBlank() ?
                                $msg->text() :
                                MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
+                       $grouppageLocalizedTitle = Title::newFromText( $grouppageLocalized );
 
-                       if ( $group == '*' ) {
-                               // Do not make a link for the generic * group
+                       if ( $group == '*' || !$grouppageLocalizedTitle ) {
+                               // Do not make a link for the generic * group or group with invalid group page
                                $grouppage = htmlspecialchars( $groupnameLocalized );
                        } else {
                                $grouppage = Linker::link(
-                                       Title::newFromText( $grouppageLocalized ),
+                                       $grouppageLocalizedTitle,
                                        htmlspecialchars( $groupnameLocalized )
                                );
                        }
index 3e9313c..892ff5b 100644 (file)
@@ -493,25 +493,32 @@ class UserrightsPage extends SpecialPage {
                }
 
                $language = $this->getLanguage();
-               $displayedList = $this->msg( 'userrights-groupsmember-type',
-                       $language->listToText( $list ),
-                       $language->listToText( $membersList )
-               )->plain();
-               $displayedAutolist = $this->msg( 'userrights-groupsmember-type',
-                       $language->listToText( $autoList ),
-                       $language->listToText( $autoMembersList )
-               )->plain();
+               $displayedList = $this->msg( 'userrights-groupsmember-type' )
+                       ->rawParams(
+                               $language->listToText( $list ),
+                               $language->listToText( $membersList )
+                       )->escaped();
+               $displayedAutolist = $this->msg( 'userrights-groupsmember-type' )
+                       ->rawParams(
+                               $language->listToText( $autoList ),
+                               $language->listToText( $autoMembersList )
+                       )->escaped();
 
                $grouplist = '';
                $count = count( $list );
                if ( $count > 0 ) {
-                       $grouplist = $this->msg( 'userrights-groupsmember', $count, $user->getName() )->parse();
+                       $grouplist = $this->msg( 'userrights-groupsmember' )
+                               ->numParams( $count )
+                               ->params( $user->getName() )
+                               ->parse();
                        $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n";
                }
 
                $count = count( $autoList );
                if ( $count > 0 ) {
-                       $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto', $count, $user->getName() )
+                       $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto' )
+                               ->numParams( $count )
+                               ->params( $user->getName() )
                                ->parse();
                        $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n";
                }
@@ -669,9 +676,9 @@ class UserrightsPage extends SpecialPage {
 
                                $member = User::getGroupMember( $group, $user->getName() );
                                if ( $checkbox['irreversible'] ) {
-                                       $text = $this->msg( 'userrights-irreversible-marker', $member )->escaped();
+                                       $text = $this->msg( 'userrights-irreversible-marker', $member )->text();
                                } else {
-                                       $text = htmlspecialchars( $member );
+                                       $text = $member;
                                }
                                $checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
                                        "wpGroup-" . $group, $checkbox['set'], $attr );
index d057b7c..3747e92 100644 (file)
@@ -132,6 +132,7 @@ class SpecialVersion extends SpecialPage {
                                $out->addHtml(
                                        $this->getSkinCredits() .
                                        $this->getExtensionCredits() .
+                                       $this->getExternalLibraries() .
                                        $this->getParserTags() .
                                        $this->getParserFunctionHooks()
                                );
@@ -503,6 +504,45 @@ class SpecialVersion extends SpecialPage {
                return $out;
        }
 
+       /**
+        * Generate an HTML table for external libraries that are installed
+        *
+        * @return string
+        */
+       protected function getExternalLibraries() {
+               global $IP;
+               $path = "$IP/composer.lock";
+               if ( !file_exists( $path ) ) {
+                       // Maybe they're using mediawiki/vendor?
+                       $path = "$IP/vendor/composer.lock";
+                       if ( !file_exists( $path ) ) {
+                               return '';
+                       }
+               }
+
+               $lock = new ComposerLock( $path );
+               $out = Html::element(
+                       'h2',
+                       array( 'id' => 'mw-version-libraries' ),
+                       $this->msg( 'version-libraries' )->text()
+               );
+               $out .= Html::openElement( 'table', array( 'class' => 'wikitable plainlinks', 'id' => 'sv-libraries' ) );
+               $out .= Html::openElement( 'tr' )
+                       . Html::element( 'th', array(), $this->msg( 'version-libraries-library' )->text() )
+                       . Html::element( 'th', array(), $this->msg( 'version-libraries-version' )->text() )
+                       . Html::closeElement( 'tr' );
+               ;
+               foreach ( $lock->getInstalledDependencies() as $name => $version ) {
+                       $out .= Html::openElement( 'tr' )
+                               . Html::rawElement( 'td', array(), Linker::makeExternalLink( "https://packagist.org/packages/$name", $name ) )
+                               . Html::element( 'td', array(), $version )
+                               . Html::closeElement( 'tr' );
+               }
+               $out .= Html::closeElement( 'table' );
+
+               return $out;
+       }
+
        /**
         * Obtains a list of installed parser tags and the associated H2 header
         *
index 93c186c..72cc1ac 100644 (file)
@@ -962,7 +962,17 @@ class Language {
         * @return string
         */
        function getMessageFromDB( $msg ) {
-               return wfMessage( $msg )->inLanguage( $this )->text();
+               return $this->msg( $msg )->text();
+       }
+
+       /**
+        * Get message object in this language. Only for use inside this class.
+        *
+        * @param string $msg Message name
+        * @return Message
+        */
+       protected function msg( $msg ) {
+               return wfMessage( $msg )->inLanguage( $this );
        }
 
        /**
@@ -3406,10 +3416,10 @@ class Language {
                        return '';
                }
                if ( $m > 0 ) {
-                       $and = $this->getMessageFromDB( 'and' );
-                       $space = $this->getMessageFromDB( 'word-separator' );
+                       $and = $this->msg( 'and' )->escaped();
+                       $space = $this->msg( 'word-separator' )->escaped();
                        if ( $m > 1 ) {
-                               $comma = $this->getMessageFromDB( 'comma-separator' );
+                               $comma = $this->msg( 'comma-separator' )->escaped();
                        }
                }
                $s = $l[$m];
@@ -4643,17 +4653,22 @@ class Language {
         * Make a list item, used by various special pages
         *
         * @param string $page Page link
-        * @param string $details Text between brackets
+        * @param string $details HTML safe text between brackets
         * @param bool $oppositedm Add the direction mark opposite to your
         *   language, to display text properly
-        * @return string
+        * @return HTML escaped string
         */
        function specialList( $page, $details, $oppositedm = true ) {
-               $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) .
-                       $this->getDirMark();
-               $details = $details ? $dirmark . $this->getMessageFromDB( 'word-separator' ) .
-                       wfMessage( 'parentheses' )->rawParams( $details )->inLanguage( $this )->escaped() : '';
-               return $page . $details;
+               if ( !$details ) {
+                       return $page;
+               }
+
+               $dirmark = ( $oppositedm ? $this->getDirMark( true ) : '' ) . $this->getDirMark();
+               return
+                       $page .
+                       $dirmark .
+                       $this->msg( 'word-separator' )->escaped() .
+                       $this->msg( 'parentheses' )->rawParams( $details )->escaped();
        }
 
        /**
index 14efc70..fddc975 100644 (file)
        "version-entrypoints-index-php": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:index.php index.php]",
        "version-entrypoints-api-php": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:api.php api.php]",
        "version-entrypoints-load-php": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:load.php load.php]",
+       "version-libraries": "Installed libraries",
+       "version-libraries-library": "Library",
+       "version-libraries-version": "Version",
        "redirect": "Redirect by file, user, page or revision ID",
        "redirect-legend": "Redirect to a file or page",
        "redirect-text": "",
index 04a6f9d..69cfd8c 100644 (file)
        "version-entrypoints-index-php": "A short description of the index.php entry point. Links to the mediawiki.org documentation page for index.php.",
        "version-entrypoints-api-php": "A short description of the api.php entry point. Links to the mediawiki.org documentation page for api.php.",
        "version-entrypoints-load-php": "A short description of the load.php entry point. Links to the mediawiki.org documentation page for load.php.",
+       "version-libraries": "Header on [[Special:Version]] above a table that lists installed external libraries and their version numbers.",
+       "version-libraries-library": "Column header for the library's name",
+       "version-libraries-version": "Column header for the library's version number",
        "redirect": "{{doc-special|Redirect}}\nThis means \"Redirect by file'''name''', user '''ID''', page '''ID''', or revision ID\".",
        "redirect-legend": "Legend of fieldset around input box in [[Special:Redirect]]",
        "redirect-text": "Inside fieldset for [[Special:Redirect]]",
diff --git a/maintenance/checkComposerLockUpToDate.php b/maintenance/checkComposerLockUpToDate.php
new file mode 100644 (file)
index 0000000..5ef2de6
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Checks whether your composer-installed dependencies are up to date
+ *
+ * Composer creates a "composer.lock" file which specifies which versions are installed
+ * (via `composer install`). It has a hash, which can be compared to the value of
+ * the composer.json file to see if dependencies are up to date.
+ */
+class CheckComposerLockUpToDate extends Maintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->mDescription = 'Checks whether your composer.lock file is up to date with the current composer.json';
+       }
+
+       public function execute() {
+               global $IP;
+               $lockLocation = "$IP/composer.lock";
+               $jsonLocation = "$IP/composer.json";
+               if ( !file_exists( $lockLocation ) ) {
+                       // Maybe they're using mediawiki/vendor?
+                       $lockLocation = "$IP/vendor/composer.lock";
+                       if ( !file_exists( $lockLocation ) ) {
+                               $this->error( 'Could not find composer.lock file. Have you run "composer install"?', 1 );
+                       }
+               }
+
+               $lock = new ComposerLock( $lockLocation );
+               $json = new ComposerJson( $jsonLocation );
+
+               if ( $lock->getHash() === $json->getHash() ) {
+                       $this->output( "Your composer.lock file is up to date with current dependencies!\n" );
+                       return;
+               }
+               // Out of date, lets figure out which dependencies are old
+               $found = false;
+               $installed = $lock->getInstalledDependencies();
+               foreach ( $json->getRequiredDependencies() as $name => $version ) {
+                       if ( isset( $installed[$name] ) ) {
+                               if ( $installed[$name] !== $version ) {
+                                       $this->output( "$name: {$installed[$name]} installed, $version required.\n" );
+                                       $found = true;
+                               }
+                       } else {
+                               $this->output( "$name: not installed, $version required.\n" );
+                               $found = true;
+                       }
+               }
+               if ( $found ) {
+                       $this->error( 'Error: your composer.lock file is not up to date, run "composer update" to install newer dependencies', 1 );
+               } else {
+                       // The hash is the entire composer.json file, so it can be updated without any of the dependencies changing
+                       // We couldn't find any out-of-date dependencies, so assume everything is ok!
+                       $this->output( "Your composer.lock file is up to date with current dependencies!\n" );
+               }
+
+       }
+}
+
+$maintClass = 'CheckComposerLockUpToDate';
+require_once RUN_MAINTENANCE_IF_MAIN;
\ No newline at end of file
diff --git a/tests/phpunit/data/composer/composer.json b/tests/phpunit/data/composer/composer.json
new file mode 100644 (file)
index 0000000..bcd196f
--- /dev/null
@@ -0,0 +1,48 @@
+{
+       "name": "mediawiki/core",
+       "description": "Free software wiki application developed by the Wikimedia Foundation and others",
+       "keywords": ["mediawiki", "wiki"],
+       "homepage": "https://www.mediawiki.org/",
+       "authors": [
+               {
+                       "name": "MediaWiki Community",
+                       "homepage": "https://www.mediawiki.org/wiki/Special:Version/Credits"
+               }
+       ],
+       "license": "GPL-2.0",
+       "support": {
+               "issues": "https://bugzilla.wikimedia.org/",
+               "irc": "irc://irc.freenode.net/mediawiki",
+               "wiki": "https://www.mediawiki.org/"
+       },
+       "require": {
+               "leafo/lessphp": "0.5.0",
+               "php": ">=5.3.3",
+               "psr/log": "1.0.0",
+               "cssjanus/cssjanus": "1.1.1",
+               "cdb/cdb": "1.0.0"
+       },
+       "require-dev": {
+               "phpunit/phpunit": "*"
+       },
+       "suggest": {
+               "ext-fileinfo": "*",
+               "ext-mbstring": "*",
+               "ext-wikidiff2": "*",
+               "ext-apc": "*",
+               "monolog/monolog": "*"
+       },
+       "autoload": {
+               "psr-0": {
+                       "ComposerHookHandler": "includes/composer"
+               }
+       },
+       "scripts": {
+               "pre-update-cmd": "ComposerHookHandler::onPreUpdate",
+               "pre-install-cmd": "ComposerHookHandler::onPreInstall"
+       },
+       "config": {
+               "prepend-autoloader": false,
+               "optimize-autoloader": true
+       }
+}
diff --git a/tests/phpunit/data/composer/composer.lock b/tests/phpunit/data/composer/composer.lock
new file mode 100644 (file)
index 0000000..8b490de
--- /dev/null
@@ -0,0 +1,921 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "hash": "cc6e7fc565b246cb30b0cac103a2b31e",
+    "packages": [
+        {
+            "name": "cdb/cdb",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/wikimedia/cdb.git",
+                "reference": "918601ea3d31b8c37312e9c0e54446aa8bfb3425"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/wikimedia/cdb/zipball/918601ea3d31b8c37312e9c0e54446aa8bfb3425",
+                "reference": "918601ea3d31b8c37312e9c0e54446aa8bfb3425",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "GPLv2"
+            ],
+            "authors": [
+                {
+                    "name": "Tim Starling",
+                    "email": "tstarling@wikimedia.org"
+                },
+                {
+                    "name": "Chad Horohoe",
+                    "email": "chad@wikimedia.org"
+                }
+            ],
+            "description": "Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.",
+            "homepage": "https://www.mediawiki.org/wiki/CDB",
+            "time": "2014-11-12 19:03:26"
+        },
+        {
+            "name": "cssjanus/cssjanus",
+            "version": "v1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cssjanus/php-cssjanus.git",
+                "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cssjanus/php-cssjanus/zipball/62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+                "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "jakub-onderka/php-parallel-lint": "0.8.*",
+                "phpunit/phpunit": "3.7.*",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "description": "Convert CSS stylesheets between left-to-right and right-to-left.",
+            "time": "2014-11-14 20:00:50"
+        },
+        {
+            "name": "leafo/lessphp",
+            "version": "v0.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/leafo/lessphp.git",
+                "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/leafo/lessphp/zipball/0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+                "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+                "shasum": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "lessc.inc.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT",
+                "GPL-3.0"
+            ],
+            "authors": [
+                {
+                    "name": "Leaf Corcoran",
+                    "email": "leafot@gmail.com",
+                    "homepage": "http://leafo.net"
+                }
+            ],
+            "description": "lessphp is a compiler for LESS written in PHP.",
+            "homepage": "http://leafo.net/lessphp/",
+            "time": "2014-11-24 18:39:20"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Psr\\Log\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2012-12-21 11:40:51"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f976e5de371104877ebc89bd8fecb0019ed9c119",
+                "reference": "f976e5de371104877ebc89bd8fecb0019ed9c119",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "2.0.*@ALPHA"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Doctrine\\Instantiator\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2014-10-13 12:58:55"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.0.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "7ce9da20f96964bb7a4033f53834df13328dbeab"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7ce9da20f96964bb7a4033f53834df13328dbeab",
+                "reference": "7ce9da20f96964bb7a4033f53834df13328dbeab",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "~1.0",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4.1"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2014-12-02 13:17:01"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.3.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb",
+                "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "File/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2013-10-10 15:34:57"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+                "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "Text/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2014-01-30 17:20:04"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+                "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "PHP/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                ""
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2013-08-02 07:42:54"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "f8d5d08c56de5cfd592b3340424a81733259a876"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/f8d5d08c56de5cfd592b3340424a81733259a876",
+                "reference": "f8d5d08c56de5cfd592b3340424a81733259a876",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2014-08-31 06:12:13"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.3.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
+                "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpunit/php-code-coverage": "~2.0",
+                "phpunit/php-file-iterator": "~1.3.2",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "~1.0.2",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.0",
+                "sebastian/diff": "~1.1",
+                "sebastian/environment": "~1.0",
+                "sebastian/exporter": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "include-path": [
+                "",
+                "../../symfony/yaml/"
+            ],
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "http://www.phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2014-11-11 10:11:09"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "c63d2367247365f688544f0d500af90a11a44c65"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/c63d2367247365f688544f0d500af90a11a44c65",
+                "reference": "c63d2367247365f688544f0d500af90a11a44c65",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "~1.0,>=1.0.1",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.3"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Mock Object library for PHPUnit",
+            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "keywords": [
+                "mock",
+                "xunit"
+            ],
+            "time": "2014-10-03 05:12:11"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
+                "reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.1",
+                "sebastian/exporter": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2014-05-11 23:00:21"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "5843509fed39dee4b356a306401e9dd1a931fec7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/5843509fed39dee4b356a306401e9dd1a931fec7",
+                "reference": "5843509fed39dee4b356a306401e9dd1a931fec7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "http://www.github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2014-08-15 10:29:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e6c71d918088c251b181ba8b3088af4ac336dd7",
+                "reference": "6e6c71d918088c251b181ba8b3088af4ac336dd7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2014-10-25 08:00:45"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0",
+                "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2014-09-10 00:51:36"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
+                "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2014-03-07 15:35:33"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v2.6.0",
+            "target-dir": "Symfony/Component/Yaml",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/Yaml.git",
+                "reference": "51c845cf3e4bfc182d1d5c05ed1c7338361d86f8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/Yaml/zipball/51c845cf3e4bfc182d1d5c05ed1c7338361d86f8",
+                "reference": "51c845cf3e4bfc182d1d5c05ed1c7338361d86f8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\Yaml\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "http://symfony.com",
+            "time": "2014-11-20 13:24:23"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "platform": {
+        "php": ">=5.3.3"
+    },
+    "platform-dev": []
+}
diff --git a/tests/phpunit/data/composer/new-composer.json b/tests/phpunit/data/composer/new-composer.json
new file mode 100644 (file)
index 0000000..0634c2d
--- /dev/null
@@ -0,0 +1,48 @@
+{
+       "name": "mediawiki/core",
+       "description": "Free software wiki application developed by the Wikimedia Foundation and others",
+       "keywords": ["mediawiki", "wiki"],
+       "homepage": "https://www.mediawiki.org/",
+       "authors": [
+               {
+                       "name": "MediaWiki Community",
+                       "homepage": "https://www.mediawiki.org/wiki/Special:Version/Credits"
+               }
+       ],
+       "license": "GPL-2.0",
+       "support": {
+               "issues": "https://bugzilla.wikimedia.org/",
+               "irc": "irc://irc.freenode.net/mediawiki",
+               "wiki": "https://www.mediawiki.org/"
+       },
+       "require": {
+               "leafo/lessphp": "0.5.0",
+               "php": ">=5.3.3",
+               "psr/log": "1.0.0",
+               "cssjanus/cssjanus": "1.1.1",
+               "wikimedia/cdb": "1.0.1"
+       },
+       "require-dev": {
+               "phpunit/phpunit": "*"
+       },
+       "suggest": {
+               "ext-fileinfo": "*",
+               "ext-mbstring": "*",
+               "ext-wikidiff2": "*",
+               "ext-apc": "*",
+               "monolog/monolog": "*"
+       },
+       "autoload": {
+               "psr-0": {
+                       "ComposerHookHandler": "includes/composer"
+               }
+       },
+       "scripts": {
+               "pre-update-cmd": "ComposerHookHandler::onPreUpdate",
+               "pre-install-cmd": "ComposerHookHandler::onPreInstall"
+       },
+       "config": {
+               "prepend-autoloader": false,
+               "optimize-autoloader": true
+       }
+}
diff --git a/tests/phpunit/includes/libs/composer/ComposerJsonTest.php b/tests/phpunit/includes/libs/composer/ComposerJsonTest.php
new file mode 100644 (file)
index 0000000..5c5c828
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+class ComposerJsonTest extends MediaWikiTestCase {
+
+       private $json, $json2;
+
+       public function setUp() {
+               parent::setUp();
+               global $IP;
+               $this->json = "$IP/tests/phpunit/data/composer/composer.json";
+               $this->json2 = "$IP/tests/phpunit/data/composer/new-composer.json";
+       }
+
+       public static function provideGetHash() {
+               return array(
+                       array( 'json', 'cc6e7fc565b246cb30b0cac103a2b31e' ),
+                       array( 'json2', '19921dd1fc457f1b00561da932432001' ),
+               );
+       }
+
+       /**
+        * @dataProvider provideGetHash
+        * @covers ComposerJsonParser::getHash
+        */
+       public function testIsHashUpToDate( $file, $expected ) {
+               $json = new ComposerJson( $this->$file );
+               $this->assertEquals( $expected, $json->getHash() );
+       }
+
+       /**
+        * @covers ComposerLockComparer::getRequiredDependencies
+        */
+       public function testGetRequiredDependencies() {
+               $json = new ComposerJson( $this->json );
+               $this->assertArrayEquals( array(
+                       'cdb/cdb' => '1.0.0',
+                       'cssjanus/cssjanus' => '1.1.1',
+                       'leafo/lessphp' => '0.5.0',
+                       'psr/log' => '1.0.0',
+               ), $json->getRequiredDependencies(), false, true );
+       }
+
+       public static function provideNormalizeVersion() {
+               return array(
+                       array( 'v1.0.0', '1.0.0' ),
+                       array( '0.0.5', '0.0.5' ),
+               );
+       }
+
+       /**
+        * @dataProvider provideNormalizeVersion
+        * @covers ComposerJsonParser::normalizeVersion
+        */
+       public function testNormalizeVersion( $input, $expected ) {
+               $this->assertEquals( $expected, ComposerJson::normalizeVersion( $input ) );
+       }
+}
diff --git a/tests/phpunit/includes/libs/composer/ComposerLockTest.php b/tests/phpunit/includes/libs/composer/ComposerLockTest.php
new file mode 100644 (file)
index 0000000..5b09cdb
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+
+class ComposerLockTest extends MediaWikiTestCase {
+
+       private $lock;
+
+       public function setUp() {
+               parent::setUp();
+               global $IP;
+               $this->lock = "$IP/tests/phpunit/data/composer/composer.lock";
+       }
+
+       public function testGetHash() {
+               $lock = new ComposerLock( $this->lock );
+               $this->assertEquals( 'cc6e7fc565b246cb30b0cac103a2b31e', $lock->getHash() );
+       }
+
+       /**
+        * @covers ComposerLockParser::getInstalledDependencies
+        */
+       public function testGetInstalledDependencies() {
+               $lock = new ComposerLock( $this->lock );
+               $this->assertArrayEquals( array(
+                       'cdb/cdb' => '1.0.0',
+                       'cssjanus/cssjanus' => '1.1.1',
+                       'leafo/lessphp' => '0.5.0',
+                       'psr/log' => '1.0.0',
+               ), $lock->getInstalledDependencies(), false, true );
+       }
+
+}