PageRenderTime 60ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/QuickApps/View/Helper/HooktagsCollectionHelper.php

http://github.com/QuickAppsCMS/QuickApps-CMS
PHP | 426 lines | 216 code | 76 blank | 134 comment | 35 complexity | 5b4be9de8a35677a4cdb69d41dfa9500 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Hooktagss collection is used as a registry for loaded hooktags helpers and handles dispatching
  4. * and loading hooktags methods.
  5. *
  6. * PHP version 5
  7. *
  8. * @package QuickApps.View.Helper
  9. * @version 1.0
  10. * @author Christopher Castro <chris@quickapps.es>
  11. * @link http://www.quickappscms.org
  12. */
  13. class HooktagsCollectionHelper extends AppHelper {
  14. /**
  15. * Temporaly information used by some methods.
  16. *
  17. * @var array
  18. */
  19. private $__tmp;
  20. /**
  21. * Instance of View class.
  22. *
  23. * @var View
  24. */
  25. private $__view;
  26. /**
  27. * Associtive array of methods and Hooktag classes.
  28. *
  29. * @var array
  30. */
  31. private $__map = array();
  32. /**
  33. * List of all available hooktag methods.
  34. *
  35. * @var array
  36. */
  37. protected $_methods = array();
  38. /**
  39. * List of all available hooktags objects.
  40. *
  41. * @var array
  42. */
  43. protected $_hookObjects = array();
  44. public function beforeRender($viewFile) {
  45. $this->__view = $this->_View;
  46. $this->__loadHooktags();
  47. return true;
  48. }
  49. /**
  50. * Parse string for special hooktags placeholders and replace
  51. * them with the corresponding hooktag method return.
  52. *
  53. * ### Hooktag example
  54. *
  55. * [self_closing_hooktag param1=text param=2 param3=0 /]
  56. * [other_hook_hooktag]only content & no params[/other_hook_hooktag]
  57. *
  58. * @param string $text Text to replace.
  59. * @return string HTML with all hooktags replaced.
  60. */
  61. public function hooktags($text) {
  62. $text = $this->specialTags($text);
  63. if (!empty($this->__tmp['__hooktags_reg'])) {
  64. $tags = $this->__tmp['__hooktags_reg'];
  65. } else {
  66. $tags = $this->__tmp['__hooktags_reg'] = implode('|', $this->hooktagsList());
  67. }
  68. return preg_replace_callback('/(.?)\[(' . $tags . ')\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?(.?)/s', array($this, '__doHooktag'), $text);
  69. }
  70. /**
  71. * Load all hooks (and optionally hooktags) of specified Module.
  72. *
  73. * @param string $module Name of the module.
  74. * @return boolean TRUE on success. FALSE otherwise.
  75. */
  76. public function attachModuleHooktags($module) {
  77. $Plugin = Inflector::camelize($module);
  78. if (!CakePlugin::loaded($Plugin) || isset($this->_hookObjects[$Plugin . 'Hook'])) {
  79. return false;
  80. }
  81. $folder = new Folder;
  82. $folder->path = CakePlugin::path($Plugin) . 'View' . DS . 'Helper' . DS;
  83. $file_pattern = '(.*)HooktagsHelper\.php';
  84. $files = $folder->find($file_pattern);
  85. foreach ($files as $object) {
  86. $object = str_replace('Helper.php', '', $object);
  87. $this->{$object} = $this->_View->loadHelper("{$Plugin}.{$object}");
  88. if (!is_object($this->{$object})) {
  89. continue;
  90. }
  91. $methods = array();
  92. $_methods = QuickApps::get_this_class_methods($this->{$object});
  93. foreach ($_methods as $method) {
  94. $methods[] = $method;
  95. $this->__map[$method][] = (string)$object;
  96. }
  97. $this->_hookObjects["{$Plugin}.{$object}"] = $methods;
  98. }
  99. $this->_methods = array_keys($this->__map);
  100. $this->__tmp['__hooktags_reg'] = implode('|', $this->hooktagsList());
  101. return true;
  102. }
  103. /**
  104. * Unload all hooktags of specified Module.
  105. *
  106. * @param string $module Name of the module
  107. * @return boolean TRUE on success. FALSE otherwise.
  108. */
  109. public function detachModuleHooktags($module) {
  110. $Plugin = Inflector::camelize($module);
  111. $found = 0;
  112. foreach ($this->_hookObjects as $object => $hooks) {
  113. if (strpos($object, "{$Plugin}.") === 0) {
  114. foreach ($hooks as $hook) {
  115. unset($this->__map[$hook]);
  116. }
  117. $className = str_replace("{$Plugin}.", '', $object);
  118. unset($this->_hookObjects[$object]);
  119. unset($this->__view->{$className});
  120. $found++;
  121. }
  122. }
  123. $this->_methods = array_keys($this->__map);
  124. $this->__tmp['__hooktags_reg'] = implode('|', $this->hooktagsList());
  125. return $found > 0;
  126. }
  127. /**
  128. * Chech if hooktag method exists.
  129. *
  130. * @param string $hooktag Name of the hooktag method to check
  131. * @return boolean
  132. */
  133. public function hooktagDefined($hooktag) {
  134. return isset($this->__map[$hooktag]);
  135. }
  136. /**
  137. * Turn on hooktag method if is turned off.
  138. *
  139. * @param string $hooktag Hooktag name to turn on.
  140. * @return boolean TRUE on success. FALSE hooktag does not exists or is already on.
  141. */
  142. public function hooktagEnable($hooktag) {
  143. $hooktag = Inflector::underscore($hooktag);
  144. if (isset($this->__map["{$hooktag}::Disabled"])) {
  145. $this->__map[$hooktag] = $this->__map["{$hooktag}::Disabled"];
  146. unset($this->__map["{$hooktag}::Disabled"]);
  147. if (in_array("{$hooktag}::Disabled", $this->_methods)) {
  148. $this->_methods[] = $hooktag;
  149. unset($this->_methods[array_search("{$hooktag}::Disabled", $this->_methods)]);
  150. }
  151. return true;
  152. }
  153. return false;
  154. }
  155. /**
  156. * Turns off hooktag method.
  157. *
  158. * @param string $hooktag Hooktag name to turn off.
  159. * @return boolean TRUE on success. FALSE hooktag does not exists.
  160. */
  161. public function hooktagDisable($hooktag) {
  162. $hooktag = Inflector::underscore($hooktag);
  163. if (isset($this->__map[$hooktag])) {
  164. $this->__map["{$hooktag}::Disabled"] = $this->__map[$hooktag];
  165. unset($this->__map[$hooktag]);
  166. if (in_array($hooktag, $this->_methods)) {
  167. $this->_methods[] = "{$hooktag}::Disabled";
  168. unset($this->_methods[array_search("{$hooktag}", $this->_methods)]);
  169. }
  170. return true;
  171. }
  172. return false;
  173. }
  174. /**
  175. * Return an array list of all registered hooktag methods.
  176. *
  177. * @return array Array list of all available hooktag methods.
  178. */
  179. public function hooktagsList() {
  180. return $this->_methods;
  181. }
  182. /**
  183. * Removes all hooktags from the given content (except special tags).
  184. * Useful for plain text converting.
  185. *
  186. * @param string $text Text to remove hooktags.
  187. * @return string Content without hooktags.
  188. */
  189. public function stripHooktags($text) {
  190. $text = $this->specialTags($text);
  191. if (!empty($this->__tmp['__hooktags_reg'])) {
  192. $tags = $this->__tmp['__hooktags_reg'];
  193. } else {
  194. $tags = $this->__tmp['__hooktags_reg'] = implode('|', $this->hooktagsList());
  195. }
  196. return preg_replace('/(.?)\[(' . $tags . ')\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?(.?)/s', '$1$6', $text);
  197. }
  198. /**
  199. * Special hooktags that are not managed by any modules.
  200. *
  201. * `[date=FORMAT]` Return current date(FORMAT).
  202. * `[rand={values,by,comma}]` Returns a radom value from the specified group.
  203. * If only two numeric values are given as group, then rand(num1, num2) is returned.
  204. * `[language.OPTION]` Current language option (code, name, native, direction).
  205. * `[language]` Shortcut to [language.code] which return current language code.
  206. * `[url]YourURL[/url]` or `[url=YourURL]` Formatted url.
  207. * `[url=LINK]LABEL[/url]` Returns link tag <href="LINK">LABEL</a>
  208. * `[t=stringToTranslate]` or `[t]stringToTranslate[/t]` text translation: __t(stringToTranslate)
  209. * `[t=domain@@stringToTranslate]` Translation by domain __d(domain, stringToTranslate)
  210. * `[Layout.PATH]` Get any value from `Layout` variable. i.e.: [Layout.display] gets current display mode
  211. * if path does not exists then '' (empty) is rendered instead the hooktag code.
  212. *
  213. * @param string $text Original text where to replace tags.
  214. * @return string.
  215. */
  216. public function specialTags($text) {
  217. //[locale]
  218. $text = str_replace('[language]', Configure::read('Variable.language.code'), $text);
  219. //[locale.OPTION]
  220. preg_match_all('/\[language.(.+)\]/iUs', $text, $localeMatches);
  221. foreach ($localeMatches[1] as $attr) {
  222. $text = str_replace("[language.{$attr}]", Configure::read('Variable.language.' .$attr), $text);
  223. }
  224. //[url]URL[/url]
  225. preg_match_all('/\[url\](.+)\[\/url\]/iUs', $text, $urlMatches);
  226. foreach ($urlMatches[1] as $url) {
  227. $text = str_replace("[url]{$url}[/url]", Router::url($url, true), $text);
  228. }
  229. //[url=URL]
  230. preg_match_all('/\[url\=(.+)\]/iUs', $text, $urlMatches);
  231. foreach ($urlMatches[1] as $url) {
  232. $text = str_replace("[url={$url}]", Router::url($url, true), $text);
  233. }
  234. //[t=text to translate]
  235. preg_match_all('/\[t\=(.+)\]/iUs', $text, $tMatches);
  236. foreach ($tMatches[1] as $string) {
  237. $text = str_replace("[t={$string}]", __t($string), $text);
  238. }
  239. //[t]text to translate[/t]
  240. preg_match_all('/\[t\](.+)\[\/t\]/iUs', $text, $tMatches);
  241. foreach ($tMatches[1] as $string) {
  242. $text = str_replace("[t]{$string}[/t]", __t($string), $text);
  243. }
  244. //[t=domain@@text to translate]
  245. preg_match_all('/\[t\=(.+)\@\@(.+)\]/iUs', $text, $dMatches);
  246. foreach ($dMatches[1] as $key => $domain) {
  247. $text = str_replace("[d={$domain}@@{$dMatches[2][$key]}]", __d($domain, $dMatches[2][$key]), $text);
  248. }
  249. //[date=FORMAT@@TIME_STAMP]
  250. preg_match_all('/\[date\=(.+)\@\@(.+)\]/iUs', $text, $dateMatches);
  251. foreach ($dateMatches[1] as $key => $format) {
  252. $stamp = $dateMatches[2][$key];
  253. $replace = is_numeric($stamp) ? date($format, $stamp) : date($format, strtotime($stamp));
  254. $text = str_replace("[date={$format}@@{$stamp}]", $replace, $text);
  255. }
  256. //[date=FORMAT]
  257. preg_match_all('/\[date\=(.+)\]/iUs', $text, $dateMatches);
  258. foreach ($dateMatches[1] as $format) {
  259. $text = str_replace("[date={$format}]", date($format), $text);
  260. }
  261. //[rand=a,b,c]
  262. preg_match_all('/\[rand\=(.+)\]/iUs', $text, $randomMatches);
  263. foreach ($randomMatches[1] as $_values) {
  264. $values = explode(',', $_values);
  265. $values = array_map('trim', $values);
  266. $c = count($values);
  267. if ($c == 2 && is_numeric($values[0]) && is_numeric($values[1])) {
  268. $replace = rand($values[0], $values[1]);
  269. } else {
  270. $replace = $values[rand(0, $c-1)];
  271. }
  272. $text = str_replace("[rand={$_values}]", $replace, $text);
  273. }
  274. //[Layout.PATH]
  275. preg_match_all('/\[Layout.(.+)\]/iUs', $text, $layoutPaths);
  276. foreach ($layoutPaths[1] as $path) {
  277. $extract = Hash::extract($this->_View->viewVars['Layout'], $path);
  278. $text = str_replace("[Layout.{$path}]", $extract, $text);
  279. }
  280. // pass text to modules so they can apply their own special tags
  281. $this->hook('special_tags_alter', $text);
  282. return $text;
  283. }
  284. /**
  285. * Callback function.
  286. *
  287. * @return mixed Hook response or false in case of no response.
  288. */
  289. private function __doHooktag($m) {
  290. // allow [[foo]] syntax for escaping a tag
  291. if ($m[1] == '[' && $m[6] == ']') {
  292. return substr($m[0], 1, -1);
  293. }
  294. $tag = $m[2];
  295. if (isset($this->__map["{$tag}::Disabled"])) {
  296. return false;
  297. }
  298. $attr = QuickApps::parseHooktagAttributes($m[3]);
  299. $hook = isset($this->__map[$tag]) ? $this->__map[$tag] : false;
  300. if ($hook) {
  301. foreach ($this->__map[$tag] as $object) {
  302. $hook =& $this->__view->{$object};
  303. if (isset($m[5])) {
  304. // enclosing tag - extra parameter
  305. return $m[1] . call_user_func(array($hook, $tag), $attr, $m[5], $tag) . $m[6];
  306. } else {
  307. // self-closing tag
  308. return $m[1] . call_user_func(array($hook, $tag), $attr, null, $tag) . $m[6];
  309. }
  310. }
  311. }
  312. return false;
  313. }
  314. /**
  315. * Loads Hooktag classes.
  316. *
  317. * @return void
  318. */
  319. private function __loadHooktags() {
  320. foreach ((array)Configure::read('Hook.hooktags') as $helper) {
  321. list($plugin, $helper) = pluginSplit($helper);
  322. $this->__view->Helpers->load("{$plugin}.{$helper}");
  323. if ($helper == 'HooktagsCollection' || !is_object($this->__view->{$helper})) {
  324. continue;
  325. }
  326. if (strpos($helper, 'Hook') !== false) {
  327. $methods = array();
  328. $_methods = QuickApps::get_this_class_methods($this->__view->{$helper});
  329. foreach ($_methods as $method) {
  330. // ignore private and protected methods
  331. if (strpos($method, '__') === 0 || strpos($method, '_') === 0) {
  332. continue;
  333. }
  334. $methods[] = $method;
  335. if (isset($this->__map[$method])) {
  336. $this->__map[$method][] = (string)$helper;
  337. } else {
  338. $this->__map[$method] = array((string)$helper);
  339. }
  340. }
  341. if ($plugin) {
  342. $this->_hookObjects["{$plugin}.{$helper}"] = $methods;
  343. } else {
  344. $this->_hookObjects[$helper] = $methods;
  345. }
  346. }
  347. }
  348. $this->_methods = array_keys($this->__map);
  349. }
  350. }