Merge "registration: Allow extensions to specify which MW core versions they require"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 22 Sep 2015 04:57:47 +0000 (04:57 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 22 Sep 2015 04:57:47 +0000 (04:57 +0000)
1  2 
autoload.php
composer.json
docs/extension.schema.json
includes/GlobalFunctions.php
includes/registration/ExtensionProcessor.php
includes/registration/ExtensionRegistry.php

diff --combined autoload.php
@@@ -149,7 -149,6 +149,7 @@@ $wgAutoloadLocalClasses = array
        'AutoLoader' => __DIR__ . '/includes/AutoLoader.php',
        'AutoloadGenerator' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
        'Autopromote' => __DIR__ . '/includes/Autopromote.php',
 +      'AvroValidator' => __DIR__ . '/includes/utils/AvroValidator.php',
        'BacklinkCache' => __DIR__ . '/includes/cache/BacklinkCache.php',
        'BacklinkJobUtils' => __DIR__ . '/includes/jobqueue/utils/BacklinkJobUtils.php',
        'BackupDumper' => __DIR__ . '/maintenance/backup.inc',
        'CopyJobQueue' => __DIR__ . '/maintenance/copyJobQueue.php',
        'CoreParserFunctions' => __DIR__ . '/includes/parser/CoreParserFunctions.php',
        'CoreTagHooks' => __DIR__ . '/includes/parser/CoreTagHooks.php',
+       'CoreVersionChecker' => __DIR__ . '/includes/registration/CoreVersionChecker.php',
        'CreateAndPromote' => __DIR__ . '/maintenance/createAndPromote.php',
        'CreateFileOp' => __DIR__ . '/includes/filebackend/FileOp.php',
        'CreditsAction' => __DIR__ . '/includes/actions/CreditsAction.php',
        'MWOldPassword' => __DIR__ . '/includes/password/MWOldPassword.php',
        'MWSaltedPassword' => __DIR__ . '/includes/password/MWSaltedPassword.php',
        'MWTidy' => __DIR__ . '/includes/parser/MWTidy.php',
 -      'MWTidyWrapper' => __DIR__ . '/includes/parser/MWTidy.php',
        'MWTimestamp' => __DIR__ . '/includes/MWTimestamp.php',
        'MachineReadableRCFeedFormatter' => __DIR__ . '/includes/rcfeed/MachineReadableRCFeedFormatter.php',
        'MagicWord' => __DIR__ . '/includes/MagicWord.php',
        'MediaWiki\\Logger\\LegacySpi' => __DIR__ . '/includes/debug/logger/LegacySpi.php',
        'MediaWiki\\Logger\\LoggerFactory' => __DIR__ . '/includes/debug/logger/LoggerFactory.php',
        'MediaWiki\\Logger\\MonologSpi' => __DIR__ . '/includes/debug/logger/MonologSpi.php',
 +      'MediaWiki\\Logger\\Monolog\\AvroFormatter' => __DIR__ . '/includes/debug/logger/monolog/AvroFormatter.php',
 +      'MediaWiki\\Logger\\Monolog\\BufferHandler' => __DIR__ . '/includes/debug/logger/monolog/BufferHandler.php',
 +      'MediaWiki\\Logger\\Monolog\\KafkaHandler' => __DIR__ . '/includes/debug/logger/monolog/KafkaHandler.php',
        'MediaWiki\\Logger\\Monolog\\LegacyFormatter' => __DIR__ . '/includes/debug/logger/monolog/LegacyFormatter.php',
        'MediaWiki\\Logger\\Monolog\\LegacyHandler' => __DIR__ . '/includes/debug/logger/monolog/LegacyHandler.php',
        'MediaWiki\\Logger\\Monolog\\LineFormatter' => __DIR__ . '/includes/debug/logger/monolog/LineFormatter.php',
        'MediaWiki\\Logger\\Monolog\\WikiProcessor' => __DIR__ . '/includes/debug/logger/monolog/WikiProcessor.php',
        'MediaWiki\\Logger\\NullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
        'MediaWiki\\Logger\\Spi' => __DIR__ . '/includes/debug/logger/Spi.php',
 +      'MediaWiki\\Tidy\\Html5Depurate' => __DIR__ . '/includes/tidy/Html5Depurate.php',
 +      'MediaWiki\\Tidy\\RaggettBase' => __DIR__ . '/includes/tidy/RaggettBase.php',
 +      'MediaWiki\\Tidy\\RaggettExternal' => __DIR__ . '/includes/tidy/RaggettExternal.php',
 +      'MediaWiki\\Tidy\\RaggettInternalHHVM' => __DIR__ . '/includes/tidy/RaggettInternalHHVM.php',
 +      'MediaWiki\\Tidy\\RaggettInternalPHP' => __DIR__ . '/includes/tidy/RaggettInternalPHP.php',
 +      'MediaWiki\\Tidy\\RaggettWrapper' => __DIR__ . '/includes/tidy/RaggettWrapper.php',
 +      'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php',
        'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
 +      'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php',
        'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php',
        'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php',
        'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
diff --combined composer.json
                "wiki": "https://www.mediawiki.org/"
        },
        "require": {
+               "composer/semver": "0.1.0",
                "cssjanus/cssjanus": "1.1.1",
                "ext-iconv": "*",
 -              "leafo/lessphp": "0.5.0",
                "liuggio/statsd-php-client": "1.0.16",
 -              "mediawiki/at-ease": "1.0.0",
 -              "oojs/oojs-ui": "0.12.6",
 +              "oyejorge/less.php": "1.7.0.5",
 +              "mediawiki/at-ease": "1.1.0",
 +              "oojs/oojs-ui": "0.12.8.1",
                "php": ">=5.3.3",
                "psr/log": "1.0.0",
                "wikimedia/assert": "0.2.2",
 -              "wikimedia/cdb": "1.0.1",
 +              "wikimedia/cdb": "1.3.0",
                "wikimedia/composer-merge-plugin": "1.2.1",
                "wikimedia/ip-set": "1.0.1",
 -              "wikimedia/utfnormal": "1.0.2",
 +              "wikimedia/utfnormal": "1.0.3",
                "wikimedia/wrappedstring": "2.0.0",
                "zordius/lightncandy": "0.21"
        },
                "ext-wikidiff2": "Diff accelerator",
                "ext-apc": "Local data and opcode cache",
                "monolog/monolog": "Flexible debug logging system",
 +              "nmred/kafka-php": "Send debug log events to kafka",
                "pear/mail": "Mail sending support",
                "pear/mail_mime": "Mail sending support",
 -              "pear/mail_mime-decode": "Mail sending support"
 +              "pear/mail_mime-decode": "Mail sending support",
 +              "wikimedia/avro": "Binary serialization format used with kafka"
        },
        "autoload": {
                "psr-0": {
                                "Unlicense"
                        ]
                },
+               "requires": {
+                       "type": "object",
+                       "description": "Indicates what versions of MediaWiki core are required. This syntax may be extended in the future, for example to check dependencies between other extensions.",
+                       "properties": {
+                               "MediaWiki": {
+                                       "type": "string",
+                                       "description": "Version constraint string against MediaWiki core."
+                               }
+                       }
+               },
                "ResourceFileModulePaths": {
                        "type": "object",
                        "description": "Default paths to use for all ResourceLoader file modules",
                        "type": "object",
                        "description": "ResourceLoader LESS variables"
                },
 -              "ResourceLoaderLESSFunctions": {
 -                      "type": "object",
 -                      "description": "ResourceLoader LESS functions"
 -              },
                "ResourceLoaderLESSImportPaths": {
                        "type": "object",
                        "description": "ResourceLoader import paths"
                "config": {
                        "type": "object",
                        "description": "Configuration options for this extension",
 +                      "properties": {
 +                              "_prefix": {
 +                                      "type": "string",
 +                                      "default": "wg",
 +                                      "description": "Prefix to put in front of configuration settings when exporting them to $GLOBALS"
 +                              }
 +                      },
                        "patternProperties": {
                                "^[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*$": {
                                        "type": ["object", "array", "string", "integer", "null", "boolean"],
@@@ -171,7 -171,6 +171,7 @@@ if ( !function_exists( 'hash_equals' ) 
   *
   * @param string $ext Name of the extension to load
   * @param string|null $path Absolute path of where to find the extension.json file
 + * @since 1.25
   */
  function wfLoadExtension( $ext, $path = null ) {
        if ( !$path ) {
   *
   * @see wfLoadExtension
   * @param string[] $exts Array of extension names to load
 + * @since 1.25
   */
  function wfLoadExtensions( array $exts ) {
        global $wgExtensionDirectory;
   * @see wfLoadExtension
   * @param string $skin Name of the extension to load
   * @param string|null $path Absolute path of where to find the skin.json file
 + * @since 1.25
   */
  function wfLoadSkin( $skin, $path = null ) {
        if ( !$path ) {
   *
   * @see wfLoadExtensions
   * @param string[] $skins Array of extension names to load
 + * @since 1.25
   */
  function wfLoadSkins( array $skins ) {
        global $wgStyleDirectory;
@@@ -2183,24 -2179,14 +2183,24 @@@ function wfResetOutputBuffers( $resetGz
                $wgDisableOutputCompression = true;
        }
        while ( $status = ob_get_status() ) {
 -              if ( $status['type'] == 0 /* PHP_OUTPUT_HANDLER_INTERNAL */ ) {
 -                      // Probably from zlib.output_compression or other
 -                      // PHP-internal setting which can't be removed.
 -                      //
 +              if ( isset( $status['flags'] ) ) {
 +                      $flags = PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_REMOVABLE;
 +                      $deleteable = ( $status['flags'] & $flags ) === $flags;
 +              } elseif ( isset( $status['del'] ) ) {
 +                      $deleteable = $status['del'];
 +              } else {
 +                      // Guess that any PHP-internal setting can't be removed.
 +                      $deleteable = $status['type'] !== 0; /* PHP_OUTPUT_HANDLER_INTERNAL */
 +              }
 +              if ( !$deleteable ) {
                        // Give up, and hope the result doesn't break
                        // output behavior.
                        break;
                }
 +              if ( $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier' ) {
 +                      // Unit testing barrier to prevent this function from breaking PHPUnit.
 +                      break;
 +              }
                if ( !ob_end_clean() ) {
                        // Could not remove output buffer handler; abort now
                        // to avoid getting in some kind of infinite loop.
@@@ -3216,6 -3202,7 +3216,7 @@@ function wfUsePHP( $req_ver ) 
   *
   * @see perldoc -f use
   *
+  * @deprecated since 1.26, use the "requires' property of extension.json
   * @param string|int|float $req_ver The version to check, can be a string, an integer, or a float
   * @throws MWException
   */
@@@ -4287,28 -4274,3 +4288,28 @@@ function wfThumbIsStandard( File $file
  
        return true;
  }
 +
 +/**
 + * Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
 + *
 + * Values that exist in both values will be combined with += (all values of the array
 + * of $newValues will be added to the values of the array of $baseArray, while values,
 + * that exists in both, the value of $baseArray will be used).
 + *
 + * @param array $baseArray The array where you want to add the values of $newValues to
 + * @param array $newValues An array with new values
 + * @return array The combined array
 + * @since 1.26
 + */
 +function wfArrayPlus2d( array $baseArray, array $newValues ) {
 +      // First merge items that are in both arrays
 +      foreach ( $baseArray as $name => &$groupVal ) {
 +              if ( isset( $newValues[$name] ) ) {
 +                      $groupVal += $newValues[$name];
 +              }
 +      }
 +      // Now add items that didn't exist yet
 +      $baseArray += $newValues;
 +
 +      return $baseArray;
 +}
@@@ -192,6 -192,16 +192,16 @@@ class ExtensionProcessor implements Pro
                );
        }
  
+       public function getRequirements( array $info ) {
+               $requirements = array();
+               $key = ExtensionRegistry::MEDIAWIKI_CORE;
+               if ( isset( $info['requires'][$key] ) ) {
+                       $requirements[$key] = $info['requires'][$key];
+               }
+               return $requirements;
+       }
        protected function extractHooks( array $info ) {
                if ( isset( $info['Hooks'] ) ) {
                        foreach ( $info['Hooks'] as $name => $value ) {
         */
        protected function extractConfig( array $info ) {
                if ( isset( $info['config'] ) ) {
 +                      if ( isset( $info['config']['_prefix'] ) ) {
 +                              $prefix = $info['config']['_prefix'];
 +                              unset( $info['config']['_prefix'] );
 +                      } else {
 +                              $prefix = 'wg';
 +                      }
                        foreach ( $info['config'] as $key => $val ) {
                                if ( $key[0] !== '@' ) {
 -                                      $this->globals["wg$key"] = $val;
 +                                      $this->globals["$prefix$key"] = $val;
                                }
                        }
                }
   */
  class ExtensionRegistry {
  
+       /**
+        * "requires" key that applies to MediaWiki core/$wgVersion
+        */
+       const MEDIAWIKI_CORE = 'MediaWiki';
        /**
         * Version of the highest supported manifest version
         */
         * @throws Exception
         */
        public function readFromQueue( array $queue ) {
+               global $wgVersion;
                $autoloadClasses = array();
                $processor = new ExtensionProcessor();
+               $incompatible = array();
+               $coreVersionParser = new CoreVersionChecker( $wgVersion );
                foreach ( $queue as $path => $mtime ) {
                        $json = file_get_contents( $path );
                        if ( $json === false ) {
                        // Set up the autoloader now so custom processors will work
                        $GLOBALS['wgAutoloadClasses'] += $autoload;
                        $autoloadClasses += $autoload;
+                       // Check any constraints against MediaWiki core
+                       $requires = $processor->getRequirements( $info );
+                       if ( isset( $requires[self::MEDIAWIKI_CORE] )
+                               && !$coreVersionParser->check( $requires[self::MEDIAWIKI_CORE] )
+                       ) {
+                               // Doesn't match, mark it as incompatible.
+                               $incompatible[] = "{$info['name']} is not compatible with the current "
+                                       . "MediaWiki core (version {$wgVersion}), it requires: ". $requires[self::MEDIAWIKI_CORE]
+                                       . '.';
+                               continue;
+                       }
+                       // Compatible, read and extract info
                        $processor->extractInfo( $path, $info, $version );
                }
+               if ( $incompatible ) {
+                       if ( count( $incompatible ) === 1 ) {
+                               throw new Exception( $incompatible[0] );
+                       } else {
+                               throw new Exception( implode( "\n", $incompatible ) );
+                       }
+               }
                $data = $processor->getExtractedInfo();
                // Need to set this so we can += to it later
                $data['globals']['wgAutoloadClasses'] = array();
                                        $GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
                                        break;
                                case 'array_plus_2d':
 -                                      // First merge items that are in both arrays
 -                                      foreach ( $GLOBALS[$key] as $name => &$groupVal ) {
 -                                              if ( isset( $val[$name] ) ) {
 -                                                      $groupVal += $val[$name];
 -                                              }
 -                                      }
 -                                      // Now add items that didn't exist yet
 -                                      $GLOBALS[$key] += $val;
 +                                      $GLOBALS[$key] = wfArrayPlus2d( $GLOBALS[$key], $val );
                                        break;
                                case 'array_plus':
 -                                      $GLOBALS[$key] = $val + $GLOBALS[$key];
 +                                      $GLOBALS[$key] += $val;
                                        break;
                                case 'array_merge':
                                        $GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );