MediaWiki  REL1_22
Action.php
Go to the documentation of this file.
00001 <?php
00037 abstract class Action {
00038 
00043     protected $page;
00044 
00049     protected $context;
00050 
00055     protected $fields;
00056 
00064     final private static function getClass( $action, array $overrides ) {
00065         global $wgActions;
00066         $action = strtolower( $action );
00067 
00068         if ( !isset( $wgActions[$action] ) ) {
00069             return null;
00070         }
00071 
00072         if ( $wgActions[$action] === false ) {
00073             return false;
00074         } elseif ( $wgActions[$action] === true && isset( $overrides[$action] ) ) {
00075             return $overrides[$action];
00076         } elseif ( $wgActions[$action] === true ) {
00077             return ucfirst( $action ) . 'Action';
00078         } else {
00079             return $wgActions[$action];
00080         }
00081     }
00082 
00091     final public static function factory( $action, Page $page, IContextSource $context = null ) {
00092         $classOrCallable = self::getClass( $action, $page->getActionOverrides() );
00093 
00094         if ( is_string( $classOrCallable ) ) {
00095             $obj = new $classOrCallable( $page, $context );
00096             return $obj;
00097         }
00098 
00099         if ( is_callable( $classOrCallable ) ) {
00100             return call_user_func_array( $classOrCallable, array( $page, $context ) );
00101         }
00102 
00103         return $classOrCallable;
00104     }
00105 
00115     final public static function getActionName( IContextSource $context ) {
00116         global $wgActions;
00117 
00118         $request = $context->getRequest();
00119         $actionName = $request->getVal( 'action', 'view' );
00120 
00121         // Check for disabled actions
00122         if ( isset( $wgActions[$actionName] ) && $wgActions[$actionName] === false ) {
00123             $actionName = 'nosuchaction';
00124         }
00125 
00126         // Workaround for bug #20966: inability of IE to provide an action dependent
00127         // on which submit button is clicked.
00128         if ( $actionName === 'historysubmit' ) {
00129             if ( $request->getBool( 'revisiondelete' ) ) {
00130                 $actionName = 'revisiondelete';
00131             } else {
00132                 $actionName = 'view';
00133             }
00134         } elseif ( $actionName == 'editredlink' ) {
00135             $actionName = 'edit';
00136         }
00137 
00138         // Trying to get a WikiPage for NS_SPECIAL etc. will result
00139         // in WikiPage::factory throwing "Invalid or virtual namespace -1 given."
00140         // For SpecialPages et al, default to action=view.
00141         if ( !$context->canUseWikiPage() ) {
00142             return 'view';
00143         }
00144 
00145         $action = Action::factory( $actionName, $context->getWikiPage(), $context );
00146         if ( $action instanceof Action ) {
00147             return $action->getName();
00148         }
00149 
00150         return 'nosuchaction';
00151     }
00152 
00159     final public static function exists( $name ) {
00160         return self::getClass( $name, array() ) !== null;
00161     }
00162 
00167     final public function getContext() {
00168         if ( $this->context instanceof IContextSource ) {
00169             return $this->context;
00170         } else if ( $this->page instanceof Article ) {
00171             // NOTE: $this->page can be a WikiPage, which does not have a context.
00172             wfDebug( __METHOD__ . ': no context known, falling back to Article\'s context.' );
00173             return $this->page->getContext();
00174         }
00175 
00176         wfWarn( __METHOD__ . ': no context known, falling back to RequestContext::getMain().' );
00177         return RequestContext::getMain();
00178     }
00179 
00185     final public function getRequest() {
00186         return $this->getContext()->getRequest();
00187     }
00188 
00194     final public function getOutput() {
00195         return $this->getContext()->getOutput();
00196     }
00197 
00203     final public function getUser() {
00204         return $this->getContext()->getUser();
00205     }
00206 
00212     final public function getSkin() {
00213         return $this->getContext()->getSkin();
00214     }
00215 
00221     final public function getLanguage() {
00222         return $this->getContext()->getLanguage();
00223     }
00224 
00231     final public function getLang() {
00232         wfDeprecated( __METHOD__, '1.19' );
00233         return $this->getLanguage();
00234     }
00235 
00240     final public function getTitle() {
00241         return $this->page->getTitle();
00242     }
00243 
00250     final public function msg() {
00251         $params = func_get_args();
00252         return call_user_func_array( array( $this->getContext(), 'msg' ), $params );
00253     }
00254 
00263     public function __construct( Page $page, IContextSource $context = null ) {
00264         if ( $context === null ) {
00265             wfWarn( __METHOD__ . ' called without providing a Context object.' );
00266             // NOTE: We could try to initialize $context using $page->getContext(),
00267             //      if $page is an Article. That however seems to not work seamlessly.
00268         }
00269 
00270         $this->page = $page;
00271         $this->context = $context;
00272     }
00273 
00278     abstract public function getName();
00279 
00285     public function getRestriction() {
00286         return null;
00287     }
00288 
00298     protected function checkCanExecute( User $user ) {
00299         $right = $this->getRestriction();
00300         if ( $right !== null ) {
00301             $errors = $this->getTitle()->getUserPermissionsErrors( $right, $user );
00302             if ( count( $errors ) ) {
00303                 throw new PermissionsError( $right, $errors );
00304             }
00305         }
00306 
00307         if ( $this->requiresUnblock() && $user->isBlocked() ) {
00308             $block = $user->getBlock();
00309             throw new UserBlockedError( $block );
00310         }
00311 
00312         // This should be checked at the end so that the user won't think the
00313         // error is only temporary when he also don't have the rights to execute
00314         // this action
00315         if ( $this->requiresWrite() && wfReadOnly() ) {
00316             throw new ReadOnlyError();
00317         }
00318         return true;
00319     }
00320 
00325     public function requiresWrite() {
00326         return true;
00327     }
00328 
00333     public function requiresUnblock() {
00334         return true;
00335     }
00336 
00341     protected function setHeaders() {
00342         $out = $this->getOutput();
00343         $out->setRobotPolicy( "noindex,nofollow" );
00344         $out->setPageTitle( $this->getPageTitle() );
00345         $this->getOutput()->setSubtitle( $this->getDescription() );
00346         $out->setArticleRelated( true );
00347     }
00348 
00354     protected function getPageTitle() {
00355         return $this->getTitle()->getPrefixedText();
00356     }
00357 
00363     protected function getDescription() {
00364         return $this->msg( strtolower( $this->getName() ) )->escaped();
00365     }
00366 
00373     abstract public function show();
00374 
00379     abstract public function execute();
00380 }
00381 
00385 abstract class FormAction extends Action {
00386 
00391     abstract protected function getFormFields();
00392 
00397     protected function preText() {
00398         return '';
00399     }
00400 
00404     protected function postText() {
00405         return '';
00406     }
00407 
00412     protected function alterForm( HTMLForm $form ) {
00413     }
00414 
00419     protected function getForm() {
00420         $this->fields = $this->getFormFields();
00421 
00422         // Give hooks a chance to alter the form, adding extra fields or text etc
00423         wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
00424 
00425         $form = new HTMLForm( $this->fields, $this->getContext(), $this->getName() );
00426         $form->setSubmitCallback( array( $this, 'onSubmit' ) );
00427 
00428         // Retain query parameters (uselang etc)
00429         $form->addHiddenField( 'action', $this->getName() ); // Might not be the same as the query string
00430         $params = array_diff_key(
00431             $this->getRequest()->getQueryValues(),
00432             array( 'action' => null, 'title' => null )
00433         );
00434         $form->addHiddenField( 'redirectparams', wfArrayToCgi( $params ) );
00435 
00436         $form->addPreText( $this->preText() );
00437         $form->addPostText( $this->postText() );
00438         $this->alterForm( $form );
00439 
00440         // Give hooks a chance to alter the form, adding extra fields or text etc
00441         wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
00442 
00443         return $form;
00444     }
00445 
00453     abstract public function onSubmit( $data );
00454 
00460     abstract public function onSuccess();
00461 
00469     public function show() {
00470         $this->setHeaders();
00471 
00472         // This will throw exceptions if there's a problem
00473         $this->checkCanExecute( $this->getUser() );
00474 
00475         $form = $this->getForm();
00476         if ( $form->show() ) {
00477             $this->onSuccess();
00478         }
00479     }
00480 
00489     public function execute( array $data = null, $captureErrors = true ) {
00490         try {
00491             // Set a new context so output doesn't leak.
00492             $this->context = clone $this->getContext();
00493 
00494             // This will throw exceptions if there's a problem
00495             $this->checkCanExecute( $this->getUser() );
00496 
00497             $fields = array();
00498             foreach ( $this->fields as $key => $params ) {
00499                 if ( isset( $data[$key] ) ) {
00500                     $fields[$key] = $data[$key];
00501                 } elseif ( isset( $params['default'] ) ) {
00502                     $fields[$key] = $params['default'];
00503                 } else {
00504                     $fields[$key] = null;
00505                 }
00506             }
00507             $status = $this->onSubmit( $fields );
00508             if ( $status === true ) {
00509                 // This might do permanent stuff
00510                 $this->onSuccess();
00511                 return true;
00512             } else {
00513                 return false;
00514             }
00515         }
00516         catch ( ErrorPageError $e ) {
00517             if ( $captureErrors ) {
00518                 return false;
00519             } else {
00520                 throw $e;
00521             }
00522         }
00523     }
00524 }
00525 
00529 abstract class FormlessAction extends Action {
00530 
00536     abstract public function onView();
00537 
00542     protected function getFormFields() {
00543         return false;
00544     }
00545 
00550     public function onSubmit( $data ) {
00551         return false;
00552     }
00553 
00557     public function onSuccess() {
00558         return false;
00559     }
00560 
00561     public function show() {
00562         $this->setHeaders();
00563 
00564         // This will throw exceptions if there's a problem
00565         $this->checkCanExecute( $this->getUser() );
00566 
00567         $this->getOutput()->addHTML( $this->onView() );
00568     }
00569 
00578     public function execute( array $data = null, $captureErrors = true ) {
00579         try {
00580             // Set a new context so output doesn't leak.
00581             $this->context = clone $this->getContext();
00582             if ( is_array( $data ) ) {
00583                 $this->context->setRequest( new FauxRequest( $data, false ) );
00584             }
00585 
00586             // This will throw exceptions if there's a problem
00587             $this->checkCanExecute( $this->getUser() );
00588 
00589             $this->onView();
00590             return true;
00591         }
00592         catch ( ErrorPageError $e ) {
00593             if ( $captureErrors ) {
00594                 return false;
00595             } else {
00596                 throw $e;
00597             }
00598         }
00599     }
00600 }