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

/libraries/Controller.php

https://github.com/wilkerlucio/fishy-framework
PHP | 778 lines | 536 code | 98 blank | 144 comment | 39 complexity | 7f37ef2ec75fe07d78a96f313b6e7600 MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright 2008 Wilker Lucio <wilkerlucio@gmail.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. abstract class Fishy_Controller
  18. {
  19. protected $_current_route;
  20. protected $_data;
  21. protected $_render;
  22. protected $_render_layout;
  23. protected $_layout;
  24. protected $_page_cache;
  25. protected $_cycle;
  26. protected $_cycle_it;
  27. protected $_params;
  28. protected $_block_cache_stack;
  29. public final function __construct($route = null)
  30. {
  31. $this->_data = array();
  32. $this->_render = true;
  33. $this->_render_layout = true;
  34. $this->_page_cache = false;
  35. $this->_layout = null;
  36. $this->_cycle = array();
  37. $this->_cycle_it = 0;
  38. $this->_params = array_merge($_GET, $_POST);
  39. $this->_block_cache_stack = array();
  40. $this->_current_route = $route;
  41. //load helpers
  42. foreach (glob(FISHY_HELPERS_PATH . "/*.php") as $helper) {
  43. require_once $helper;
  44. }
  45. foreach (glob(FISHY_SLICES_PATH . "/*/app/helpers/*.php") as $helper) {
  46. require_once $helper;
  47. }
  48. $this->initialize();
  49. }
  50. protected function initialize() {}
  51. public static function build_view_path($controller, $action)
  52. {
  53. $sufix = '/' . str_replace('_', '/', strtolower($controller)) . '/' . $action . '.php';
  54. $files = glob(FISHY_SLICES_PATH . '/*/app/views' . $sufix);
  55. $files[] = FISHY_VIEWS_PATH . $sufix;
  56. return $files[0];
  57. }
  58. protected function classname($lowercase = true)
  59. {
  60. $name = substr(get_class($this), 0, -strlen('Controller'));
  61. if ($lowercase) {
  62. $name = strtolower($name);
  63. }
  64. return $name;
  65. }
  66. protected function check_for_haml($path)
  67. {
  68. $haml_path = $path . ".haml";
  69. if (!file_exists($haml_path)) return $path;
  70. $new_path = FISHY_CACHE_PATH . "/haml" . substr($path, strlen(FISHY_VIEWS_PATH));
  71. if (file_exists($new_path)) {
  72. $current = filemtime($haml_path);
  73. $old = filemtime($new_path);
  74. if ($current <= $old) return $new_path;
  75. }
  76. require_once FISHY_VENDOR_PATH . "/haml/lib/haml.php";
  77. $haml = new Haml();
  78. $generated = $haml->parse(file_get_contents($haml_path));
  79. Fishy_DirectoryHelper::mkdir($new_path, true);
  80. file_put_contents($new_path, $generated);
  81. return $new_path;
  82. }
  83. protected function view_path($action, $controller = null)
  84. {
  85. if ($controller === null) {
  86. $controller = $this->classname();
  87. }
  88. $path = self::build_view_path($controller, $action);
  89. $path = $this->check_for_haml($path);
  90. return $path;
  91. }
  92. public function get_current_route()
  93. {
  94. return $this->_current_route;
  95. }
  96. /**
  97. * Execute main routine of an action
  98. *
  99. * @param $method The action to be executed
  100. * @param $args The arguments to pass to action
  101. * @return void
  102. */
  103. public function execute($method, $args = array())
  104. {
  105. $view_path = $this->view_path($method);
  106. if (!method_exists($this, $method) && !file_exists($view_path)) {
  107. dispatch_error(new Exception("Not found $method in " . $this->classname()), 404);
  108. }
  109. if (method_exists($this, $method)) {
  110. call_user_func_array(array($this, $method), $args);
  111. }
  112. if ($this->_render) {
  113. $this->render($method);
  114. }
  115. if ($this->_page_cache) {
  116. $data = ob_get_flush();
  117. $file = Fishy_Cache::cache(Fishy_Uri::get_querystring(), $data);
  118. }
  119. }
  120. /**
  121. * Do an execution based on a route
  122. *
  123. * @param $route The route to be executed
  124. * @return void
  125. */
  126. public static function run($route) {
  127. global $CONTROLLER;
  128. $controller_name = Fishy_StringHelper::camelize($route['controller']) . 'Controller';
  129. $method = strtolower($route['action']);
  130. if (!class_exists($controller_name)) {
  131. dispatch_error(new Exception("Controller {$controller_name} doesn't exists"), 404);
  132. }
  133. $controller = new $controller_name($route);
  134. $controller->_current_route = $route;
  135. $controller->_params = array_merge($controller->_params, $route['params']);
  136. $CONTROLLER = $controller;
  137. $controller->execute($method);
  138. }
  139. protected function render_options()
  140. {
  141. return array(
  142. 'return' => false,
  143. 'controller' => $this->classname(),
  144. 'as' => null,
  145. 'layout' => $this->layout_file(),
  146. 'locals' => array()
  147. );
  148. }
  149. protected function layout_file()
  150. {
  151. if ($this->_layout) {
  152. return $this->_layout;
  153. }
  154. $layout = $this->classname();
  155. $current_class = $this;
  156. while (!$this->layout_path($layout)) {
  157. $class = new ReflectionClass($current_class);
  158. $class = $class->getParentClass();
  159. $class = $class->getName();
  160. if ($class == 'Fishy_Controller') {
  161. break;
  162. }
  163. $current_class = new $class;
  164. $layout = $current_class->classname();
  165. }
  166. return $layout;
  167. }
  168. protected function layout_path($layout)
  169. {
  170. $layouts = glob(FISHY_SLICES_PATH . "/*/app/views/layouts/" . $layout . '.php');
  171. $layouts[] = FISHY_VIEWS_PATH . '/layouts/' . $layout . '.php';
  172. $layout = $layouts[0];
  173. $layout = $this->check_for_haml($layout);
  174. return file_exists($layout) ? $layout : null;
  175. }
  176. /**
  177. * Render the view of an action
  178. *
  179. * @action The action to be rendered
  180. */
  181. protected function render($action, $options = array())
  182. {
  183. $options = array_merge($this->render_options(), $options);
  184. ob_start();
  185. $view_path = $this->view_path($action, $options['controller']);
  186. if (file_exists($view_path)) {
  187. include $view_path;
  188. }
  189. $output = ob_get_clean();
  190. $layout = $this->layout_path($options['layout']);
  191. if ($layout && $this->_render_layout) {
  192. $content = $output;
  193. ob_start();
  194. include $layout;
  195. $output = ob_get_clean();
  196. }
  197. $this->_render = false;
  198. if ($options['return']) {
  199. return $output;
  200. } else {
  201. echo $output;
  202. }
  203. }
  204. /**
  205. * Renderize a partial template
  206. *
  207. * @param $partial The name o partial template
  208. * @param $data The data to send to partial template
  209. * @param $return If you pass true to this parameter, the output will be returned instead of printed
  210. * @return mixed
  211. */
  212. protected function render_partial($partial, $data = null, $options = array())
  213. {
  214. $options = array_merge($this->render_options(), $options);
  215. if (preg_match("/^(\w+)\/(\w+)$/", $partial, $matches)) {
  216. $partial = $matches[2];
  217. $options["controller"] = $matches[1];
  218. }
  219. if ($options['as'] === null) {
  220. $options['as'] = $partial;
  221. }
  222. ob_start();
  223. $view_path = $this->view_path("_$partial", $options['controller']);
  224. if (file_exists($view_path)) {
  225. //instance locals
  226. foreach ($options['locals'] as $key => $value) {
  227. $$key = $value;
  228. }
  229. $$options['as'] = $data;
  230. include $view_path;
  231. }
  232. $output = ob_get_clean();
  233. if ($options['return']) {
  234. return $output;
  235. } else {
  236. echo $output;
  237. }
  238. }
  239. /**
  240. * Renderize a collection into a partial template
  241. *
  242. * @param $partial The name o partial template
  243. * @param $collection The collection to send to partial template
  244. * @param $return If you pass true to this parameter, the output will be returned instead of printed
  245. * @return mixed
  246. */
  247. protected function render_collection($partial, $collection, $options = array())
  248. {
  249. $output = array();
  250. $options = array_merge($this->render_options(), $options);
  251. $return = $options['return'];
  252. $options['return'] = true;
  253. foreach ($collection as $data) {
  254. $output[] = $this->render_partial($partial, $data, $options);
  255. }
  256. $output = implode('', $output);
  257. if ($return) {
  258. return $output;
  259. } else {
  260. echo $output;
  261. }
  262. }
  263. /**
  264. * Redirect current flow
  265. *
  266. * @param $path The path to be redirected, this path works like if you are using site_url() method
  267. * @return void
  268. */
  269. public function redirect_to($path)
  270. {
  271. header('Location: ' . $this->url_to($path));
  272. exit;
  273. }
  274. /**
  275. * This function get the base URL of site
  276. *
  277. * @param $sulfix Sulfix to be append at end of file
  278. * @return string The final path
  279. */
  280. public final function base_url($sulfix = '')
  281. {
  282. if (preg_match("/^([a-z]+):\/\//", $sulfix)) {
  283. return $sulfix;
  284. }
  285. return FISHY_BASE_URL . $sulfix;
  286. }
  287. /**
  288. * This function get one internal url of site
  289. *
  290. * @param $sulfix Sulfix to be append at end of file
  291. * @return string The final path
  292. */
  293. public function site_url($sulfix = '')
  294. {
  295. return $this->base_url(FISHY_INDEX_PAGE . $sulfix);
  296. }
  297. /**
  298. * This function get one public url of site
  299. *
  300. * @param $sulfix Sulfix to be append at end of file
  301. * @return string The final path
  302. */
  303. public function public_url($sulfix = '')
  304. {
  305. return $this->base_url($sulfix);
  306. }
  307. /**
  308. * Schedule cache when page load is finished
  309. *
  310. * @return void
  311. */
  312. protected function cache_output()
  313. {
  314. $this->_page_cache = true;
  315. ob_start();
  316. }
  317. /**
  318. *
  319. */
  320. public function cache_block($identifier)
  321. {
  322. $this->_block_cache_stack[] = $identifier;
  323. ob_start();
  324. }
  325. public function cache_block_end()
  326. {
  327. $block_content = ob_get_clean();
  328. $identifier = array_pop($this->_block_cache_stack);
  329. $cache_path = FISHY_CACHE_PATH . "/{$identifier}.block";
  330. //check for cache
  331. if (file_exists($cache_path)) {
  332. echo file_get_contents($cache_path);
  333. return;
  334. }
  335. //generate cache
  336. $parsed_block = preg_replace("/<#(.*?)#>/ms", "<?\$1?>", $block_content);
  337. $tmp_path = tempnam(FISHY_TMP_PATH, "BC");
  338. file_put_contents($tmp_path, $parsed_block);
  339. ob_start();
  340. include($tmp_path);
  341. $cache = ob_get_clean();
  342. unlink($tmp_path);
  343. file_put_contents($cache_path, $cache);
  344. echo $cache;
  345. }
  346. /**
  347. * Find a element at $_GET, or return a default value
  348. *
  349. * @param string $propertie The name of propertie
  350. * @param mixed $default The default value
  351. * @return mixed
  352. */
  353. public function gp($propertie, $default = null)
  354. {
  355. return isset($_GET[$propertie]) ? $_GET[$propertie] : $default;
  356. }
  357. /**
  358. * Find a element at $_POST, or return a default value
  359. *
  360. * @param string $propertie The name of propertie
  361. * @param mixed $default The default value
  362. * @return mixed
  363. */
  364. public function pp($propertie, $default = null)
  365. {
  366. return isset($_POST[$propertie]) ? $_POST[$propertie] : $default;
  367. }
  368. /**
  369. * Find a element at $_SESSION, or return a default value
  370. *
  371. * @param string $propertie The name of propertie
  372. * @param mixed $default The default value
  373. * @return mixed
  374. */
  375. public function sp($propertie, $default = null)
  376. {
  377. return isset($_SESSION[$propertie]) ? $_SESSION[$propertie] : $default;
  378. }
  379. public function cycle()
  380. {
  381. $args = func_get_args();
  382. if ($args !== $this->_cycle) {
  383. $this->_cycle = $args;
  384. $this->_cycle_it = 0;
  385. } else {
  386. $this->_cycle_it++;
  387. if ($this->_cycle_it >= count($this->_cycle)) {
  388. $this->_cycle_it = 0;
  389. }
  390. }
  391. return $this->_cycle[$this->_cycle_it];
  392. }
  393. public function image_cache($model, $field, $id = null, $configuration = array(), $user_vars = array())
  394. {
  395. $configuration = array_merge(array(
  396. 'width' => 100,
  397. 'height' => 100,
  398. 'mode' => 1,
  399. 'format' => 'jpg',
  400. 'path_format' => '#cache/#model/#field/#id.#width.#height.#mode.#format',
  401. 'default' => ''
  402. ), $configuration);
  403. $vars = array_merge(array(
  404. 'cache' => FISHY_CACHE_PATH,
  405. 'model' => $model,
  406. 'field' => $field,
  407. 'id' => $id,
  408. 'width' => $configuration['width'],
  409. 'height' => $configuration['height'],
  410. 'mode' => $configuration['mode'],
  411. 'format' => $configuration['format']
  412. ), $user_vars);
  413. $path = Fishy_StringHelper::simple_template($configuration['path_format'], $vars);
  414. $object = is_a($model, 'ActiveRecord') ? $model : ActiveRecord::model($model)->find($id);
  415. if ($object->$field) {
  416. if (file_exists($path) && file_exists($object->$field) && filemtime($object->$field) > filemtime($path)) {
  417. unlink($path);
  418. }
  419. if (!file_exists($path)) {
  420. if ($object->$field) {
  421. Fishy_DirectoryHelper::mkdir($path, true);
  422. $image = new Fishy_Image($object->$field);
  423. $image->resize($configuration['width'], $configuration['height'], $configuration['mode']);
  424. $image->save($path);
  425. }
  426. }
  427. } else {
  428. $this->redirect_to('public/' . $configuration['default']);
  429. }
  430. return file_get_contents($path);
  431. }
  432. public function url_to($params)
  433. {
  434. global $ROUTER;
  435. $route = "";
  436. if (is_string($params) && preg_match("/^@([a-z][a-z0-9_]*)(?:\/([a-z][a-z0-9_]*))?/i", $params, $matches)) {
  437. $p = array();
  438. if (isset($matches[2])) {
  439. $p['controller'] = $matches[1];
  440. $p['action'] = $matches[2];
  441. } else {
  442. $p['action'] = $matches[1];
  443. }
  444. $params = $p;
  445. }
  446. if (is_array($params)) {
  447. $params = array_merge(array("controller" => $this->classname()), $params);
  448. if (!isset($params['action'])) {
  449. $params['action'] = $ROUTER->default_action();
  450. }
  451. $controller = $params['controller'];
  452. $action = $params['action'];
  453. unset($params['controller']);
  454. unset($params['action']);
  455. $route = $ROUTER->discovery_route($controller, $action, $params);
  456. } else {
  457. $route = $params;
  458. }
  459. if (!preg_match("/^[a-z]+:\/\//", $route)) {
  460. $route = FISHY_BASE_URL . FISHY_INDEX_PAGE . $route;
  461. }
  462. return $route;
  463. }
  464. /**
  465. * Get a request param
  466. *
  467. * @param string $name The name of parameter
  468. * @param mixed $default The default value
  469. * @return mixed
  470. */
  471. public function param($name, $default = null)
  472. {
  473. return isset($this->_params[$name]) ? $this->_params[$name] : $default;
  474. }
  475. /**
  476. * Display a Javascript alert and then redirect user to another page
  477. * Note: this method only works with client javascript enabled,
  478. * don't use it if you are not sure that your client will be
  479. * with javascript enabled
  480. *
  481. * @param string $message Message to display for client
  482. * @param mixed $redirect_to String or Array with path to use in redirect
  483. */
  484. public function show_message_and_redirect_to($message, $redirect_to)
  485. {
  486. $message = preg_replace('/(\r\n|\r|\n)/', '\\n', $message);
  487. $this->_render = false;
  488. $url = $this->url_to($redirect_to);
  489. $template_file = dirname(__FILE__) . '/../misc/show_message_and_redirect_to_template.html';
  490. $template_data = array("message" => $message, "url" => $url);
  491. $template = file_get_contents($template_file);
  492. echo Fishy_StringHelper::simple_template($template, $template_data);
  493. }
  494. /**
  495. * Test if the request method is a POST request
  496. *
  497. * @return boolean true if is a POST request, false otherwise
  498. */
  499. public function post_request()
  500. {
  501. return $_SERVER['REQUEST_METHOD'] == "POST";
  502. }
  503. /**
  504. * Get a value at data store
  505. *
  506. * @param $propertie The name o propertie
  507. * @param $default The default value (if propertie doesn't exists)
  508. * @return mixed
  509. */
  510. public function get($propertie, $default = null)
  511. {
  512. return isset($this->_data[$propertie]) ? $this->_data[$propertie] : $default;
  513. }
  514. public function __get($propertie)
  515. {
  516. return $this->get($propertie);
  517. }
  518. public function __set($propertie, $value)
  519. {
  520. $this->_data[$propertie] = $value;
  521. }
  522. public function __call($method, $args)
  523. {
  524. global $ROUTER;
  525. if (Fishy_StringHelper::ends_with($method, '_url')) {
  526. $route_name = substr($method, 0, -4);
  527. return FISHY_BASE_URL . FISHY_INDEX_PAGE . $ROUTER->named_route($route_name, @$args[0]);
  528. }
  529. if (Fishy_StringHelper::starts_with($method, 'redirect_to_')) {
  530. $route_name = substr($method, strlen('redirect_to_'));
  531. return $this->redirect_to($ROUTER->named_route($route_name, @$args[0]));
  532. }
  533. throw new Exception("Method $method doesn't exists");
  534. }
  535. /* VIEW HELPERS */
  536. public function stylesheet_tag($css, $attr = array())
  537. {
  538. if (!preg_match('/\.css$/', $css)) {
  539. $css .= '.css';
  540. }
  541. $path_sufix = "stylesheets/{$css}";
  542. $real_path = FISHY_PUBLIC_PATH . '/' . $path_sufix;
  543. $mtime = @filemtime($real_path);
  544. $path = $this->public_url("stylesheets/{$css}?{$mtime}");
  545. $attr = array_merge(array(
  546. "href" => $path,
  547. "media" => "screen",
  548. "rel" => "stylesheet",
  549. "type" => "text/css"
  550. ), $attr);
  551. $attr = $this->build_tag_attributes($attr);
  552. return "<link{$attr} />";
  553. }
  554. public function ie_stylesheet_tag($file)
  555. {
  556. $css = $this->stylesheet_tag($file);
  557. return "<!--[if IE]>" . $css . "<![endif]-->";
  558. }
  559. public function javascript_tag()
  560. {
  561. $args = func_get_args();
  562. $last = $args[count($args) - 1];
  563. if (is_array($last)) {
  564. $attr = array_pop($args);
  565. } else {
  566. $attr = array();
  567. }
  568. $buffer = "";
  569. foreach ($args as $js) {
  570. if (!preg_match('/\.js$/', $js)) {
  571. $js .= '.js';
  572. }
  573. if (preg_match("/^[a-z]+:\/\//", $js)) {
  574. $path = $js;
  575. } else {
  576. $path_sufix = "javascripts/{$js}";
  577. $real_path = FISHY_PUBLIC_PATH . '/' . $path_sufix;
  578. $mtime = @filemtime($real_path);
  579. $path = $this->public_url("javascripts/{$js}?{$mtime}");
  580. }
  581. $cur_attr = array_merge(array(
  582. "src" => $path,
  583. "type" => "text/javascript"
  584. ), $attr);
  585. $cur_attr = $this->build_tag_attributes($cur_attr);
  586. $buffer .= "<script{$cur_attr}></script>\r\n";
  587. }
  588. return $buffer;
  589. }
  590. public function image_tag($url, $params = array())
  591. {
  592. $url = preg_match("/^[a-z]+:\/\//", $url) ? $url : $this->public_url("images/" . $url);
  593. $params = array_merge(array(
  594. "src" => $url
  595. ), $params);
  596. $attr = $this->build_tag_attributes($params);
  597. $img = "<img{$attr} />";
  598. return $img;
  599. }
  600. public function link_to($label, $url, $params = array())
  601. {
  602. $params = array_merge(array(
  603. "title" => $label,
  604. "href" => $this->url_to($url)
  605. ), $params);
  606. $attr = $this->build_tag_attributes($params);
  607. $img = "<a{$attr}>{$label}</a>";
  608. return $img;
  609. }
  610. private function build_tag_attributes($attributes)
  611. {
  612. $attr = "";
  613. foreach ($attributes as $key => $value) {
  614. $attr .= " {$key}=\"{$value}\"";
  615. }
  616. return $attr;
  617. }
  618. }