diff options
Diffstat (limited to 'OAuth/src/Control/SubmitControl.php')
-rw-r--r-- | OAuth/src/Control/SubmitControl.php | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/OAuth/src/Control/SubmitControl.php b/OAuth/src/Control/SubmitControl.php new file mode 100644 index 00000000..e80a30d5 --- /dev/null +++ b/OAuth/src/Control/SubmitControl.php @@ -0,0 +1,228 @@ +<?php + +namespace MediaWiki\Extensions\OAuth\Control; + +/** + * (c) Aaron Schulz 2013, GPL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +/** + * Handle the logic of submitting a client request + */ +abstract class SubmitControl extends \ContextSource { + /** @var array (field name => value) */ + protected $vals; + + /** + * @param \IContextSource $context + * @param array $params + */ + public function __construct( \IContextSource $context, array $params ) { + $this->setContext( $context ); + $this->vals = $params; + } + + /** + * @param array $params + */ + public function setInputParameters( array $params ) { + $this->vals = $params; + } + + /** + * Attempt to validate and submit this data + * + * This will check basic permissions, validate the action and parameters + * and route the submission handling to the internal subclass function. + * + * @throws \MWException + * @return \Status + */ + public function submit() { + $status = $this->checkBasePermissions(); + if ( !$status->isOK() ) { + return $status; + } + + $action = $this->vals['action']; + $required = $this->getRequiredFields(); + if ( !isset( $required[$action] ) ) { + // @TODO: check for field-specific message first + return $this->failure( 'invalid_field_action', 'mwoauth-invalid-field', 'action' ); + } + + $status = $this->validateFields( $required[$action] ); + if ( !$status->isOK() ) { + return $status; + } + + $status = $this->processAction( $action ); + if ( $status instanceof \Status ) { + return $status; + } else { + throw new \MWException( "Submission action '$action' not handled." ); + } + } + + /** + * Given an HTMLForm descriptor array, register the field validation callbacks + * + * @param array $descriptors + * @return array + */ + public function registerValidators( array $descriptors ) { + foreach ( $descriptors as $field => &$description ) { + if ( array_key_exists( 'validation-callback', $description ) ) { + continue; // already set to something + } + $control = $this; + $description['validation-callback'] = + function ( $value, $allValues, $form ) use ( $control, $field ) { + return $control->validateFieldInternal( $field, $value, $allValues, $form ); + }; + } + return $descriptors; + } + + /** + * This method should not be called outside MWOAuthSubmitControl + * + * @param string $field + * @param string $value + * @param array $allValues + * @param \HTMLForm $form + * @throws \MWException + * @return bool|string + */ + public function validateFieldInternal( $field, $value, $allValues, $form ) { + if ( !isset( $allValues['action'] ) && isset( $this->vals['action'] ) ) { + // The action may be derived, especially for multi-button forms. + // Such an HTMLForm will not have an action key set in $allValues. + $allValues['action'] = $this->vals['action']; // injected + } + if ( !isset( $allValues['action'] ) ) { + throw new \MWException( "No form action defined; cannot validate fields." ); + } + $validators = $this->getRequiredFields(); + if ( !isset( $validators[$allValues['action']][$field] ) ) { + return true; // nothing to check + } + $validator = $validators[$allValues['action']][$field]; + $isValid = is_string( $validator ) // regex + ? preg_match( $validator, $value ) + : $validator( $value, $allValues ); + if ( !$isValid ) { + $errorMessage = $this->msg( 'mwoauth-invalid-field-' . $field ); + if ( !$errorMessage->isDisabled() ) { + return $errorMessage->text(); + } + + $generic = ''; + if ( $form->getField( $field )->canDisplayErrors() ) { + // error can be attached to the field so no need to mention the field name + $generic = '-generic'; + } + + $problem = 'invalid'; + if ( $value === '' && !$generic ) { + $problem = 'missing'; + } + + // messages: mwoauth-missing-field, mwoauth-invalid-field, mwoauth-invalid-field-generic + return $this->msg( "mwoauth-$problem-field$generic", $field )->text(); + } + return true; + } + + /** + * Get the field names and their validation regexes or functions + * (which return a boolean) for each action that this controller handles. + * When functions are used, they take (field value, field/value map) as params. + * + * @return array (action => (field name => validation regex or function)) + */ + abstract protected function getRequiredFields(); + + /** + * Check action-independent permissions against the user for this submission + * + * @return \Status + */ + abstract protected function checkBasePermissions(); + + /** + * Check that the action is valid and that the required fields are valid + * + * @param array $required (field => regex or callback) + * @return \Status + */ + protected function validateFields( array $required ) { + foreach ( $required as $field => $validator ) { + if ( !isset( $this->vals[$field] ) ) { + // @TODO: check for field-specific message first + return $this->failure( "missing_field_$field", 'mwoauth-missing-field', $field ); + } elseif ( !is_scalar( $this->vals[$field] ) && $field !== 'restrictions' ) { + // @TODO: check for field-specific message first + return $this->failure( "invalid_field_$field", 'mwoauth-invalid-field', $field ); + } + if ( is_string( $this->vals[$field] ) ) { + $this->vals[$field] = trim( $this->vals[$field] ); // trim all input + } + $valid = is_string( $validator ) // regex + ? preg_match( $validator, $this->vals[$field] ) + : $validator( $this->vals[$field], $this->vals ); + if ( !$valid ) { + // @TODO: check for field-specific message first + return $this->failure( "invalid_field_$field", 'mwoauth-invalid-field', $field ); + } + } + return $this->success(); + } + + /** + * Attempt to validate and submit this data for the given action + * + * @param string $action + * @return \Status + */ + abstract protected function processAction( $action ); + + /** + * @param string $error API error key + * @param string $msg Message key + * @param mixed ...$params Additional arguments used as message parameters + * @return \Status + */ + protected function failure( $error, $msg, ...$params ) { + // Use the same logic as wfMessage + if ( isset( $params[0] ) && is_array( $params[0] ) ) { + $params = $params[0]; + } + $status = \Status::newFatal( $this->msg( $msg, $params ) ); + $status->value = [ 'error' => $error, 'result' => null ]; + return $status; + } + + /** + * @param mixed|null $value + * @return \Status + */ + protected function success( $value = null ) { + return \Status::newGood( [ 'error' => null, 'result' => $value ] ); + } +} |