Your IP : 216.73.216.54


Current Path : /var/www/html/mediawiki-1.43.1/extensions/Maintenance/
Upload File :
Current File : /var/www/html/mediawiki-1.43.1/extensions/Maintenance/Maintenance_body.php

<?php

use MediaWiki\MediaWikiServices;

class SpecialMaintenance extends SpecialPage {
	private $type = '';
	private $metadata = array();
	private $scripts = array();
	private $loc = ''; // for temporary files
	private $errmsg = false; // if this gets set, we halt execution and display this message

	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct( 'Maintenance'/*class*/, 'maintenance'/*restriction*/ );
	}

	public function doesWrites() {
		return true;
	}

	public function getType() { return $this->type; }
	public function getMetadata() { return $this->metadata; }

	/**
	 * Show the special page
	 *
	 * @param $par Mixed: parameter passed to the page or null
	 */
	public function execute( $par ) {
		$user = $this->getUser();
		$out = $this->getOutput();

		# If user is blocked, s/he doesn't need to access this page
		if ( $user->getBlock() ) {
			throw new UserBlockedError( $user->getBlock() );
		}

		# Show a message if the database is in read-only mode
		$this->checkReadOnly();

		# If the user doesn't have the required 'maintenance' permission, display an error
		if ( !$user->isAllowed( 'maintenance' ) ) {
			throw new PermissionsError( 'maintenance' );
		}

		# Grab the ini file and validate it
		$this->metadata = parse_ini_file( __DIR__ . '/metadata.ini', true );
		if ( $this->metadata === false ) {
			throw new ErrorPageError( 'error', 'maintenance-error-badini' );
		}

		$this->scripts = array_keys( $this->metadata );
		$valid = $this->parseMetadata(); // parses the metadata ini and validates it
		if ( !$valid ) {
			throw new ErrorPageError( 'error', 'maintenance-error-badini' );
		}

		$this->type = $par ? $par : '';
		$this->validateType(); // this checks to ensure $par is valid so we don't execute arbitrary code
		if ( $this->type === '' ) {
			$this->makeInitialForm();
		} elseif ( $this->type !== '' && !$this->getRequest()->wasPosted() ) {
			$this->makeForm( $this->type );
		} elseif ( $this->type !== '' && $this->getRequest()->wasPosted() ) {
			$this->executeScript( $this->type );
		}
	}

	private function validateType() {
		if ( !in_array( $this->type, $this->scripts ) ) {
			$this->errmsg = 'maintenance-error-invalidtype';
		}
	}

	private function makeInitialForm() {
		$this->setHeaders();

		$out = $this->getOutput();
		$linkRenderer = $this->getLinkRenderer();

		$out->addWikiMsg( 'maintenance-header' );
		$out->addHTML( '<ul>' );

		// scripts that we allow to run via this interface, from the metadata.ini file
		$scripts = $this->scripts;
		sort( $scripts );
		foreach ( $scripts as $type ) {
			$title = $this->getPageTitle( $type );
			$out->addHTML( '<li>' . $linkRenderer->makeKnownLink(
					$title,
					$type
				) . ' -- ' .
				$this->msg( 'maintenance-' . $type . '-desc' )->parse() . '</li>'
			);
		}
		$out->addHTML( '</ul>' );
	}

	private function makeForm( $type ) {
		global $wgMaintenanceDebug;

		$this->setHeaders();

		$out = $this->getOutput();
		$linkRenderer = $this->getLinkRenderer();

		$out->addHTML( $linkRenderer->makeKnownLink(
				$this->getPageTitle(),
				$this->msg( 'maintenance-backlink' )->text()
			) . '<br />'
		);

		if ( $this->errmsg ) {
			$out->addWikiMsg( $this->errmsg );
			return;
		}

		$out->addWikiMsg( 'maintenance-' . $type );

		$out->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getPageTitle( $type )->getFullURL() ) ) );
		// build the form
		$options = array_merge( $this->metadata[$type]['option'], $this->metadata[$type]['arg'] );
		foreach ( $options as $option ) {
			switch( $option['type'] ) {
				case 'check':
					$out->addHTML( Xml::checkLabel( $this->msg( "maintenance-$type-option-" . $option['name'] )->text(), 'wp' . ucfirst( $option['name'] ), 'wp' . ucfirst( $option['name'] ), $option['default'] ) . '<br />' );
					break;
				case 'input':
					$out->addHTML( Xml::inputLabel( $this->msg( "maintenance-$type-option-" . $option['name'] )->text(), 'wp' . ucfirst( $option['name'] ), 'wp' . ucfirst( $option['name'] ), $option['size'], false, $option['attrib'] ) . '<br />' );
					break;
				case 'password':
					$out->addHTML( Xml::inputLabel( $this->msg( "maintenance-$type-option-" . $option['name'] )->text(), 'wp' . ucfirst( $option['name'] ), 'wp' . ucfirst( $option['name'] ), $option['size'], false, array( 'type' => 'password' ) + $option['attrib'] ) . '<br />' );
					break;
				case 'textarea':
					$out->addHTML( $this->msg( "maintenance-$type-option-" . $option['name'] )->text() . '<textarea name="wp' . ucfirst( $option['name'] ) . '" rows="25" cols="80"></textarea><br />' );
					break;
			}
		}
		$out->addHTML( Xml::checkLabel( $this->msg( 'maintenance-option-quiet' )->text(), 'wpQuiet', 'wpQuiet' ) . '<br />' );
		if ( $wgMaintenanceDebug ) {
			$out->addHTML( Xml::checkLabel( $this->msg( 'maintenance-option-globals' )->text(), 'wpGlobals', 'wpGlobals' ) . '<br />' );
		}
		if ( $this->metadata[$type]['batch'] ) {
			$out->addHTML( Xml::inputLabel( $this->msg( 'maintenance-option-batch-size', $this->metadata[$type]['batch'] )->text(), 'wpBatch-size', 'wpBatch-size' ) . '<br />' );
		}
		$out->addHTML( Xml::submitButton( $this->msg( 'maintenance-option-confirm' )->text(), array( 'name' => 'wpConfirm' ) ) . '</form>' );
		return;
	}

	private function executeScript( $type ) {
		global $IP, $wgMaintenanceDebug;

		$out = $this->getOutput();
		$linkRenderer = $this->getLinkRenderer();

		$this->setHeaders();

		$out->addHTML(
			$linkRenderer->makeKnownLink(
				$this->getPageTitle(),
				$this->msg( 'maintenance-backlink' )->text()
			) . '<br />'
		);

		if ( $this->errmsg ) {
			$out->addWikiMsg( $this->errmsg );
			return;
		}

		@set_time_limit( 0 ); // if we can, disable the time limit
		@ini_set( 'memory_limit', '-1' ); // also try to disable the memory limit
		// run the script and capture output
		define( 'MW_NO_SETUP', true ); // we use our own setup method to work with web requests
		$maintClass = false; // initialize
		// some scripts misbehave and use exit at the end, which messes us up. Let's fix those here
		// (EPIC HAX MAGIC FOLLOWS)
		$needhax = false;
		switch( $type ) {
			case 'sql':
				file_put_contents( $fname = tempnam( sys_get_temp_dir(), 'maintenance' ),
					str_replace(
						array( 'dirname( __FILE__ )' ), // search
						array( '"$IP/maintenance"' ), // replace
						preg_replace( '/\s+(exit|die)\s*\(.*?\)\s*;/', ';',
							file_get_contents( "$IP/maintenance/sql.php" )
						)
					)
				);
				$needhax = true;
				break;
		}
		if ( $needhax ) {
			require_once $fname;
		} elseif ( file_exists( "$IP/maintenance/$type.php" ) ) {
			require_once "$IP/maintenance/$type.php";
		} else {
			throw new MWException( "No such maintenance script $type" );
		}

		// epic hax magic (TODO: figure out a way to do this without using eval, might require core changes)
		eval( 'class WebMaintenanceHack extends ' . $maintClass . ' {
			public $mSpecialMaintenance; //our SpecialMaintenance object
			protected $atLineStart = true;
			protected $lastChannel = null;

			public function setSpecialMaintenance( &$abj ) {
				$this->mSpecialMaintenance = &$abj;
			}

			protected function output( $out, $channel = null ) {
				if ( $this->mQuiet ) {
					return;
				}
				if ( $channel === null ) {
					$this->cleanupChanneled();
					$this->outputChanneled( $out );
				} else {
					$out = preg_replace( \'/\n\z/\', \'\', $out );
					$this->outputChanneled( $out, $channel );
				}
			}

			public function cleanupChanneled() {
				if ( !$this->atLineStart ) {
					$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
					$this->atLineStart = true;
				}
			}

			public function outputChanneled( $msg, $channel = null ) {
				if ( $msg === false ) {
					// For cleanup
					$this->cleanupChanneled();
					return;
				}

				// End the current line if necessary
				if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
					$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
				}

				//look up $msg for l10n, treat as plaintext
				$this->output_i18n( $msg, "output" );

				$this->atLineStart = false;
				if ( $channel === null ) {
					// For unchanneled messages, output trailing newline immediately
					$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
					$this->atLineStart = true;
				}
				$this->lastChannel = $channel;
			}

			protected function error( $err, $die = false ) {
				$this->outputChanneled( false );

				//look up $err for l10n, treat as plaintext
				$this->output_i18n( $err, "error" );

				if( $die ) throw new SpecialMaintenanceException();
			}

			protected function fatalError( $msg, $exitCode = 1 ) {
				$this->error( $msg );
				throw new SpecialMaintenanceException();
			}

			//$type is either "output" or "error"
			private function output_i18n( $msg, $type ) {
				$found = false;
				$metadata = $this->mSpecialMaintenance->getMetadata();
				$script = $this->mSpecialMaintenance->getType();
				foreach( $metadata[$script][$type] as $a ) {
					if( $a["type"] == "string" ) {
						if( trim( $msg ) == $a["match"] ) {
							$this->mSpecialMaintenance->getOutput()->addHTML(
								$this->mSpecialMaintenance->msg( "maintenance-$script-$type-" . $a["name"] )->escaped()
							);
							if( $type == "error" ) {
								$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
							}
							$found = true;
							break;
						}
					} elseif( $a["type"] == "regex" ) {
						$matches = array();
						if( preg_match( "/^" . $a["match"] . "\$/", trim( $msg ), $matches ) ) {
							//$matches contains the respective $1, $2, $3, $4, etc. in order once we take out the first match
							array_shift( $matches );
							$this->mSpecialMaintenance->getOutput()->addHTML(
								$this->mSpecialMaintenance->msg( "maintenance-$script-$type-" . $a["name"], $matches )->escaped()
							);
							if( $type == "error" ) {
								$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
							}
							$found = true;
							break;
						}
					}
				}
				//not found, so just output it raw
				if( !$found ) {
					$this->mSpecialMaintenance->getOutput()->addHTML( htmlspecialchars( $msg ) );
					if( $type == "error" ) {
						$this->mSpecialMaintenance->getOutput()->addHTML( "\n" );
					}
				}
			}

			public function validateParamsAndArgs() {
				$die = false;

				foreach( $this->mParams as $opt => $info ) {
					if( $info["require"] && !$this->hasOption( $opt ) ) {
						$die = true;
					}
				}
				# Check arg list too
				foreach( $this->mArgList as $k => $info ) {
					if( $info["require"] && !$this->hasArg( $k ) ) {
						$die = true;
					}
				}

				return $die;
			}

			public function doValidateParamsAndArgs() {
				return $this->validateParamsAndArgs();
			}

			public function globals() {
				if( $this->hasOption( "globals" ) ) {
					$this->mSpecialMaintenance->getOutput()->addHTML( htmlspecialchars( print_r( $GLOBALS, true ) ) );
				}
			}

			public function memoryLimit() {
				return -1;
			}

		}' );

		// run the script, throwing output into a <pre> block
		$out->addHTML( '<pre>' );
		$script = new WebMaintenanceHack();
		$script->setSpecialMaintenance( $this );
		$res = $this->setup( $script );
		if ( !$res ) {
			return;
		}
		try {
			$script->execute();
			if ( $needhax ) {
				unlink( $fname );
			}
			if ( $wgMaintenanceDebug ) {
				$script->globals();
			}
			$out->addHTML( $this->msg( 'maintenance-output-success', $this->type )->escaped() . "\n" );
		} catch ( SpecialMaintenanceException $e ) {
			$out->addHTML( $this->msg( 'maintenance-output-failure', $this->type )->escaped() . "\n" );
		} catch ( MWException $mwe ) {
			$out->addHTML( htmlspecialchars( $mwe->getText() ) . "\n" );
			$out->addHTML( $this->msg( 'maintenance-output-failure', $this->type )->escaped() . "\n" );
		}
		$out->addHTML( '</pre>' );
		$this->scriptDone( $script );
		$out->addHTML( $linkRenderer->makeKnownLink(
				$this->getPageTitle(),
				$this->msg( 'maintenance-backlink' )->text()
			) . '<br />'
		);
	}

	private function parseMetadata() {
		global $IP;
		$metadata = $this->metadata;
		$i = -1;
		foreach ( $metadata as $script => &$stuff ) {
			// increment $i, which is the index of the $this->scripts array
			$i++;

			// is the script disabled for whatever reason?
			if ( isset( $stuff['enabled'] ) && !$stuff['enabled'] ) {
				unset( $this->scripts[$i] ); // remove it from the list of scripts
				continue;
			}

			// make sure that the script exists
			if ( !file_exists( "$IP/maintenance/$script.php" ) ) {
				unset( $this->scripts[$i] ); // remove it from the list of scripts
				continue;
			}

			// parse options
			if ( !isset( $stuff['option'] ) ) {
				$stuff['option'] = array();
			} elseif ( !is_array( $stuff['option'] ) ) {
				$stuff['option'] = array( $stuff['option'] );
			}
			foreach ( $stuff['option'] as &$option ) {
				$name = strtok( $option, ' ' );
				if ( !preg_match( '/^[a-z0-9-]+$/i', $name ) ) {
					return false;
				}
				$type = strtok( ' ' );
				if ( !in_array( $type, array( 'check', 'input', 'password', 'textarea' ) ) ) {
					return false;
				}
				if ( $type == 'check' ) {
					$default = strtok( ' ' ); // if it's missing, it's automatically false due to how strtok works
					$option = array();
					$option['default'] = $default;
				} elseif ( $type == 'input' || $type == 'password' ) {
					$size = strtok( ' ' ); // integer size or false
					if ( $size !== false && !ctype_digit( $size ) ) {
						return false;
					}
					// rest are attributes, we just ignore poorly-formatted ones here instead of rejecting the entire file
					$attrib = array();
					while ( ( $attr = strtok( ' ' ) ) !== false ) {
						$bits = explode( '=', $attr, 2 );
						if ( count( $bits ) < 2 || !ctype_alpha( $bits[0] ) ) {
							continue;
						}
						$attrib[strtolower( $bits[0] )] = $bits[1]; // $bits[1] gets sanitized by the Sanitizer before being sent off to output
					}
					$option = array();
					$option['size'] = $size;
					$option['attrib'] = $attrib;
				} elseif ( $type == 'textarea' ) {
					$tmpfile = strtok( ' ' );
					$option = array();
					$option['tmpfile'] = $tmpfile;
				}
				$option['name'] = $name;
				$option['type'] = $type;
			}

			// parse args
			if ( !isset( $stuff['arg'] ) ) {
				$stuff['arg'] = array();
			} elseif ( !is_array( $stuff['arg'] ) ) {
				$stuff['arg'] = array( $stuff['arg'] );
			}
			foreach ( $stuff['arg'] as &$arg ) {
				$name = strtok( $arg, ' ' );
				if ( !preg_match( '/^[a-z0-9-]+$/i', $name ) ) {
					return false;
				}
				$type = strtok( ' ' );
				if ( !in_array( $type, array( 'check', 'input', 'password', 'textarea' ) ) ) {
					return false;
				}
				if ( $type == 'check' ) {
					$default = strtok( ' ' ); // if it's missing, it's automatically false due to how strtok works
					$arg = array();
					$arg['default'] = $default;
				} elseif ( $type == 'input' || $type == 'password' ) {
					$size = strtok( ' ' ); // integer size or false
					if ( $size !== false && !ctype_digit( $size ) ) {
						return false;
					}
					// rest are attributes, we just ignore poorly-formatted ones here instead of rejecting the entire file
					$attrib = array();
					while ( ( $attr = strtok( ' ' ) ) !== false ) {
						$bits = explode( '=', $attr, 2 );
						if ( count( $bits ) < 2 || !ctype_alpha( $bits[0] ) ) {
							continue;
						}
						$attrib[strtolower( $bits[0] )] = $bits[1]; // $bits[1] gets sanitized by the Sanitizer before being sent off to output
					}
					$arg = array();
					$arg['size'] = $size;
					$arg['attrib'] = $attrib;
				} elseif ( $type == 'textarea' ) {
					$tmpfile = strtok( ' ' );
					$arg = array();
					$arg['tmpfile'] = $tmpfile;
				}
				$arg['name'] = $name;
				$arg['type'] = $type;
			}

			// parse output messages (for i18n)
			if ( !isset( $stuff['output'] ) ) {
				$stuff['output'] = array();
			} elseif ( !is_array( $stuff['output'] ) ) {
				$stuff['output'] = array( $stuff['output'] );
			}
			foreach ( $stuff['output'] as &$autput ) {
				$bits = explode( ' ', $autput, 3 );
				if ( count( $bits ) < 3 ) {
					return false;
				}
				$autput = array();
				$autput['name'] = strtolower( $bits[0] );
				if ( !preg_match( '/^[a-z0-9-]+$/i', $autput['name'] ) ) {
					return false;
				}
				$autput['type'] = $bits[1];
				if ( !in_array( $autput['type'], array( 'string', 'regex' ) ) ) {
					return false;
				}
				$autput['match'] = $bits[2];
			}

			// parse error messages (for i18n)
			if ( !isset( $stuff['error'] ) ) {
				$stuff['error'] = array();
			} elseif ( !is_array( $stuff['error'] ) ) {
				$stuff['error'] = array( $stuff['error'] );
			}
			foreach ( $stuff['error'] as &$error ) {
				$bits = explode( ' ', $error, 3 );
				if ( count( $bits ) < 3 ) {
					return false;
				}
				$error = array();
				$error['name'] = strtolower( $bits[0] );
				if ( !preg_match( '/^[a-z0-9-]+$/i', $error['name'] ) ) {
					return false;
				}
				$error['type'] = $bits[1];
				if ( !in_array( $error['type'], array( 'string', 'regex' ) ) ) {
					return false;
				}
				$error['match'] = $bits[2];
			}

			// parse special options (batch, stdin)
			if ( !isset( $stuff['batch'] ) ) {
				$stuff['batch'] = 0;
			}
			if ( !isset( $stuff['stdin'] ) ) {
				$stuff['stdin'] = false;
			}
		}
		$this->metadata = $metadata;
		return true;
	}

	private function setup( $maintenance ) {
		// set the memory limit
		@ini_set( 'memory_limit', $maintenance->memoryLimit() );

		// set up the params and args
		$request = $this->getRequest();
		$goptions = $this->metadata[$this->type]['option'];
		$gargs = $this->metadata[$this->type]['arg'];
		$opts = $args = null;
		if ( $goptions != array() ) {
			$opts = array();
			foreach ( $goptions as $a ) {
				if ( $a['type'] == 'textarea' && $a['tmpfile'] ) {
					$fname = tempnam( sys_get_temp_dir(), $a['tmpfile'] );
					$f = fopen( $fname, 'wt' );
					fwrite( $f, $request->getText( 'wp' . ucfirst( $a['name'] ) ) );
					fclose( $f );
					$opts[$a['name']] = $fname;
				} elseif ( $a['type'] == 'textarea' ) {
					$opts[$a['name']] = $request->getText( 'wp' . ucfirst( $a['name'] ) );
				} elseif ( $a['type'] != 'check' ) {
					$opts[$a['name']] = $request->getVal( 'wp' . ucfirst( $a['name'] ) );
				} else {
					$opts[$a['name']] = $request->getCheck( 'wp' . ucfirst( $a['name'] ) );
				}
			}
		}
		if ( $gargs != array() ) {
			$args = array();
			foreach ( $gargs as $a ) {
				if ( $a['type'] == 'textarea' && $a['tmpfile'] ) {
					$fname = tempnam( sys_get_temp_dir(), $a['tmpfile'] );
					$f = fopen( $fname, 'wt' );
					fwrite( $f, $request->getText( 'wp' . ucfirst( $a['name'] ) ) );
					fclose( $f );
					$args[] = $fname;
				} elseif ( $a['type'] == 'textarea' ) {
					$args[] = $request->getText( 'wp' . ucfirst( $a['name'] ) );
				} elseif ( $a['type'] != 'check' ) {
					$args[] = $request->getVal( 'wp' . ucfirst( $a['name'] ) );
				} else {
					$args[] = $request->getCheck( 'wp' . ucfirst( $a['name'] ) );
				}
			}
		}

		// special opts
		if ( $request->getCheck( 'wpQuiet' ) ) {
			$opts['quiet'] = true;
		}
		if ( $request->getCheck( 'wpGlobals' ) ) {
			$opts['globals'] = true;
		}
		if ( $request->getVal( 'wpBatch-size', false ) ) {
			$opts['batch-size'] = $request->getVal( 'wpBatch-size' );
		}

		// per-script special cases
		switch( $this->type ) {
			case 'checkSyntax':
				// need to save list-file to a temp txt file and set list-file to that file's location
				$this->loc = tempnam( sys_get_temp_dir(), "tmpfile" );
				$f = fopen( $this->loc, 'wt' );
				fwrite( $f, $request->getText( 'wpList-file' ) );
				fclose( $f );
				$opts['list-file'] = $this->loc;
				break;
		}

		$maintenance->loadParamsAndArgs( $this->type, $opts, $args );
		$die = $maintenance->doValidateParamsAndArgs();
		if ( $die ) {
			$this->getOutput()->addHTML( $this->msg( 'maintenance-error-badargs' )->text() );
			return false;
		}

		global $IP;
		if ( $maintenance->getDbType() === Maintenance::DB_ADMIN ) {
			global $wgDBuser, $wgDBpassword, $wgDBadminuser, $wgDBadminpassword, $wgDBuserold, $wgDBpasswordold;
			if ( !isset( $wgDBadminuser ) && is_readable( "$IP/AdminSettings.php" ) ) {
				require "$IP/AdminSettings.php";
			}
			if ( isset( $wgDBadminuser ) ) {
				$wgDBuserold = $wgDBuser;
				$wgDBpasswordold = $wgDBpassword;
				$wgDBuser = $wgDBadminuser;
				$wgDBpassword = $wgDBadminpassword;

			}
		}

		return true;
	}

	private function scriptDone( $script ) {
		global $wgDBuser, $wgDBpassword, $wgDBadminuser, $wgDBadminpassword, $wgDBuserold, $wgDBpasswordold;
		if ( $script->getDbType() === Maintenance::DB_ADMIN && isset( $wgDBadminuser ) ) {
			$wgDBuser = $wgDBuserold;
			$wgDBpassword = $wgDBpasswordold;
			unset( $GLOBALS['wgDBuserold'], $GLOBALS['wgDBpasswordold'] );
			MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
		}

		$goptions = $this->metadata[$this->type]['option'];
		$gargs = $this->metadata[$this->type]['arg'];

		if ( $goptions != array() ) {
			foreach ( $goptions as $a ) {
				if ( $a['type'] == 'textarea' && $a['tmpfile'] && file_exists( $a['tmpfile'] ) ) {
					unlink( $a['tmpfile'] );
				}
			}
		}

		if ( $gargs != array() ) {
			foreach ( $gargs as $a ) {
				if ( $a['type'] == 'textarea' && $a['tmpfile'] && file_exists( $a['tmpfile'] ) ) {
					unlink( $a['tmpfile'] );
				}
			}
		}
	}

	protected function getGroupName() {
		return 'wiki';
	}
}

class SpecialMaintenanceException extends MWException {
	// don't need anything here since we never print this out
	// only used as an exception for the "die" feature of Maintenance::error() as opposed to terminating the script
}