PageRenderTime 63ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/library/XenForo/Controller.php

https://github.com/hanguyenhuu/DTUI_201105
PHP | 1097 lines | 560 code | 122 blank | 415 comment | 81 complexity | fab11069466d9def1945267c68d90de4 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * General base class for controllers. Controllers should implement methods named
  4. * actionX with no arguments. These will be called by the dispatcher based on the
  5. * requested route. They should return the object returned by {@link responseReroute()},
  6. * {@link responseError()}, or {@link responseView()},.
  7. *
  8. * All responses can take paramaters that will be passed to the container view
  9. * (ie, two-phase view), if there is one.
  10. *
  11. * @package XenForo_Mvc
  12. */
  13. abstract class XenForo_Controller
  14. {
  15. /**
  16. * Request object.
  17. *
  18. * @var Zend_Controller_Request_Http
  19. */
  20. protected $_request;
  21. /**
  22. * Response object.
  23. *
  24. * @var Zend_Controller_Response_Http
  25. */
  26. protected $_response;
  27. /**
  28. * The route match object for this request.
  29. *
  30. * @var XenForo_RouteMatch
  31. */
  32. protected $_routeMatch;
  33. /**
  34. * Input object.
  35. *
  36. * @var XenForo_Input
  37. */
  38. protected $_input;
  39. /**
  40. * Standard approach to caching model objects for the lifetime of the controller.
  41. *
  42. * @var array
  43. */
  44. protected $_modelCache = array();
  45. /**
  46. * List of explicit changes to the view state. View state changes are specific
  47. * to the dependency manager, but may include things like changing the styleId.
  48. *
  49. * @var array Key-value pairs
  50. */
  51. protected $_viewStateChanges = array();
  52. /**
  53. * Container for various items that have been "executed" in one controller and
  54. * shouldn't be executed again in this request.
  55. *
  56. * @var array
  57. */
  58. protected static $_executed = array();
  59. /**
  60. * Gets the response for a generic no permission page.
  61. *
  62. * @return XenForo_ControllerResponse_Error
  63. */
  64. abstract public function responseNoPermission();
  65. /**
  66. * Constructor
  67. *
  68. * @param Zend_Controller_Request_Http
  69. * @param Zend_Controller_Response_Http
  70. * @param XenForo_RouteMatch
  71. */
  72. public function __construct(Zend_Controller_Request_Http $request, Zend_Controller_Response_Http $response, XenForo_RouteMatch $routeMatch)
  73. {
  74. $this->_request = $request;
  75. $this->_response = $response;
  76. $this->_routeMatch = $routeMatch;
  77. $this->_input = new XenForo_Input($this->_request);
  78. }
  79. /**
  80. * Gets the specified model object from the cache. If it does not exist,
  81. * it will be instantiated.
  82. *
  83. * @param string $class Name of the class to load
  84. *
  85. * @return XenForo_Model
  86. */
  87. public function getModelFromCache($class)
  88. {
  89. if (!isset($this->_modelCache[$class]))
  90. {
  91. $this->_modelCache[$class] = XenForo_Model::create($class);
  92. }
  93. return $this->_modelCache[$class];
  94. }
  95. /**
  96. * Gets the request object.
  97. *
  98. * @return Zend_Controller_Request_Http
  99. */
  100. public function getRequest()
  101. {
  102. return $this->_request;
  103. }
  104. /**
  105. * Gets the input object.
  106. *
  107. * @return XenForo_Input
  108. */
  109. public function getInput()
  110. {
  111. return $this->_input;
  112. }
  113. /**
  114. * Sets a change to the view state.
  115. *
  116. * @param string $state Name of state to change
  117. * @param mixed $data
  118. */
  119. public function setViewStateChange($state, $data)
  120. {
  121. $this->_viewStateChanges[$state] = $data;
  122. }
  123. /**
  124. * Gets all the view state changes.
  125. *
  126. * @return array Key-value pairs
  127. */
  128. public function getViewStateChanges()
  129. {
  130. return $this->_viewStateChanges;
  131. }
  132. /**
  133. * Gets the type of response that has been requested.
  134. *
  135. * @return string
  136. */
  137. public function getResponseType()
  138. {
  139. return $this->_routeMatch->getResponseType();
  140. }
  141. /**
  142. * Gets the route match for this request. This can be modified to change
  143. * the response type, and the major/minor sections that will be used to
  144. * setup navigation.
  145. *
  146. * @return XenForo_RouteMatch
  147. */
  148. public function getRouteMatch()
  149. {
  150. return $this->_routeMatch;
  151. }
  152. /**
  153. * Checks a request for CSRF issues. This is only checked for POST requests
  154. * (with session info) that aren't Ajax requests (relies on browser-level
  155. * cross-domain policies).
  156. *
  157. * The token is retrieved from the "_xfToken" request param.
  158. *
  159. * @param string $action
  160. */
  161. protected function _checkCsrf($action)
  162. {
  163. if (isset(self::$_executed['csrf']))
  164. {
  165. return;
  166. }
  167. self::$_executed['csrf'] = true;
  168. if (!XenForo_Application::isRegistered('session'))
  169. {
  170. return;
  171. }
  172. if ($this->_request->isPost() || substr($this->getResponseType(), 0, 2) == 'js')
  173. {
  174. // post and all json requests require a token
  175. $this->_checkCsrfFromToken($this->_request->getParam('_xfToken'));
  176. }
  177. }
  178. /**
  179. * Performs particular actions if the request method is POST
  180. *
  181. * @param string $action
  182. */
  183. protected function _handlePost($action)
  184. {
  185. if ($this->_request->isPost() && $delay = XenForo_Application::get('options')->delayPostResponses)
  186. {
  187. usleep($delay * 1000000);
  188. }
  189. }
  190. /**
  191. * Gets for a CSRF issue using a standard formatted token.
  192. * Throws an exception if a CSRF issue is detected.
  193. *
  194. * @param string $token Format: <user id>,<request time>,<token>
  195. * @param boolean $throw If true, an exception is thrown when failing; otherwise, a return is used
  196. *
  197. * @return boolean True if passed, false otherwise; only applies when $throw is false
  198. */
  199. protected function _checkCsrfFromToken($token, $throw = true)
  200. {
  201. $visitingUser = XenForo_Visitor::getInstance();
  202. $visitingUserId = $visitingUser['user_id'];
  203. if (!$visitingUserId)
  204. {
  205. // don't check for guests
  206. return true;
  207. }
  208. $token = strval($token);
  209. $csrfAttempt = 'invalid';
  210. if ($token === '')
  211. {
  212. $csrfAttempt = 'missing';
  213. }
  214. $tokenParts = explode(',', $token);
  215. if (count($tokenParts) == 3)
  216. {
  217. list($tokenUserId, $tokenTime, $tokenValue) = $tokenParts;
  218. if (strval($tokenUserId) === strval($visitingUserId))
  219. {
  220. if (($tokenTime + 86400) < XenForo_Application::$time)
  221. {
  222. $csrfAttempt = 'expired';
  223. }
  224. else if (sha1($tokenTime . $visitingUser['csrf_token']) == $tokenValue)
  225. {
  226. $csrfAttempt = false;
  227. }
  228. }
  229. }
  230. if ($csrfAttempt)
  231. {
  232. if ($throw)
  233. {
  234. throw $this->responseException(
  235. $this->responseError(new XenForo_Phrase('security_error_occurred'))
  236. );
  237. }
  238. else
  239. {
  240. return false;
  241. }
  242. }
  243. return true;
  244. }
  245. /**
  246. * Setup the session.
  247. *
  248. * @param string $action
  249. */
  250. protected function _setupSession($action)
  251. {
  252. if (XenForo_Application::isRegistered('session'))
  253. {
  254. return;
  255. }
  256. $session = XenForo_Session::startPublicSession($this->_request);
  257. }
  258. /**
  259. * This function is called immediately before an action is dispatched.
  260. *
  261. * @param string Action that is requested
  262. */
  263. final public function preDispatch($action)
  264. {
  265. $this->_preDispatchFirst($action);
  266. $this->_setupSession($action);
  267. $this->_checkCsrf($action);
  268. $this->_handlePost($action);
  269. $this->_preDispatchType($action);
  270. $this->_preDispatch($action);
  271. XenForo_CodeEvent::fire('controller_pre_dispatch', array($this, $action));
  272. }
  273. /**
  274. * Method designed to be overridden by child classes to add pre-dispatch behaviors
  275. * before any other pre-dispatch checks are called.
  276. *
  277. * @param string $action
  278. */
  279. protected function _preDispatchFirst($action)
  280. {
  281. }
  282. /**
  283. * Method designed to be overridden by child classes to add pre-dispatch
  284. * behaviors. This differs from {@link _preDispatch()} in that it is designed
  285. * for abstract controller type classes to override. Specific controllers
  286. * should override preDispatch instead.
  287. *
  288. * @param string $action Action that is requested
  289. */
  290. protected function _preDispatchType($action)
  291. {
  292. }
  293. /**
  294. * Method designed to be overridden by child classes to add pre-dispatch
  295. * behaviors. This method should only be overridden by specific, concrete
  296. * controllers.
  297. *
  298. * @param string Action that is requested
  299. */
  300. protected function _preDispatch($action)
  301. {
  302. }
  303. /**
  304. * This function is called immediately after an action is dispatched.
  305. *
  306. * @param mixed The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
  307. * @param string The name of the final controller that was invoked
  308. * @param string The name of the final action that was invoked
  309. */
  310. final public function postDispatch($controllerResponse, $controllerName, $action)
  311. {
  312. $this->updateSession($controllerResponse, $controllerName, $action);
  313. $this->updateSessionActivity($controllerResponse, $controllerName, $action);
  314. $this->_postDispatch($controllerResponse, $controllerName, $action);
  315. }
  316. /**
  317. * Method designed to be overridden by child classes to add post-dispatch behaviors
  318. *
  319. * @param mixed The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
  320. * @param string The name of the final controller that was invoked
  321. * @param string The name of the final action that was invoked
  322. */
  323. protected function _postDispatch($controllerResponse, $controllerName, $action)
  324. {
  325. }
  326. /**
  327. * Updates the session records. This should run on all pages, provided they not rerouting
  328. * to another controller. Session saving should handle double calls, if they happen.
  329. *
  330. * @param mixed $controllerResponse The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
  331. * @param string $controllerName
  332. * @param string $action
  333. */
  334. public function updateSession($controllerResponse, $controllerName, $action)
  335. {
  336. if (!XenForo_Application::isRegistered('session'))
  337. {
  338. return;
  339. }
  340. if (!$controllerResponse || $controllerResponse instanceof XenForo_ControllerResponse_Reroute)
  341. {
  342. return;
  343. }
  344. XenForo_Application::get('session')->save();
  345. }
  346. /**
  347. * Update a user's session activity.
  348. *
  349. * @param mixed $controllerResponse The response from the controller. Generally, a XenForo_ControllerResponse_Abstract object.
  350. * @param string $controllerName
  351. * @param string $action
  352. */
  353. public function updateSessionActivity($controllerResponse, $controllerName, $action)
  354. {
  355. if (!XenForo_Application::isRegistered('session'))
  356. {
  357. return;
  358. }
  359. if ($controllerResponse instanceof XenForo_ControllerResponse_Abstract)
  360. {
  361. switch (get_class($controllerResponse))
  362. {
  363. case 'XenForo_ControllerResponse_Redirect':
  364. case 'XenForo_ControllerResponse_Reroute':
  365. return; // don't update anything, assume the next page will do it
  366. case 'XenForo_ControllerResponse_Message':
  367. case 'XenForo_ControllerResponse_View':
  368. $newState = 'valid';
  369. break;
  370. default:
  371. $newState = 'error';
  372. }
  373. }
  374. else
  375. {
  376. $newState = 'error';
  377. }
  378. if ($this->canUpdateSessionActivity($controllerName, $action, $newState))
  379. {
  380. $this->getModelFromCache('XenForo_Model_User')->updateSessionActivity(
  381. XenForo_Visitor::getUserId(), $this->_request->getClientIp(false),
  382. $controllerName, $action, $newState, $this->_request->getUserParams()
  383. );
  384. }
  385. }
  386. /**
  387. * Can this controller update the session activity? Returns false by default for AJAX requests.
  388. * Override this in specific controllers if you want action-specific behaviour.
  389. *
  390. * @param string $controllerName
  391. * @param string $action
  392. * @param string $newState
  393. *
  394. * @return boolean
  395. */
  396. public function canUpdateSessionActivity($controllerName, $action, &$newState)
  397. {
  398. // don't update session activity for an AJAX request
  399. if ($this->_request->isXmlHttpRequest())
  400. {
  401. return false;
  402. }
  403. return true;
  404. }
  405. /**
  406. * Gets session activity details of activity records that are pointing to this controller.
  407. * This must check the visiting user's permissions before returning item info.
  408. * Return value may be:
  409. * * false - means page is unknown
  410. * * string/XenForo_Phrase - gives description for all, but no item details
  411. * * array (keyed by activity keys) of strings/XenForo_Phrase objects - individual description, no item details
  412. * * array (keyed by activity keys) of arrays. Sub-arrays keys: 0 = description, 1 = specific item title, 2 = specific item url.
  413. *
  414. * @param array $activities List of activity records
  415. *
  416. * @return mixed See above.
  417. */
  418. public static function getSessionActivityDetailsForList(array $activities)
  419. {
  420. return false;
  421. }
  422. /**
  423. * Checks for the presence of the _xfNoRedirect parameter that is sent by AutoValidator forms when they submit via AJAX
  424. *
  425. * @return boolean
  426. */
  427. protected function _noRedirect()
  428. {
  429. return ($this->_input->filterSingle('_xfNoRedirect', XenForo_Input::UINT) ? true : false);
  430. }
  431. /**
  432. * Canonicalizes the request URL based on the given link URL. Canonicalization will
  433. * only happen when requesting an HTML page, as it is primarily an SEO benefit.
  434. *
  435. * A response exception will be thrown is redirection is required.
  436. *
  437. * @param string $linkUrl
  438. */
  439. public function canonicalizeRequestUrl($linkUrl)
  440. {
  441. if ($this->getResponseType() != 'html')
  442. {
  443. return;
  444. }
  445. if (!$this->_request->isGet())
  446. {
  447. return;
  448. }
  449. $linkUrl = strval($linkUrl);
  450. if (strlen($linkUrl) == 0)
  451. {
  452. return;
  453. }
  454. if ($linkUrl[0] == '.')
  455. {
  456. $linkUrl = substr($linkUrl, 1);
  457. }
  458. $basePath = $this->_request->getBasePath();
  459. $requestUri = $this->_request->getRequestUri();
  460. if (substr($requestUri, 0, strlen($basePath)) != $basePath)
  461. {
  462. return;
  463. }
  464. $routeBase = substr($requestUri, strlen($basePath));
  465. if (isset($routeBase[0]) && $routeBase[0] === '/')
  466. {
  467. $routeBase = substr($routeBase, 1);
  468. }
  469. if (preg_match('#^([^?]*\?[^=&]*)(&(.*))?$#U', $routeBase, $match))
  470. {
  471. $requestUrlPrefix = $match[1];
  472. $requestParams = isset($match[3]) ? $match[3] : false;
  473. }
  474. else
  475. {
  476. $parts = explode('?', $routeBase);
  477. $requestUrlPrefix = $parts[0];
  478. $requestParams = isset($parts[1]) ? $parts[1]: false;
  479. }
  480. if (preg_match('#^([^?]*\?[^=&]*)(&(.*))?$#U', $linkUrl, $match))
  481. {
  482. $linkUrlPrefix = $match[1];
  483. //$linkParams = isset($match[3]) ? $match[3] : false;
  484. }
  485. else
  486. {
  487. $parts = explode('?', $linkUrl);
  488. $linkUrlPrefix = $parts[0];
  489. //$linkParams = isset($parts[1]) ? $parts[1]: false;
  490. }
  491. if (urldecode($requestUrlPrefix) != urldecode($linkUrlPrefix))
  492. {
  493. $redirectUrl = $linkUrlPrefix;
  494. if ($requestParams !== false)
  495. {
  496. $redirectUrl .= (strpos($redirectUrl, '?') === false ? '?' : '&') . $requestParams;
  497. }
  498. throw $this->responseException($this->responseRedirect(
  499. XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT,
  500. $redirectUrl
  501. ));
  502. }
  503. }
  504. /**
  505. * Ensures that the page that has been requested is valid based on the total
  506. * number of results. If it's not valid, the page is redirected to the last
  507. * valid page (via a response exception).
  508. *
  509. * @param integer $page
  510. * @param integer $perPage
  511. * @param integer $total
  512. * @param string $linkType
  513. * @param mixed $linkData
  514. */
  515. public function canonicalizePageNumber($page, $perPage, $total, $linkType, $linkData = null)
  516. {
  517. if ($this->getResponseType() != 'html' || !$this->_request->isGet())
  518. {
  519. return;
  520. }
  521. if ($perPage < 1 || $total < 1)
  522. {
  523. return;
  524. }
  525. $page = max(1, $page);
  526. $maxPage = ceil($total / $perPage);
  527. if ($page <= $maxPage)
  528. {
  529. return; // within the range
  530. }
  531. $params = $_GET;
  532. if ($maxPage <= 1)
  533. {
  534. unset($params['page']);
  535. }
  536. else
  537. {
  538. $params['page'] = $maxPage;
  539. }
  540. $redirectUrl = $this->_buildLink($linkType, $linkData, $params);
  541. throw $this->responseException($this->responseRedirect(
  542. XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL,
  543. $redirectUrl
  544. ));
  545. }
  546. /**
  547. * If the controller needs to build a link in a type-specific way (when the type isn't
  548. * known), this function can be used. As of this writing, only canonicalizePageNumber
  549. * uses this function.
  550. *
  551. * @param string $type
  552. * @param mixed $data
  553. * @param array $params
  554. *
  555. * @return string URL for link
  556. */
  557. protected function _buildLink($type, $data = null, array $params = array())
  558. {
  559. throw new XenForo_Exception('_buildLink must be overridden in the abstract controller for the specified type.');
  560. }
  561. /**
  562. * Controller response for when you want to reroute to a different controller/action.
  563. *
  564. * @param string Name of the controller to reroute to
  565. * @param string Name of the action to reroute to
  566. * @param array Key-value pairs of parameters to pass to the container view
  567. *
  568. * @return XenForo_ControllerResponse_Reroute
  569. */
  570. public function responseReroute($controllerName, $action, array $containerParams = array())
  571. {
  572. $controllerResponse = new XenForo_ControllerResponse_Reroute();
  573. $controllerResponse->controllerName = $controllerName;
  574. $controllerResponse->action = $action;
  575. $controllerResponse->containerParams = $containerParams;
  576. return $controllerResponse;
  577. }
  578. /**
  579. * Controller response for when you want to redirect to a different URL. This will
  580. * happen in a separate request.
  581. *
  582. * @param integer See {@link XenForo_ControllerResponse_Redirect}
  583. * @param string Target to redirect to
  584. * @param mixed Message with which to redirect
  585. * @param array Extra parameters for the redirect
  586. *
  587. * @return XenForo_ControllerResponse_Redirect
  588. */
  589. public function responseRedirect($redirectType, $redirectTarget, $redirectMessage = null, array $redirectParams = array())
  590. {
  591. switch ($redirectType)
  592. {
  593. case XenForo_ControllerResponse_Redirect::RESOURCE_CREATED:
  594. case XenForo_ControllerResponse_Redirect::RESOURCE_UPDATED:
  595. case XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL:
  596. case XenForo_ControllerResponse_Redirect::RESOURCE_CANONICAL_PERMANENT:
  597. case XenForo_ControllerResponse_Redirect::SUCCESS:
  598. break;
  599. default:
  600. throw new XenForo_Exception('Unknown redirect type');
  601. }
  602. $controllerResponse = new XenForo_ControllerResponse_Redirect();
  603. $controllerResponse->redirectType = $redirectType;
  604. $controllerResponse->redirectTarget = $redirectTarget;
  605. $controllerResponse->redirectMessage = $redirectMessage;
  606. $controllerResponse->redirectParams = $redirectParams;
  607. return $controllerResponse;
  608. }
  609. /**
  610. * Controller response for when you want to throw an error and display it to the user.
  611. *
  612. * @param string|array Error text to be use
  613. * @param integer An optional HTTP response code to output
  614. * @param array Key-value pairs of parameters to pass to the container view
  615. *
  616. * @return XenForo_ControllerResponse_Error
  617. */
  618. public function responseError($error, $responseCode = 200, array $containerParams = array())
  619. {
  620. $controllerResponse = new XenForo_ControllerResponse_Error();
  621. $controllerResponse->errorText = $error;
  622. $controllerResponse->responseCode = $responseCode;
  623. $controllerResponse->containerParams = $containerParams;
  624. return $controllerResponse;
  625. }
  626. /**
  627. * Controller response for when you want to display a message to a user.
  628. *
  629. * @param string Error text to be use
  630. * @param array Key-value pairs of parameters to pass to the container view
  631. *
  632. * @return XenForo_ControllerResponse_Message
  633. */
  634. public function responseMessage($message, array $containerParams = array())
  635. {
  636. $controllerResponse = new XenForo_ControllerResponse_Message();
  637. $controllerResponse->message = $message;
  638. $controllerResponse->containerParams = $containerParams;
  639. return $controllerResponse;
  640. }
  641. /**
  642. * Gets the exception object for controller response-style behavior. This object
  643. * cannot be returned from the controller; an exception must be thrown with it.
  644. *
  645. * This allows any type of controller response to be invoked via an exception.
  646. *
  647. * @param XenForo_ControllerResponse_Abstract $controllerResponse Type of response to invoke
  648. * @param integer HTTP response code
  649. *
  650. * @return XenForo_ControllerResponse_Exception
  651. */
  652. public function responseException(XenForo_ControllerResponse_Abstract $controllerResponse, $responseCode = null)
  653. {
  654. if ($responseCode)
  655. {
  656. $controllerResponse->responseCode = $responseCode;
  657. }
  658. return new XenForo_ControllerResponse_Exception($controllerResponse);
  659. }
  660. /**
  661. * Gets the response for a generic CAPTCHA failed error.
  662. *
  663. * @return XenForo_ControllerResponse_Error
  664. */
  665. public function responseCaptchaFailed()
  666. {
  667. return $this->responseError(new XenForo_Phrase('did_not_complete_the_captcha_verification_properly'));
  668. }
  669. /**
  670. * Gets a general no permission error wrapped in an exception response.
  671. *
  672. * @return XenForo_ControllerResponse_Exception
  673. */
  674. public function getNoPermissionResponseException()
  675. {
  676. return $this->responseException($this->responseNoPermission());
  677. }
  678. /**
  679. * Gets a specific error or a general no permission response exception.
  680. * If the first param is a string and $stringToPhrase is true, it will be treated
  681. * as a phrase key and turned into a phrase.
  682. *
  683. * If a specific phrase is requested, a general error will be thrown. Otherwise,
  684. * a generic no permission error will be shown.
  685. *
  686. * @param string|XenForo_Phrase|mixed $errorPhraseKey A phrase key, a phrase object, or hard coded text. Or, may be empty.
  687. * @param boolean $stringToPhrase If true and the $errorPhraseKey is a string, $errorPhraseKey is treated as the name of a phrase.
  688. *
  689. * @return XenForo_ControllerResponse_Exception
  690. */
  691. public function getErrorOrNoPermissionResponseException($errorPhraseKey, $stringToPhrase = true)
  692. {
  693. if ($errorPhraseKey && (is_string($errorPhraseKey) || is_array($errorPhraseKey)) && $stringToPhrase)
  694. {
  695. $error = new XenForo_Phrase($errorPhraseKey);
  696. }
  697. else
  698. {
  699. $error = $errorPhraseKey;
  700. }
  701. if ($errorPhraseKey)
  702. {
  703. return $this->responseException($this->responseError($error));
  704. }
  705. else
  706. {
  707. return $this->getNoPermissionResponseException();
  708. }
  709. }
  710. /**
  711. * Gets the response for a generic flooding page.
  712. *
  713. * @param integer $floodSeconds Numbers of seconds the user must wait to perform the action
  714. *
  715. * @return XenForo_ControllerResponse_Error
  716. */
  717. public function responseFlooding($floodSeconds)
  718. {
  719. return $this->responseError(new XenForo_Phrase('must_wait_x_seconds_before_performing_this_action', array('count' => $floodSeconds)));
  720. }
  721. /**
  722. * Helper to assert that this action is available over POST only. Throws
  723. * an exception if the request is not via POST.
  724. */
  725. protected function _assertPostOnly()
  726. {
  727. if (!$this->_request->isPost())
  728. {
  729. throw $this->responseException(
  730. $this->responseError(new XenForo_Phrase('action_available_via_post_only'), 500)
  731. );
  732. }
  733. }
  734. /**
  735. * Fetches name/value/existingDataKey from input. Primarily used for AJAX autovalidation actions of single fields.
  736. *
  737. * @return array [name, value, existingDataKey]
  738. */
  739. protected function _getFieldValidationInputParams()
  740. {
  741. return $this->_input->filter(array(
  742. 'name' => XenForo_Input::STRING,
  743. 'value' => XenForo_Input::STRING,
  744. 'existingDataKey' => XenForo_Input::STRING,
  745. ));
  746. }
  747. /**
  748. * Validates a field against a DataWriter.
  749. * Expects 'name' and 'value' keys to be present in the request.
  750. *
  751. * @param string Name of DataWriter against which this field will be validated
  752. * @param array Array containing name, value or existingDataKey, which will override those fetched from _getFieldValidationInputParams
  753. *
  754. * @return XenForo_ControllerResponse_Redirect|XenForo_ControllerResponse_Error
  755. */
  756. protected function _validateField($dataWriterName, array $data = array())
  757. {
  758. $data = array_merge($this->_getFieldValidationInputParams(), $data);
  759. $writer = XenForo_DataWriter::create($dataWriterName);
  760. if (!empty($data['existingDataKey']) || $data['existingDataKey'] === '0')
  761. {
  762. $writer->setExistingData($data['existingDataKey']);
  763. }
  764. $writer->set($data['name'], $data['value']);
  765. if ($errors = $writer->getErrors())
  766. {
  767. return $this->responseError($errors);
  768. }
  769. return $this->responseRedirect(
  770. XenForo_ControllerResponse_Redirect::SUCCESS,
  771. '',
  772. new XenForo_Phrase('redirect_field_validated', array('name' => $data['name'], 'value' => $data['value']))
  773. );
  774. }
  775. /**
  776. * Instructs a DataWriter to delete data based on a POST input parameter.
  777. *
  778. * @param string Name of DataWriter class that will perform the deletion
  779. * @param string|array Name of input parameter that contains the existing data key OR array containing the keys for a multi-key parameter
  780. * @param string URL to which to redirect on success
  781. * @param string Redirection message to show on successful deletion
  782. */
  783. protected function _deleteData($dataWriterName, $existingDataKeyName, $redirectLink, $redirectMessage = null)
  784. {
  785. $this->_assertPostOnly();
  786. $dw = XenForo_DataWriter::create($dataWriterName);
  787. $dw->setExistingData((is_array($existingDataKeyName)
  788. ? $existingDataKeyName
  789. : $this->_input->filterSingle($existingDataKeyName, XenForo_Input::STRING)
  790. ));
  791. $dw->delete();
  792. if (is_null($redirectMessage))
  793. {
  794. $redirectMessage = new XenForo_Phrase('deletion_successful');
  795. }
  796. return $this->responseRedirect(XenForo_ControllerResponse_Redirect::SUCCESS, $redirectLink, $redirectMessage);
  797. }
  798. /**
  799. * Returns true if the request method is POST and an _xfConfirm parameter exists and is true
  800. *
  801. * @return boolean
  802. */
  803. public function isConfirmedPost()
  804. {
  805. return ($this->_request->isPost() && $this->_input->filterSingle('_xfConfirm', XenForo_Input::UINT));
  806. }
  807. /**
  808. * Controller response for when you want to output using a view class.
  809. *
  810. * @param string Name of the view class to be rendered
  811. * @param string Name of the template that should be displayed (may be ignored by view)
  812. * @param array Key-value pairs of parameters to pass to the view
  813. * @param array Key-value pairs of parameters to pass to the container view
  814. *
  815. * @return XenForo_ControllerResponse_View
  816. */
  817. public function responseView($viewName, $templateName = 'DEFAULT', array $params = array(), array $containerParams = array())
  818. {
  819. $controllerResponse = new XenForo_ControllerResponse_View();
  820. $controllerResponse->viewName = $viewName;
  821. $controllerResponse->templateName = $templateName;
  822. $controllerResponse->params = $params;
  823. $controllerResponse->containerParams = $containerParams;
  824. return $controllerResponse;
  825. }
  826. /**
  827. * Creates the specified helper class. If no underscore is present in the class
  828. * name, "XenForo_ControllerHelper_" is prefixed. Otherwise, a full class name
  829. * is assumed.
  830. *
  831. * @param string $class Full class name, or partial suffix (if no underscore)
  832. *
  833. * @return XenForo_ControllerHelper_Abstract
  834. */
  835. public function getHelper($class)
  836. {
  837. if (strpos($class, '_') === false)
  838. {
  839. $class = 'XenForo_ControllerHelper_' . $class;
  840. }
  841. return new $class($this);
  842. }
  843. /**
  844. * Gets a valid record or throws an exception.
  845. *
  846. * @param mixed $id ID of the record to get
  847. * @param XenForo_Model $model Model object to request from
  848. * @param string $method Method to call in the model object
  849. * @param string $errorPhraseKey Key of error phrase to use when not found
  850. *
  851. * @return array
  852. */
  853. public function getRecordOrError($id, $model, $method, $errorPhraseKey)
  854. {
  855. $info = $model->$method($id);
  856. if (!$info)
  857. {
  858. throw $this->responseException($this->responseError(new XenForo_Phrase($errorPhraseKey), 404));
  859. }
  860. return $info;
  861. }
  862. /**
  863. * Gets a dynamic redirect target based on a redirect param or the referrer.
  864. *
  865. * @param string|false $fallbackUrl Fallback if no redirect or referrer is available; if false, uses index
  866. * @param boolean $useReferrer True uses the referrer if no redirect param is available
  867. *
  868. * @return string
  869. */
  870. public function getDynamicRedirect($fallbackUrl = false, $useReferrer = true)
  871. {
  872. $redirect = $this->_input->filterSingle('redirect', XenForo_Input::STRING);
  873. if (!$redirect && $useReferrer)
  874. {
  875. $redirect = $this->_request->getServer('HTTP_REFERER');
  876. }
  877. if ($redirect)
  878. {
  879. $redirectParts = @parse_url(XenForo_Link::convertUriToAbsoluteUri($redirect, true));
  880. if ($redirectParts)
  881. {
  882. $paths = XenForo_Application::get('requestPaths');
  883. $pageParts = @parse_url($paths['fullUri']);
  884. if ($pageParts && $pageParts['host'] == $redirectParts['host'])
  885. {
  886. return $redirect;
  887. }
  888. }
  889. }
  890. if ($fallbackUrl === false)
  891. {
  892. if ($this instanceof XenForo_ControllerAdmin_Abstract)
  893. {
  894. $fallbackUrl = XenForo_Link::buildAdminLink('index');
  895. }
  896. else
  897. {
  898. $fallbackUrl = XenForo_Link::buildPublicLink('index');
  899. }
  900. }
  901. return $fallbackUrl;
  902. }
  903. /**
  904. * Turns a serialized (by jQuery) query string from input into a XenForo_Input object.
  905. *
  906. * @param string Name of index to fetch from $this->_input
  907. * @param boolean On error, throw an exception or return false
  908. * @param string
  909. *
  910. * @return XenForo_Input|false
  911. */
  912. protected function _getInputFromSerialized($varname, $throw = true, &$errorPhraseKey = null)
  913. {
  914. if ($inputString = $this->_input->filterSingle($varname, XenForo_Input::STRING))
  915. {
  916. try
  917. {
  918. return new XenForo_Input(XenForo_Application::parseQueryString($inputString));
  919. }
  920. catch (Exception $e)
  921. {
  922. $errorPhraseKey = 'string_could_not_be_converted_to_input';
  923. if ($throw)
  924. {
  925. throw $this->responseException(
  926. $this->responseError(new XenForo_Phrase($errorPhraseKey))
  927. );
  928. }
  929. }
  930. }
  931. return false;
  932. }
  933. /**
  934. * Checks for a match of one or more IPs against a list of IP and IP fragments
  935. *
  936. * @param string|array IP address(es)
  937. * @param array List of IP addresses
  938. *
  939. * @return boolean
  940. */
  941. public function ipMatch($checkIps, array $ipList)
  942. {
  943. if (!is_array($checkIps))
  944. {
  945. $checkIps = array($checkIps);
  946. }
  947. foreach ($checkIps AS $ip)
  948. {
  949. $ipClassABlock = intval($ip);
  950. $long = sprintf('%u', ip2long($ip));
  951. if (isset($ipList[$ipClassABlock]))
  952. {
  953. foreach ($ipList[$ipClassABlock] AS $range)
  954. {
  955. if ($long >= $range[0] && $long <= $range[1])
  956. {
  957. return true;
  958. }
  959. }
  960. }
  961. }
  962. return false;
  963. }
  964. /**
  965. * Returns an array of IPs for the current client
  966. *
  967. * @return
  968. */
  969. protected function _getClientIps()
  970. {
  971. $ips = preg_split('/,\s*/', $this->_request->getClientIp(true));
  972. $ips[] = $this->_request->getClientIp(false);
  973. return array_unique($ips);
  974. }
  975. }