* @author Daniel Werner */ abstract class ParserHook { const TYPE_TAG = 0; const TYPE_FUNCTION = 1; /** * @since 0.4.3 * * @var array */ protected static $registeredHooks = array(); /** * Returns an array of registered parser hooks (keys) and their handling * ParserHook deriving class names (values). * * @since 0.4.3 * * @return array */ public static function getRegisteredParserHooks() { return self::$registeredHooks; } /** * Returns the name of the ParserHook deriving class that defines a certain parser hook, * or false if there is none. * * @since 0.4.3 * * @param string $hookName * * @return mixed string or false */ public static function getHookClassName( $hookName ) { return array_key_exists( $hookName, self::$registeredHooks ) ? self::$registeredHooks[$hookName] : false; } /** * @since 0.4 * * @var Processor */ protected $validator; /** * @since 0.4 * * @var Parser */ protected $parser; /** * @since 0.4.4 * * @var PPFrame */ protected $frame; /** * @since 0.4.4 * * @var ParserHook::TYPE_ enum item */ protected $currentType; /** * @since 0.4 * * @var boolean */ public $forTagExtensions; /** * @since 0.4 * * @var boolean */ public $forParserFunctions; /** * Bitfifeld of Options influencing the characteristics of the registered * tag/parser function. * * @since 0.4.13 * * @var int */ protected $parserHookOptions; /** * Gets the name of the parser hook. * * @since 0.4 * * @return string or array of string */ protected abstract function getName(); /** * Renders and returns the output. * * @since 0.4 * * @param array $parameters * * @return string */ protected abstract function render( array $parameters ); /** * Flag for constructor, whether the function hook should be one callable without * leading hash, i.e. {{plural:...}} instead of {{#if:...}} * * @since 0.4.13 */ const FH_NO_HASH = 1; /* * * @ToDo: implementation of this functionality * * Flag for constructor, whether the tag hook should be handled as function tag hook * and not as a normal tag hook. See Parser::setFunctionTagHook() for details. */ #const TH_AS_FUNCTION_TAG = 2; /** * Constructor. * * @since 0.4 * * @param boolean $forTagExtensions * @param boolean $forParserFunctions * @param integer $flag combination of option flags to manipulare the parser hooks * characteristics. The following are available: * - ParserHook::FH_NO_HASH makes the function callable without leading hash. */ public function __construct( $forTagExtensions = true, $forParserFunctions = true, $flags = 0 ) { $this->forTagExtensions = $forTagExtensions; $this->forParserFunctions = $forParserFunctions; // store flags: $this->parserHookOptions = $flags; } /** * Function to hook up the coordinate rendering functions to the parser. * * @since 0.4 * * @param Parser $parser * * @return true */ public function init( Parser &$parser ) { $className = get_class( $this ); $first = true; foreach ( $this->getNames() as $name ) { if ( $first ) { self::$registeredHooks[$name] = $className; $first = false; } // Parser Tag hooking: if ( $this->forTagExtensions ) { $parser->setHook( $name, array( new ParserHookCaller( $className, 'renderTag' ), 'runTagHook' ) ); } // Parser Function hooking: if ( $this->forParserFunctions ) { $flags = 0; $function = 'renderFunction'; $callerFunction = 'runFunctionHook'; // use object arguments if available: if ( defined( 'SFH_OBJECT_ARGS' ) ) { $flags = $flags | SFH_OBJECT_ARGS; $function .= 'Obj'; $callerFunction .= 'Obj'; } // no leading Hash required? if ( $this->parserHookOptions & self::FH_NO_HASH ) { $flags = $flags | SFH_NO_HASH; } $parser->setFunctionHook( $name, array( new ParserHookCaller( $className, $function ), $callerFunction ), $flags ); } } return true; } /** * Returns an array with the names for the parser hook. * * @since 0.4 * * @return array */ protected function getNames() { $names = $this->getName(); if ( !is_array( $names ) ) { $names = array( $names ); } return $names; } /** * Function to add the magic word in pre MW 1.16. * * @since 0.4 * * @param array $magicWords * @param string $langCode * * @return boolean */ public function magic( array &$magicWords, $langCode ) { foreach ( $this->getNames() as $name ) { $magicWords[$name] = array( 0, $name ); } return true; } /** * Handler for rendering the tag hook registered by Parser::setHook() * * @since 0.4 * * @param mixed $input string or null * @param array $args * @param Parser $parser * @param PPFrame $frame Available from 1.16 * * @return string */ public function renderTag( $input, array $args, Parser $parser, PPFrame $frame = null ) { $this->parser = $parser; $this->frame = $frame; $defaultParameters = $this->getDefaultParameters( self::TYPE_TAG ); $defaultParam = array_shift( $defaultParameters ); // If there is a first default parameter, set the tag contents as its value. if ( !is_null( $defaultParam ) && !is_null( $input ) ) { $args[$defaultParam] = $input; } return $this->validateAndRender( $args, self::TYPE_TAG ); } /** * Handler for rendering the function hook registered by Parser::setFunctionHook() * * @since 0.4 * * @param Parser &$parser * ... further arguments ... * * @return array */ public function renderFunction( Parser &$parser /*, n args */ ) { $args = func_get_args(); $this->parser = array_shift( $args ); $output = $this->validateAndRender( $args, self::TYPE_FUNCTION ); $options = $this->getFunctionOptions(); if ( array_key_exists( 'isHTML', $options ) && $options['isHTML'] ) { /** @ToDo: FIXME: Is this really necessary? The same thing is propably going to * happen in Parser::braceSubstitution() if 'isHTML' is set! * @ToDo: other options besides 'isHTML' like 'noparse' are ignored here! */ return $this->parser->insertStripItem( $output, $this->parser->mStripState ); } return array_merge( array( $output ), $options ); } /** * Handler for rendering the function hook registered by Parser::setFunctionHook() together * with object style arguments (SFH_OBJECT_ARGS flag). * * @since 0.4.13 * * @param Parser &$parser * @param PPFrame $frame * @param type $args * @return array */ public function renderFunctionObj( Parser &$parser, PPFrame $frame, $args ) { $this->frame = $frame; // create non-object args for old style 'renderFunction()' $oldStyleArgs = array( &$parser ); foreach( $args as $arg ) { $oldStyleArgs[] = trim( $frame->expand( $arg ) ); } /* * since we can't validate un-expandet arguments, we just go on with old-style function * handling from here. Only advantage is that we have $this->frame set properly. */ return call_user_func_array( array( $this, 'renderFunction' ), $oldStyleArgs ); } /** * Returns the parser function otpions. * * @since 0.4 * * @return array */ protected function getFunctionOptions() { return array(); } /** * Takes care of validation and rendering, and returns the output. * * @since 0.4 * * @param array $arguments * @param integer $type Item of the ParserHook::TYPE_ enum * * @return string */ public function validateAndRender( array $arguments, $type ) { $names = $this->getNames(); $this->validator = Processor::newDefault(); $this->validator->getOptions()->setName( $names[0] ); if ( $type === self::TYPE_FUNCTION ) { $this->validator->setFunctionParams( $arguments, $this->getParameterInfo( $type ), $this->getDefaultParameters( $type ) ); } else { $this->validator->setParameters( $arguments, $this->getParameterInfo( $type ) ); } $this->validator->validateParameters(); $fatalError = $this->validator->hasFatalError(); if ( $fatalError === false ) { $output = $this->render( $this->validator->getParameterValues() ); } else { $output = $this->renderFatalError( $fatalError ); } return $output; } /** * Returns the ProcessingError objects for the errors and warnings that should be displayed. * * @since 0.4 * * @return array of array of ProcessingError */ protected function getErrorsToDisplay() { $errors = array(); $warnings = array(); foreach ( $this->validator->getErrors() as $error ) { // Check if the severity of the error is high enough to display it. if ( $error->shouldShow() ) { $errors[] = $error; } elseif ( $error->shouldWarn() ) { $warnings[] = $error; } } return array( 'errors' => $errors, 'warnings' => $warnings ); } /** * Creates and returns the output when a fatal error prevent regular rendering. * * @since 0.4 * * @param ProcessingError $error * * @return string */ protected function renderFatalError( ProcessingError $error ) { return '