CommonComponent.php 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. namespace Tools\Controller\Component;
  3. use Cake\Controller\Component;
  4. use Cake\Core\Configure;
  5. use Cake\Event\EventInterface;
  6. use Cake\Http\ServerRequest;
  7. use Cake\Routing\Router;
  8. use Tools\Utility\Utility;
  9. /**
  10. * A component included in every app to take care of common stuff.
  11. *
  12. * @author Mark Scherer
  13. * @license MIT
  14. */
  15. class CommonComponent extends Component {
  16. /**
  17. * @var \Cake\Controller\Controller
  18. */
  19. protected $controller;
  20. /**
  21. * @param array $config
  22. * @return void
  23. */
  24. public function initialize(array $config): void {
  25. parent::initialize($config);
  26. $this->controller = $this->getController();
  27. }
  28. /**
  29. * @param \Cake\Event\EventInterface $event
  30. * @return void
  31. */
  32. public function startup(EventInterface $event) {
  33. if (Configure::read('DataPreparation.notrim')) {
  34. return;
  35. }
  36. $request = $this->controller->getRequest();
  37. if ($this->controller->getRequest()->getData()) {
  38. $newData = Utility::trimDeep($request->getData());
  39. foreach ($newData as $k => $v) {
  40. if ($request->getData($k) !== $v) {
  41. $request = $request->withData($k, $v);
  42. }
  43. }
  44. }
  45. if ($request->getQuery()) {
  46. $queryData = Utility::trimDeep($request->getQuery());
  47. if ($queryData !== $request->getQuery()) {
  48. $request = $request->withQueryParams($queryData);
  49. }
  50. }
  51. if ($request->getParam('pass')) {
  52. $passData = Utility::trimDeep($request->getParam('pass'));
  53. if ($passData !== $request->getParam('pass')) {
  54. $request = $request->withParam('pass', $passData);
  55. }
  56. }
  57. if ($request === $this->controller->getRequest()) {
  58. return;
  59. }
  60. $this->controller->setRequest($request);
  61. }
  62. /**
  63. * Returns internal redirect only, otherwise falls back to default.
  64. *
  65. * Lookup order:
  66. * - POST data
  67. * - query string
  68. * - provided default
  69. *
  70. * @param array|string $default
  71. * @param array|string|null $data
  72. * @param string $key
  73. *
  74. * @return array|string
  75. */
  76. public function getSafeRedirectUrl($default, $data = null, $key = 'redirect') {
  77. $redirectUrl = $data ?: ($this->controller->getRequest()->getData($key) ?: $this->controller->getRequest()->getQuery($key));
  78. if ($redirectUrl && (mb_substr($redirectUrl, 0, 1) !== '/' || mb_substr($redirectUrl, 0, 2) === '//')) {
  79. $redirectUrl = null;
  80. }
  81. return $redirectUrl ?: $default;
  82. }
  83. /**
  84. * List all direct actions of a controller
  85. *
  86. * @return array<string> Actions
  87. */
  88. public function listActions() {
  89. /** @var string|null $parentClass */
  90. $parentClass = get_parent_class($this->controller);
  91. if (!$parentClass) {
  92. return [];
  93. }
  94. $parentClassMethods = get_class_methods($parentClass);
  95. $subClassMethods = get_class_methods($this->controller);
  96. $classMethods = array_diff($subClassMethods, $parentClassMethods);
  97. foreach ($classMethods as $key => $classMethod) {
  98. if (mb_substr($classMethod, 0, 1) === '_') {
  99. unset($classMethods[$key]);
  100. }
  101. }
  102. return $classMethods;
  103. }
  104. /**
  105. * Convenience method to check on POSTED data.
  106. * Doesn't matter if it's POST, PUT or PATCH.
  107. *
  108. * Note that you can also use request->is(array('post', 'put', 'patch') directly.
  109. *
  110. * @return bool If it is of type POST/PUT/PATCH
  111. */
  112. public function isPosted() {
  113. return $this->controller->getRequest()->is(['post', 'put', 'patch']);
  114. }
  115. /**
  116. * Adds helpers just in time (inside actions - only when needed).
  117. *
  118. * @param array $helpers
  119. * @return void
  120. */
  121. public function addHelpers(array $helpers) {
  122. $this->controller->viewBuilder()->setHelpers($helpers, true);
  123. }
  124. /**
  125. * Used to get the value of a passed param.
  126. *
  127. * @param mixed $var
  128. * @param mixed $default
  129. * @return mixed
  130. */
  131. public function getPassedParam($var, $default = null) {
  132. $passed = $this->controller->getRequest()->getParam('pass');
  133. return $passed[$var] ?? $default;
  134. }
  135. /**
  136. * Returns defaultUrlParams including configured prefixes.
  137. *
  138. * Deprecated: Routing.prefixes config is not needed anymore as it is always
  139. * just "prefix" now.
  140. *
  141. * @return array URL params
  142. */
  143. public static function defaultUrlParams() {
  144. $defaults = [
  145. 'plugin' => false,
  146. 'prefix' => false,
  147. ];
  148. return $defaults;
  149. }
  150. /**
  151. * Returns current url (with all missing params automatically added).
  152. * Necessary for Router::url() and comparison of urls to work.
  153. *
  154. * @param bool $asString Defaults to false = array
  155. * @return mixed URL
  156. */
  157. public function currentUrl($asString = false) {
  158. $action = $this->controller->getRequest()->getParam('action');
  159. $passed = (array)$this->controller->getRequest()->getParam('pass');
  160. $url = [
  161. 'prefix' => $this->controller->getRequest()->getParam('prefix'),
  162. 'plugin' => $this->controller->getRequest()->getParam('plugin'),
  163. 'action' => $action,
  164. 'controller' => $this->controller->getRequest()->getParam('controller'),
  165. ];
  166. $url = array_merge($passed, $url);
  167. if ($asString === true) {
  168. return Router::url($url);
  169. }
  170. return $url;
  171. }
  172. /**
  173. * Smart Referer Redirect - will try to use an existing referer first
  174. * otherwise it will use the default url
  175. *
  176. * @param mixed $whereTo URL
  177. * @param bool $allowSelf if redirect to the same controller/action (url) is allowed
  178. * @param int $status
  179. * @return \Cake\Http\Response
  180. */
  181. public function autoRedirect($whereTo, $allowSelf = false, $status = 302) {
  182. if ($allowSelf || $this->controller->referer(null, true) !== $this->controller->getRequest()->getRequestTarget()) {
  183. return $this->controller->redirect($this->controller->referer($whereTo, true), $status);
  184. }
  185. return $this->controller->redirect($whereTo, $status);
  186. }
  187. /**
  188. * Should be a 303, but:
  189. * Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
  190. *
  191. * TODO: change to 303 with backwards-compatibility for older browsers...
  192. *
  193. * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
  194. *
  195. * @param mixed $whereTo URL
  196. * @param int $status
  197. * @return \Cake\Http\Response
  198. */
  199. public function postRedirect($whereTo, $status = 302) {
  200. return $this->controller->redirect($whereTo, $status);
  201. }
  202. /**
  203. * Combine auto with post
  204. * also allows whitelisting certain actions for autoRedirect (use Controller::$autoRedirectActions)
  205. *
  206. * @param mixed $whereTo URL
  207. * @param bool $conditionalAutoRedirect false to skip whitelisting
  208. * @param int $status
  209. * @return \Cake\Http\Response
  210. */
  211. public function autoPostRedirect($whereTo, $conditionalAutoRedirect = true, $status = 302) {
  212. $referer = $this->controller->referer($whereTo, true);
  213. if (!$conditionalAutoRedirect && !empty($referer)) {
  214. return $this->postRedirect($referer, $status);
  215. }
  216. if (!empty($referer)) {
  217. //FIXME
  218. $referer = Router::parseRequest(new ServerRequest(['url' => $referer, 'environment' => ['REQUEST_METHOD' => 'GET']]));
  219. }
  220. if ($conditionalAutoRedirect && !empty($this->controller->autoRedirectActions) && is_array($referer) && !empty($referer['action'])) {
  221. // Be sure that controller offset exists, otherwise you
  222. // will run into problems, if you use url rewriting.
  223. $refererController = null;
  224. if (isset($referer['controller'])) {
  225. $refererController = $referer['controller'];
  226. }
  227. // fixme
  228. if (!isset($this->controller->autoRedirectActions)) {
  229. $this->controller->autoRedirectActions = [];
  230. }
  231. foreach ($this->controller->autoRedirectActions as $action) {
  232. [$controller, $action] = pluginSplit($action);
  233. if (!empty($controller) && $refererController !== '*' && $refererController !== $controller) {
  234. continue;
  235. }
  236. if (empty($controller) && $refererController !== $this->controller->getRequest()->getParam('controller')) {
  237. continue;
  238. }
  239. if (!in_array($referer['action'], (array)$this->controller->autoRedirectActions, true)) {
  240. continue;
  241. }
  242. return $this->autoRedirect($whereTo, true, $status);
  243. }
  244. }
  245. return $this->postRedirect($whereTo, $status);
  246. }
  247. /**
  248. * Automatically add missing URL parts of the current URL including
  249. * - querystring (especially for 3.x then)
  250. * - passed params
  251. *
  252. * @param mixed|null $url
  253. * @param int|null $status
  254. * @return \Cake\Http\Response
  255. */
  256. public function completeRedirect($url = null, $status = 302) {
  257. if ($url === null) {
  258. $url = [
  259. 'plugin' => $this->controller->getRequest()->getParam('plugin'),
  260. 'controller' => $this->controller->getRequest()->getParam('controller'),
  261. 'action' => $this->controller->getRequest()->getParam('action'),
  262. '_ext' => $this->controller->getRequest()->getParam('_ext'),
  263. ];
  264. }
  265. if (is_array($url)) {
  266. $url += $this->controller->getRequest()->getParam('pass');
  267. }
  268. return $this->controller->redirect($url, $status);
  269. }
  270. /**
  271. * Set headers to cache this request.
  272. * Opposite of Controller::disableCache()
  273. *
  274. * @param int $seconds
  275. * @return void
  276. */
  277. public function forceCache($seconds = HOUR) {
  278. $response = $this->controller->getResponse();
  279. $response = $response->withHeader('Cache-Control', 'public, max-age=' . $seconds)
  280. ->withHeader('Last-modified', gmdate('D, j M Y H:i:s', time()) . ' GMT')
  281. ->withHeader('Expires', gmdate('D, j M Y H:i:s', time() + $seconds) . ' GMT');
  282. $this->controller->setResponse($response);
  283. }
  284. /**
  285. * Referrer checking (where does the user come from)
  286. * Only returns true for a valid external referrer.
  287. *
  288. * @param string|null $ref Referer
  289. * @return bool Success
  290. */
  291. public function isForeignReferer($ref = null) {
  292. if ($ref === null) {
  293. /** @var string|null $ref */
  294. $ref = env('HTTP_REFERER');
  295. }
  296. if (!$ref) {
  297. return false;
  298. }
  299. $base = Configure::read('App.fullBaseUrl') . '/';
  300. if (mb_strpos($ref, $base) === 0) {
  301. return false;
  302. }
  303. return true;
  304. }
  305. }