CommonComponent.php 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. <?php
  2. if (!defined('CLASS_USER')) {
  3. define('CLASS_USER', 'User');
  4. }
  5. App::uses('Component', 'Controller');
  6. App::uses('Sanitize', 'Utility');
  7. App::uses('Utility', 'Tools.Utility');
  8. App::uses('Hash', 'Utility');
  9. /**
  10. * A component included in every app to take care of common stuff.
  11. *
  12. * @author Mark Scherer
  13. * @copyright 2012 Mark Scherer
  14. * @license http://opensource.org/licenses/mit-license.php MIT
  15. */
  16. class CommonComponent extends Component {
  17. public $components = ['Session', 'RequestHandler'];
  18. public $userModel = CLASS_USER;
  19. /**
  20. * For automatic startup
  21. * for this helper the controller has to be passed as reference
  22. *
  23. * @return void
  24. */
  25. public function initialize(Controller $Controller) {
  26. parent::initialize($Controller);
  27. $this->Controller = $Controller;
  28. }
  29. /**
  30. * For this helper the controller has to be passed as reference
  31. * for manual startup with $disableStartup = true (requires this to be called prior to any other method)
  32. *
  33. * @return void
  34. */
  35. public function startup(Controller $Controller = null) {
  36. parent::startup($Controller);
  37. // Data preparation
  38. if (!empty($this->Controller->request->data) && !Configure::read('DataPreparation.notrim')) {
  39. $this->Controller->request->data = $this->trimDeep($this->Controller->request->data);
  40. }
  41. if (!empty($this->Controller->request->query) && !Configure::read('DataPreparation.notrim')) {
  42. $this->Controller->request->query = $this->trimDeep($this->Controller->request->query);
  43. }
  44. if (!empty($this->Controller->request->params['named']) && !Configure::read('DataPreparation.notrim')) {
  45. $this->Controller->request->params['named'] = $this->trimDeep($this->Controller->request->params['named']);
  46. }
  47. if (!empty($this->Controller->request->params['pass']) && !Configure::read('DataPreparation.notrim')) {
  48. $this->Controller->request->params['pass'] = $this->trimDeep($this->Controller->request->params['pass']);
  49. }
  50. // Information gathering
  51. if (!Configure::read('App.disableMobileDetection') && ($mobile = $this->Session->read('Session.mobile')) === null) {
  52. App::uses('UserAgentLib', 'Tools.Lib');
  53. $UserAgentLib = new UserAgentLib();
  54. $mobile = (int)$UserAgentLib->isMobile();
  55. $this->Session->write('Session.mobile', $mobile);
  56. }
  57. // Auto layout switch
  58. if ($this->Controller->request->is('ajax')) {
  59. $this->Controller->layout = 'ajax';
  60. }
  61. }
  62. /**
  63. * Called after the Controller::beforeRender(), after the view class is loaded, and before the
  64. * Controller::render()
  65. *
  66. * @param object $Controller Controller with components to beforeRender
  67. * @return void
  68. */
  69. public function beforeRender(Controller $Controller) {
  70. if (Configure::read('Common.messages') !== false && $messages = $this->Session->read('Message')) {
  71. foreach ($messages as $message) {
  72. $this->flashMessage($message['message'], 'error');
  73. }
  74. $this->Session->delete('Message');
  75. }
  76. if ($this->Controller->request->is('ajax')) {
  77. $ajaxMessages = array_merge(
  78. (array)$this->Session->read('messages'),
  79. (array)Configure::read('messages')
  80. );
  81. // The header can be read with JavaScript and a custom Message can be displayed
  82. $this->Controller->response->header('X-Ajax-Flashmessage', json_encode($ajaxMessages));
  83. $this->Session->delete('messages');
  84. }
  85. // Custom options
  86. if (isset($Controller->options)) {
  87. $Controller->set('options', $Controller->options);
  88. }
  89. }
  90. /**
  91. * List all direct actions of a controller
  92. *
  93. * @return array Actions
  94. */
  95. public function listActions() {
  96. $class = Inflector::camelize($this->Controller->name) . 'Controller';
  97. $parentClassMethods = get_class_methods(get_parent_class($class));
  98. $subClassMethods = get_class_methods($class);
  99. $classMethods = array_diff($subClassMethods, $parentClassMethods);
  100. foreach ($classMethods as $key => $value) {
  101. if (substr($value, 0, 1) === '_') {
  102. unset($classMethods[$key]);
  103. }
  104. }
  105. return $classMethods;
  106. }
  107. /**
  108. * Convenience method to check on POSTED data.
  109. * Doesn't matter if it's POST or PUT.
  110. *
  111. * Note that since 2.4 you can use request->is(array('post', 'put') directly.
  112. *
  113. * @return bool If it is of type POST/PUT
  114. */
  115. public function isPosted() {
  116. return $this->Controller->request->is(['post', 'put']);
  117. }
  118. /**
  119. * Adds a flash message.
  120. * Updates "messages" session content (to enable multiple messages of one type).
  121. *
  122. * @param string $message Message to output.
  123. * @param string $type Type ('error', 'warning', 'success', 'info' or custom class).
  124. * @return void
  125. * @deprecated Use FlashComponent::message() instead.
  126. */
  127. public function flashMessage($message, $type = null) {
  128. if (!$type) {
  129. $type = 'info';
  130. }
  131. $old = (array)$this->Session->read('messages');
  132. if (isset($old[$type]) && count($old[$type]) > 99) {
  133. array_shift($old[$type]);
  134. }
  135. $old[$type][] = $message;
  136. $this->Session->write('messages', $old);
  137. }
  138. /**
  139. * Adds a transient flash message.
  140. * These flash messages that are not saved (only available for current view),
  141. * will be merged into the session flash ones prior to output.
  142. *
  143. * @param string $message Message to output.
  144. * @param string $type Type ('error', 'warning', 'success', 'info' or custom class).
  145. * @return void
  146. * @deprecated Use FlashComponent::transientMessage() instead.
  147. */
  148. public static function transientFlashMessage($message, $type = null) {
  149. if (!$type) {
  150. $type = 'info';
  151. }
  152. $old = (array)Configure::read('messages');
  153. if (isset($old[$type]) && count($old[$type]) > 99) {
  154. array_shift($old[$type]);
  155. }
  156. $old[$type][] = $message;
  157. Configure::write('messages', $old);
  158. }
  159. /**
  160. * Add helper just in time (inside actions - only when needed)
  161. * aware of plugins
  162. *
  163. * Note that this method will not exist in 3.x anymore. Lazyloading of helpers
  164. * makes this unnecessary.
  165. *
  166. * @param mixed $helpers (single string or multiple array)
  167. */
  168. public function loadHelper($helpers = []) {
  169. $this->Controller->helpers = array_merge($this->Controller->helpers, (array)$helpers);
  170. }
  171. /**
  172. * Add lib just in time (inside actions - only when needed)
  173. * aware of plugins and config array (if passed)
  174. * ONLY works if constructor consists only of one param (settings)!
  175. *
  176. * Note that this method will not exist in 3.x anymore.
  177. *
  178. * @param mixed $libs (single string or multiple array)
  179. * e.g.: array('Tools.MyLib'=>array('key'=>'value'), ...)
  180. * @return void
  181. */
  182. public function loadLib($libs = []) {
  183. foreach ((array)$libs as $lib => $config) {
  184. if (is_int($lib)) {
  185. $lib = $config;
  186. $config = null;
  187. }
  188. list($plugin, $libName) = pluginSplit($lib);
  189. if (isset($this->Controller->{$libName})) {
  190. continue;
  191. }
  192. $package = 'Lib';
  193. if ($plugin) {
  194. $package = $plugin . '.' . $package;
  195. }
  196. App::uses($libName, $package);
  197. $this->Controller->{$libName} = new $libName($config);
  198. }
  199. }
  200. /**
  201. * Add component just in time (inside actions - only when needed)
  202. * aware of plugins and config array (if passed)
  203. * @param mixed $components (single string or multiple array)
  204. * @poaram bool $callbacks (defaults to true)
  205. */
  206. public function loadComponent($components = [], $callbacks = true) {
  207. foreach ((array)$components as $component => $config) {
  208. if (is_int($component)) {
  209. $component = $config;
  210. $config = [];
  211. }
  212. list($plugin, $componentName) = pluginSplit($component);
  213. if (isset($this->Controller->{$componentName})) {
  214. continue;
  215. }
  216. $this->Controller->{$componentName} = $this->Controller->Components->load($component, $config);
  217. if (!$callbacks) {
  218. continue;
  219. }
  220. if (method_exists($this->Controller->{$componentName}, 'initialize')) {
  221. $this->Controller->{$componentName}->initialize($this->Controller);
  222. }
  223. if (method_exists($this->Controller->{$componentName}, 'startup')) {
  224. $this->Controller->{$componentName}->startup($this->Controller);
  225. }
  226. }
  227. }
  228. /**
  229. * Used to get the value of a passed param.
  230. *
  231. * @param mixed $var
  232. * @param mixed $default
  233. * @return mixed
  234. */
  235. public function getPassedParam($var, $default = null) {
  236. return (isset($this->Controller->request->params['pass'][$var])) ? $this->Controller->request->params['pass'][$var] : $default;
  237. }
  238. /**
  239. * Returns defaultUrlParams including configured prefixes.
  240. *
  241. * @return array Url params
  242. */
  243. public static function defaultUrlParams() {
  244. $defaults = ['plugin' => false];
  245. $prefixes = (array)Configure::read('Routing.prefixes');
  246. foreach ($prefixes as $prefix) {
  247. $defaults[$prefix] = false;
  248. }
  249. return $defaults;
  250. }
  251. /**
  252. * Returns current url (with all missing params automatically added).
  253. * Necessary for Router::url() and comparison of urls to work.
  254. *
  255. * @param bool $asString: defaults to false = array
  256. * @return mixed Url
  257. */
  258. public function currentUrl($asString = false) {
  259. if (isset($this->Controller->request->params['prefix']) && mb_strpos($this->Controller->request->params['action'], $this->Controller->request->params['prefix']) === 0) {
  260. $action = mb_substr($this->Controller->request->params['action'], mb_strlen($this->Controller->request->params['prefix']) + 1);
  261. } else {
  262. $action = $this->Controller->request->params['action'];
  263. }
  264. $url = array_merge($this->Controller->request->params['named'], $this->Controller->request->params['pass'], ['prefix' => isset($this->Controller->request->params['prefix']) ? $this->Controller->request->params['prefix'] : null,
  265. 'plugin' => $this->Controller->request->params['plugin'], 'action' => $action, 'controller' => $this->Controller->request->params['controller']]);
  266. if ($asString === true) {
  267. return Router::url($url);
  268. }
  269. return $url;
  270. }
  271. /**
  272. * Tries to allow super admin access for certain urls via `Config.pwd`.
  273. * Only used in admin actions and only to prevent accidental data loss due to incorrect access.
  274. * Do not assume this to be a safe access control mechanism!
  275. *
  276. * Password can be passed as named param or query string param.
  277. *
  278. * @return bool Success
  279. */
  280. public function validAdminUrlAccess() {
  281. $pwd = Configure::read('Config.pwd');
  282. if (!$pwd) {
  283. return false;
  284. }
  285. $urlPwd = $this->getNamedParam('pwd');
  286. if (!$urlPwd) {
  287. $urlPwd = $this->getQueryParam('pwd');
  288. }
  289. if (!$urlPwd) {
  290. return false;
  291. }
  292. return $pwd === $urlPwd;
  293. }
  294. /**
  295. * Direct login for a specific user id.
  296. * Will respect full login scope (if defined in auth setup) as well as contained data and
  297. * can therefore return false if the login fails due to unmatched scope.
  298. *
  299. * @see DirectAuthentication auth adapter
  300. * @param mixed $id User id
  301. * @param array $settings Settings for DirectAuthentication
  302. * - fields
  303. * @return bool Success
  304. */
  305. public function manualLogin($id, $settings = []) {
  306. $requestData = $this->Controller->request->data;
  307. $authData = $this->Controller->Auth->authenticate;
  308. $settings = array_merge($authData, $settings);
  309. $settings['fields'] = ['username' => 'id'];
  310. $this->Controller->request->data = [$this->userModel => ['id' => $id]];
  311. $this->Controller->Auth->authenticate = ['Tools.Direct' => $settings];
  312. $result = $this->Controller->Auth->login();
  313. $this->Controller->Auth->authenticate = $authData;
  314. $this->Controller->request->data = $requestData;
  315. return $result;
  316. }
  317. /**
  318. * Force login for a specific user id.
  319. * Only fails if the user does not exist or if he is already
  320. * logged in as it ignores the usual scope.
  321. *
  322. * Better than Auth->login($data) since it respects the other auth configs such as
  323. * fields, contain, recursive and userModel.
  324. *
  325. * @param mixed $id User id
  326. * @return bool Success
  327. */
  328. public function forceLogin($id) {
  329. $settings = [
  330. 'scope' => [],
  331. ];
  332. return $this->manualLogin($id, $settings);
  333. }
  334. /**
  335. * Smart Referer Redirect - will try to use an existing referer first
  336. * otherwise it will use the default url
  337. *
  338. * @param mixed $url
  339. * @param bool $allowSelf if redirect to the same controller/action (url) is allowed
  340. * @param int $status
  341. * @return void
  342. */
  343. public function autoRedirect($whereTo, $allowSelf = true, $status = null) {
  344. if ($allowSelf || $this->Controller->referer(null, true) !== '/' . $this->Controller->request->url) {
  345. return $this->Controller->redirect($this->Controller->referer($whereTo, true), $status);
  346. }
  347. return $this->Controller->redirect($whereTo, $status);
  348. }
  349. /**
  350. * Should be a 303, but:
  351. * 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.
  352. *
  353. * TODO: change to 303 with backwardscompatability for older browsers...
  354. *
  355. * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
  356. * @param mixed $url
  357. * @param int $status
  358. * @return void
  359. */
  360. public function postRedirect($whereTo, $status = 302) {
  361. return $this->Controller->redirect($whereTo, $status);
  362. }
  363. /**
  364. * Combine auto with post
  365. * also allows whitelisting certain actions for autoRedirect (use Controller::$autoRedirectActions)
  366. * @param mixed $url
  367. * @param bool $conditionalAutoRedirect false to skip whitelisting
  368. * @param int $status
  369. * @return void
  370. */
  371. public function autoPostRedirect($whereTo, $conditionalAutoRedirect = true, $status = 302) {
  372. $referer = $this->Controller->referer($whereTo, true);
  373. if (!$conditionalAutoRedirect && !empty($referer)) {
  374. return $this->postRedirect($referer, $status);
  375. }
  376. if (!empty($referer)) {
  377. $referer = Router::parse($referer);
  378. }
  379. if (!$conditionalAutoRedirect || empty($this->Controller->autoRedirectActions) || is_array($referer) && !empty($referer['action'])) {
  380. // Be sure that controller offset exists, otherwise you
  381. // will run into problems, if you use url rewriting.
  382. $refererController = null;
  383. if (isset($referer['controller'])) {
  384. $refererController = Inflector::camelize($referer['controller']);
  385. }
  386. // fixme
  387. if (!isset($this->Controller->autoRedirectActions)) {
  388. $this->Controller->autoRedirectActions = [];
  389. }
  390. foreach ($this->Controller->autoRedirectActions as $action) {
  391. list($controller, $action) = pluginSplit($action);
  392. if (!empty($controller) && $refererController !== '*' && $refererController != $controller) {
  393. continue;
  394. }
  395. if (empty($controller) && $refererController != Inflector::camelize($this->Controller->request->params['controller'])) {
  396. continue;
  397. }
  398. if (!in_array($referer['action'], $this->Controller->autoRedirectActions)) {
  399. continue;
  400. }
  401. return $this->autoRedirect($whereTo, true, $status);
  402. }
  403. }
  404. return $this->postRedirect($whereTo, $status);
  405. }
  406. /**
  407. * Automatically add missing url parts of the current url including
  408. * - querystring (especially for 3.x then)
  409. * - named params (until 3.x when they will become deprecated)
  410. * - passed params
  411. *
  412. * @param mixed $url
  413. * @param int $status
  414. * @param bool $exit
  415. * @return void
  416. */
  417. public function completeRedirect($url = null, $status = null, $exit = true) {
  418. if ($url === null) {
  419. $url = $this->Controller->request->params;
  420. unset($url['named']);
  421. unset($url['pass']);
  422. unset($url['isAjax']);
  423. }
  424. if (is_array($url)) {
  425. $url += $this->Controller->request->params['named'];
  426. $url += $this->Controller->request->params['pass'];
  427. }
  428. return $this->Controller->redirect($url, $status, $exit);
  429. }
  430. /**
  431. * Only redirect to itself if cookies are on
  432. * Prevents problems with lost data
  433. * 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.
  434. *
  435. * @see http://en.wikipedia.org/wiki/Post/Redirect/Get
  436. * TODO: change to 303 with backwardscompatability for older browsers...
  437. * @param int $status
  438. * @return void
  439. */
  440. public function prgRedirect($status = 302) {
  441. if (!empty($_COOKIE[Configure::read('Session.cookie')])) {
  442. return $this->Controller->redirect('/' . $this->Controller->request->url, $status);
  443. }
  444. }
  445. /**
  446. * Handler for passing some meta data to the view
  447. * uses CommonHelper to include them in the layout
  448. *
  449. * @param type (relevance):
  450. * - title (10), description (9), robots(7), language(5), keywords (0)
  451. * - custom: abstract (1), category(1), GOOGLEBOT(0) ...
  452. * @return void
  453. */
  454. public function setMeta($type, $content, $prep = true) {
  455. if (!in_array($type, ['title', 'canonical', 'description', 'keywords', 'robots', 'language', 'custom'])) {
  456. trigger_error(sprintf('Meta Type %s invalid', $type), E_USER_WARNING);
  457. return;
  458. }
  459. if ($type === 'canonical' && $prep) {
  460. $content = Router::url($content);
  461. }
  462. if ($type === 'canonical' && $prep) {
  463. $content = h($content);
  464. }
  465. Configure::write('Meta.' . $type, $content);
  466. }
  467. /**
  468. * Set headers to cache this request.
  469. * Opposite of Controller::disableCache()
  470. * TODO: set response class header instead
  471. *
  472. * @param int $seconds
  473. * @return void
  474. */
  475. public function forceCache($seconds = HOUR) {
  476. $this->Controller->response->header('Cache-Control', 'public, max-age=' . $seconds);
  477. $this->Controller->response->header('Last-modified', gmdate("D, j M Y H:i:s", time()) . " GMT");
  478. $this->Controller->response->header('Expires', gmdate("D, j M Y H:i:s", time() + $seconds) . " GMT");
  479. }
  480. /**
  481. * Referrer checking (where does the user come from)
  482. * Only returns true for a valid external referrer.
  483. *
  484. * @return bool Success
  485. */
  486. public function isForeignReferer($ref = null) {
  487. if ($ref === null) {
  488. $ref = env('HTTP_REFERER');
  489. }
  490. if (!$ref) {
  491. return false;
  492. }
  493. $base = Configure::read('App.fullBaseUrl') . '/';
  494. if (strpos($ref, $base) === 0) {
  495. return false;
  496. }
  497. return true;
  498. }
  499. /**
  500. * CommonComponent::denyAccess()
  501. *
  502. * @return void
  503. */
  504. public function denyAccess() {
  505. $ref = env('HTTP_USER_AGENT');
  506. if ($this->isForeignReferer($ref)) {
  507. if (strpos(strtolower($ref), 'http://anonymouse.org/') === 0) {
  508. $this->cakeError('error406', []);
  509. }
  510. }
  511. }
  512. /**
  513. * CommonComponent::monitorCookieProblems()
  514. *
  515. * @return void
  516. */
  517. public function monitorCookieProblems() {
  518. $ip = $this->Controller->request->clientIp();
  519. $host = gethostbyaddr($ip);
  520. $sessionId = session_id();
  521. if (empty($sessionId)) {
  522. $sessionId = '--';
  523. }
  524. if (empty($_REQUEST[Configure::read('Session.cookie')]) && !($res = Cache::read($ip))) {
  525. $this->log('CookieProblem:: SID: ' . $sessionId . ' | IP: ' . $ip . ' (' . $host . ') | REF: ' . $this->Controller->referer() . ' | Agent: ' . env('HTTP_USER_AGENT'), 'noscript');
  526. Cache::write($ip, 1);
  527. }
  528. }
  529. /**
  530. * //todo: move to Utility?
  531. *
  532. * @return bool true if disabled (bots, etc), false if enabled
  533. */
  534. public static function cookiesDisabled() {
  535. if (!empty($_COOKIE) && !empty($_COOKIE[Configure::read('Session.cookie')])) {
  536. return false;
  537. }
  538. return true;
  539. }
  540. /**
  541. * Quick sql debug from controller dynamically
  542. * or statically from just about any other place in the script
  543. *
  544. * @param bool $exit If script should exit.
  545. * @return bool Success
  546. */
  547. public function sql($exit = true) {
  548. if (isset($this->Controller)) {
  549. $object = $this->Controller->{$this->Controller->modelClass};
  550. } else {
  551. $object = ClassRegistry::init(defined('CLASS_USER') ? CLASS_USER : $this->userModel);
  552. }
  553. $log = $object->getDataSource()->getLog(false, false);
  554. foreach ($log['log'] as $key => $value) {
  555. if (strpos($value['query'], 'SHOW ') === 0 || strpos($value['query'], 'SELECT CHARACTER_SET_NAME ') === 0) {
  556. unset($log['log'][$key]);
  557. continue;
  558. }
  559. }
  560. if ($exit) {
  561. debug($log);
  562. exit();
  563. }
  564. $log = print_r($log, true);
  565. App::uses('CakeLog', 'Log');
  566. return CakeLog::write('sql', $log);
  567. }
  568. /**
  569. * Localize
  570. *
  571. * @return bool Success
  572. */
  573. public function localize($lang = null) {
  574. if ($lang === null) {
  575. $lang = Configure::read('Config.language');
  576. }
  577. if (empty($lang)) {
  578. return false;
  579. }
  580. if (($pos = strpos($lang, '-')) !== false) {
  581. $lang = substr($lang, 0, $pos);
  582. }
  583. if ($lang === DEFAULT_LANGUAGE) {
  584. return null;
  585. }
  586. if (!($pattern = Configure::read('LocalizationPattern.' . $lang))) {
  587. return false;
  588. }
  589. foreach ((array)$pattern as $key => $value) {
  590. Configure::write('Localization.' . $key, $value);
  591. }
  592. return true;
  593. }
  594. /**
  595. * Main controller function for consistency in controller naming
  596. *
  597. * @deprecated Will be removed in 1.0
  598. * @return void
  599. */
  600. public function ensureControllerConsistency() {
  601. // problems with plugins
  602. if (!empty($this->Controller->request->params['plugin'])) {
  603. return;
  604. }
  605. if (($name = strtolower(Inflector::underscore($this->Controller->name))) !== $this->Controller->request->params['controller']) {
  606. $this->Controller->log('301: ' . $this->Controller->request->params['controller'] . ' => ' . $name . ' (Ref ' . $this->Controller->referer() . ')', '301'); // log problem with controller naming
  607. if (!$this->Controller->RequestHandler->isPost()) {
  608. // underscored version is the only valid one to avoid duplicate content
  609. $url = ['controller' => $name, 'action' => $this->Controller->request->params['action']];
  610. $url = array_merge($url, $this->Controller->request->params['pass'], $this->Controller->request->params['named']);
  611. //TODO: add plugin/admin stuff which right now is supposed to work automatically
  612. $this->Controller->redirect($url, 301);
  613. }
  614. }
  615. return true;
  616. // problem with extensions (rss etc)
  617. if (empty($this->Controller->request->params['prefix']) && ($currentUrl = $this->currentUrl(true)) != $this->Controller->here) {
  618. //pr($this->Controller->here);
  619. //pr($currentUrl);
  620. $this->log('301: ' . $this->Controller->here . ' => ' . $currentUrl . ' (Referer ' . $this->Controller->referer() . ')', '301');
  621. if (!$this->Controller->RequestHandler->isPost()) {
  622. $url = ['controller' => $this->Controller->request->params['controller'], 'action' => $this->Controller->request->params['action']];
  623. $url = array_merge($url, $this->Controller->request->params['pass'], $this->Controller->request->params['named']);
  624. $this->Controller->redirect($url, 301);
  625. }
  626. }
  627. }
  628. /**
  629. * Try to detect group for a multidim array for select boxes.
  630. * Extracts the group name of the selected key.
  631. *
  632. * @param array $array
  633. * @param string $key
  634. * @param array $matching
  635. * @return string result
  636. */
  637. public static function getGroup($multiDimArray, $key, $matching = []) {
  638. if (!is_array($multiDimArray) || empty($key)) {
  639. return '';
  640. }
  641. foreach ($multiDimArray as $group => $data) {
  642. if (array_key_exists($key, $data)) {
  643. if (!empty($matching)) {
  644. if (array_key_exists($group, $matching)) {
  645. return $matching[$group];
  646. }
  647. return '';
  648. }
  649. return $group;
  650. }
  651. }
  652. return '';
  653. }
  654. /*** DEEP FUNCTIONS ***/
  655. /**
  656. * Move to boostrap?
  657. */
  658. public function trimDeep($value) {
  659. $value = is_array($value) ? array_map([$this, 'trimDeep'], $value) : trim($value);
  660. return $value;
  661. }
  662. /**
  663. * Move to boostrap?
  664. */
  665. public function specialcharsDeep($value) {
  666. $value = is_array($value) ? array_map([$this, 'specialcharsDeep'], $value) : htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
  667. return $value;
  668. }
  669. /**
  670. * Move to boostrap?
  671. */
  672. public function deep($function, $value) {
  673. $value = is_array($value) ? array_map([$this, $function], $value) : $function($value);
  674. return $value;
  675. }
  676. /**
  677. * Takes list of items and transforms it into an array
  678. * + cleaning (trim, no empty parts, etc).
  679. * Similar to String::tokenize, but with more logic.
  680. *
  681. * //TODO: 3.4. parameter as array, move to Lib
  682. *
  683. * @deprecated Will be removed in 1.0.
  684. * @param string $string containing the parts
  685. * @param string $separator (defaults to ',')
  686. * @param bool $camelize (true/false): problems with äöüß etc!
  687. * @return array Results as list
  688. */
  689. public function parseList($string, $separator = null, $camelize = false, $capitalize = true) {
  690. if ($separator === null) {
  691. $separator = ',';
  692. }
  693. // parses the list, but leaves tokens untouched inside () brackets
  694. $stringArray = String::tokenize($string, $separator);
  695. $returnArray = [];
  696. if (empty($stringArray)) {
  697. return [];
  698. }
  699. foreach ($stringArray as $t) {
  700. $t = trim($t);
  701. if (!empty($t)) {
  702. if ($camelize === true) {
  703. $t = mb_strtolower($t);
  704. $t = Inflector::camelize(Inflector::underscore($t)); // problems with non-alpha chars!
  705. } elseif ($capitalize === true) {
  706. $t = ucwords($t);
  707. }
  708. $returnArray[] = $t;
  709. }
  710. }
  711. return $returnArray;
  712. }
  713. /**
  714. * //todo move to lib!!!
  715. *
  716. * @param string $s
  717. * @return mixed
  718. */
  719. public static function separators($s = null, $valueOnly = false) {
  720. $separatorsValues = [SEPARATOR_COMMA => ',', SEPARATOR_SEMI => ';', SEPARATOR_SPACE => ' ', SEPARATOR_TAB => TB, SEPARATOR_NL => NL];
  721. $separators = [SEPARATOR_COMMA => '[ , ] ' . __d('tools', 'Comma'), SEPARATOR_SEMI => '[ ; ] ' . __d('tools', 'Semicolon'), SEPARATOR_SPACE => '[ &nbsp; ] ' . __d('tools', 'Space'), SEPARATOR_TAB =>
  722. '[ &nbsp;&nbsp;&nbsp;&nbsp; ] ' . __d('tools', 'Tabulator'), SEPARATOR_NL => '[ \n ] ' . __d('tools', 'New Line')];
  723. if ($s !== null) {
  724. if (array_key_exists($s, $separators)) {
  725. if ($valueOnly) {
  726. return $separatorsValues[$s];
  727. }
  728. return $separators[$s];
  729. }
  730. return '';
  731. }
  732. return $valueOnly ? $separatorsValues : $separators;
  733. }
  734. /**
  735. * Expects email to be valid!
  736. * TODO: move to Lib
  737. *
  738. * @return array email - pattern: array('email'=>,'name'=>)
  739. */
  740. public function splitEmail($email, $abortOnError = false) {
  741. $array = ['email' => '', 'name' => ''];
  742. if (($pos = mb_strpos($email, '<')) !== false) {
  743. $name = substr($email, 0, $pos);
  744. $email = substr($email, $pos + 1);
  745. }
  746. if (($pos = mb_strrpos($email, '>')) !== false) {
  747. $email = substr($email, 0, $pos);
  748. }
  749. $email = trim($email);
  750. if (!empty($email)) {
  751. $array['email'] = $email;
  752. }
  753. if (!empty($name)) {
  754. $array['name'] = trim($name);
  755. }
  756. return $array;
  757. }
  758. /**
  759. * TODO: move to Lib
  760. * @param string $email
  761. * @param string $name (optional, will use email otherwise)
  762. */
  763. public function combineEmail($email, $name = null) {
  764. if (empty($email)) {
  765. return '';
  766. }
  767. if (empty($name)) {
  768. $name = $email;
  769. }
  770. return $name . ' <' . $email['email'] . '>';
  771. }
  772. /**
  773. * TODO: move to Lib
  774. * returns type
  775. * - username: everything till @ (xyz@abc.de => xyz)
  776. * - hostname: whole domain (xyz@abc.de => abc.de)
  777. * - tld: top level domain only (xyz@abc.de => de)
  778. * - domain: if available (xyz@e.abc.de => abc)
  779. * - subdomain: if available (xyz@e.abc.de => e)
  780. * @param string $email: well formatted email! (containing one @ and one .)
  781. * @param string $type (TODO: defaults to return all elements)
  782. * @return string or false on failure
  783. */
  784. public function extractEmailInfo($email, $type = null) {
  785. //$checkpos = strrpos($email, '@');
  786. $nameParts = Hash::filter(explode('@', $email));
  787. if (count($nameParts) !== 2) {
  788. return false;
  789. }
  790. if ($type === 'username') {
  791. return $nameParts[0];
  792. }
  793. if ($type === 'hostname') {
  794. return $nameParts[1];
  795. }
  796. $checkpos = strrpos($nameParts[1], '.');
  797. $tld = trim(mb_substr($nameParts[1], $checkpos + 1));
  798. if ($type === 'tld') {
  799. return $tld;
  800. }
  801. $server = trim(mb_substr($nameParts[1], 0, $checkpos));
  802. //TODO; include 3rd-Level-Label
  803. $domain = '';
  804. $subdomain = '';
  805. $checkpos = strrpos($server, '.');
  806. if ($checkpos !== false) {
  807. $subdomain = trim(mb_substr($server, 0, $checkpos));
  808. $domain = trim(mb_substr($server, $checkpos + 1));
  809. }
  810. if ($type === 'domain') {
  811. return $domain;
  812. }
  813. if ($type === 'subdomain') {
  814. return $subdomain;
  815. }
  816. //$hostParts = explode();
  817. //$check = trim(mb_substr($email, $checkpos));
  818. return '';
  819. }
  820. /**
  821. * Returns searchArray (options['wildcard'] TRUE/FALSE)
  822. * TODO: move to SearchLib etc
  823. *
  824. * @param string $keyword
  825. * @param string $searchphrase
  826. * @param array $options
  827. * @return array Cleaned array('keyword'=>'searchphrase') or array('keyword LIKE'=>'searchphrase')
  828. */
  829. public function getSearchItem($keyword = null, $searchphrase = null, $options = []) {
  830. if (isset($options['wildcard']) && $options['wildcard'] == true) {
  831. if (strpos($searchphrase, '*') !== false || strpos($searchphrase, '_') !== false) {
  832. $keyword .= ' LIKE';
  833. $searchphrase = str_replace('*', '%', $searchphrase);
  834. // additionally remove % ?
  835. //$searchphrase = str_replace(array('%','_'), array('',''), $searchphrase);
  836. }
  837. } else {
  838. // allow % and _ to remain in searchstring (without LIKE not problematic), * has no effect either!
  839. }
  840. return [$keyword => $searchphrase];
  841. }
  842. /**
  843. * TODO: move to Lib
  844. * Checks if string contains @ sign
  845. *
  846. * @param string
  847. * @return true if at least one @ is in the string, false otherwise
  848. */
  849. public static function containsAtSign($string = null) {
  850. if (!empty($string) && strpos($string, '@') !== false) {
  851. return true;
  852. }
  853. return false;
  854. }
  855. /**
  856. * Get the Corresponding Message to an HTTP Error Code
  857. *
  858. * @param int $code: 100...505
  859. * @param bool $autoTranslate
  860. * @return array codes if code is NULL, otherwise string $code (empty string on failure)
  861. */
  862. public function responseCodes($code = null, $autoTranslate = false) {
  863. //TODO: use core ones Controller::httpCodes
  864. $responses = [
  865. 100 => 'Continue',
  866. 101 => 'Switching Protocols',
  867. 200 => 'OK',
  868. 201 => 'Created',
  869. 202 => 'Accepted',
  870. 203 => 'Non-Authoritative Information',
  871. 204 => 'No Content',
  872. 205 => 'Reset Content',
  873. 206 => 'Partial Content',
  874. 300 => 'Multiple Choices',
  875. 301 => 'Moved Permanently',
  876. 302 => 'Found',
  877. 303 => 'See Other',
  878. 304 => 'Not Modified',
  879. 305 => 'Use Proxy',
  880. 307 => 'Temporary Redirect',
  881. 400 => 'Bad Request',
  882. 401 => 'Unauthorized',
  883. 402 => 'Payment Required',
  884. 403 => 'Forbidden',
  885. 404 => 'Not Found',
  886. 405 => 'Method Not Allowed',
  887. 406 => 'Not Acceptable',
  888. 407 => 'Proxy Authentication Required',
  889. 408 => 'Request Time-out',
  890. 409 => 'Conflict',
  891. 410 => 'Gone',
  892. 411 => 'Length Required',
  893. 412 => 'Precondition Failed',
  894. 413 => 'Request Entity Too Large',
  895. 414 => 'Request-URI Too Large',
  896. 415 => 'Unsupported Media Type',
  897. 416 => 'Requested range not satisfiable',
  898. 417 => 'Expectation Failed',
  899. 500 => 'Internal Server Error',
  900. 501 => 'Not Implemented',
  901. 502 => 'Bad Gateway',
  902. 503 => 'Service Unavailable',
  903. 504 => 'Gateway Time-out',
  904. 505 => 'HTTP Version not supported' # MOD 2009-07-21 ms: 505 added!!!
  905. ];
  906. if ($code === null) {
  907. if ($autoTranslate) {
  908. foreach ($responses as $key => $value) {
  909. $responses[$key] = __d('tools', $value);
  910. }
  911. }
  912. return $responses;
  913. }
  914. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  915. // base code in their class.
  916. if (!isset($responses[$code])) {
  917. $code = floor($code / 100) * 100;
  918. }
  919. if (!empty($code) && array_key_exists((int)$code, $responses)) {
  920. if ($autoTranslate) {
  921. return __d('tools', $responses[$code]);
  922. }
  923. return $responses[$code];
  924. }
  925. return '';
  926. }
  927. /**
  928. * Get the Corresponding Message to an HTTP Error Code
  929. *
  930. * @param int $code: 4xx...5xx
  931. * @return string
  932. */
  933. public function smtpResponseCodes($code = null, $autoTranslate = false) {
  934. // 550 5.1.1 User is unknown
  935. // 552 5.2.2 Storage Exceeded
  936. $responses = [
  937. 451 => 'Need to authenticate',
  938. 550 => 'User Unknown',
  939. 552 => 'Storage Exceeded',
  940. 554 => 'Refused'
  941. ];
  942. if (!empty($code) && array_key_exists((int)$code, $responses)) {
  943. if ($autoTranslate) {
  944. return __d('tools', $responses[$code]);
  945. }
  946. return $responses[$code];
  947. }
  948. return '';
  949. }
  950. }