Файл: library/XenForo/Controller.php
Строк: 1657
<?php
/**
* General base class for controllers. Controllers should implement methods named
* actionX with no arguments. These will be called by the dispatcher based on the
* requested route. They should return the object returned by {@link responseReroute()},
* {@link responseError()}, or {@link responseView()},.
*
* All responses can take paramaters that will be passed to the container view
* (ie, two-phase view), if there is one.
*
* @package XenForo_Mvc
*/
abstract class XenForo_Controller
{
/**
* Request object.
*
* @var Zend_Controller_Request_Http
*/
protected $_request;
/**
* Response object.
*
* @var Zend_Controller_Response_Http
*/
protected $_response;
/**
* The route match object for this request.
*
* @var XenForo_RouteMatch
*/
protected $_routeMatch;
/**
* Input object.
*
* @var XenForo_Input
*/
protected $_input;
/**
* Standard approach to caching model objects for the lifetime of the controller.
*
* @var array
*/
protected $_modelCache = array();
/**
* List of explicit changes to the view state. View state changes are specific
* to the dependency manager, but may include things like changing the styleId.
*
* @var array Key-value pairs
*/
protected $_viewStateChanges = array();
/**
* Container for various items that have been "executed" in one controller and
* shouldn't be executed again in this request.
*
* @var array
*/
protected static $_executed = array();
/**
* Gets the response for a generic no permission page.
*
* @return XenForo_ControllerResponse_Error
*/
abstract public function responseNoPermission();
/**
* Constructor
*
* @param Zend_Controller_Request_Http
* @param Zend_Controller_Response_Http
* @param XenForo_RouteMatch
*/
public function __construct(Zend_Controller_Request_Http $request, Zend_Controller_Response_Http $response, XenForo_RouteMatch $routeMatch)
{
$this->_request = $request;
$this->_response = $response;
$this->_routeMatch = $routeMatch;
$this->_input = new XenForo_Input($this->_request);
}
/**
* Gets the specified model object from the cache. If it does not exist,
* it will be instantiated.
*
* @param string $class Name of the class to load
*
* @return XenForo_Model
*/
public function getModelFromCache($class)
{
if (!isset($this->_modelCache[$class]))
{
$this->_modelCache[$class] = XenForo_Model::create($class);
}
return $this->_modelCache[$class];
}
/**
* Gets the request object.
*
* @return Zend_Controller_Request_Http
*/
public function getRequest()
{
return $this->_request;
}
/**
* Gets the input object.
*
* @return XenForo_Input
*/
public function getInput()
{
return $this->_input;
}
/**
* Sets a change to the view state.
*
* @param string $state Name of state to change
* @param mixed $data
*/
public function setViewStateChange($state, $data)
{
$this->_viewStateChanges[$state] = $data;
}
/**
* Gets all the view state changes.
*
* @return array Key-value pairs
*/
public function getViewStateChanges()
{
return $this->_viewStateChanges;
}
/**
* Gets the type of response that has been requested.
*
* @return string
*/
public function getResponseType()
{
return $this->_routeMatch->getResponseType();
}
/**
* Gets the route match for this request. This can be modified to change
* the response type, and the major/minor sections that will be used to
* setup navigation.
*
* @return XenForo_RouteMatch
*/
public function getRouteMatch()
{
return $this->_routeMatch;
}
/**
* Checks a request for CSRF issues. This is only checked for POST requests
* (with session info) that aren't Ajax requests (relies on browser-level
* cross-domain policies).
*
* The token is retrieved from the "_xfToken" request param.
*
* @param string $action
*/
protected function _checkCsrf($action)
{
if (isset(self::$_executed['csrf']))
{
return;
}
self::$_executed['csrf'] = true;
if (!XenForo_Application::isRegistered('session'))
{
return;
}
if ($this->_request->isPost() || substr($this->getResponseType(), 0, 2) == 'js')
{
// post and all json requests require a token
$this->_checkCsrfFromToken($this->_request->getParam('_xfToken'));
}
}
/**
* Performs particular actions if the request method is POST
*
* @param string $action
*/
protected function _handlePost($action)
{
if ($this->_request->isPost() && $delay = XenForo_Application::get('options')->delayPostResponses)
{
usleep($delay * 1000000);
}
}
/**
* Gets for a CSRF issue using a standard formatted token.
* Throws an exception if a CSRF issue is detected.
*
* @param string|null $token Format: <user id>,<request time>,<token>. Null pulls from _xfToken
* @param boolean $throw If true, an exception is thrown when failing; otherwise, a return is used
*
* @return boolean True if passed, false otherwise; only applies when $throw is false
*/
protected function _checkCsrfFromToken($token = null, $throw = true)
{
if ($token === null)
{
$token = $this->_input->filterSingle('_xfToken', XenForo_Input::STRING);
}
$visitingUser = XenForo_Visitor::getInstance();
$visitingUserId = $visitingUser['user_id'];
if (!$visitingUserId)
{
// don't check for guests
return true;
}
$token = strval($token);
$csrfAttempt = 'invalid';
if ($token === '')
{
$csrfAttempt = 'missing';
}
$tokenParts = explode(',', $token);
if (count($tokenParts) == 3)
{
list($tokenUserId, $tokenTime, $tokenValue) = $tokenParts;
if (strval($tokenUserId) === strval($visitingUserId))
{
if (($tokenTime + 86400) < XenForo_Application::$time)
{
$csrfAttempt = 'expired';
}
else if (sha1($tokenTime . $visitingUser['csrf_token']) == $tokenValue)
{
$csrfAttempt = false;
}
}
}
if ($csrfAttempt)
{
if ($throw)
{
throw $this->responseException(
$this->responseError(new XenForo_Phrase('security_error_occurred'))
);
}
else
{
return false;
}
}
return true;
}
/**
* Setup the session.
*
* @param string $action
*/
protected function _setupSession($action)
{
if (XenForo_Application::isRegistered('session'))
{
return;
}
$session = XenForo_Session::startPublicSession($this->_request);
}
/**
* This function is called immediately before an action is dispatched.
*
* @param string Action that is requested
* @param string
*/
final public function preDispatch($action, $controllerName)
{
$this->_preDispatchFirst($action);
$this->_setupSession($action);
$this->_checkCsrf($action);
$this->_handlePost($action);
$this->_preDispatchType($action);
$this->_preDispatch($action);
XenForo_CodeEvent::fire('controller_pre_dispatch', array($this, $action, $controllerName), $controllerName);
}
/**
* Method designed to be overridden by child classes to add pre-dispatch behaviors
* before any other pre-dispatch checks are called.
*
* @param string $action
*/
protected function _preDispatchFirst($action)
{
}
/**
* Method designed to be overridden by child classes to add pre-dispatch
* behaviors. This differs from {@link _preDispatch()} in that it is designed
* for abstract controller type classes to override. Specific controllers
* should override _preDispatch instead.
*
* @param string $action Action that is requested
*/
protected function _preDispatchType($action)
{
}
/**
* Method designed to be overridden by child classes to add pre-dispatch
* behaviors. This method should only be overridden by specific, concrete
* controllers.
*
* @param string Action that is requested
*/
protected function _preDispatch($action)
{
}
/**
* This function is called immediately after an action is dispatched.
*
* @param mixed The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
* @param string The name of the final controller that was invoked
* @param string The name of the final action that was invoked
*/
final public function postDispatch($controllerResponse, $controllerName, $action)
{
$this->updateSession($controllerResponse, $controllerName, $action);
$this->updateSessionActivity($controllerResponse, $controllerName, $action);
$this->_postDispatchType($controllerResponse, $controllerName, $action);
$this->_postDispatch($controllerResponse, $controllerName, $action);
XenForo_CodeEvent::fire('controller_post_dispatch', array($this, $controllerResponse, $controllerName, $action), $controllerName);
}
/**
* Method designed to be overridden by child classes to add post-dispatch behaviors.
* This differs from {@link _postDispatch()} in that it is designed
* for abstract controller type classes to override. Specific controllers
* should override _postDispatch instead.
*
* @param mixed The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
* @param string The name of the final controller that was invoked
* @param string The name of the final action that was invoked
*/
protected function _postDispatchType($controllerResponse, $controllerName, $action)
{
}
/**
* Method designed to be overridden by child classes to add post-dispatch behaviors
*
* @param mixed The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
* @param string The name of the final controller that was invoked
* @param string The name of the final action that was invoked
*/
protected function _postDispatch($controllerResponse, $controllerName, $action)
{
}
/**
* Updates the session records. This should run on all pages, provided they not rerouting
* to another controller. Session saving should handle double calls, if they happen.
*
* @param mixed $controllerResponse The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
* @param string $controllerName
* @param string $action
*/
public function updateSession($controllerResponse, $controllerName, $action)
{
if (!XenForo_Application::isRegistered('session'))
{
return;
}
if (!$controllerResponse
|| $controllerResponse instanceof XenForo_ControllerResponse_Reroute
|| $controllerResponse instanceof XenForo_ControllerResponse_ReroutePath
)
{
return;
}
XenForo_Application::get('session')->save();
}
/**
* Update a user's session activity.
*
* @param mixed $controllerResponse The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
* @param string $controllerName
* @param string $action
*/
public function updateSessionActivity($controllerResponse, $controllerName, $action)
{
if (!XenForo_Application::isRegistered('session'))
{
return;
}
if ($this->_request->getServer('HTTP_X_MOZ') == 'prefetch')
{
return;
}
if ($controllerResponse instanceof XenForo_ControllerResponse_Abstract)
{
switch (get_class($controllerResponse))
{
case 'XenForo_ControllerResponse_Redirect':
case 'XenForo_ControllerResponse_Reroute':
case 'XenForo_ControllerResponse_ReroutePath':
return; // don't update anything, assume the next page will do it
case 'XenForo_ControllerResponse_Message':
case 'XenForo_ControllerResponse_View':
$newState = 'valid';
break;
default:
$newState = 'error';
}
if ($controllerResponse->responseCode && $controllerResponse->responseCode >= 400)
{
$newState = 'error';
}
}
else
{
$newState = 'error';
}
$session = XenForo_Application::getSession();
if ($this->canUpdateSessionActivity($controllerName, $action, $newState))
{
/** @var $userModel XenForo_Model_User */
$userModel = $this->getModelFromCache('XenForo_Model_User');
$userModel->updateSessionActivity(
XenForo_Visitor::getUserId(), $this->_request->getClientIp(false),
$controllerName, $action, $newState, $this->_request->getUserParams(),
null, $session->isRegistered('robotId') ? $session->get('robotId') : ''
);
}
}
/**
* Can this controller update the session activity? Returns false by default for AJAX requests.
* Override this in specific controllers if you want action-specific behaviour.
*
* @param string $controllerName
* @param string $action
* @param string $newState
*
* @return boolean
*/
public function canUpdateSessionActivity($controllerName, $action, &$newState)
{
// don't update session activity for an AJAX request
if ($this->_request->isXmlHttpRequest())
{
return false;
}
return true;
}
/**
* Gets session activity details of activity records that are pointing to this controller.
* This must check the visiting user's permissions before returning item info.
* Return value may be:
* * false - means page is unknown
* * string/XenForo_Phrase - gives description for all, but no item details
* * array (keyed by activity keys) of strings/XenForo_Phrase objects - individual description, no item details
* * array (keyed by activity keys) of arrays. Sub-arrays keys: 0 = description, 1 = specific item title, 2 = specific item url.
*
* @param array $activities List of activity records
*
* @return mixed See above.
*/
public static function getSessionActivityDetailsForList(array $activities)
{
return false;
}
/**
* Checks for the presence of the _xfNoRedirect parameter that is sent by AutoValidator forms when they submit via AJAX
*
* @return boolean
*/
protected function _noRedirect()
{
return ($this->_input->filterSingle('_xfNoRedirect', XenForo_Input::UINT) ? true : false);
}
/**
* Canonicalizes the request URL based on the given link URL. Canonicalization will
* only happen when requesting an HTML page, as it is primarily an SEO benefit.
*
* A response exception will be thrown is redirection is required.
*
* @param string $linkUrl
*/
public function canonicalizeRequestUrl($linkUrl)
{
if ($this->getResponseType() != 'html')
{
return;
}
if (!$this->_request->isGet())
{
return;
}
$linkUrl = strval($linkUrl);
if (strlen($linkUrl) == 0)
{
return;
}
if ($linkUrl[0] == '.')
{
$linkUrl = substr($linkUrl, 1);
}
$basePath = $this->_request->getBasePath();
$requestUri = $this->_request->getRequestUri();
if (substr($requestUri, 0, strlen($basePath)) != $basePath)
{
return;
}
$routeBase = substr($requestUri, strlen($basePath));
if (isset($routeBase[0]) && $routeBase[0] === '/')
{
$routeBase = substr($routeBase, 1);
}
if (preg_match('#^([^?]*?[^=&]*)(&(.*))?$#U', $routeBase, $match))
{
$requestUrlPrefix = $match[1];
$requestParams = isset($match[3]) ? $match[3] : false;
}
else
{
$parts = explode('?', $routeBase);
$requestUrlPrefix = $parts[0];
$requestParams = isset($parts[1]) ? $parts[1]: false;
}
if (preg_match('#^([^?]*?[^=&]*)(&(.*))?$#U', $linkUrl, $match))
{
$linkUrlPrefix = $match[1];
//$linkParams = isset($match[3]) ? $match[3] : false;
}
else
{
$parts = explode('?', $linkUrl);
$linkUrlPrefix = $parts[0];
//$linkParams = isset($parts[1]) ? $parts[1]: false;
}
if (urldecode($requestUrlPrefix) != urldecode($linkUrlPrefix))
{
$redirectUrl = $linkUrlPrefix;
if ($requestParams !== false)
{
$redirectUrl .= (strpos($redirectUrl, '?') === false ? '?' : '&') . $requestParams;
}
throw $this->responseException($this->responseRedirect(
XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT,
$redirectUrl
));
}
}
/**
* Ensures that the page that has been requested is valid based on the total
* number of results. If it's not valid, the page is redirected to the last
* valid page (via a response exception).
*
* @param integer $page
* @param integer $perPage
* @param integer $total
* @param string $linkType
* @param mixed $linkData
*/
public function canonicalizePageNumber($page, $perPage, $total, $linkType, $linkData = null)
{
if ($this->getResponseType() != 'html' || !$this->_request->isGet())
{
return;
}
if ($perPage < 1 || $total < 1)
{
return;
}
$page = max(1, $page);
$maxPage = ceil($total / $perPage);
if ($page <= $maxPage)
{
return; // within the range
}
$params = $_GET;
if ($maxPage <= 1)
{
unset($params['page']);
}
else
{
$params['page'] = $maxPage;
}
$redirectUrl = $this->_buildLink($linkType, $linkData, $params);
throw $this->responseException($this->responseRedirect(
XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL,
$redirectUrl
));
}
/**
* If the controller needs to build a link in a type-specific way (when the type isn't
* known), this function can be used. As of this writing, only canonicalizePageNumber
* uses this function.
*
* @param string $type
* @param mixed $data
* @param array $params
*
* @return string URL for link
*/
protected function _buildLink($type, $data = null, array $params = array())
{
throw new XenForo_Exception('_buildLink must be overridden in the abstract controller for the specified type.');
}
/**
* Controller response for when you want to reroute to a different controller/action.
*
* @param string Name of the controller to reroute to
* @param string Name of the action to reroute to
* @param array Key-value pairs of parameters to pass to the container view
*
* @return XenForo_ControllerResponse_Reroute
*/
public function responseReroute($controllerName, $action, array $containerParams = array())
{
if (is_object($controllerName))
{
// TODO: go above all XFCP classes
$controllerName = get_class($controllerName);
}
$controllerResponse = new XenForo_ControllerResponse_Reroute();
$controllerResponse->controllerName = $controllerName;
$controllerResponse->action = $action;
$controllerResponse->containerParams = $containerParams;
return $controllerResponse;
}
/**
* Controller response for when you want to reroute to a different path internally.
*
* @param string $path
*
* @return XenForo_ControllerResponse_ReroutePath
*/
public function responseReroutePath($path, array $containerParams = array())
{
$controllerResponse = new XenForo_ControllerResponse_ReroutePath();
$controllerResponse->path = $path;
$controllerResponse->containerParams = $containerParams;
return $controllerResponse;
}
/**
* Controller response for when you want to redirect to a different URL. This will
* happen in a separate request.
*
* @param integer See {@link XenForo_ControllerResponse_Redirect}
* @param string Target to redirect to
* @param mixed Message with which to redirect
* @param array Extra parameters for the redirect
*
* @return XenForo_ControllerResponse_Redirect
*/
public function responseRedirect($redirectType, $redirectTarget, $redirectMessage = null, array $redirectParams = array())
{
switch ($redirectType)
{
case XenForo_ControllerResponse_Redirect::RESOURCE_CREATED:
case XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED:
case XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL:
case XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT:
case XenForo_ControllerResponse_Redirect::SUCCESS:
break;
default:
throw new XenForo_Exception('Unknown redirect type');
}
$controllerResponse = new XenForo_ControllerResponse_Redirect();
$controllerResponse->redirectType = $redirectType;
$controllerResponse->redirectTarget = $redirectTarget;
$controllerResponse->redirectMessage = $redirectMessage;
$controllerResponse->redirectParams = $redirectParams;
return $controllerResponse;
}
/**
* Controller response for when you want to throw an error and display it to the user.
*
* @param string|array Error text to be use
* @param integer An optional HTTP response code to output
* @param array Key-value pairs of parameters to pass to the container view
*
* @return XenForo_ControllerResponse_Error
*/
public function responseError($error, $responseCode = 200, array $containerParams = array())
{
$controllerResponse = new XenForo_ControllerResponse_Error();
$controllerResponse->errorText = $error;
$controllerResponse->responseCode = $responseCode;
$controllerResponse->containerParams = $containerParams;
return $controllerResponse;
}
/**
* Controller response for when you want to display a message to a user.
*
* @param string Error text to be use
* @param array Key-value pairs of parameters to pass to the container view
*
* @return XenForo_ControllerResponse_Message
*/
public function responseMessage($message, array $containerParams = array())
{
$controllerResponse = new XenForo_ControllerResponse_Message();
$controllerResponse->message = $message;
$controllerResponse->containerParams = $containerParams;
return $controllerResponse;
}
/**
* Gets the exception object for controller response-style behavior. This object
* cannot be returned from the controller; an exception must be thrown with it.
*
* This allows any type of controller response to be invoked via an exception.
*
* @param XenForo_ControllerResponse_Abstract $controllerResponse Type of response to invoke
* @param integer HTTP response code
*
* @return XenForo_ControllerResponse_Exception
*/
public function responseException(XenForo_ControllerResponse_Abstract $controllerResponse, $responseCode = null)
{
if ($responseCode)
{
$controllerResponse->responseCode = $responseCode;
}
return new XenForo_ControllerResponse_Exception($controllerResponse);
}
/**
* Gets the response for a generic CAPTCHA failed error.
*
* @return XenForo_ControllerResponse_Error
*/
public function responseCaptchaFailed()
{
return $this->responseError(new XenForo_Phrase('did_not_complete_the_captcha_verification_properly'));
}
/**
* Gets a general no permission error wrapped in an exception response.
*
* @return XenForo_ControllerResponse_Exception
*/
public function getNoPermissionResponseException()
{
return $this->responseException($this->responseNoPermission());
}
/**
* Gets a specific error or a general no permission response exception.
* If the first param is a string and $stringToPhrase is true, it will be treated
* as a phrase key and turned into a phrase.
*
* If a specific phrase is requested, a general error will be thrown. Otherwise,
* a generic no permission error will be shown.
*
* @param string|XenForo_Phrase|mixed $errorPhraseKey A phrase key, a phrase object, or hard coded text. Or, may be empty.
* @param boolean $stringToPhrase If true and the $errorPhraseKey is a string, $errorPhraseKey is treated as the name of a phrase.
*
* @return XenForo_ControllerResponse_Exception
*/
public function getErrorOrNoPermissionResponseException($errorPhraseKey, $stringToPhrase = true)
{
$responseCode = 403;
if ($errorPhraseKey && (is_string($errorPhraseKey) || is_array($errorPhraseKey)) && $stringToPhrase)
{
$error = new XenForo_Phrase($errorPhraseKey);
if (preg_match('/^requested_.*_not_found$/i', $error->getPhraseName()))
{
$responseCode = 404;
}
}
else
{
$error = $errorPhraseKey;
}
if ($errorPhraseKey)
{
return $this->responseException($this->responseError($error, $responseCode));
}
else
{
return $this->getNoPermissionResponseException();
}
}
/**
* Gets the response for a generic flooding page.
*
* @param integer $floodSeconds Numbers of seconds the user must wait to perform the action
*
* @return XenForo_ControllerResponse_Error
*/
public function responseFlooding($floodSeconds)
{
return $this->responseError(new XenForo_Phrase('must_wait_x_seconds_before_performing_this_action', array('count' => $floodSeconds)));
}
public function getNotFoundResponse()
{
if (XenForo_Application::debugMode())
{
$controllerName = $this->_request->getParam('_controllerName');
if (!$controllerName)
{
return $this->responseError(
new XenForo_Phrase('route_x_not_found', array(
'route' => $this->_request->getParam('_origRoutePath'),
)), 404
);
}
else if (!class_exists($controllerName, false))
{
return $this->responseError(
new XenForo_Phrase('controller_x_for_route_y_not_found', array(
'controller' => $controllerName,
'route' => $this->_request->getParam('_origRoutePath'),
)), 404
);
}
else
{
return $this->responseError(
new XenForo_Phrase('controller_x_does_not_define_action_y', array(
'controller' => $controllerName,
'action' => $this->_request->getParam('_action')
)), 404
);
}
}
else
{
return $this->responseError(new XenForo_Phrase('requested_page_not_found'), 404);
}
}
/**
* Helper to assert that this action is available over POST only. Throws
* an exception if the request is not via POST.
*/
protected function _assertPostOnly()
{
if (!$this->_request->isPost())
{
throw $this->responseException(
$this->responseError(new XenForo_Phrase('action_available_via_post_only'), 405, array('allowHeader' => 'POST'))
);
}
}
/**
* Fetches name/value/existingDataKey from input. Primarily used for AJAX autovalidation actions of single fields.
*
* @return array [name, value, existingDataKey]
*/
protected function _getFieldValidationInputParams()
{
return $this->_input->filter(array(
'name' => XenForo_Input::STRING,
'value' => XenForo_Input::STRING,
'existingDataKey' => XenForo_Input::STRING,
));
}
/**
* Validates a field against a DataWriter.
* Expects 'name' and 'value' keys to be present in the request.
*
* @param string $dataWriterName Name of DataWriter against which this field will be validated
* @param array $data Array containing name, value or existingDataKey, which will override those fetched from _getFieldValidationInputParams
* @param array $options Key-value pairs of options to set
* @param array $extraData Key-value pairs of extra data to set
*
* @return XenForo_ControllerResponse_Redirect|XenForo_ControllerResponse_Error
*/
protected function _validateField($dataWriterName, array $data = array(), array $options = array(), array $extraData = array())
{
$data = array_merge($this->_getFieldValidationInputParams(), $data);
$writer = XenForo_DataWriter::create($dataWriterName);
if (!empty($data['existingDataKey']) || $data['existingDataKey'] === '0')
{
$writer->setExistingData($data['existingDataKey']);
}
foreach ($options AS $key => $value)
{
$writer->setOption($key, $value);
}
foreach ($extraData AS $key => $value)
{
$writer->setExtraData($key, $value);
}
$writer->set($data['name'], $data['value']);
if ($errors = $writer->getErrors())
{
return $this->responseError($errors);
}
return $this->responseRedirect(
XenForo_ControllerResponse_Redirect::SUCCESS,
'',
new XenForo_Phrase('redirect_field_validated', array('name' => $data['name'], 'value' => $data['value']))
);
}
/**
* Instructs a DataWriter to delete data based on a POST input parameter.
*
* @param string Name of DataWriter class that will perform the deletion
* @param string|array Name of input parameter that contains the existing data key OR array containing the keys for a multi-key parameter
* @param string URL to which to redirect on success
* @param string Redirection message to show on successful deletion
* @param array Options to send to the target data writer
*/
protected function _deleteData($dataWriterName, $existingDataKeyName, $redirectLink, $redirectMessage = null, array $dwOptions = array())
{
$this->_assertPostOnly();
$dw = XenForo_DataWriter::create($dataWriterName);
if (!empty($dwOptions))
{
foreach ($dwOptions AS $name => $value)
{
$dw->setOption($name, $value);
}
}
$dw->setExistingData((is_array($existingDataKeyName)
? $existingDataKeyName
: $this->_input->filterSingle($existingDataKeyName, XenForo_Input::STRING)
));
$dw->delete();
if (is_null($redirectMessage))
{
$redirectMessage = new XenForo_Phrase('deletion_successful');
}
return $this->responseRedirect(XenForo_ControllerResponse_Redirect::SUCCESS, $redirectLink, $redirectMessage);
}
/**
* Returns true if the request method is POST and an _xfConfirm parameter exists and is true
*
* @return boolean
*/
public function isConfirmedPost()
{
return ($this->_request->isPost() && $this->_input->filterSingle('_xfConfirm', XenForo_Input::UINT));
}
/**
* Controller response for when you want to output using a view class.
*
* @param string Name of the view class to be rendered
* @param string Name of the template that should be displayed (may be ignored by view)
* @param array Key-value pairs of parameters to pass to the view
* @param array Key-value pairs of parameters to pass to the container view
*
* @return XenForo_ControllerResponse_View
*/
public function responseView($viewName = '', $templateName = '', array $params = array(), array $containerParams = array())
{
$controllerResponse = new XenForo_ControllerResponse_View();
$controllerResponse->viewName = $viewName;
$controllerResponse->templateName = $templateName;
$controllerResponse->params = $params;
$controllerResponse->containerParams = $containerParams;
return $controllerResponse;
}
/**
* Creates the specified helper class. If no underscore is present in the class
* name, "XenForo_ControllerHelper_" is prefixed. Otherwise, a full class name
* is assumed.
*
* @param string $class Full class name, or partial suffix (if no underscore)
*
* @return XenForo_ControllerHelper_Abstract
*/
public function getHelper($class)
{
if (strpos($class, '_') === false)
{
$class = 'XenForo_ControllerHelper_' . $class;
}
$class = XenForo_Application::resolveDynamicClass($class);
return new $class($this);
}
/**
* Gets a valid record or throws an exception.
*
* @param mixed $id ID of the record to get
* @param XenForo_Model $model Model object to request from
* @param string $method Method to call in the model object
* @param string $errorPhraseKey Key of error phrase to use when not found
*
* @return array
*/
public function getRecordOrError($id, $model, $method, $errorPhraseKey)
{
$info = $model->$method($id);
if (!$info)
{
throw $this->responseException($this->responseError(new XenForo_Phrase($errorPhraseKey), 404));
}
return $info;
}
/**
* Gets a dynamic redirect target based on a redirect param or the referrer.
*
* @param string|false $fallbackUrl Fallback if no redirect or referrer is available; if false, uses index
* @param boolean $useReferrer True uses the referrer if no redirect param is available
*
* @return string
*/
public function getDynamicRedirect($fallbackUrl = false, $useReferrer = true)
{
$redirect = $this->_input->filterSingle('redirect', XenForo_Input::STRING);
if (!$redirect && $useReferrer)
{
$redirect = $this->_request->getServer('HTTP_X_AJAX_REFERER');
if (!$redirect)
{
$redirect = $this->_request->getServer('HTTP_REFERER');
}
}
if ($redirect)
{
$redirect = strval($redirect);
if (strlen($redirect) && !preg_match('/./u', $redirect))
{
$redirect = utf8_strip($redirect);
}
if (strpos($redirect, "n") === false && strpos($redirect, "r") === false) {
$fullRedirect = XenForo_Link::convertUriToAbsoluteUri($redirect, true);
$redirectParts = @parse_url($fullRedirect);
if ($redirectParts && !empty($redirectParts['host']))
{
$paths = XenForo_Application::get('requestPaths');
$pageParts = @parse_url($paths['fullUri']);
if ($pageParts && !empty($pageParts['host']) && $pageParts['host'] == $redirectParts['host'])
{
return $fullRedirect;
}
}
}
}
if ($fallbackUrl === false)
{
if ($this instanceof XenForo_ControllerAdmin_Abstract)
{
$fallbackUrl = XenForo_Link::buildAdminLink('index');
}
else
{
$fallbackUrl = XenForo_Link::buildPublicLink('index');
}
}
return $fallbackUrl;
}
/**
* Gets a dynamic redirect provided it doesn't start with the given
* URL. Note that the URLs will be converted to absolute before comparison,
* so the domains/ports/leading paths need to be the same.
*
* @param $notUrl
* @param bool $fallbackUrl
* @param bool $useReferrer
*
* @return string
*/
public function getDynamicRedirectIfNot($notUrl, $fallbackUrl = false, $useReferrer = true)
{
$redirect = XenForo_Link::convertUriToAbsoluteUri(
$this->getDynamicRedirect($fallbackUrl, $useReferrer), true
);
$notUrl = XenForo_Link::convertUriToAbsoluteUri($notUrl, true);
if (strpos($redirect, $notUrl) === 0)
{
// the URL we can't redirect to is at the start
if ($fallbackUrl === false)
{
if ($this instanceof XenForo_ControllerAdmin_Abstract)
{
$fallbackUrl = XenForo_Link::buildAdminLink('index');
}
else
{
$fallbackUrl = XenForo_Link::buildPublicLink('index');
}
}
return XenForo_Link::convertUriToAbsoluteUri($fallbackUrl);
}
else
{
return $redirect;
}
}
/**
* Parses a URL that should contain a route match in it into
* the route match and any request params set by it.
*
* @param string $url
*
* @return array|bool False if parsing fails, otherwise array with match and params keys
*/
public function parseRouteUrl($url)
{
if (!XenForo_Application::isRegistered('fc'))
{
return false;
}
$requestPaths = XenForo_Application::get('requestPaths');
$url = XenForo_Link::convertUriToAbsoluteUri($url);
if (strpos($url, $requestPaths['fullBasePath']) !== 0)
{
return false;
}
$urlParts = parse_url($url);
$requestUri = (isset($urlParts['path']) ? $urlParts['path'] : '/')
. (isset($urlParts['query']) ? '?' . $urlParts['query'] : '');
$request = clone $this->_request;
$request->setRequestUri($requestUri);
$match = XenForo_Application::getFc()->getDependencies()->getRouter()->match($request);
return array(
'match' => $match,
'params' => $request->getUserParams()
);
}
/**
* Turns a serialized (by jQuery) query string from input into a XenForo_Input object.
*
* @param string Name of index to fetch from $this->_input
* @param boolean On error, throw an exception or return false
* @param string
*
* @return XenForo_Input|false
*/
protected function _getInputFromSerialized($varname, $throw = true, &$errorPhraseKey = null)
{
if ($inputString = $this->_input->filterSingle($varname, XenForo_Input::STRING))
{
try
{
return new XenForo_Input(XenForo_Application::parseQueryString($inputString));
}
catch (Exception $e)
{
$errorPhraseKey = 'string_could_not_be_converted_to_input';
if ($throw)
{
throw $this->responseException(
$this->responseError(new XenForo_Phrase($errorPhraseKey))
);
}
}
}
return false;
}
/**
* Checks for a match of one or more IPs against a list of IP and IP fragments
*
* @param string|array IP address(es)
* @param array List of IP addresses
*
* @return boolean
*/
public function ipMatch($checkIps, array $ipList)
{
if (!is_array($checkIps))
{
$checkIps = array($checkIps);
}
foreach ($checkIps AS $ip)
{
$binary = XenForo_Helper_Ip::convertIpStringToBinary($ip);
if (!$binary)
{
continue;
}
$firstByte = $binary[0];
if (!empty($ipList[$firstByte]))
{
foreach ($ipList[$firstByte] AS $range)
{
if (XenForo_Helper_Ip::ipMatchesRange($binary, $range[0], $range[1]))
{
return true;
}
}
}
}
return false;
}
/**
* Returns an array of IPs for the current client
*
* @return
*/
protected function _getClientIps()
{
$ips = preg_split('/,s*/', $this->_request->getClientIp(true));
$ips[] = $this->_request->getClientIp(false);
return array_unique($ips);
}
/**
* Attempts to determine whether the request is referred from the same host
*
* @return boolean
*/
protected function _isSelfReferer()
{
if ($referer = $this->_request->getServer('HTTP_REFERER'))
{
$refererParts = @parse_url($referer);
if ($refererParts && !empty($refererParts['host']))
{
$paths = XenForo_Application::get('requestPaths');
$requestParts = @parse_url($paths['fullUri']);
if ($requestParts && !empty($requestParts['host']))
{
if ($refererParts['host'] != $requestParts['host'])
{
// referer is not the same as request host
return false;
}
}
}
}
// either we have the same host and referer, or we just don't know...
return true;
}
}