PageRenderTime 68ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/shared/codes.php

https://github.com/altatof/yacs
PHP | 4235 lines | 2154 code | 753 blank | 1328 comment | 384 complexity | 16fac73c75f329bb4e89037c5b60a86a MD5 | raw file
  1. <?php
  2. /**
  3. * Transform some text containing UBB-like code sequences.
  4. *
  5. * @todo CDATA for proxy http://javascript.about.com/library/blxhtml.htm
  6. * @todo &#91;files] - most recent files, in a compact list
  7. * @todo &#91;files=section:&lt;id>] - files attached in the given section
  8. * @todo &#91;links] - most recent links, in a compact list
  9. * @todo &#91;links=section:&lt;id>] - links attached in the given section
  10. * @todo for [read, add hits aside
  11. * @todo add a code to link images with clickable maps
  12. * @todo replace marquee with our own customizable scroller
  13. * @todo WiKi rendering for lists
  14. *
  15. * This module uses the Skin class for the actual rendering.
  16. *
  17. * Basic codes, demonstrated into [link]codes/basic.php[/link]:
  18. * - **...** - wiki bold text
  19. * - &#91;b]...[/b] - bold text
  20. * - //...// - italics
  21. * - &#91;i]...[/i] - italics
  22. * - __...__ - underlined
  23. * - &#91;u]...[/u] - underlined
  24. * - ##...## - monospace
  25. * - &#91;code]...[/code] - a short sample of fixed-size text (e.g. a file name)
  26. * - &#91;color]...[/color] - change font color
  27. * - &#91;tiny]...[/tiny] - tiny size
  28. * - &#91;small]...[/small] - small size
  29. * - &#91;big]...[/big] - big size
  30. * - &#91;huge]...[/huge] - huge size
  31. * - &#91;subscript]...[/subscript] - subscript
  32. * - &#91;superscript]...[/superscript] - superscript
  33. * - ++...++ - inserted
  34. * - &#91;inserted]...[/inserted] - inserted
  35. * - --...-- - deleted
  36. * - &#91;deleted]...[/deleted] - deleted
  37. * - &#91;flag]...[/flag] - draw attention
  38. * - &#91;lang=xy]...[/lang] - show some text only on matching language
  39. * - &#91;style=sans-serif]...[/style] - use a sans-serif font
  40. * - &#91;style=serif]...[/style] - use a serif font
  41. * - &#91;style=cursive]...[/style] - mimic hand writing
  42. * - &#91;style=comic]...[/style] - make it funny
  43. * - &#91;style=fantasy]...[/style] - guess what will appear
  44. * - &#91;style=my_style]...[/style] - translated to &lt;span class="my_style"&gt;...&lt;/span&gt;
  45. *
  46. * @see codes/basic.php
  47. *
  48. * Block codes, demonstrated in [link]codes/blocks.php[/link]:
  49. * - &#91;indent]...[/indent] - shift text to the right
  50. * - &#91;center]...[/center] - some centered text
  51. * - &#91;right]...[/right] - some right-aligned text
  52. * - &#91;decorated]...[/decorated] - some pretty paragraphs
  53. * - &#91;caution]...[/caution] - a warning paragraph
  54. * - &#91;note]...[/note] - a noticeable paragraph
  55. * - &#91;php]...[/php] - a snippet of php
  56. * - &#91;snippet]...[/snippet] - a snippet of fixed font data
  57. * - &#91;quote]...[/quote] - a block of quoted text
  58. * - &#91;folded]...[/folded] - click to view its content, or to fold it away
  59. * - &#91;folded=foo bar]...[/folded] - with title 'foo bar'
  60. * - &#91;unfolded]...[/unfolded] - click to fold
  61. * - &#91;unfolded=foo bar]...[/unfolded] - with title 'foo bar'
  62. * - &#91;sidebar]...[/sidebar] - a nice box aside
  63. * - &#91;sidebar=foo bar]...[/sidebar] - with title 'foo bar'
  64. * - &#91;scroller]...[/scroller] - some scrolling text
  65. *
  66. * @see codes/blocks.php
  67. *
  68. * List codes, demonstrated in [link]codes/lists.php[/link]:
  69. * - &#91;*] - for simple lists
  70. * - &#91;list]...[/list] - bulleted list
  71. * - &#91;list=1]...[/list] - numbered list, use numbers
  72. * - &#91;list=a]...[/list] - numbered list, use letters
  73. * - &#91;list=A]...[/list] - numbered list, use capital letters
  74. * - &#91;list=i]...[/list] - numbered list, use roman numbers
  75. * - &#91;list=I]...[/list] - numbered list, use upper case roman numbers
  76. *
  77. * @see codes/lists.php
  78. *
  79. * Codes for links, demonstrated in [link]codes/links.php[/link]:
  80. * - &lt;url&gt; - &lt;a href="url">url&lt;/a> or &lt;a href="url" class="external">url&lt;/a>
  81. * - &#91;link]&lt;url&gt;[/link] - &lt;a href="url">url&lt;/a> or &lt;a href="url" class="external">url&lt;/a>
  82. * - &#91;&lt;label&gt;|&lt;url&gt;] - &lt;a href="url">label&lt;/a> or &lt;a href="url" class="external">label&lt;/a>
  83. * - &#91;link=&lt;label&gt;]&lt;url&gt;[/link] - &lt;a href="url">label&lt;/a> or &lt;a href="url" class="external">label&lt;/a>
  84. * - &#91;url]&lt;url&gt;[/url] - deprecated by &#91;link]
  85. * - &#91;url=&lt;url&gt;]&lt;label&gt;[/url] - deprecated by &#91;link]
  86. * - &#91;button=&lt;label&gt;|&lt;url&gt;] - build simple buttons with css
  87. * - &#91;click=&lt;label&gt;|&lt;url&gt;] - a button that counts clicks
  88. * - &#91;clicks=&lt;url&gt;] - lists people who have clicked
  89. * - &lt;address&gt; - &lt;a href="mailto:address" class="email">address&lt;/a>
  90. * - &#91;email]&lt;address&gt;[/email] - &lt;a href="mailto:address" class="email">address&lt;/a>
  91. * - &#91;email=&lt;name&gt;]&lt;address&gt;[/email] - &lt;a href="mailto:address" class="email">name&lt;/a>
  92. * - &#91;go=&lt;name&gt;, &lt;label&gt;] - trigger the selector on 'name'
  93. * - &#91;&#91;&lt;name&gt;, &lt;label&gt;]] - Wiki selector
  94. * - &#91;article=&lt;id>] - use article title as link label
  95. * - &#91;article=&lt;id>, foo bar] - with label 'foo bar'
  96. * - &#91;article.description=&lt;id>] - insert article description
  97. * - &#91;next=&lt;id>] - shortcut to next article
  98. * - &#91;next=&lt;id>, foo bar] - with label 'foo bar'
  99. * - &#91;previous=&lt;id>] - shortcut to previous article
  100. * - &#91;previous=&lt;id>, foo bar] - with label 'foo bar'
  101. * - &#91;random] - pick up one page randomly
  102. * - &#91;random=&lt;section:id>] - one page in this section
  103. * - &#91;section=&lt;id>] - use section title as link label
  104. * - &#91;section=&lt;id>, foo bar] - with label 'foo bar'
  105. * - &#91;category=&lt;id>] - use category title as link label
  106. * - &#91;category=&lt;id>, foo bar] - with label 'foo bar'
  107. * - &#91;category.description=&lt;id>] - insert category description
  108. * - &#91;user=&lt;id>] - use nick name as link label
  109. * - &#91;user=&lt;id>, foo bar] - with label 'foo bar'
  110. * - &#91;server=&lt;id>] - use server title as link label
  111. * - &#91;server=&lt;id>, foo bar] - with label 'foo bar'
  112. * - &#91;file=&lt;id>] - use file title as link label
  113. * - &#91;file=&lt;id>, foo bar] - with label 'foo bar'
  114. * - &#91;download=&lt;id>] - a link to download a file
  115. * - &#91;download=&lt;id>, foo bar] - with label 'foo bar'
  116. * - &#91;comment=&lt;id>] - use comment id in link label
  117. * - &#91;comment=&lt;id>, foo bar] - with label 'foo bar'
  118. * - &#91;script]&lt;path/script.php&gt;[/script] - to the phpDoc page for script 'path/script.php'
  119. * - &#91;search] - a search form
  120. * - &#91;search=&lt;word&gt;] - hit Enter to search for 'word'
  121. * - &#91;wikipedia=&lt;keyword] - search Wikipedia
  122. * - &#91;wikipedia=&lt;keyword, foo bar] - search Wikipedia, with label 'foo bar'
  123. * - &#91;proxy]&lt;url&gt;[/proxy] - proxy a remote address
  124. *
  125. * @see codes/links.php
  126. *
  127. * Titles and questions, demonstrated in [link]codes/titles.php[/link]:
  128. * - &#91;toc] - table of contents
  129. * - ==...== - a level 1 headline
  130. * - &#91;title]...[/title] - a level 1 headline, put in the table of contents
  131. * - ===...=== - a level 2 headline
  132. * - &#91;subtitle]...[/subtitle] - a level 2 headline
  133. * - &#91;header1]...[/header1] - a level 1 headline
  134. * - &#91;header2]...[/header2] - a level 2 headline
  135. * - &#91;header3]...[/header3] - a level 3 headline
  136. * - &#91;header4]...[/header4] - a level 4 headline
  137. * - &#91;header5]...[/header5] - a level 5 headline
  138. * - &#91;toq] - the table of questions for this page
  139. * - &#91;question]...[/question] - a question-title
  140. * - &#91;question] - a simple question
  141. * - &#91;answer] - some answer in a FAQ
  142. *
  143. * @see codes/titles.php
  144. *
  145. * Tables, demonstrated in [link]codes/tables.php[/link]:
  146. * - &#91;table]...[/table] - one simple table
  147. * - &#91;table=grid]...[/table] - add a grid
  148. * - &#91;table].[body].[/table] - a table with headers
  149. * - &#91;csv]...[/csv] - import some data from a spreadsheet
  150. * - &#91;csv=;]...[/csv] - import some data from a spreadsheet
  151. * - &#91;table.json] - format a table as json
  152. *
  153. * @see codes/tables.php
  154. *
  155. * Live codes, demonstrated in [link]codes/live.php[/link]:
  156. * - &#91;sections] - site map
  157. * - &#91;sections=section:&lt;id>] - sub-sections
  158. * - &#91;sections=self] - sections assigned to current surfer
  159. * - &#91;sections=user:&lt;id>] - sections assigned to given user
  160. * - &#91;categories] - category tree
  161. * - &#91;categories=category:&lt;id>] - sub-categories
  162. * - &#91;categories=self] - categories assigned to current surfer
  163. * - &#91;categories=user:&lt;id>] - categories assigned to given user
  164. * - &#91;published] - most recent published pages, in a compact list
  165. * - &#91;published=section:&lt;id>] - articles published most recently in the given section
  166. * - &#91;published=category:&lt;id>] - articles published most recently in the given category
  167. * - &#91;published=user:&lt;id>] - articles published most recently created by given user
  168. * - &#91;published.decorated=self, 20] - 20 most recent pages from current surfer, as a decorated list
  169. * - &#91;updated] - most recent updated pages, in a compact list
  170. * - &#91;updated=section:&lt;id>] - articles updated most recently in the given section
  171. * - &#91;updated=category:&lt;id>] - articles updated most recently in the given category
  172. * - &#91;updated=user:&lt;id>] - articles updated most recently created by given user
  173. * - &#91;updated.simple=self, 12] - articles updated most recently created by current surfer, as a simple list
  174. * - &#91;read] - most read articles, in a compact list
  175. * - &#91;read=section:&lt;id>] - articles of fame in the given section
  176. * - &#91;read=self] - personal hits
  177. * - &#91;read=user:&lt;id>] - personal hits
  178. * - &#91;voted] - most voted articles, in a compact list
  179. * - &#91;voted=section:&lt;id>] - articles of fame in the given section
  180. * - &#91;voted=self] - personal hits
  181. * - &#91;voted=user:&lt;id>] - personal hits
  182. * - &#91;users=present] - list of users present on site
  183. *
  184. * @see codes/live.php
  185. *
  186. * Widgets, demonstrated in [link]codes/widgets.php[/link]:
  187. * - &#91;newsfeed=url] - integrate a newsfeed dynamically
  188. * - &#91;newsfeed.embed=url] - integrate a newsfeed dynamically
  189. * - &#91;twitter=id] - twitter updates of one person
  190. * - &#91;tsearch=token] - twitter search on a given topic
  191. * - &#91;iframe=&lt;width&gt;, &lt;height&gt;]&lt;url&gt;[/iframe] - include some external page
  192. * - &#91;cloud] - the tags used at this site
  193. * - &#91;cloud=12] - maximum count of tags used at this site
  194. * - &#91;calendar] - events for this month
  195. * - &#91;calendar=section:&lt;id>] - dates in one section
  196. * - &#91;locations=all] - newest locations
  197. * - &#91;locations=users] - map user locations on Google maps
  198. * - &#91;location=latitude, longitude, label] - to build a dynamic map
  199. *
  200. * @see codes/widgets.php
  201. *
  202. * Miscellaneous codes, demonstrated in [link]codes/misc.php[/link]:
  203. * - &#91;hint=&lt;help popup]...[/hint] - &lt;acronym tite="help popup">...&lt;/acronym>
  204. * - &#91;nl] - new line
  205. * - ----... - line break
  206. * - &#91;---] or &#91;___] - horizontal rule
  207. * - &#91;new] - something new
  208. * - &#91;popular] - people love it
  209. * - &#91;be] - country flag
  210. * - &#91;ca] - country flag
  211. * - &#91;ch] - country flag
  212. * - &#91;de] - country flag
  213. * - &#91;en] - country flag
  214. * - &#91;es] - country flag
  215. * - &#91;fr] - country flag
  216. * - &#91;gb] - country flag
  217. * - &#91;gr] - country flag
  218. * - &#91;it] - country flag
  219. * - &#91;pt] - country flag
  220. * - &#91;us] - country flag
  221. * - &#91;chart]...[/chart] - draw a dynamic chart
  222. * - &#91;execute=script] - include another local script
  223. * - &#91;redirect=link] - jump to another local page
  224. * - &#91;parameter=name] - value of one attribute of the global context
  225. * - &#91;escape]...[/escape]
  226. * - &#91;anonymous]...[/anonymous] - for non-logged people only
  227. * - &#91;authenticated]...[/authenticated] - for logged members only
  228. * - &#91;associate]...[/associate] - for associates only
  229. *
  230. * @see codes/misc.php
  231. *
  232. * In-line elements:
  233. * - &#91;embed=&lt;id>, &lt;width>, &lt;height>, &lt;flashparams>] - embed a multimedia file
  234. * - &#91;embed=&lt;id>, window] - render a multimedia file in a separate window
  235. * - &#91;sound=&lt;id>] - play a sound
  236. * - &#91;image=&lt;id>] - an inline image
  237. * - &#91;image=&lt;id>,left] - a left-aligned image
  238. * - &#91;image=&lt;id>,center] - a centered image
  239. * - &#91;image=&lt;id>,right] - a right-aligned image
  240. * - &#91;image]src[/image]
  241. * - &#91;image=&lt;alt>]src[/image]
  242. * - &#91;images=&lt;id1>, &lt;id2>, ...] - a stack of images
  243. * - &#91;img]src[/img] (deprecated)
  244. * - &#91;img=&lt;alt>]src[/img] (deprecated)
  245. * - &#91;table=&lt;id>] - an inline table
  246. * - &#91;location=&lt;id>] - embed a map
  247. * - &#91;location=&lt;id>, foo bar] - with label 'foo bar'
  248. * - &#91;clear] - to introduce breaks after floating elements
  249. *
  250. * @link http://www.estvideo.com/dew/index/2005/02/16/370-player-flash-mp3-leger-comme-une-plume the dewplayer page
  251. *
  252. * Other codes:
  253. * - &#91;menu=label]url[/menu] - one of the main menu command
  254. * - &#91;submenu=label]url[/submenu] - one of the second-level menu commands
  255. *
  256. * This script attempts to fight bbCode code injections by filtering strings to be used
  257. * as [code]src[/code] or as [code]href[/code] attributes (Thank you Mordread).
  258. *
  259. * @author Bernard Paques
  260. * @author Mordread Wallas
  261. * @author GnapZ
  262. * @author Alain Lesage (Lasares)
  263. * @tester Viviane Zaniroli
  264. * @tester Agnes
  265. * @tester Pat
  266. * @tester Guillaume Perez
  267. * @tester Fw_crocodile
  268. * @tester Christian Piercot
  269. * @tester Christian Loubechine
  270. * @tester Daniel Dupuis
  271. * @reference
  272. * @license http://www.gnu.org/copyleft/lesser.txt GNU Lesser General Public License
  273. */
  274. Class Codes {
  275. /**
  276. * beautify some text for final rendering
  277. *
  278. * This function is used to transform some text before sending it back to the browser.
  279. * It actually performs following analysis:
  280. * - implicit formatting
  281. * - formatting codes
  282. * - smileys
  283. *
  284. * If the keyword [escape][formatted][/escape] appears at the first line of text,
  285. * or if options have the keyword ##formatted##, no implicit formatting is performed.
  286. *
  287. * If the keyword [escape][hardcoded][/escape] appears at the first line of text,
  288. * or if options have the keyword ##hardcoded##, the only transformation is new lines to breaks.
  289. *
  290. * If options feature the keyword ##compact##, then YACS codes that may
  291. * generate big objects are removed, such as [escape][table]...[/table][/escape]
  292. * and [escape][location][/escape].
  293. *
  294. * @param string the text to beautify
  295. * @param string the set of options that apply to this text
  296. * @return the beautified text
  297. *
  298. * @see articles/view.php
  299. */
  300. public static function &beautify($text, $options='') {
  301. global $context;
  302. // save CPU cycles
  303. $text = trim($text);
  304. if(!$text)
  305. return $text;
  306. //
  307. // looking for compact content
  308. //
  309. if(preg_match('/\bcompact\b/i', $options))
  310. $text = preg_replace(array('/\[table.+?\/table\]/', '/\[location.+?\]/'), '', $text);
  311. //
  312. // implicit formatting
  313. //
  314. // new lines will have to be checked
  315. $new_lines = 'proceed';
  316. // text is already formatted
  317. if(!strncmp($text, '[formatted]', 11)) {
  318. $new_lines = 'none';
  319. $text = substr($text, 11);
  320. // text is already formatted (through options)
  321. } elseif(preg_match('/\bformatted\b/i', $options))
  322. $new_lines = 'none';
  323. // newlines are hard coded
  324. elseif(!strncmp($text, '[hardcoded]', 11)) {
  325. $new_lines = 'hardcoded';
  326. $text = substr($text, 11);
  327. // newlines are hard coded (through options)
  328. } elseif(preg_match('/\bhardcoded\b/i', $options))
  329. $new_lines = 'hardcoded';
  330. // implicit formatting
  331. else
  332. $text =& Codes::beautify_implied($text, 'text');
  333. //
  334. // translate codes
  335. //
  336. // render codes
  337. $text =& Codes::render($text);
  338. // render smileys after codes, else it will break escaped strings
  339. if(is_callable(array('Smileys', 'render_smileys')))
  340. $text = Smileys::render_smileys($text);
  341. // relocate images
  342. $text = str_replace('"skins/', '"'.$context['path_to_root'].'skins/', $text);
  343. //
  344. // adjust end of lines
  345. //
  346. // newlines are hard coded
  347. if($new_lines == 'hardcoded')
  348. $text = nl2br($text);
  349. // implicit formatting
  350. elseif($new_lines == 'proceed')
  351. $text =& Codes::beautify_implied($text, 'newlines');
  352. return $text;
  353. }
  354. /**
  355. * beautify some text in the extra panel
  356. *
  357. * @param string the text to beautify
  358. * @return the beautified text
  359. *
  360. * @see articles/view.php
  361. */
  362. public static function &beautify_extra($text) {
  363. global $context;
  364. $search = array();
  365. $replace = array();
  366. // [box.extra=title]...[/box]
  367. $search[] = '/\[box\.(extra)=([^\]]+?)\](.*?)\[\/box\]/ise';
  368. $replace[] = "Skin::build_box(stripslashes('$2'), stripslashes('$3'), '$1')";
  369. // [box.navigation=title]...[/box]
  370. $search[] = '/\[box\.(navigation)=([^\]]+?)\](.*?)\[\/box\]/ise';
  371. $replace[] = "Skin::build_box(stripslashes('$2'), stripslashes('$3'), '$1')";
  372. // process all codes
  373. $text = preg_replace($search, $replace, $text);
  374. // regular rendering
  375. $text =& Codes::beautify($text);
  376. return $text;
  377. }
  378. /**
  379. * render some basic formatting
  380. *
  381. * - suppress multiple newlines
  382. * - render empty lines
  383. * - render simple bulleted lines
  384. * - make URL clickable (http://..., www.foo.bar, foo.bar@foo.com)
  385. *
  386. * Now this function looks for the keyword &#91;escape] in order
  387. * to avoid for formatting pre-formatted areas.
  388. *
  389. * For example, if you type:
  390. * [snippet]
  391. * hello
  392. * world
  393. *
  394. * how are
  395. * you doing?
  396. *
  397. * - my first item
  398. * - my second item
  399. *
  400. * > quoted from
  401. * > a previous message
  402. * [/snippet]
  403. *
  404. * This will be rendered visually in the browser as:
  405. * [snippet]
  406. * hello world
  407. *
  408. * how are you doing?
  409. *
  410. * - my first item
  411. * - my second item
  412. *
  413. * > quoted from
  414. * > a previous message
  415. * [/snippet]
  416. *
  417. * @param string the text to transform
  418. * @param sring either 'text' or 'newlines'
  419. * @return the modified string
  420. */
  421. public static function &beautify_implied($text, $variant='text') {
  422. // streamline newlines, even if this has been done elsewhere
  423. $text = str_replace(array("\r\n", "\r"), "\n", $text);
  424. // only change end of lines
  425. if($variant == 'newlines') {
  426. // formatting patterns
  427. $search = array(
  428. "|<br\s*/>\n+|i", /* don't insert additional \n after <br /> */
  429. "|\n\n+|i" /* force an html space between paragraphs */
  430. );
  431. $replace = array(
  432. BR,
  433. BR.BR
  434. );
  435. // change everything, except new lines
  436. } else {
  437. // formatting patterns
  438. $search = array(
  439. "|</h1>\n+|i", /* strip \n after title */
  440. "|</h2>\n+|i",
  441. "|</h3>\n+|i",
  442. "|</h4>\n+|i",
  443. '/http:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_\-]+)[a-zA-Z0-9_\-&=]*/i', // YouTube link
  444. '/http:\/\/youtu\.be\/([a-zA-Z0-9_\-]+)/i', // YouTube link too
  445. "#([\n\t \(])([a-z]+?)://([a-z0-9_\-\.\~\/@&;:=%$\?]+)#ie", /* make URL clickable */
  446. "#([\n\t \(])www\.([a-z0-9\-]+)\.([a-z0-9_\-\.\~]+)((?:/[^,< \r\n\)]*)?)#ie", /* web server */
  447. "/\n[ \t]*(From|To|cc|bcc|Subject|Date):(\s*)/i", /* common message headers */
  448. "|\n[ \t]*-(\s+)|i", /* - list item > */
  449. "|\n[ \t]*>(\s*)|i", /* quoted by > */
  450. "|\n[ \t]*\|(\s*)|i", /* quoted by | */
  451. "#([\n\t ])(mailto:|)([a-z0-9_\-\.\~]+?)@([a-z0-9_\-\.\~]+\.[a-z0-9_\-\.\~]+)([\n\t ]*)#ie" /* mail address*/
  452. );
  453. $replace = array(
  454. "</h1>",
  455. "</h2>",
  456. "</h3>",
  457. "</h4>",
  458. '<iframe class="youtube-player" type="text/html" width="445" height="364" src="http://www.youtube.com/embed/$1" frameborder="0"></iframe>', // YouTube link
  459. '<iframe class="youtube-player" type="text/html" width="445" height="364" src="http://www.youtube.com/embed/$1" frameborder="0"></iframe>', // YouTube link too
  460. "'$1'.Skin::build_link('$2://$3', '$2://$3')",
  461. "'$1'.Skin::build_link('http://www.$2.$3$4', 'www.$2.$3$4')",
  462. BR."$1:$2",
  463. BR."-$1",
  464. BR.">$1",
  465. BR."|$1",
  466. "'$1'.Skin::build_link('mailto:$3@$4', '$3@$4', 'email').'$5'"
  467. );
  468. }
  469. // preserve escaped areas
  470. $text = str_replace(array('[escape]', '[/escape]', '[list]', '[/list]', '[php]', '[/php]', '[snippet]', '[/snippet]'),
  471. array('<escape>', '</escape>', '<list>', '</list>', '<php>', '</php>', '<snippet>', '</snippet>'), $text);
  472. // locate pre-formatted areas
  473. $areas = preg_split('#<(code|escape|list|php|snippet|pre)>(.*?)</\1>#is', trim($text), -1, PREG_SPLIT_DELIM_CAPTURE);
  474. // format only adequate areas
  475. $index = 0;
  476. $formatted = '';
  477. $inside = FALSE;
  478. $target = '';
  479. foreach($areas as $area) {
  480. switch($index%3) {
  481. case 0: // area to be formatted
  482. // do not rewrite tags
  483. $items = preg_split('/<(.+?)>/is', $area, -1, PREG_SPLIT_DELIM_CAPTURE);
  484. $where = 0;
  485. foreach($items as $item) {
  486. switch($where%2) {
  487. case 0: // outside a tag
  488. if($inside)
  489. $target .= $item;
  490. else
  491. $formatted .= preg_replace($search, $replace, $item);
  492. break;
  493. case 1: // inside a tag
  494. // inside or outside a link
  495. if($inside && !strncmp($item, '/a', 2)) {
  496. $formatted .= preg_replace($search, $replace, $target).'<'.$item.'>';
  497. $target = '';
  498. $inside = FALSE;
  499. } elseif($inside)
  500. $target .= '<'.$item.'>';
  501. elseif(!strncmp($item, 'a ', 2)) {
  502. $formatted .= '<'.$item.'>';
  503. $inside = TRUE;
  504. } else
  505. $formatted .= '<'.$item.'>';
  506. break;
  507. }
  508. $where++;
  509. }
  510. break;
  511. case 1: // area boundary
  512. $tag = $area;
  513. break;
  514. case 2: // pre-formatted area - left unmodified
  515. // inside a link, or regular text
  516. if($inside)
  517. $target .= '<'.$tag.'>'.$area.'</'.$tag.'>';
  518. else
  519. $formatted .= '<'.$tag.'>'.$area.'</'.$tag.'>';
  520. break;
  521. }
  522. $index++;
  523. }
  524. // post-optimization
  525. if($variant == 'text')
  526. $formatted = preg_replace('#</ul>\n{0,1}<ul>#', '', $formatted);
  527. $formatted = preg_replace('#\n\n+<ul#', "\n<ul", $formatted);
  528. // restore escaped areas
  529. $formatted = str_replace(array('<escape>', '</escape>', '<list>', '</list>', '<php>', '</php>', '<snippet>', '</snippet>'),
  530. array('[escape]', '[/escape]', '[list]', '[/list]', '[php]', '[/php]', '[snippet]', '[/snippet]'), $formatted);
  531. return $formatted;
  532. }
  533. /**
  534. * format an introduction
  535. *
  536. * @param string raw introduction
  537. * @return string finalized title
  538. */
  539. public static function &beautify_introduction($text) {
  540. // render codes
  541. $output =& Codes::render($text);
  542. // render smileys after codes, else it will break escaped strings
  543. if(is_callable(array('Smileys', 'render_smileys')))
  544. $output = Smileys::render_smileys($output);
  545. // return by reference
  546. return $output;
  547. }
  548. /**
  549. * format a title
  550. *
  551. * New lines and images are the only things accepted in titles.
  552. * The goal is to provide a faster service than beautify()
  553. *
  554. * @param string raw title
  555. * @return string finalized title
  556. */
  557. public static function &beautify_title($text) {
  558. // suppress pairing codes
  559. $output =& Codes::strip($text, FALSE);
  560. // the only code transformed in titles
  561. $output = str_replace(array('[nl]', '[NL]'), '<br />', $output);
  562. // remove everything, except links, breaks and images, and selected tags
  563. $output = strip_tags($output, '<a><abbr><acronym><b><big><br><code><del><div><dfn><em><i><img><ins><p><q><small><span><strong><sub><sup><tt><u>');
  564. // return by reference
  565. return $output;
  566. }
  567. /**
  568. * determine if a code is already in some text
  569. *
  570. * @param string the text to check
  571. * @param string code to check (e.g., 'embed')
  572. * @param int the id of the object
  573. * @return boolean TRUE if the code is present, false otherwise
  574. */
  575. public static function check_embedded($text, $code, $id) {
  576. // we check the string of digits
  577. $id = strval($id);
  578. // parse the full string
  579. $count = strlen($text);
  580. $position = 0;
  581. // look for '[embed' or similar
  582. while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
  583. $position += 1+strlen($code);
  584. // parse remaining chars
  585. while($position < $count) {
  586. // digits just follow the '=' sign
  587. if($text[$position] == '=') {
  588. $position++;
  589. // exact match
  590. if(($position + 2 + strlen($id) < $count) && !strcmp(substr($text, $position, strlen($id)), $id))
  591. return TRUE;
  592. // not in this code, look at next one
  593. break;
  594. // malformed code
  595. } elseif($text[$position] == ']') {
  596. $position++;
  597. break;
  598. }
  599. // next char
  600. $position++;
  601. }
  602. }
  603. // not found
  604. return FALSE;
  605. }
  606. /**
  607. * delete a code if it is present in some text
  608. *
  609. * @param string the text to check
  610. * @param string code to check (e.g., 'embed')
  611. * @param int the id of the object
  612. * @return string the resulting string
  613. */
  614. public static function delete_embedded($text, $code, $id) {
  615. // we check the string of digits
  616. $id = strval($id);
  617. // parse the full string
  618. $count = strlen($text);
  619. $position = 0;
  620. // look for '[embed' or similar
  621. while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
  622. // we have to take everything before that point
  623. $prefix = $position;
  624. // next char
  625. $position += 1+strlen($code);
  626. // parse remaining chars
  627. while($position < $count) {
  628. // digits just follow the '=' sign
  629. if($text[$position] == '=') {
  630. $position++;
  631. // exact match
  632. if(($position + strlen($id) <= $count) && !strcmp(substr($text, $position, strlen($id)), $id)) {
  633. $position += strlen($id);
  634. // look for ']'
  635. while($position < $count) {
  636. if($text[$position] == ']') {
  637. $position++;
  638. break;
  639. }
  640. $position++;
  641. }
  642. // do the deletion
  643. $modified = '';
  644. if($prefix > 0)
  645. $modified .= substr($text, 0, $prefix);
  646. if($position < $count)
  647. $modified .= substr($text, $position, $count-$position);
  648. return $modified;
  649. }
  650. // not in this code, look at next one
  651. break;
  652. // malformed code
  653. } elseif($text[$position] == ']') {
  654. $position++;
  655. break;
  656. }
  657. // next char
  658. $position++;
  659. }
  660. }
  661. // not found
  662. return $text;
  663. }
  664. /**
  665. * fix line breaks
  666. *
  667. * This function moves unclosed tags to the beginning of content.
  668. *
  669. * @param string input
  670. * @return string original or modified content
  671. */
  672. public static function &fix_tags($text) {
  673. // look for opening tag at content end
  674. $last_open = strrpos($text, '<p>');
  675. $last_close = strrpos($text, '</p');
  676. if($last_open && (($last_close === FALSE) || ($last_open > $last_close))) {
  677. // trail
  678. $trail = '';
  679. if(strlen($text) > $last_open + 3)
  680. $trail = substr($text, $last_open + 3);
  681. // move it to content start to restore pairing tags
  682. $text = '<p>'.substr($text, 0, $last_open).$trail;
  683. }
  684. // also fix broken img tags, if any
  685. $text = preg_replace('#<(img[^</]+)>#i', '<$1 />', $text);
  686. // remove slashes added by preg_replace -- only for double quotes
  687. $text = str_replace('\"', '"', $text);
  688. // done
  689. return $text;
  690. }
  691. /**
  692. * reset global variables used for rendering
  693. *
  694. * This function should be called between the processing of different articles in a loop
  695. *
  696. * @param string the target URL for this rendering (e.g., 'articles/view.php/123')
  697. */
  698. public static function initialize($main_target=NULL) {
  699. global $context;
  700. if($main_target)
  701. $context['self_url'] = $context['url_to_root'].$main_target;
  702. }
  703. /**
  704. * list all ids matching some code
  705. *
  706. * @param string the text to check
  707. * @param string code to check (e.g., 'embed')
  708. * @return array the list of matching ids
  709. */
  710. public static function list_embedded($text, $code='embed') {
  711. // all ids we have found
  712. $ids = array();
  713. // parse the full string
  714. $count = strlen($text);
  715. $position = 0;
  716. // look for '[embed' or similar
  717. while(($position = strpos($text, '['.$code, $position)) !== FALSE) {
  718. $position += 1+strlen($code);
  719. // parse remaining chars
  720. while($position < $count) {
  721. // digits just follow the '=' sign
  722. if($text[$position] == '=') {
  723. $position++;
  724. // capture all digits
  725. $id = '';
  726. while($position < $count) {
  727. if(($text[$position] >= '0') && ($text[$position] <= '9')) {
  728. $id .= $text[$position];
  729. $position++;
  730. } else
  731. break;
  732. }
  733. // save this id
  734. if(strlen($id))
  735. $ids[] = $id;
  736. // look at next code
  737. break;
  738. // malformed code
  739. } elseif($text[$position] == ']') {
  740. $position++;
  741. break;
  742. }
  743. // next char
  744. $position++;
  745. }
  746. }
  747. // job done
  748. return $ids;
  749. }
  750. /**
  751. * transform codes to html
  752. *
  753. * [php]
  754. * // build the page
  755. * $context['text'] .= ...
  756. *
  757. * // transform codes
  758. * $context['text'] = Codes::render($context['text']);
  759. *
  760. * // final rendering
  761. * render_skin();
  762. * [/php]
  763. *
  764. * @link http://pureform.wordpress.com/2008/01/04/matching-a-word-characters-outside-of-html-tags/
  765. *
  766. * @param string the input string
  767. * @return string the transformed string
  768. */
  769. public static function &render($text) {
  770. global $context;
  771. // streamline newlines, even if this has been done elsewhere
  772. $text = str_replace(array("\r\n", "\r"), "\n", $text);
  773. // prevent wysiwyg editors to bracket our own tags
  774. $text = preg_replace('#^<p>(\[.+\])</p>$#m', '$1', $text);
  775. // initialize only once
  776. static $pattern;
  777. if(!isset($pattern)) {
  778. // $pattern[] = ;
  779. // $replace[] = ;
  780. //
  781. // $pattern[] = ;
  782. // $replace[] = ;
  783. //
  784. // $pattern[] = ;
  785. // $replace[] = ;
  786. //
  787. // $pattern[] = ;
  788. // $replace[] = ;
  789. //
  790. // $pattern[] = ;
  791. // $replace[] = ;
  792. $pattern = array(
  793. "|<!-- .* -->|i", // remove HTML comments
  794. '/\[escape\](.*?)\[\/escape\]/ise', // [escape]...[/escape] (before everything)
  795. '/\[php\](.*?)\[\/php\]/ise', // [php]...[/php]
  796. '/\[snippet\](.*?)\[\/snippet\]/ise', // [snippet]...[/snippet]
  797. '/(\[page\].*)$/is', // [page] (provide only the first one)
  798. '/\[associate\](.*?)\[\/associate\]/ise', // [associate]...[/associate] (save some cycles if at the beginning)
  799. '/\[hidden\](.*?)\[\/hidden\]/ise', // [hidden]...[/hidden] obsolete, replaced by [associate]...[/associate]
  800. '/\[authenticated\](.*?)\[\/authenticated\]/ise', // [authenticated]...[/authenticated] (save some cycles if at the beginning)
  801. '/\[restricted\](.*?)\[\/restricted\]/ise', // [restricted]...[/restricted] obsolete, replaced by [authenticated]...[/authenticated]
  802. '/\[anonymous\](.*?)\[\/anonymous\]/ise', // [anonymous]...[/anonymous] (save some cycles if at the beginning)
  803. '/\[redirect=([^\]]+?)\]/ise', // [redirect=<link>]
  804. '/\[execute=([^\]]+?)\]/ise', // [execute=<name>]
  805. '/\[parameter=([^\]]+?)\]/ise', // [parameter=<name>]
  806. '/\[lang=([^\]]+?)\](.*?)\[\/lang\]/ise', // [lang=xy]...[/lang]
  807. '/\[csv=(.)\](.*?)\[\/csv\]/ise', // [csv=;]...[/csv] (before [table])
  808. '/\[csv\](.*?)\[\/csv\]/ise', // [csv]...[/csv] (before [table])
  809. '/\[table=([^\]]+?)\](.*?)\[\/table\]/ise', // [table=variant]...[/table]
  810. '/\[table\](.*?)\[\/table\]/ise', // [table]...[/table]
  811. '/\[images=([^\]]+?)\]/ie', // [images=<ids>] (before other links)
  812. '/\[image\](.*?)\[\/image\]/ise', // [image]src[/image]
  813. '/\[image=([^\]]+?)\](.*?)\[\/image\]/ise', // [image=alt]src[/image]
  814. '/\[img\](.*?)\[\/img\]/ise', // [img]src[/img]
  815. '/\[img=([^\]]+?)\](.*?)\[\/img\]/ise', // [img=alt]src[/img]
  816. '/\[image=([^\]]+?)\]/ie', // [image=<id>]
  817. '/##(\S.*?\S)##/is', // ##...##
  818. '/\[code\](.*?)\[\/code\]/is', // [code]...[/code]
  819. '/\[indent\](.*?)\[\/indent\]/ise', // [indent]...[/indent]
  820. '/\[quote\](.*?)\[\/quote\]/ise', // [quote]...[/quote]
  821. '/\[folded=([^\]]+?)\](.*?)\[\/folded\]\s*/ise', // [folded=...]...[/folded]
  822. '/\[folded\](.*?)\[\/folded\]\s*/ise', // [folded]...[/folded]
  823. '/\[folder=([^\]]+?)\](.*?)\[\/folder\]\s*/ise', // [folder=...]...[/folder]
  824. '/\[folder\](.*?)\[\/folder\]\s*/ise', // [folder]...[/folder]
  825. '/\[unfolded=([^\]]+?)\](.*?)\[\/unfolded\]\s*/ise', // [unfolded=...]...[/unfolded]
  826. '/\[unfolded\](.*?)\[\/unfolded\]\s*/ise', // [unfolded]...[/unfolded]
  827. '/\[sidebar=([^\]]+?)\](.*?)\[\/sidebar\]\s*/ise', // [sidebar=...]...[/sidebar]
  828. '/\[sidebar\](.*?)\[\/sidebar\]\s*/ise', // [sidebar]...[/sidebar]
  829. '/\[note\](.*?)\[\/note\]\s*/ise', // [note]...[/note]
  830. '/\[caution\](.*?)\[\/caution\]\s*/ise', // [caution]...[/caution]
  831. '/\[search=([^\]]+?)\]/ise', // [search=words]
  832. '/\[search\]/ise', // [search]
  833. '/\[cloud=(\d+?)\]/ise', // [cloud=12]
  834. '/\[cloud\]/ise', // [cloud]
  835. '/\[login=([^\]]+?)\]/is', // [login=words] --obsoleted
  836. '/\[login\]/is', // [login] --obsoleted
  837. '/\[center\](.*?)\[\/center\]/ise', // [center]...[/center]
  838. '/\[right\](.*?)\[\/right\]/ise', // [right]...[/right]
  839. '/\[decorated\](.*?)\[\/decorated\]/ise',// [decorated]...[/decorated]
  840. '/\[style=([^\]]+?)\](.*?)\[\/style\]/ise', // [style=variant]...[/style]
  841. '/\[hint=([^\]]+?)\](.*?)\[\/hint\]/is', // [hint=help]...[/hint]
  842. '/\[tiny\](.*?)\[\/tiny\]/ise', // [tiny]...[/tiny]
  843. '/\[small\](.*?)\[\/small\]/ise', // [small]...[/small]
  844. '/\[big\](.*?)\[\/big\]/ise', // [big]...[/big]
  845. '/\[huge\](.*?)\[\/huge\]/ise', // [huge]...[/huge]
  846. '/\[subscript\](.*?)\[\/subscript\]/is',// [subscript]...[/subscript]
  847. '/\[superscript\](.*?)\[\/superscript\]/is',// [superscript]...[/superscript]
  848. '/\+\+(\S.*?\S)\+\+(?!([^<]+)?>)/is', // ++...++
  849. '/\[(---+|___+)\]\s*/ise', // [---], [___] --- before inserted
  850. '/^-----*/me', // ----
  851. '/\[inserted\](.*?)\[\/inserted\]/is', // [inserted]...[/inserted]
  852. '/ --(\S.*?\S)--(?!([^<]+)?>)/is', // --...--
  853. '/\[deleted\](.*?)\[\/deleted\]/is', // [deleted]...[/deleted]
  854. '/\*\*(\S.*?\S)\*\*/is', // **...**
  855. '/\[b\](.*?)\[\/b\]/is', // [b]...[/b]
  856. '/ \/\/(\S.*?\w)\/\/(?!([^<]+)?>)/is', // //...//
  857. '/\[i\](.*?)\[\/i\]/is', // [i]...[/i]
  858. '/__(\S.*?\S)__(?!([^<]+)?>)/is', // __...__
  859. '/\[u\](.*?)\[\/u\]/is', // [u]...[/u]
  860. '/\[color=([^\]]+?)\](.*?)\[\/color\]/is', // [color=<color>]...[/color]
  861. '/\[new\]/ie', // [new]
  862. '/\[popular\]/ie', // [popular]
  863. '/\[flag=([^\]]+?)\]/ie', // [flag=<flag>]
  864. '/\[flag\](.*?)\[\/flag\]/ise', // [flag]...[/flag]
  865. '/\[list\](.*?)\[\/list\]/ise', // [list]...[/list]
  866. '/\[list=([^\]]+?)\](.*?)\[\/list\]/ise', // [list=1]...[/list]
  867. '/\n\n+[ \t]*\[\*\][ \t]*/ie', // [*] (outside [list]...[/list])
  868. '/\n?[ \t]*\[\*\][ \t]*/ie',
  869. '/\[li\](.*?)\[\/li\]/is', // [li]...[/li] (outside [list]...[/list])
  870. '/\[chart=([^\]]+?)\](.*?)\[\/chart\]/ise', // [chart=<width>, <height>, <params>]...[/chart]
  871. '/\[embed=([^\]]+?)\]/ie', // [embed=<id>, <width>, <height>, <params>] or [embed=<id>, window]
  872. '/\[flash=([^\]]+?)\]/ie', // [flash=<id>, <width>, <height>, <params>] or [flash=<id>, window]
  873. '/\[sound=([^\]]+?)\]/ie', // [sound=<id>]
  874. '/\[go=([^\]]+?)\]/ie', // [go=<name>]
  875. '/\[\[([^\]]+?)\]\]/ie', // [[<name>]]
  876. '/\[article\.description=([^\]]+?)\]/ie', // [article.description=<id>]
  877. '/\[article=([^\]]+?)\]/ie', // [article=<id>] or [article=<id>, title]
  878. '/\[next=([^\]]+?)\]/ie', // [next=<id>]
  879. '/\[previous=([^\]]+?)\]/ie', // [previous=<id>]
  880. '/\[random\]/ie', // [random]
  881. '/\[random\.description=([^\]]+?)\]/ie', // [random.description=section:<id>]
  882. '/\[random=([^\]]+?)\]/ie', // [random=section:<id>] or [random=category:<id>]
  883. '/\[form=([^\]]+?)\]/ie', // [form=<id>] or [form=<id>, title]
  884. '/\[section=([^\]]+?)\]/ie', // [section=<id>] or [section=<id>, title]
  885. '/\[category\.description=([^\]]+?)\]\n*/ise', // [category.description=<id>]
  886. '/\[category=([^\]]+?)\]/ie', // [category=<id>] or [category=<id>, title]
  887. '/\[user=([^\]]+?)\]/ie', // [user=<id>] or [user=<id>, title]
  888. '/\[server=([^\]]+?)\]/ie', // [server=<id>]
  889. '/\[file=([^\]]+?)\]/ie', // [file=<id>] or [file=<id>, title]
  890. '/\[download=([^\]]+?)\]/ie', // [download=<id>] or [download=<id>, title]
  891. '/\[comment=([^\]]+?)\]/ie', // [comment=<id>] or [comment=<id>, title]
  892. '/\[url=([^\]]+?)\](.*?)\[\/url\]/ise', // [url=url]label[/url] (deprecated by [link])
  893. '/\[url\](.*?)\[\/url\]/ise', // [url]url[/url] (deprecated by [link])
  894. '/\[link=([^\]]+?)\](.*?)\[\/link\]/ise', // [link=label]url[/link]
  895. '/\[link\](.*?)\[\/link\]/ise', // [link]url[/link]
  896. '/\[proxy\](.*?)\[\/proxy\]/ise', // [proxy]url[/proxy]
  897. '/\[button=([^\]]+?)\](.*?)\[\/button\]/ise', // [button=label]url[/button]
  898. '/\[button=([^\|]+?)\|([^\]]+?)]/ise', // [button=label|url]
  899. '/\[click=([^\|]+?)\|([^\]]+?)]/ise', // [click=label|url]
  900. '/\[clicks=([^\]]+?)]/ise', // [clicks=url]
  901. '/\[script\](.*?)\[\/script\]/ise', // [script]url[/script]
  902. '/\[menu\](.*?)\[\/menu\]\n*/ise', // [menu]url[/menu]
  903. '/\[menu=([^\]]+?)\](.*?)\[\/menu\]\n{0,1}/ise', // [menu=label]url[/menu]
  904. '/\[submenu\](.*?)\[\/submenu\]\n{0,1}/ise', // [submenu]url[/submenu]
  905. '/\[submenu=([^\]]+?)\](.*?)\[\/submenu\]\n*/ise', // [submenu=label]url[/submenu]
  906. '/\[email=([^\]]+?)\](.*?)\[\/email\]/ise', // [email=label]url[/email]
  907. '/\[email\](.*?)\[\/email\]/ise', // [email]url[/email]
  908. '/\[([^ ][^\]\|]+?[^ ])\|([^ ][^\]]+?[^ ])\]/ise', // [label|url]
  909. '/\[question\](.*?)\[\/question\]\n*/ise', // [question]...[/question]
  910. '/\[question\]/ise', // [question]
  911. '/\[answer\]/ise', // [answer]
  912. '/\[newsfeed=([^\]]+?)\]/ise', // [newsfeed=url]
  913. '/\[newsfeed\.([^=\]]+?)=([^\]]+?)\]/ise', // [newsfeed.variant=url]
  914. '/\[twitter=([^\]]+?)\]/ise', // [twitter=id]
  915. '/\[tsearch=([^\]]+?)\]/ise', // [tsearch=id]
  916. '/\[retweet\]/ise', // [retweet]
  917. '/\[iframe\](.*?)\[\/iframe\]/ise', // [iframe]<url>[/iframe]
  918. '/\[iframe=([^\]]+?)\](.*?)\[\/iframe\]/ise', // [iframe=<width>, <height>]<url>[/iframe]
  919. '/\[scroller\](.*?)\[\/scroller\]/ise', // [scroller]...[/scroller]
  920. '/\[toq\]\n*/ise', // [toq] (table of questions)
  921. '/\[title\](.*?)\[\/title\]\n*/is', // [title]...[/title]
  922. '/\[subtitle\](.*?)\[\/subtitle\]\n*/is', // [subtitle]...[/subtitle]
  923. '#\[(header[1-5])\](.*?)\[/\1\]\n*#ise', // [header1]...[/header1] ... [header5]...[/header5]
  924. '/^======(\S.*?\S)======/me', // ======...====== level 5 headline
  925. '/<(br \/|p)>======(\S.*?\S)======<(br \/|\/p)>/me', // ======...====== level 5 headline
  926. '/^=====(\S.*?\S)=====/me', // =====...===== level 4 headline
  927. '/<(br \/|p)>=====(\S.*?\S)=====<(br \/|\/p)>/me', // =====...===== level 4 headline
  928. '/^====(\S.*?\S)====/me', // ====...==== level 3 headline
  929. '/<(br \/|p)>====(\S.*?\S)====<(br \/|\/p)>/me', // ====...==== level 3 headline
  930. '/^===(\S.*?\S)===/me', // ===...=== level 2 headline
  931. '/<(br \/|p)>===(\S.*?\S)===<(br \/|\/p)>/me', // ===...=== level 2 headline
  932. '/^==(\S.*?\S)==/me', // ==...== level 1 headline
  933. '/<(br \/|p)>==(\S.*?\S)==<(br \/|\/p)>/me', // ==...== level 1 headline
  934. '/\[toc\]\n*/ise', // [toc] (table of content)
  935. '/\[published\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [published.decorated=section:4029]
  936. '/\[published\.([^\]]+?)\]\n*/ise', // [published.decorated]
  937. '/\[published=([^\]]+?)\]\n*/ise', // [published=section:4029]
  938. '/\[published\]\n*/ise', // [published]
  939. '/\[read\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [read.decorated=section:4029]
  940. '/\[read\.([^\]]+?)\]\n*/ise', // [read.decorated]
  941. '/\[read=([^\]]+?)\]\n*/ise', // [read=section:4029]
  942. '/\[read\]\n*/ise', // [read]
  943. '/\[updated\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [updated.simple=section:4029] (a list of recent updates)
  944. '/\[updated\.([^\]]+?)\]\n*/ise', // [updated.simple] (a list of recent updates)
  945. '/\[updated=([^\]]+?)\]\n*/ise', // [updated=section:4029] (a compact list of recent updates)
  946. '/\[updated\]\n*/ise', // [updated] (a compact list of recent updates)
  947. '/\[voted\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [voted.decorated=section:4029]
  948. '/\[voted\.([^\]]+?)\]\n*/ise', // [voted.decorated]
  949. '/\[voted=([^\]]+?)\]\n*/ise', // [voted=section:4029]
  950. '/\[voted\]\n*/ise', // [voted]
  951. '/\[sections\]\n*/ise', // [sections] (site map)
  952. '/\[sections\.([^\]=]+?)\]\n*/ise', // [sections.folded] (site map)
  953. '/\[sections=([^\]]+?)\]\n*/ise', // [sections=section:4029] (sub-sections)
  954. '/\[sections\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [sections.simple=self] (assigned)
  955. '/\[categories\]\n*/ise', // [categories] (category tree)
  956. '/\[categories\.([^\]=]+?)\]\n*/ise', // [categories.folded] (category tree)
  957. '/\[categories=([^\]]+?)\]\n*/ise', // [categories=section:4029] (sub-categories)
  958. '/\[categories\.([^\]=]+?)=([^\]]+?)\]\n*/ise', // [categories.simple=self] (assigned)
  959. '/\[calendar\]\n*/ise', // [calendar]
  960. '/\[calendar=([^\]]+?)\]\n*/ise', // [calendar=section:4029]
  961. '/\[users=([^\]]+?)\]/ie', // [users=present]
  962. '/\[news=([^\]]+?)\]/ise', // [news=flash]
  963. '/\[table=([^\]]+?)\]/ise', // [table=<id>]
  964. '/\[table\.([^=\]]+?)=([^\]]+?)\]/ise', // [table.json=<id>] [table.timeplot=<id>]
  965. '/\[locations=([^\]]+?)\]/ise', // [locations=<id>]
  966. '/\[location=([^\]]+?)\]/ise', // [location=<id>]
  967. '/\[wikipedia=([^\]]+?)\]/ise', // [wikipedia=keyword] or [wikipedia=keyword, title]
  968. '/\[digraph\](.*?)\[\/digraph\]/ise', // [digraph]url[/digraph]
  969. '/\[be\]/i', // [be] belgian flag
  970. '/\[ca\]/i', // [ca] canadian flag
  971. '/\[ch\]/i', // [ch] swiss flag
  972. '/\[de\]/i', // [de] german flag
  973. '/\[en\]/i', // [en] english flag
  974. '/\[es\]/i', // [es] spanish flag
  975. '/\[fr\]/i', // [fr] french flag
  976. '/\[gb\]/i', // [gb] gb flag
  977. '/\[gr\]/i', // [gr] greek flag
  978. '/\[it\]/i', // [it] italian flag
  979. '/\[pt\]/i', // [pt] portuguese flag
  980. '/\[us\]/i', // [pt] us flag
  981. '/\[clear\]\n*/i', // [clear]
  982. '/\[nl\]\n*/si', // [nl] (after tables)
  983. '/\[br\]/i' // [br] (deprecated by [nl])
  984. );
  985. }
  986. // initialize only once
  987. static $replace;
  988. if(!isset($replace)) {
  989. $replace = array(
  990. '', // delete HTML comments
  991. "Codes::render_escaped(Codes::fix_tags('$1'))", // [escape]...[/escape]
  992. "Codes::render_pre(Codes::fix_tags('$1'), 'php')", // [php]...[/php]
  993. "Codes::render_pre(Codes::fix_tags('$1'), 'snippet')", // [snippet]...[/snippet]
  994. '', // [page]
  995. "Codes::render_hidden(Codes::fix_tags('$1'), 'associate')", // [associate]...[/associate]
  996. "Codes::render_hidden(Codes::fix_tags('$1'), 'associate')", // [hidden]...[/hidden]
  997. "Codes::render_hidden(Codes::fix_tags('$1'), 'authenticated')", // [authenticated]...[/authenticated]
  998. "Codes::render_hidden(Codes::fix_tags('$1'), 'authenticated')", // [restricted]...[/restricted]
  999. "Codes::render_hidden(Codes::fix_tags('$1'), 'anonymous')", // [anonymous]...[/anonymous]
  1000. "Codes::render_redirect('$1')", // [redirect=<link>]
  1001. "Codes::render_execute('$1')", // [execute=<name>]
  1002. "Codes::render_parameter('$1')", // [parameter=<name>]
  1003. "i18n::filter(Codes::fix_tags('$2'), '$1')", // [lang=xy]...[/lang]
  1004. "utf8::encode(str_replace('$1', '|', utf8::from_unicode(Codes::fix_tags('$2'))))", // [csv=;]...[/csv]
  1005. "str_replace(',', '|', Codes::fix_tags('$1'))", // [csv]...[/csv]
  1006. "Codes::render_static_table(Codes::fix_tags('$2'), '$1')", // [table=variant]...[/table]
  1007. "Codes::render_static_table(Codes::fix_tags('$1'), '')", // [table]...[/table]
  1008. "Codes::render_object('images', '$1')", // [images=<ids>]
  1009. "'<div class=\"external_image\"><img src=\"'.encode_link('$1').'\" alt=\"\" /></div>'", // [image]src[/image]
  1010. "'<div class=\"external_image\"><img src=\"'.encode_link('$2').'\" alt=\"'.encode_link('$1').'\" /></div>'", // [image=alt]src[/image]
  1011. "'<div class=\"external_image\"><img src=\"'.encode_link('$1').'\" alt=\"\" /></div>'", // [img]src[/img]
  1012. "'<div class=\"external_image\"><img src=\"'.encode_link('$2').'\" alt=\"'.encode_link('$1').'\" /></div>'", // [img=alt]src[/img]
  1013. "Codes::render_object('image', Codes::fix_tags('$1'))", // [image=<id>]
  1014. '<code>$1</code>', // ##...##
  1015. '<code>$1</code>', // [code]...[/code]
  1016. "Skin::build_block(Codes::fix_tags('$1'), 'indent')", // [indent]...[indent]
  1017. "Skin::build_block(Codes::fix_tags('$1'), 'quote')", // [quote]...[/quote]
  1018. "Skin::build_box('$1', Codes::fix_tags('$2'), 'folded')", // [folded=title]...[/folded]
  1019. "Skin::build_box(NULL, Codes::fix_tags('$1'), 'folded')", // [folded]...[/folded]
  1020. "Skin::build_box('$1', Codes::fix_tags('$2'), 'folded')", // [folder=title]...[/folder]
  1021. "Skin::build_box(NULL, Codes::fix_tags('$1'), 'folded')", // [folder]...[/folder]
  1022. "Skin::build_box('$1', Codes::fix_tags('$2'), 'unfolded')", // [unfolded=title]...[/unfolded]
  1023. "Skin::build_box(NULL, Codes::fix_tags('$1'), 'unfolded')", // [unfolded]...[/unfolded]
  1024. "Skin::build_box('$1', Codes::fix_tags('$2'), 'sidebar')", // [sidebar=title]...[/sidebar]
  1025. "Skin::build_box(NULL, Codes::fix_tags('$1'), 'sidebar')", // [sidebar]...[/sidebar]
  1026. "Skin::build_block(Codes::fix_tags('$1'), 'note')", // [note]...[/note]
  1027. "Skin::build_block(Codes::fix_tags('$1'), 'caution')", // [caution]...[/caution]
  1028. "Skin::build_block('$1', 'search')", // [search=<words>]
  1029. "Skin::build_block(NULL, 'search')", // [search]
  1030. "Codes::render_cloud('$1')", // [cloud=12]
  1031. "Codes::render_cloud(20)", // [cloud]
  1032. '', // [login=<words>] --obsoleted
  1033. '', // [login] --obsoleted
  1034. "Skin::build_block(Codes::fix_tags('$1'), 'center')", // [center]...[/center]
  1035. "Skin::build_block(Codes::fix_tags('$1'), 'right')", // [right]...[/right]
  1036. "Skin::build_block(Codes::fix_tags('$1'), 'decorated')", // [decorated]...[/decorated]
  1037. "Skin::build_block(Codes::fix_tags('$2'), '$1')", // [style=variant]...[/style]
  1038. '<acronym title="$1">$2</acronym>', // [hint=help]...[/hint]
  1039. "Skin::build_block(Codes::fix_tags('$1'), 'tiny')", // [tiny]...[/tiny]
  1040. "Skin::build_block(Codes::fix_tags('$1'), 'small')", // [small]...[/small]
  1041. "Skin::build_block(Codes::fix_tags('$1'), 'big')", // [big]...[/big]
  1042. "Skin::build_block(Codes::fix_tags('$1'), 'huge')", // [huge]...[/huge]
  1043. '<sub>$1</sub>', // [subscript]...[/subscript]
  1044. '<sup>$1</sup>', // [superscript]...[/superscript]
  1045. '<ins>$1</ins>', // ++...++
  1046. "HORIZONTAL_RULER", // [---], [___]
  1047. "HORIZONTAL_RULER", // ----
  1048. '<ins>$1</ins>', // [inserted]...[/inserted]
  1049. ' <del>$1</del>', // --...--
  1050. '<del>$1</del>', // [deleted]...[/deleted]
  1051. '<b>$1</b>', // **...**
  1052. '<b>$1</b>', // [b]...[/b]
  1053. ' <i>$1</i>', // //...//
  1054. '<i>$1</i>', // [i]...[/i]
  1055. '<span style="text-decoration: underline">$1</span>', // __...__
  1056. '<span style="text-decoration: underline">$1</span>', // [u]...[/u]
  1057. '<span style="color: $1">$2</span>', // [color]...[/color]
  1058. "NEW_FLAG", // [new]
  1059. "POPULAR_FLAG", // [popular]
  1060. "Skin::build_flag('$1')", // [flag=....]
  1061. "Skin::build_flag('$1')", // [flag]...[/flag]
  1062. "Codes::render_list(Codes::fix_tags('$1'), NULL)", // [list]...[/list]
  1063. "Codes::render_list(Codes::fix_tags('$2'), '$1')", // [list=?]...[/list]
  1064. "BR.BR.BULLET_IMG.'&nbsp;'", // standalone [*]
  1065. "BR.BULLET_IMG.'&nbsp;'",
  1066. '<li>$1</li>', // [li]...[/li]
  1067. "Codes::render_chart(Codes::fix_tags('$2'), '$1')", // [chart=<width>, <height>, <params>]...[/chart]
  1068. "Codes::render_embed(Codes::fix_tags('$1'))", // [embed=<id>, <width>, <height>, <params>]
  1069. "Codes::render_embed(Codes::fix_tags('$1'))", // [flash=<id>, <width>, <height>, <params>] -- obsoleted by 'embed'
  1070. "Codes::render_object('sound', Codes::fix_tags('$1'))", // [sound=<id>]
  1071. "Codes::render_object('go', Codes::fix_tags('$1'))", // [go=<name>]
  1072. "Codes::render_object('go', Codes::fix_tags('$1'))", // [[<name>]]
  1073. "Codes::render_object('article.description', Codes::fix_tags('$1'))",// [article.description=<id>]
  1074. "Codes::render_object('article', Codes::fix_tags('$1'))", // [article=<id>]
  1075. "Codes::render_object('next', Codes::fix_tags('$1'))", // [next=<id>]
  1076. "Codes::render_object('previous', Codes::fix_tags('$1'))", // [previous=<id>]
  1077. "Codes::render_random()", // [random]
  1078. "Codes::render_random('$1', 'description')", // [random.description=section:<id>]
  1079. "Codes::render_random('$1')", // [random=section:<id>]
  1080. "Codes::render_object('form', Codes::fix_tags('$1'))", // [form=<id>]
  1081. "Codes::render_object('section', Codes::fix_tags('$1'))", // [section=<id>]
  1082. "Codes::render_object('category.description', '$1')", // [category.description=<id>]
  1083. "Codes::render_object('category', Codes::fix_tags('$1'))", // [category=<id>]
  1084. "Codes::render_object('user', Codes::fix_tags('$1'))", // [user=<id>]
  1085. "Codes::render_object('server', Codes::fix_tags('$1'))", // [server=<id>]
  1086. "Codes::render_object('file', Codes::fix_tags('$1'))", // [file=<id>] or [file=<id>, title]
  1087. "Codes::render_object('download', Codes::fix_tags('$1'))", // [download=<id>] or [download=<id>, title]
  1088. "Codes::render_object('comment', Codes::fix_tags('$1'))", // [comment=<id>] or [comment=<id>, title]
  1089. "Skin::build_link(encode_link('$1'), Codes::fix_tags('$2'))", // [url=url]label[/link] (deprecated by [link])
  1090. "Skin::build_link(encode_link('$1'), NULL)", // [url]url[/url] (deprecated by [link])
  1091. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'))", // [link=label]url[/link]
  1092. "Skin::build_link(encode_link('$1'), NULL)", // [link]url[/link]
  1093. "proxy(encode_link('$1'))", // [proxy]url[/proxy]
  1094. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'), 'button')", // [button=label]url[/button]
  1095. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'), 'button')", // [button=label|url]
  1096. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'), 'click')", // [click=label|url]
  1097. "Codes::render_clicks('$1')", // [clicks=url]
  1098. "Skin::build_link(encode_link('$1'), '$1', 'script')", // [script]url[/script]
  1099. "Skin::build_link(encode_link('$1'), '$1', 'menu_1')", // [menu]url[/menu]
  1100. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'), 'menu_1')", // [menu=label]url[/menu]
  1101. "Skin::build_link(encode_link('$1'), '$1', 'menu_2')", // [submenu]url[/submenu]
  1102. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'), 'menu_2')", // [submenu=label]url[/submenu]
  1103. "Codes::render_email(encode_link('$2'), Codes::fix_tags('$1'))", // [email=label]url[/email]
  1104. "Codes::render_email(encode_link('$1'), '$1')", // [email]url[/email]
  1105. "Skin::build_link(encode_link('$2'), Codes::fix_tags('$1'))", // [label|url]
  1106. "Codes::render_title(Codes::fix_tags('$1'), 'question')", // [question]...[/question]
  1107. "QUESTION_FLAG", // [question]
  1108. "ANSWER_FLAG", // [answer]
  1109. "Codes::render_newsfeed('$1', 'ajax')", // [newsfeed=url]
  1110. "Codes::render_newsfeed('$2', '$1')", // [newsfeed=url]
  1111. "Codes::render_twitter('$1')", // [twitter=id]
  1112. "Codes::render_twitter_search('$1')", // [tsearch=id]
  1113. "Codes::render_retweet()", // [retweet]
  1114. "Codes::render_iframe(Codes::fix_tags('$1'), '500, 320')", // [iframe]<url>[/iframe]
  1115. "Codes::render_iframe(Codes::fix_tags('$2'), '$1')", // [iframe=<width>, <height>]<url>[/iframe]
  1116. "Codes::render_animated(Codes::fix_tags('$1'), 'scroller')", // [scroller]...[/scroller]
  1117. "Codes::render_table_of('questions')", // [toq]
  1118. '[header1]$1[/header1]', // [title]...[/title]
  1119. '[header2]$1[/header2]', // [subtitle]...[/subtitle]
  1120. "Codes::render_title(Codes::fix_tags('$2'), '$1')", // [header1]...[/header1] ... [header5]...[/header5]
  1121. "Codes::render_title(Codes::fix_tags('$1'), 'header5')", // ======...====== level 5 header
  1122. "Codes::render_title(Codes::fix_tags('$2'), 'header5')", // ======...====== level 5 header
  1123. "Codes::render_title(Codes::fix_tags('$1'), 'header4')", // =====...===== level 4 header
  1124. "Codes::render_title(Codes::fix_tags('$2'), 'header4')", // =====...===== level 4 header
  1125. "Codes::render_title(Codes::fix_tags('$1'), 'header3')", // ====...==== level 3 header
  1126. "Codes::render_title(Codes::fix_tags('$2'), 'header3')", // ====...==== level 3 header
  1127. "Codes::render_title(Codes::fix_tags('$1'), 'header2')", // ===...=== level 2 header
  1128. "Codes::render_title(Codes::fix_tags('$2'), 'header2')", // ===...=== level 2 header
  1129. "Codes::render_title(Codes::fix_tags('$1'), 'header1')", // ==...== level 1 header
  1130. "Codes::render_title(Codes::fix_tags('$2'), 'header1')", // ==...== level 1 header
  1131. "Codes::render_table_of('content')", // [toc]
  1132. "Codes::render_published('$2', '$1')", // [published.decorated=section:4029]
  1133. "Codes::render_published('', '$1')", // [published.decorated]
  1134. "Codes::render_published('$1', 'simple')", // [published=section:4029]
  1135. "Codes::render_published('', 'simple')", // [published]
  1136. "Codes::render_read('$2', '$1')", // [read.decorated=section:4029]
  1137. "Codes::render_read('', '$1')", // [read.decorated]
  1138. "Codes::render_read('$1', 'hits')", // [read=section:4029]
  1139. "Codes::render_read('', 'hits')", // [read]
  1140. "Codes::render_updated('$2', '$1')", // [updated.simple=section:4029]
  1141. "Codes::render_updated('', '$1')", // [updated.simple]
  1142. "Codes::render_updated('$1', 'simple')", // [updated=section:4029]
  1143. "Codes::render_updated('', 'simple')", // [updated]
  1144. "Codes::render_voted('$2', '$1')", // [voted.decorated=section:4029]
  1145. "Codes::render_voted('', '$1')", // [voted.decorated]
  1146. "Codes::render_voted('$1', 'simple')", // [voted=section:4029]
  1147. "Codes::render_voted('', 'simple')", // [voted]
  1148. "Codes::render_sections()", // [sections] (site map)
  1149. "Codes::render_sections('', '$1')", // [sections.folded] (site map)
  1150. "Codes::render_sections('$1')", // [sections=section:4029] (sub-sections)
  1151. "Codes::render_sections('$2', '$1')", // [sections.simple=self] (assigned)
  1152. "Codes::render_categories()", // [categories] (category tree)
  1153. "Codes::render_categories('', '$1')", // [categories.folded] (category tree)
  1154. "Codes::render_categories('$1')", // [categories=category:4029] (sub-categories)
  1155. "Codes::render_categories('$2', '$1')", // [categories.simple=self] (assigned)
  1156. "Codes::render_calendar()", // [calendar]
  1157. "Codes::render_calendar('$1')", // [calendar=section:4029]
  1158. "Codes::render_users('$1')", // [users=present]
  1159. "Codes::render_news('$1')", // [news=flash]
  1160. "Codes::render_dynamic_table('$1')", // [table=<id>]
  1161. "Codes::render_dynamic_table('$2', '$1')", // [table.json=<id>] [table.timeplot=<id>]
  1162. "Codes::render_locations('$1')", // [locations=<id>]
  1163. "Codes::render_location('$1')", // [location=<id>]
  1164. "Codes::render_wikipedia(Codes::fix_tags('$1'))", // [wikipedia=keyword] or [wikipedia=keyword, title]
  1165. "Codes::render_graphviz('$1', 'digraph')", // [digraph]...[/digraph]
  1166. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/be.gif" alt="" /> ', // [be] belgian flag
  1167. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/ca.gif" alt="" /> ', // [ca] canadian flag
  1168. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/ch.gif" alt="" /> ', // [ch] swiss flag
  1169. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/de.gif" alt="" /> ', // [de] german flag
  1170. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/gb.gif" alt="" /> ', // [en] english flag
  1171. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/es.gif" alt="" /> ', // [es] spanish flag
  1172. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/fr.gif" alt="" /> ', // [fr] french flag
  1173. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/gb.gif" alt="" /> ', // [gb] english flag
  1174. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/gr.gif" alt="" /> ', // [gr] greek flag
  1175. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/it.gif" alt="" /> ', // [it] italian flag
  1176. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/pt.gif" alt="" /> ', // [pt] portuguese flag
  1177. ' <img src="'.$context['url_to_root'].'skins/_reference/flags/us.gif" alt="" /> ', // [us] us flag
  1178. ' <br style="clear: both;" /> ', // [clear]
  1179. BR, // [nl]
  1180. BR // [br] (deprecated by [nl])
  1181. );
  1182. }
  1183. // include code extensions
  1184. // include_once $context['path_to_root'].'scripts/scripts.php';
  1185. // Scripts::load_scripts_at('codes/extensions');
  1186. // ensure we have enough time to execute
  1187. Safe::set_time_limit(30);
  1188. // do it globally
  1189. $text = preg_replace($pattern, $replace, $text);
  1190. // done
  1191. return $text;
  1192. }
  1193. /**
  1194. * render an animated block of text
  1195. *
  1196. * @param string the text
  1197. * @param string the variant
  1198. * @return string the rendered text
  1199. **/
  1200. public static function &render_animated($text, $variant) {
  1201. global $context, $scroller_counter;
  1202. $scroller_counter++;
  1203. $output = '<marquee id="scroller_'.$scroller_counter.'">'.$text.'</marquee>';
  1204. return $output;
  1205. }
  1206. /**
  1207. * render a calendar
  1208. *
  1209. * The provided anchor can reference:
  1210. * - a section 'section:123'
  1211. * - nothing
  1212. *
  1213. * @param string the anchor (e.g. 'section:123')
  1214. * @return string the rendered text
  1215. **/
  1216. public static function &render_calendar($anchor='') {
  1217. global $context;
  1218. // a list of dates
  1219. include_once $context['path_to_root'].'dates/dates.php';
  1220. // sanity check
  1221. $anchor = trim($anchor);
  1222. // get records
  1223. if(strpos($anchor, 'section:') === 0)
  1224. $items =& Dates::list_for_prefix(NULL, 'compact', $anchor);
  1225. else
  1226. $items =& Dates::list_for_prefix(NULL, 'compact', NULL);
  1227. // build calendar for current month
  1228. $text =& Dates::build_months($items, FALSE, TRUE, FALSE, TRUE, gmstrftime('%Y'), gmstrftime('%m'), 'compact calendar');
  1229. // job done
  1230. return $text;
  1231. }
  1232. /**
  1233. * render a list of categories
  1234. *
  1235. * The provided anchor can reference:
  1236. * - a section 'category:123'
  1237. * - a user 'user:789'
  1238. * - 'self'
  1239. * - nothing
  1240. *
  1241. * @param string the anchor (e.g. 'section:123')
  1242. * @param string layout to use
  1243. * @return string the rendered text
  1244. **/
  1245. public static function &render_categories($anchor='', $layout='compact') {
  1246. global $context;
  1247. // we return some text;
  1248. $text = '';
  1249. // number of items to display
  1250. $count = YAHOO_LIST_SIZE;
  1251. if(($position = strpos($anchor, ',')) !== FALSE) {
  1252. $count = (integer)trim(substr($anchor, $position+1));
  1253. if(!$count)
  1254. $count = YAHOO_LIST_SIZE;
  1255. $anchor = trim(substr($anchor, 0, $position));
  1256. }
  1257. // scope is limited to current surfer
  1258. if(($anchor == 'self') && Surfer::get_id()) {
  1259. $anchor = 'user:'.Surfer::get_id();
  1260. // refresh on every page load
  1261. Cache::poison();
  1262. }
  1263. // scope is limited to one category
  1264. if(strpos($anchor, 'category:') === 0)
  1265. $text =& Categories::list_by_title_for_anchor($anchor, 0, $count, $layout);
  1266. // scope is limited to one author
  1267. elseif(strpos($anchor, 'user:') === 0)
  1268. $text =& Members::list_categories_by_title_for_member($anchor, 0, $count, $layout);
  1269. // consider all pages
  1270. if(!$text)
  1271. $text =& Categories::list_by_title_for_anchor(NULL, 0, $count, $layout);
  1272. // we have an array to format
  1273. if(is_array($text))
  1274. $text =& Skin::build_list($text, $layout);
  1275. // job done
  1276. return $text;
  1277. }
  1278. /**
  1279. * render a chart
  1280. *
  1281. * @param string chart data, in JSON format
  1282. * @param string chart parameters
  1283. * @return string the rendered text
  1284. **/
  1285. public static function &render_chart($data, $variant) {
  1286. global $context;
  1287. // split parameters
  1288. $attributes = preg_split("/\s*,\s*/", $variant, 4);
  1289. // set a default size
  1290. if(!isset($attributes[0]))
  1291. $attributes[0] = 320;
  1292. if(!isset($attributes[1]))
  1293. $attributes[1] = 240;
  1294. // object attributes
  1295. $width = $attributes[0];
  1296. $height = $attributes[1];
  1297. $flashvars = '';
  1298. if(isset($attributes[2]))
  1299. $flashvars = $attributes[2];
  1300. // allow several charts to co-exist in the same page
  1301. static $chart_index;
  1302. if(!isset($chart_index))
  1303. $chart_index = 1;
  1304. else
  1305. $chart_index++;
  1306. $url = $context['url_to_home'].$context['url_to_root'].'included/browser/open-flash-chart.swf';
  1307. $text = '<div id="open_flash_chart_'.$chart_index.'" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  1308. Page::insert_script(
  1309. 'var params = {};'."\n"
  1310. .'params.base = "'.dirname($url).'/";'."\n"
  1311. .'params.quality = "high";'."\n"
  1312. .'params.wmode = "opaque";'."\n"
  1313. .'params.allowscriptaccess = "always";'."\n"
  1314. .'params.menu = "false";'."\n"
  1315. .'params.flashvars = "'.$flashvars.'";'."\n"
  1316. .'swfobject.embedSWF("'.$url.'", "open_flash_chart_'.$chart_index.'", "'.$width.'", "'.$height.'", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", {"get-data":"get_open_flash_chart_'.$chart_index.'"}, params);'."\n"
  1317. ."\n"
  1318. .'var chart_data_'.$chart_index.' = '.trim(str_replace(array('<br />', "\n"), ' ', $data)).';'."\n"
  1319. ."\n"
  1320. .'function get_open_flash_chart_'.$chart_index.'() {'."\n"
  1321. .' return $.toJSON(chart_data_'.$chart_index.');'."\n"
  1322. .'}'."\n"
  1323. );
  1324. return $text;
  1325. }
  1326. /**
  1327. * list clicks
  1328. *
  1329. * @param string web address that is monitored
  1330. * @return string the rendered text
  1331. **/
  1332. public static function &render_clicks($url) {
  1333. global $context;
  1334. $text = '';
  1335. // sanity check
  1336. if(!$url)
  1337. return $text;
  1338. // if we received only an id, assume file access
  1339. if(preg_match('/^[0-9]+$/', $url))
  1340. $url = 'file:'.$url;
  1341. // the list of people who have followed this link
  1342. if($users = Activities::list_at($url, array('click', 'fetch'), 50, 'comma')) {
  1343. $count = Activities::count_at($url, array('click', 'fetch'));
  1344. $text .= sprintf(i18n::ns('%d named person has followed the link: %s', '%d named persons have followed the link: %s', $count), $count, $url);
  1345. } else
  1346. $text .= i18n::s('No authenticated person has used this link yet');
  1347. return $text;
  1348. }
  1349. /**
  1350. * render the cloud of tags
  1351. *
  1352. * @param string the number of items to list
  1353. * @return string the rendered text
  1354. **/
  1355. public static function &render_cloud($count=40) {
  1356. global $context;
  1357. // sanity check
  1358. if(!(int)$count)
  1359. $count = 40;
  1360. // query the database and layout that stuff
  1361. if(!$text =& Members::list_categories_by_count_for_anchor(NULL, 0, $count, 'cloud'))
  1362. $text = '<p>'.i18n::s('No item has been found.').'</p>';
  1363. // we have an array to format
  1364. if(is_array($text))
  1365. $text =& Skin::build_list($text, '2-columns');
  1366. // job done
  1367. return $text;
  1368. }
  1369. /**
  1370. * render a dynamic table
  1371. *
  1372. * @param string the table content
  1373. * @param string the variant, if any
  1374. * @return string the rendered text
  1375. **/
  1376. public static function &render_dynamic_table($id, $variant='inline') {
  1377. global $context;
  1378. // refresh on every page load
  1379. Cache::poison();
  1380. // get actual content
  1381. include_once $context['path_to_root'].'tables/tables.php';
  1382. // use SIMILE Exhibit
  1383. if($variant == 'filter') {
  1384. // load the SIMILE Exhibit javascript library in shared/global.php
  1385. $context['javascript']['exhibit'] = TRUE;
  1386. // load data
  1387. $context['page_header'] .= "\n".'<link href="'.$context['url_to_root'].Tables::get_url($id, 'fetch_as_json').'" type="application/json" rel="exhibit/data" />';
  1388. // exhibit data in a table
  1389. $text = '<div ex:role="exhibit-view" ex:viewClass="Exhibit.TabularView" ex:columns="'.Tables::build($id, 'json-labels').'" ex:columnLabels="'.Tables::build($id, 'json-titles').'" ex:border="0" ex:cellSpacing="0" ex:cellPadding="0" ex:showToolbox="true" ></div>'."\n";
  1390. // allow for filtering
  1391. $facets = '<div class="exhibit-facet">'
  1392. .'<div class="exhibit-facet-header"><span class="exhibit-facet-header-title">'.i18n::s('Filter').'</span></div>'
  1393. .'<div class="exhibit-facet-body-frame" style="margin: 0 2px 1em 0;">'
  1394. .'<div ex:role="facet" ex:facetClass="TextSearch" style="display: block;"></div>'
  1395. .'</div></div>';
  1396. // facets from first columns
  1397. $facets .= Tables::build($id, 'json-facets');
  1398. // filter and facets aside
  1399. $context['components']['boxes'] .= $facets;
  1400. // build sparkline
  1401. } elseif($variant == 'bars') {
  1402. $text = '<img border="0" align="baseline" hspace="0" src="'.$context['url_to_root'].Tables::get_url($id, 'fetch_as_png').'&order=0&gap;0.5" alt="" />';
  1403. // buid a Flash chart
  1404. } elseif($variant == 'chart') {
  1405. // split parameters
  1406. $attributes = preg_split("/\s*,\s*/", $id, 4);
  1407. // set a default size
  1408. if(!isset($attributes[1]))
  1409. $attributes[1] = 480;
  1410. if(!isset($attributes[2]))
  1411. $attributes[2] = 360;
  1412. // object attributes
  1413. $width = $attributes[1];
  1414. $height = $attributes[2];
  1415. $flashvars = '';
  1416. if(isset($attributes[3]))
  1417. $flashvars = $attributes[3];
  1418. // allow several charts to co-exist in the same page
  1419. static $chart_index;
  1420. if(!isset($chart_index))
  1421. $chart_index = 1;
  1422. else
  1423. $chart_index++;
  1424. // get data in the suitable format
  1425. $data = Tables::build($attributes[0], 'chart');
  1426. // load it through Javascript
  1427. $url = $context['url_to_home'].$context['url_to_root'].'included/browser/open-flash-chart.swf';
  1428. $text = '<div id="table_chart_'.$chart_index.'" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  1429. Page::insert_script(
  1430. 'var params = {};'."\n"
  1431. .'params.base = "'.dirname($url).'/";'."\n"
  1432. .'params.quality = "high";'."\n"
  1433. .'params.wmode = "opaque";'."\n"
  1434. .'params.allowscriptaccess = "always";'."\n"
  1435. .'params.menu = "false";'."\n"
  1436. .'params.flashvars = "'.$flashvars.'";'."\n"
  1437. .'swfobject.embedSWF("'.$url.'", "table_chart_'.$chart_index.'", "'.$width.'", "'.$height.'", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", {"get-data":"table_chart_'.$chart_index.'"}, params);'."\n"
  1438. ."\n"
  1439. .'var chart_data_'.$chart_index.' = '.trim(str_replace(array('<br />', "\n"), ' ', $data)).';'."\n"
  1440. ."\n"
  1441. .'function table_chart_'.$chart_index.'() {'."\n"
  1442. .' return $.toJSON(chart_data_'.$chart_index.');'."\n"
  1443. .'}'."\n"
  1444. );
  1445. // build sparkline
  1446. } elseif($variant == 'line') {
  1447. $text = '<img border="0" align="baseline" hspace="0" src="'.$context['url_to_root'].Tables::get_url($id, 'fetch_as_png').'&order=2&gap=0.0" alt="" />';
  1448. // we do the rendering ourselves
  1449. } else
  1450. $text = Tables::build($id, $variant);
  1451. // put that into the web page
  1452. return $text;
  1453. }
  1454. /**
  1455. * render an email address
  1456. *
  1457. * @param string the address
  1458. * @param string the label
  1459. * @return string the rendered text
  1460. **/
  1461. public static function &render_email($address, $text) {
  1462. // be sure to display something
  1463. if(!$text)
  1464. $text = $address;
  1465. // specify a scheme if not done yet
  1466. if(!preg_match('/^[a-z]+:/i', $address))
  1467. $address = 'mailto:'.$address;
  1468. // return a complete anchor
  1469. $output = Skin::build_link($address, $text, 'email');
  1470. return $output;
  1471. }
  1472. /**
  1473. * embed an interactive object
  1474. *
  1475. * The id designates the target file.
  1476. * It can also include width and height of the target canvas, as in: '12, 100%, 250px'
  1477. *
  1478. * @param string id of the target file
  1479. * @return string the rendered string
  1480. **/
  1481. public static function &render_embed($id) {
  1482. global $context;
  1483. // split parameters
  1484. $attributes = preg_split("/\s*,\s*/", $id, 4);
  1485. $id = $attributes[0];
  1486. // get the file
  1487. if(!$item = Files::get($id)) {
  1488. $output = '[embed='.$id.']';
  1489. return $output;
  1490. }
  1491. // stream in a separate page
  1492. if(isset($attributes[1]) && preg_match('/window/i', $attributes[1])) {
  1493. if(!isset($attributes[2]))
  1494. $attributes[2] = i18n::s('Play in a separate window');
  1495. $output = '<a href="'.$context['url_to_home'].$context['url_to_root'].Files::get_url($item['id'], 'stream', $item['file_name']).'" onclick="window.open(this.href); return false;" class="button"><span>'.$attributes[2].'</span></a>';
  1496. return $output;
  1497. }
  1498. // file extension
  1499. $extension = strtolower(substr($item['file_name'], -3));
  1500. // set a default size
  1501. if(!isset($attributes[1])) {
  1502. if(!strcmp($extension, 'gan'))
  1503. $attributes[1] = '98%';
  1504. elseif(!strcmp($extension, 'mm') && isset($context['skins_freemind_canvas_width']))
  1505. $attributes[1] = $context['skins_freemind_canvas_width'];
  1506. else
  1507. $attributes[1] = 480;
  1508. }
  1509. if(!isset($attributes[2])) {
  1510. if(!strcmp($extension, 'gan'))
  1511. $attributes[2] = '300px';
  1512. elseif(!strcmp($extension, 'mm') && isset($context['skins_freemind_canvas_height']))
  1513. $attributes[2] = $context['skins_freemind_canvas_height'];
  1514. else
  1515. $attributes[2] = 360;
  1516. }
  1517. // object attributes
  1518. $width = $attributes[1];
  1519. $height = $attributes[2];
  1520. $flashvars = '';
  1521. if(isset($attributes[3]))
  1522. $flashvars = $attributes[3];
  1523. // rendering depends on file extension
  1524. switch($extension) {
  1525. // stream a video
  1526. case '3gp':
  1527. case 'flv':
  1528. case 'm4v':
  1529. case 'mov':
  1530. case 'mp4':
  1531. // a flash player to stream a flash video
  1532. $flvplayer_url = $context['url_to_home'].$context['url_to_root'].'included/browser/player_flv_maxi.swf';
  1533. // file is elsewhere
  1534. if(isset($item['file_href']) && $item['file_href'])
  1535. $url = $item['file_href'];
  1536. // prevent leeching (the flv player will provide session cookie, etc)
  1537. else
  1538. $url = $context['url_to_home'].$context['url_to_root'].Files::get_url($item['id'], 'fetch', $item['file_name']);
  1539. // pass parameters to the player
  1540. if($flashvars)
  1541. $flashvars = str_replace('autostart=true', 'autoplay=1', $flashvars).'&';
  1542. $flashvars .= 'width='.$width.'&height='.$height;
  1543. // if there is a static image for this video, use it
  1544. if(isset($item['icon_url']) && $item['icon_url'])
  1545. $flashvars .= '&startimage='.urlencode($item['icon_url']);
  1546. // if there is a subtitle file for this video, use it
  1547. if(isset($item['file_name']) && ($srt = 'files/'.str_replace(':', '/', $item['anchor']).'/'.str_replace('.'.$extension, '.srt', $item['file_name'])) && file_exists($context['path_to_root'].$srt))
  1548. $flashvars .= '&srt=1&srturl='.urlencode($context['url_to_home'].$context['url_to_root'].$srt);
  1549. // if there is a logo file in the skin, use it
  1550. Skin::define_img_href('FLV_IMG_HREF', 'codes/flvplayer_logo.png', '');
  1551. if(FLV_IMG_HREF)
  1552. $flashvars .= '&top1='.urlencode(FLV_IMG_HREF.'|10|10');
  1553. // rely on Flash
  1554. if(Surfer::has_flash()) {
  1555. // the full object is built in Javascript --see parameters at http://flv-player.net/players/maxi/documentation/
  1556. $output = '<div id="flv_'.$item['id'].'" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  1557. Page::insert_script(
  1558. 'var flashvars = { flv:"'.$url.'", '.str_replace(array('&', '='), array('", ', ':"'), $flashvars).'", autoload:0, margin:1, showiconplay:1, playeralpha:50, iconplaybgalpha:30, showfullscreen:1, showloading:"always", ondoubleclick:"fullscreen" }'."\n"
  1559. .'var params = { allowfullscreen: "true", allowscriptaccess: "always" }'."\n"
  1560. .'var attributes = { id: "file_'.$item['id'].'", name: "file_'.$item['id'].'"}'."\n"
  1561. .'swfobject.embedSWF("'.$flvplayer_url.'", "flv_'.$item['id'].'", "'.$width.'", "'.$height.'", "9", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", flashvars, params);'."\n"
  1562. );
  1563. // native support
  1564. } else {
  1565. // <video> is HTML5, <object> is legacy
  1566. $output = '<video width="'.$width.'" height="'.$height.'" autoplay="" controls="" src="'.$url.'" >'."\n"
  1567. .' <object width="'.$width.'" height="'.$height.'" data="'.$url.'" type="'.Files::get_mime_type($item['file_name']).'">'."\n"
  1568. .' <param value="'.$url.'" name="movie" />'."\n"
  1569. .' <param value="true" name="allowFullScreen" />'."\n"
  1570. .' <param value="always" name="allowscriptaccess" />'."\n"
  1571. .' <a href="'.$url.'">No video playback capabilities, please download the file</a>'."\n"
  1572. .' </object>'."\n"
  1573. .'</video>'."\n";
  1574. }
  1575. // job done
  1576. return $output;
  1577. // a ganttproject timeline
  1578. case 'gan':
  1579. // where the file is
  1580. $path = Files::get_path($item['anchor']).'/'.rawurlencode($item['file_name']);
  1581. // we actually use a transformed version of the file
  1582. $cache_id = Cache::hash($path).'.xml';
  1583. // apply the transformation
  1584. if(!file_exists($context['path_to_root'].$cache_id) || (filemtime($context['path_to_root'].$cache_id) < filemtime($context['path_to_root'].$path)) || (!$text = Safe::file_get_contents($context['path_to_root'].$cache_id))) {
  1585. // transform from GanttProject to SIMILE Timeline
  1586. $text = Files::transform_gan_to_simile($path);
  1587. // put in cache
  1588. Safe::file_put_contents($cache_id, $text);
  1589. }
  1590. // load the SIMILE Timeline javascript library in shared/global.php
  1591. $context['javascript']['timeline'] = TRUE;
  1592. // cache would kill the loading of the library
  1593. cache::poison();
  1594. // 1 week ago
  1595. $now = gmdate('M d Y H:i:s', time()-7*24*60*60);
  1596. // load the right file
  1597. $output = '<div id="gantt" style="height: '.$height.'; width: '.$width.'; border: 1px solid #aaa; font-family: Trebuchet MS, Helvetica, Arial, sans serif; font-size: 8pt"></div>'."\n";
  1598. Page::insert_script(
  1599. 'var simile_handle;'."\n"
  1600. .'function onLoad() {'."\n"
  1601. .' var eventSource = new Timeline.DefaultEventSource();'."\n"
  1602. .' var theme = Timeline.ClassicTheme.create();'."\n"
  1603. .' theme.event.bubble.width = 350;'."\n"
  1604. .' theme.event.bubble.height = 300;'."\n"
  1605. .' var bandInfos = ['."\n"
  1606. .' Timeline.createBandInfo({'."\n"
  1607. .' eventSource: eventSource,'."\n"
  1608. .' date: "'.$now.'",'."\n"
  1609. .' width: "80%",'."\n"
  1610. .' intervalUnit: Timeline.DateTime.WEEK,'."\n"
  1611. .' intervalPixels: 200,'."\n"
  1612. .' theme: theme,'."\n"
  1613. .' layout: "original" // original, overview, detailed'."\n"
  1614. .' }),'."\n"
  1615. .' Timeline.createBandInfo({'."\n"
  1616. .' showEventText: false,'."\n"
  1617. .' trackHeight: 0.5,'."\n"
  1618. .' trackGap: 0.2,'."\n"
  1619. .' eventSource: eventSource,'."\n"
  1620. .' date: "'.$now.'",'."\n"
  1621. .' width: "20%",'."\n"
  1622. .' intervalUnit: Timeline.DateTime.MONTH,'."\n"
  1623. .' intervalPixels: 50'."\n"
  1624. .' })'."\n"
  1625. .' ];'."\n"
  1626. .' bandInfos[1].syncWith = 0;'."\n"
  1627. .' bandInfos[1].highlight = true;'."\n"
  1628. .' bandInfos[1].eventPainter.setLayout(bandInfos[0].eventPainter.getLayout());'."\n"
  1629. .' simile_handle = Timeline.create(document.getElementById("gantt"), bandInfos, Timeline.HORIZONTAL);'."\n"
  1630. .' simile_handle.showLoadingMessage();'."\n"
  1631. .' Timeline.loadXML("'.$context['url_to_home'].$context['url_to_root'].$cache_id.'", function(xml, url) { eventSource.loadXML(xml, url); });'."\n"
  1632. .' simile_handle.hideLoadingMessage();'."\n"
  1633. .'}'."\n"
  1634. ."\n"
  1635. .'var resizeTimerID = null;'."\n"
  1636. .'function onResize() {'."\n"
  1637. .' if (resizeTimerID == null) {'."\n"
  1638. .' resizeTimerID = window.setTimeout(function() {'."\n"
  1639. .' resizeTimerID = null;'."\n"
  1640. .' simile_handle.layout();'."\n"
  1641. .' }, 500);'."\n"
  1642. .' }'."\n"
  1643. .'}'."\n"
  1644. ."\n"
  1645. .'// observe page major events'."\n"
  1646. .'$(document).ready( onLoad);'."\n"
  1647. .'$(window).resize(onResize);'."\n"
  1648. );
  1649. // job done
  1650. return $output;
  1651. // a Freemind map
  1652. case 'mm':
  1653. // if we have an external reference, use it
  1654. if(isset($item['file_href']) && $item['file_href']) {
  1655. $target_href = $item['file_href'];
  1656. // else redirect to ourself
  1657. } else {
  1658. // ensure a valid file name
  1659. $file_name = utf8::to_ascii($item['file_name']);
  1660. // where the file is
  1661. $path = Files::get_path($item['anchor']).'/'.rawurlencode($item['file_name']);
  1662. // map the file on the regular web space
  1663. $url_prefix = $context['url_to_home'].$context['url_to_root'];
  1664. // redirect to the actual file
  1665. $target_href = $url_prefix.$path;
  1666. }
  1667. // allow several viewers to co-exist in the same page
  1668. static $freemind_viewer_index;
  1669. if(!isset($freemind_viewer_index))
  1670. $freemind_viewer_index = 1;
  1671. else
  1672. $freemind_viewer_index++;
  1673. // load flash player
  1674. $url = $context['url_to_home'].$context['url_to_root'].'included/browser/visorFreemind.swf';
  1675. // variables
  1676. $flashvars = 'initLoadFile='.$target_href.'&openUrl=_self';
  1677. $output = '<div id="freemind_viewer_'.$freemind_viewer_index.'">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  1678. Page::insert_script(
  1679. 'var params = {};'."\n"
  1680. .'params.base = "'.dirname($url).'/";'."\n"
  1681. .'params.quality = "high";'."\n"
  1682. .'params.wmode = "transparent";'."\n"
  1683. .'params.menu = "false";'."\n"
  1684. .'params.flashvars = "'.$flashvars.'";'."\n"
  1685. .'swfobject.embedSWF("'.$url.'", "freemind_viewer_'.$freemind_viewer_index.'", "'.$width.'", "'.$height.'", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", false, params);'."\n"
  1686. );
  1687. // offer to download a copy of the map
  1688. $menu = array($target_href => i18n::s('Browse this map with Freemind'));
  1689. // display menu commands below the viewer
  1690. $output .= Skin::build_list($menu, 'menu_bar');
  1691. // job done
  1692. return $output;
  1693. // native flash
  1694. case 'swf':
  1695. // where to get the file
  1696. if(isset($item['file_href']) && $item['file_href'])
  1697. $url = $item['file_href'];
  1698. // we provide the native file because of basename
  1699. else
  1700. $url = $context['url_to_home'].$context['url_to_root'].'files/'.str_replace(':', '/', $item['anchor']).'/'.rawurlencode($item['file_name']);
  1701. $output = '<div id="swf_'.$item['id'].'" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  1702. Page::insert_script(
  1703. 'var params = {};'."\n"
  1704. .'params.base = "'.dirname($url).'/";'."\n"
  1705. .'params.quality = "high";'."\n"
  1706. .'params.wmode = "transparent";'."\n"
  1707. .'params.allowfullscreen = "true";'."\n"
  1708. .'params.allowscriptaccess = "always";'."\n"
  1709. .'params.flashvars = "'.$flashvars.'";'."\n"
  1710. .'swfobject.embedSWF("'.$url.'", "swf_'.$item['id'].'", "'.$width.'", "'.$height.'", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", false, params);'."\n"
  1711. );
  1712. return $output;
  1713. // link to file page
  1714. default:
  1715. // link label
  1716. $text = Skin::strip( $item['title']?$item['title']:str_replace('_', ' ', $item['file_name']) );
  1717. // make a link to the target page
  1718. $url = Files::get_permalink($item);
  1719. // return a complete anchor
  1720. $output =& Skin::build_link($url, $text);
  1721. return $output;
  1722. }
  1723. }
  1724. /**
  1725. * escape code sequences
  1726. *
  1727. * @param string the text
  1728. * @return string the rendered text
  1729. **/
  1730. public static function &render_escaped($text) {
  1731. // replace strings --initialize only once
  1732. static $from, $to;
  1733. if(!isset($from)) {
  1734. // chars or strings to be escaped
  1735. $tags = array(
  1736. '##' => '&#35;&#35;',
  1737. '*' => '&#42;',
  1738. '+' => '&#43;',
  1739. '-' => '&#45;',
  1740. '/' => '&#47;',
  1741. ':' => '&#58;',
  1742. '=' => '&#61;',
  1743. '[' => '&#91;',
  1744. ']' => '&#93;',
  1745. '_' => '&#95;',
  1746. '<' => '&#139;' // escape HTML as well
  1747. );
  1748. // initialize only once
  1749. $from = array();
  1750. $to = array();
  1751. foreach($tags as $needle => $replace) {
  1752. $from[] = $needle;
  1753. $to[] = $replace;
  1754. }
  1755. }
  1756. // do the job
  1757. $text = str_replace($from, $to, $text);
  1758. $output = '<code>'.nl2br($text).'</code>';
  1759. return $output;
  1760. }
  1761. /**
  1762. * include some external PHP script
  1763. *
  1764. * @param string name of the script to include
  1765. * @param mixed default value, if any
  1766. * @return text generated during the inclusion
  1767. */
  1768. public static function &render_execute($name) {
  1769. global $context;
  1770. // check path to the file
  1771. while(TRUE) {
  1772. // remove leading /
  1773. if($name[0] == '/') {
  1774. $name = substr($name, 1);
  1775. continue;
  1776. }
  1777. // avoid reference to current directory
  1778. if(!strncmp($name, './', 2)) {
  1779. $name = substr($name, 2);
  1780. continue;
  1781. }
  1782. // can't go outside this instance of yacs
  1783. if(!strncmp($name, '../', 3)) {
  1784. $name = substr($name, 3);
  1785. continue;
  1786. }
  1787. break;
  1788. }
  1789. // capture the output of the next script in memory
  1790. ob_start();
  1791. // load this file, somewhere below the installation directory
  1792. include $context['path_to_root'].$name;
  1793. // retrieve all text generated by the script
  1794. $output = ob_get_contents();
  1795. ob_end_clean();
  1796. // display the text where the script was included
  1797. return $output;
  1798. }
  1799. /**
  1800. * render a graphviz
  1801. *
  1802. * @param string the text
  1803. * @param string the variant
  1804. * @return string the rendered text
  1805. **/
  1806. public static function &render_graphviz($text, $variant='digraph') {
  1807. global $context;
  1808. // sanity check
  1809. if(!$text)
  1810. $text = 'Hello->World!';
  1811. // remove tags put by WYSIWYG editors
  1812. $text = strip_tags(str_replace(array('&gt;', '&lt;', '&amp;', '&quot;', '\"'), array('>', '<', '&', '"', '"'), str_replace(array('<br />', '</p>'), "\n", $text)));
  1813. // build the .dot content
  1814. switch($variant) {
  1815. case 'digraph':
  1816. default:
  1817. $text = 'digraph G { '.$text.' }'."\n";
  1818. break;
  1819. }
  1820. // id for this object
  1821. $hash = md5($text);
  1822. // path to cached files
  1823. $path = $context['path_to_root'].'temporary/graphviz.';
  1824. // we cache content
  1825. if($content = Safe::file_get_contents($path.$hash.'.html'))
  1826. return $content;
  1827. // build a .dot file
  1828. if(!Safe::file_put_contents($path.$hash.'.dot', $text)) {
  1829. $content = '[error writing .dot file]';
  1830. return $content;
  1831. }
  1832. // process the .dot file
  1833. if(isset($context['dot.command']))
  1834. $command = $context['dot.command'];
  1835. else
  1836. $command = 'dot';
  1837. // $font = '"/System/Library/Fonts/Times.dfont"';
  1838. // $command = '/sw/bin/dot -v -Nfontname='.$font
  1839. $command .= ' -Tcmapx -o "'.$path.$hash.'.map"'
  1840. .' -Tpng -o "'.$path.$hash.'.png"'
  1841. .' "'.$path.$hash.'.dot"';
  1842. if(Safe::shell_exec($command) == NULL) {
  1843. $content = '[error while using graphviz]';
  1844. return $content;
  1845. }
  1846. // produce the HTML
  1847. $content = '<img src="'.$context['url_to_root'].'temporary/graphviz.'.$hash.'.png" usemap="#mainmap" />';
  1848. $content .= Safe::file_get_contents($path.$hash.'.map');
  1849. // put in cache
  1850. Safe::file_put_contents($path.$hash.'.html', $content);
  1851. // done
  1852. return $content;
  1853. }
  1854. /**
  1855. * render or not some text
  1856. *
  1857. * If variant = 'anonymous' and surfer is not logged, then display the block.
  1858. * If the surfer is an associate, then display the text.
  1859. * Else if the surfer is an authenticated member and variant = 'authenticated', then display the text
  1860. * Else return an empty string
  1861. *
  1862. * @param string the text
  1863. * @param either 'anonymous', or 'restricted' or 'hidden'
  1864. * @return string the rendered text
  1865. **/
  1866. public static function &render_hidden($text, $variant) {
  1867. // this block should only be visible from non-logged surfers
  1868. if($variant == 'anonymous') {
  1869. if(Surfer::is_logged())
  1870. $text = '';
  1871. return $text;
  1872. }
  1873. // associates may see everything else
  1874. if(Surfer::is_associate())
  1875. return $text;
  1876. // this block is restricted to members
  1877. if(Surfer::is_member() && ($variant == 'authenticated'))
  1878. return $text;
  1879. // tough luck
  1880. $text = '';
  1881. return $text;
  1882. }
  1883. /**
  1884. * render an iframe
  1885. *
  1886. * @param string URL to be embedded
  1887. * @param string iframe parameters
  1888. * @return string the rendered text
  1889. **/
  1890. public static function &render_iframe($url, $variant) {
  1891. global $context;
  1892. // split parameters
  1893. $attributes = preg_split("/\s*,\s*/", $variant, 2);
  1894. // set a default size
  1895. if(!isset($attributes[0]))
  1896. $attributes[0] = 320;
  1897. if(!isset($attributes[1]))
  1898. $attributes[1] = 240;
  1899. $text = '<iframe src="'.$url.'" style="width: '.$attributes[0].'px; height: '.$attributes[1].'px" scrolling="no" marginwidth="0" marginheight="0" frameborder="0" vspace="0" hspace="0">'."\n"
  1900. .i18n::s('Your browser does not accept iframes')
  1901. .'</iframe>';
  1902. return $text;
  1903. }
  1904. /**
  1905. * render a list
  1906. *
  1907. * @param string the list content
  1908. * @param string the variant, if any
  1909. * @return string the rendered text
  1910. **/
  1911. public static function &render_list($content, $variant='') {
  1912. global $context;
  1913. if(!$content = trim($content)) {
  1914. $output = NULL;
  1915. return $output;
  1916. }
  1917. // preserve existing list, if any --coming from implied beautification
  1918. if(preg_match('#^<ul>#', $content) && preg_match('#</ul>$#', $content))
  1919. $items = preg_replace(array('#^<ul>#', '#</ul>$#'), '', $content);
  1920. // split items
  1921. else {
  1922. $content = preg_replace(array("/<br \/>\n-/s", "/\n-/s", "/^-/", '/\[\*\]/'), '[*]', $content);
  1923. $items = '<li>'.join('</li><li>', preg_split("/\[\*\]/s", $content, -1, PREG_SPLIT_NO_EMPTY)).'</li>';
  1924. }
  1925. // an ordinary bulleted list
  1926. if(!$variant) {
  1927. $output = '<ul>'.$items.'</ul>';
  1928. return $output;
  1929. // style a bulleted list, but ensure it's not numbered '1 incremental'
  1930. } elseif($variant && (strlen($variant) > 1) && ($variant[1] != ' ')) {
  1931. $output = '<ul class="'.$variant.'">'.$items.'</ul>';
  1932. return $output;
  1933. }
  1934. // type has been deprecated, use styles
  1935. $style = '';
  1936. switch($variant) {
  1937. case 'a':
  1938. $style = 'style="list-style-type: lower-alpha"';
  1939. break;
  1940. case 'A':
  1941. $style = 'style="list-style-type: upper-alpha"';
  1942. break;
  1943. case 'i':
  1944. $style = 'style="list-style-type: lower-roman"';
  1945. break;
  1946. case 'I':
  1947. $style = 'style="list-style-type: upper-roman"';
  1948. break;
  1949. default:
  1950. $style = 'class="'.encode_field($variant).'"';
  1951. break;
  1952. }
  1953. // a numbered list with style
  1954. $output = '<ol '.$style.'>'.$items.'</ol>';
  1955. return $output;
  1956. }
  1957. /**
  1958. * render a location
  1959. *
  1960. * @param string the id, with possible options or variant
  1961. * @return string the rendered text
  1962. **/
  1963. public static function &render_location($id) {
  1964. global $context;
  1965. // the required library
  1966. include_once $context['path_to_root'].'locations/locations.php';
  1967. // check all args
  1968. $attributes = preg_split("/\s*,\s*/", $id, 3);
  1969. // [location=latitude, longitude, label]
  1970. if(count($attributes) === 3) {
  1971. $item = array();
  1972. $item['latitude'] = $attributes[0];
  1973. $item['longitude'] = $attributes[1];
  1974. $item['description'] = $attributes[2];
  1975. // [location=id, label] or [location=id]
  1976. } else {
  1977. $id = $attributes[0];
  1978. // a record is mandatory
  1979. if(!$item = Locations::get($id)) {
  1980. if(Surfer::is_member()) {
  1981. $output = '&#91;location='.$id.']';
  1982. return $output;
  1983. } else {
  1984. $output = '';
  1985. return $output;
  1986. }
  1987. }
  1988. // build a small dynamic image if we cannot use Google maps
  1989. if(!isset($context['google_api_key']) || !$context['google_api_key']) {
  1990. $output = BR.'<img src="'.$context['url_to_root'].'locations/map_on_earth.php?id='.$item['id'].'" width="310" height="155" alt="'.$item['geo_position'].'" />'.BR;
  1991. return $output;
  1992. }
  1993. // use provided text, if any
  1994. if(isset($attributes[1]))
  1995. $item['description'] = $attributes[1].BR.$item['description'];
  1996. }
  1997. // map on Google
  1998. $output = Locations::map_on_google(array($item));
  1999. return $output;
  2000. }
  2001. /**
  2002. * render several locations
  2003. *
  2004. * @param string 'all' or 'users'
  2005. * @return string the rendered text
  2006. **/
  2007. public static function &render_locations($id='all') {
  2008. global $context;
  2009. // the required library
  2010. include_once $context['path_to_root'].'locations/locations.php';
  2011. // get markers
  2012. $items = array();
  2013. switch($id) {
  2014. case 'all':
  2015. $items = Locations::list_by_date(0, 100, 'raw');
  2016. break;
  2017. case 'users':
  2018. $items = Locations::list_users_by_date(0, 100, 'raw');
  2019. break;
  2020. default:
  2021. if(Surfer::is_member()) {
  2022. $output = '&#91;locations='.$id.']';
  2023. return $output;
  2024. } else {
  2025. $output = '';
  2026. return $output;
  2027. }
  2028. }
  2029. // integrate with google maps
  2030. $output = Locations::map_on_google($items);
  2031. return $output;
  2032. }
  2033. /**
  2034. * render some animated news
  2035. *
  2036. * We have replaced the old fat object by a lean, clean, and valid XHTML solution.
  2037. * However, as explained by Jeffrey Zeldmann in his book "designing with web standards",
  2038. * it may happen that this way of doing don't display correctly sometimes.
  2039. *
  2040. * @param string the variant - default is 'flash'
  2041. * @return string the rendered text
  2042. **/
  2043. public static function &render_news($variant) {
  2044. global $context;
  2045. switch($variant) {
  2046. case 'flash':
  2047. // sanity check
  2048. if(!isset($context['root_flash_at_home']) || ($context['root_flash_at_home'] != 'Y'))
  2049. $text = '';
  2050. else {
  2051. $url = $context['url_to_home'].$context['url_to_root'].'feeds/flash/slashdot.php';
  2052. $flashvars = '';
  2053. $text = '<div id="local_news" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  2054. Page::insert_script(
  2055. 'var params = {};'."\n"
  2056. .'params.base = "'.dirname($url).'/";'."\n"
  2057. .'params.quality = "high";'."\n"
  2058. .'params.wmode = "transparent";'."\n"
  2059. .'params.menu = "false";'."\n"
  2060. .'params.flashvars = "'.$flashvars.'";'."\n"
  2061. .'swfobject.embedSWF("'.$url.'", "local_news", "80%", "50", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", false, params);'."\n"
  2062. );
  2063. }
  2064. return $text;
  2065. case 'dummy':
  2066. $text = 'hello world';
  2067. return $text;
  2068. default:
  2069. $text = '??'.$variant.'??';
  2070. return $text;
  2071. }
  2072. }
  2073. /**
  2074. * integrate content of a newsfeed
  2075. *
  2076. * @param string address of the newsfeed to get
  2077. * @return string the rendered text
  2078. **/
  2079. public static function &render_newsfeed($url, $variant='ajax') {
  2080. global $context;
  2081. // we allow multiple calls
  2082. static $count;
  2083. if(!isset($count))
  2084. $count = 1;
  2085. else
  2086. $count++;
  2087. switch($variant) {
  2088. case 'ajax': // asynchronous loading
  2089. default:
  2090. $text = '<div id="newsfeed_'.$count.'" class="no_print"></div>'."\n";
  2091. Page::insert_script('$(function() { Yacs.spin("newsfeed_'.$count.'"); Yacs.call( { method: \'feed.proxy\', params: { url: \''.$url.'\' }, id: 1 }, function(s) { if(s.text) { $("#newsfeed_'.$count.'").html(s.text.toString()); } else { $("#newsfeed_'.$count.'").html("***error***"); } } ) } );');
  2092. return $text;
  2093. case 'embed': // integrate newsfeed into the page
  2094. include_once $context['path_to_root'].'feeds/proxy_hook.php';
  2095. $parameters = array('url' => $url);
  2096. if($output = Proxy_hook::serve($parameters))
  2097. $text = $output['text'];
  2098. return $text;
  2099. }
  2100. }
  2101. /**
  2102. * render a link to an object
  2103. *
  2104. * Following types are supported:
  2105. * - article - link to an article page
  2106. * - category - link to a category page
  2107. * - comment - link to a comment page
  2108. * - download - link to a download page
  2109. * - file - link to a file page
  2110. * - flash - display a file as a native flash object, or play a flash video
  2111. * - sound - launch dewplayer
  2112. * - go
  2113. * - image - display an in-line image
  2114. * - next - link to an article page
  2115. * - previous - link to an article page
  2116. * - section - link to a section page
  2117. * - server - link to a server page
  2118. * - user - link to a user page
  2119. *
  2120. * @param string the type
  2121. * @param string the id, with possible options or variant
  2122. * @return string the rendered text
  2123. **/
  2124. public static function &render_object($type, $id) {
  2125. global $context;
  2126. // depending on type
  2127. switch($type) {
  2128. // link to an article
  2129. case 'article':
  2130. // maybe an alternate title has been provided
  2131. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2132. $id = $attributes[0];
  2133. // load the record from the database
  2134. if(!$item = Articles::get($id))
  2135. $output = '[article='.$id.']';
  2136. else {
  2137. // ensure we have a label for this link
  2138. if(isset($attributes[1])) {
  2139. $text = $attributes[1];
  2140. $type = 'basic';
  2141. } else
  2142. $text = Skin::strip($item['title']);
  2143. // make a link to the target page
  2144. $url = Articles::get_permalink($item);
  2145. // return a complete anchor
  2146. $output =& Skin::build_link($url, $text, $type);
  2147. }
  2148. return $output;
  2149. // insert article description
  2150. case 'article.description':
  2151. // maybe an alternate title has been provided
  2152. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2153. $id = $attributes[0];
  2154. // load the record from the database
  2155. if(!$item = Articles::get($id))
  2156. $output = '[article.description='.$id.']';
  2157. else {
  2158. // ensure we have a label for this link
  2159. if(isset($attributes[1])) {
  2160. $text = $attributes[1];
  2161. $type = 'basic';
  2162. } else
  2163. $text = Skin::strip($item['title']);
  2164. // make a link to the target page
  2165. $url = Articles::get_permalink($item);
  2166. // return a complete anchor
  2167. $output =& Skin::build_link($url, $text, 'article');
  2168. // the introduction text, if any
  2169. $output .= BR.Codes::beautify($item['introduction']);
  2170. // load overlay, if any
  2171. if(isset($item['overlay']) && $item['overlay']) {
  2172. $overlay = Overlay::load($item, 'article:'.$item['id']);
  2173. // get text related to the overlay, if any
  2174. if(is_object($overlay))
  2175. $output .= $overlay->get_text('view', $item);
  2176. }
  2177. // the description, which is the actual page body
  2178. $output .= '<div>'.Codes::beautify($item['description']).'</div>';
  2179. }
  2180. return $output;
  2181. // link to a category
  2182. case 'category':
  2183. // maybe an alternate title has been provided
  2184. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2185. $id = $attributes[0];
  2186. // load the record from the database
  2187. if(!$item = Categories::get($id))
  2188. $output = '[category='.$id.']';
  2189. else {
  2190. // ensure we have a label for this link
  2191. if(isset($attributes[1])) {
  2192. $text = $attributes[1];
  2193. $type = 'basic';
  2194. } else
  2195. $text = Skin::strip($item['title']);
  2196. // make a link to the target page
  2197. $url = Categories::get_permalink($item);
  2198. // return a complete anchor
  2199. $output =& Skin::build_link($url, $text, $type);
  2200. }
  2201. return $output;
  2202. // insert category description
  2203. case 'category.description':
  2204. // maybe an alternate title has been provided
  2205. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2206. $id = $attributes[0];
  2207. // load the record from the database
  2208. if(!$item = Categories::get($id))
  2209. $output = '[category.description='.$id.']';
  2210. else {
  2211. // ensure we have a label for this link
  2212. if(isset($attributes[1])) {
  2213. $text = $attributes[1];
  2214. $type = 'basic';
  2215. } else
  2216. $text = Skin::strip($item['title']);
  2217. // make a link to the target page
  2218. $url = Categories::get_permalink($item);
  2219. // return a complete anchor
  2220. $output =& Skin::build_link($url, $text, 'category');
  2221. // the introduction text, if any
  2222. $output .= BR.Codes::beautify($item['introduction']);
  2223. // load overlay, if any
  2224. if(isset($item['overlay']) && $item['overlay']) {
  2225. $overlay = Overlay::load($item, 'category:'.$item['id']);
  2226. // get text related to the overlay, if any
  2227. if(is_object($overlay))
  2228. $output .= $overlay->get_text('view', $item);
  2229. }
  2230. // the description, which is the actual page body
  2231. $output .= '<div>'.Codes::beautify($item['description']).'</div>';
  2232. }
  2233. return $output;
  2234. // link to a comment
  2235. case 'comment':
  2236. include_once $context['path_to_root'].'comments/comments.php';
  2237. // maybe an alternate title has been provided
  2238. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2239. $id = $attributes[0];
  2240. // load the record from the database
  2241. if(!$item = Comments::get($id))
  2242. $output = '[comment='.$id.']';
  2243. else {
  2244. // ensure we have a label for this link
  2245. if(isset($attributes[1]))
  2246. $text = $attributes[1];
  2247. else
  2248. $text = i18n::s('View this comment');
  2249. // make a link to the target page
  2250. $url = $context['url_to_home'].$context['url_to_root'].Comments::get_url($item['id']);
  2251. // return a complete anchor
  2252. $output =& Skin::build_link($url, $text, 'basic');
  2253. }
  2254. return $output;
  2255. // link to a download
  2256. case 'download':
  2257. // maybe an alternate title has been provided
  2258. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2259. $id = $attributes[0];
  2260. // load the record from the database
  2261. if(!$item = Files::get($id))
  2262. // file does not exist anymore
  2263. if((isset($attributes[1]) && $attributes[1]))
  2264. $output = $attributes[1].'<p class="details">'.i18n::s('[this file has been deleted]').'</p>';
  2265. else
  2266. $output = '[download='.$id.']';
  2267. else {
  2268. // label for this file
  2269. $prefix = $text = $suffix = '';
  2270. // signal restricted and private files
  2271. if($item['active'] == 'N')
  2272. $prefix .= PRIVATE_FLAG;
  2273. elseif($item['active'] == 'R')
  2274. $prefix .= RESTRICTED_FLAG;
  2275. // ensure we have a label for this link
  2276. if(isset($attributes[1]) && $attributes[1]) {
  2277. $text .= $attributes[1];
  2278. // this may describe a previous file, which has been replaced
  2279. if(($item['edit_action'] != 'file:create') && ($attributes[1] != $item['file_name'])) {
  2280. $text .= ' <p class="details">'.i18n::s('[this file has been replaced]').'</p>';
  2281. $output = $prefix.$text.$suffix;
  2282. return $output;
  2283. }
  2284. } else
  2285. $text = Skin::strip( $item['title']?$item['title']:str_replace('_', ' ', $item['file_name']) );
  2286. // flag files uploaded recently
  2287. if($item['create_date'] >= $context['fresh'])
  2288. $suffix .= NEW_FLAG;
  2289. elseif($item['edit_date'] >= $context['fresh'])
  2290. $suffix .= UPDATED_FLAG;
  2291. // always download the file
  2292. $url = $context['url_to_home'].$context['url_to_root'].Files::get_url($item['id'], 'fetch', $item['file_name']);
  2293. // return a complete anchor
  2294. $output = $prefix.Skin::build_link($url, $text, 'file').$suffix;
  2295. }
  2296. return $output;
  2297. // link to a file
  2298. case 'file':
  2299. // maybe an alternate title has been provided
  2300. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2301. $id = $attributes[0];
  2302. // load the record from the database --ensure we get a fresh copy of the record, not a cached one
  2303. if(!$item = Files::get($id, TRUE))
  2304. // file does not exist anymore
  2305. if((isset($attributes[1]) && $attributes[1]))
  2306. $output = $attributes[1].'<p class="details">'.i18n::s('[this file has been deleted]').'</p>';
  2307. else
  2308. $output = '[file='.$id.']';
  2309. else {
  2310. // maybe we want to illustrate this file
  2311. if((($item['edit_action'] != 'file:create') && isset($attributes[1]) && $attributes[1]) || (!$output = Files::interact($item))) {
  2312. // label for this file
  2313. $output = $prefix = $text = $suffix = '';
  2314. // signal restricted and private files
  2315. if($item['active'] == 'N')
  2316. $prefix .= PRIVATE_FLAG;
  2317. elseif($item['active'] == 'R')
  2318. $prefix .= RESTRICTED_FLAG;
  2319. // ensure we have a label for this link
  2320. if(isset($attributes[1]) && $attributes[1]) {
  2321. $text .= $attributes[1];
  2322. // this may describe a previous file, which has been replaced
  2323. if(($item['edit_action'] != 'file:create') && ($attributes[1] != $item['file_name'])) {
  2324. $text .= '<p class="details">'.i18n::s('[this file has been replaced]').'</p>';
  2325. $output = $prefix.$text.$suffix;
  2326. return $output;
  2327. }
  2328. } else
  2329. $text .= Skin::strip( $item['title']?$item['title']:str_replace('_', ' ', $item['file_name']) );
  2330. // flag files uploaded recently
  2331. if($item['create_date'] >= $context['fresh'])
  2332. $suffix .= NEW_FLAG;
  2333. elseif($item['edit_date'] >= $context['fresh'])
  2334. $suffix .= UPDATED_FLAG;
  2335. // make a link to the target page
  2336. $url = Files::get_download_url($item);
  2337. // return a complete anchor
  2338. $output .= $prefix.Skin::build_link($url, $text, 'basic').$suffix;
  2339. }
  2340. }
  2341. return $output;
  2342. // invoke the selector
  2343. case 'go':
  2344. // extract the label, if any
  2345. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2346. $name = $attributes[0];
  2347. // ensure we have a label for this link
  2348. if(isset($attributes[1]))
  2349. $text = $attributes[1];
  2350. else
  2351. $text = $name;
  2352. // return a complete anchor
  2353. $output = Skin::build_link($context['url_to_home'].$context['url_to_root'].normalize_shortcut($name), $text, 'basic');
  2354. return $output;
  2355. // embed an image
  2356. case 'image':
  2357. include_once $context['path_to_root'].'images/images.php';
  2358. // get the variant, if any
  2359. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2360. $id = $attributes[0];
  2361. if(isset($attributes[1]))
  2362. $variant = $attributes[1];
  2363. else
  2364. $variant = 'inline';
  2365. // get the image record
  2366. if(!$image = Images::get($id)) {
  2367. $output = '[image='.$id.']';
  2368. return $output;
  2369. }
  2370. // a title for the image --do not force a title
  2371. if(isset($image['title']))
  2372. $title = $image['title'];
  2373. else
  2374. $title = '';
  2375. // provide thumbnail if not defined, or forced, or for large images
  2376. if(!$image['use_thumbnail']
  2377. || ($image['use_thumbnail'] == 'A')
  2378. || (($image['use_thumbnail'] == 'Y') && ($image['image_size'] > $context['thumbnail_threshold'])) ) {
  2379. // not inline anymore, but thumbnail --preserve other variants
  2380. if($variant == 'inline')
  2381. $variant = 'thumbnail';
  2382. // where to fetch the image file
  2383. $href = Images::get_thumbnail_href($image);
  2384. // to drive to plain image
  2385. $link = Images::get_icon_href($image);
  2386. // add an url, if any
  2387. } elseif($image['link_url']) {
  2388. // flag large images
  2389. if($image['image_size'] > $context['thumbnail_threshold'])
  2390. $variant = rtrim('large '.$variant);
  2391. // where to fetch the image file
  2392. $href = Images::get_icon_href($image);
  2393. // transform local references, if any
  2394. include_once $context['path_to_root'].'/links/links.php';
  2395. $attributes = Links::transform_reference($image['link_url']);
  2396. if($attributes[0])
  2397. $link = $context['url_to_root'].$attributes[0];
  2398. // direct use of this link
  2399. else
  2400. $link = $image['link_url'];
  2401. // get the <img ... /> element
  2402. } else {
  2403. // do not append poor titles to inline images
  2404. if($variant == 'inline')
  2405. $title = '';
  2406. // flag large images
  2407. if($image['image_size'] > $context['thumbnail_threshold'])
  2408. $variant = rtrim('large '.$variant);
  2409. // where to fetch the image file
  2410. $href = Images::get_icon_href($image);
  2411. // no link
  2412. $link = '';
  2413. }
  2414. // use the skin
  2415. if(Images::allow_modification($image['anchor'],$id))
  2416. // build editable image
  2417. $output =& Skin::build_image($variant, $href, $title, $link, $id);
  2418. else
  2419. $output =& Skin::build_image($variant, $href, $title, $link);
  2420. return $output;
  2421. // embed a stack of images
  2422. case 'images':
  2423. include_once $context['path_to_root'].'images/images.php';
  2424. // get the list of ids
  2425. $ids = preg_split("/\s*,\s*/", $id);
  2426. if(!count($ids)) {
  2427. $output = '[images=id1, id2, ...]';
  2428. return $output;
  2429. }
  2430. // build the list of images
  2431. $items = array();
  2432. foreach($ids as $id) {
  2433. // get the image record
  2434. if($image = Images::get($id)) {
  2435. // a title for the image --do not force a title
  2436. if(isset($image['title']))
  2437. $title = $image['title'];
  2438. else
  2439. $title = '';
  2440. // provide thumbnail if not defined, or forced, or for large images
  2441. $variant = 'inline';
  2442. if(!$image['use_thumbnail']
  2443. || ($image['use_thumbnail'] == 'A')
  2444. || (($image['use_thumbnail'] == 'Y') && ($image['image_size'] > $context['thumbnail_threshold'])) ) {
  2445. // not inline anymore, but thumbnail
  2446. $variant = 'thumbnail';
  2447. // where to fetch the image file
  2448. $href = Images::get_thumbnail_href($image);
  2449. // to drive to plain image
  2450. $link = $context['url_to_root'].Images::get_url($id);
  2451. // add an url, if any
  2452. } elseif($image['link_url']) {
  2453. // flag large images
  2454. if($image['image_size'] > $context['thumbnail_threshold'])
  2455. $variant = rtrim('large '.$variant);
  2456. // where to fetch the image file
  2457. $href = Images::get_icon_href($image);
  2458. // transform local references, if any
  2459. include_once $context['path_to_root'].'/links/links.php';
  2460. $attributes = Links::transform_reference($image['link_url']);
  2461. if($attributes[0])
  2462. $link = $context['url_to_root'].$attributes[0];
  2463. // direct use of this link
  2464. else
  2465. $link = $image['link_url'];
  2466. // get the <img ... /> element
  2467. } else {
  2468. // flag large images
  2469. if($image['image_size'] > $context['thumbnail_threshold'])
  2470. $variant = rtrim('large '.$variant);
  2471. // where to fetch the image file
  2472. $href = Images::get_icon_href($image);
  2473. // no link
  2474. $link = '';
  2475. }
  2476. // use the skin
  2477. $label =& Skin::build_image($variant, $href, $title, $link);
  2478. // add item to the stack
  2479. $items[] = $label;
  2480. }
  2481. }
  2482. // format the list
  2483. $output = '';
  2484. if(count($items)) {
  2485. // stack items
  2486. $output = Skin::finalize_list($items, 'stack');
  2487. // rotate items
  2488. $output = Skin::rotate($output);
  2489. }
  2490. // done
  2491. return $output;
  2492. // link to the next article
  2493. case 'next':
  2494. // maybe an alternate title has been provided
  2495. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2496. $id = $attributes[0];
  2497. // load the record from the database
  2498. if(!$item = Articles::get($id))
  2499. $output = '[next='.$id.']';
  2500. else {
  2501. // ensure we have a label for this link
  2502. if(isset($attributes[1]))
  2503. $text = $attributes[1];
  2504. else
  2505. $text = Skin::strip($item['title']);
  2506. // make a link to the target page
  2507. $url = Articles::get_permalink($item);
  2508. // return a complete anchor
  2509. $output =& Skin::build_link($url, $text, 'next');
  2510. }
  2511. return $output;
  2512. // link to the previous article
  2513. case 'previous':
  2514. // maybe an alternate title has been provided
  2515. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2516. $id = $attributes[0];
  2517. // load the record from the database
  2518. if(!$item = Articles::get($id))
  2519. $output = '[previous='.$id.']';
  2520. else {
  2521. // ensure we have a label for this link
  2522. if(isset($attributes[1]))
  2523. $text = $attributes[1];
  2524. else
  2525. $text = Skin::strip($item['title']);
  2526. // make a link to the target page
  2527. $url = Articles::get_permalink($item);
  2528. // return a complete anchor
  2529. $output =& Skin::build_link($url, $text, 'previous');
  2530. }
  2531. return $output;
  2532. // link to a section
  2533. case 'section':
  2534. // maybe an alternate title has been provided
  2535. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2536. $id = $attributes[0];
  2537. // load the record from the database
  2538. if(!$item = Sections::get($id))
  2539. $output = '[section='.$id.']';
  2540. else {
  2541. // ensure we have a label for this link
  2542. if(isset($attributes[1])) {
  2543. $text = $attributes[1];
  2544. $type = 'basic';
  2545. } else
  2546. $text = Skin::strip($item['title']);
  2547. // make a link to the target page
  2548. $url = Sections::get_permalink($item);
  2549. // return a complete anchor
  2550. $output =& Skin::build_link($url, $text, $type);
  2551. }
  2552. return $output;
  2553. // link to a server
  2554. case 'server':
  2555. include_once $context['path_to_root'].'servers/servers.php';
  2556. // maybe an alternate title has been provided
  2557. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2558. $id = $attributes[0];
  2559. // load the record from the database
  2560. if(!$item = Servers::get($id))
  2561. $output = '[server='.$id.']';
  2562. else {
  2563. // ensure we have a label for this link
  2564. if(isset($attributes[1])) {
  2565. $text = $attributes[1];
  2566. $type = 'basic';
  2567. } else
  2568. $text = Skin::strip($item['title']);
  2569. // make a link to the target page
  2570. $url = $context['url_to_home'].$context['url_to_root'].Servers::get_url($id);
  2571. // return a complete anchor
  2572. $output =& Skin::build_link($url, $text, $type);
  2573. }
  2574. return $output;
  2575. // render a sound object
  2576. case 'sound':
  2577. // maybe an alternate title has been provided
  2578. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2579. $id = $attributes[0];
  2580. $flashvars = '';
  2581. if(isset($attributes[1]))
  2582. $flashvars = $attributes[1];
  2583. // get the file
  2584. if(!$item = Files::get($id)) {
  2585. $output = '[sound='.$id.']';
  2586. return $output;
  2587. }
  2588. // where to get the file
  2589. if(isset($item['file_href']) && $item['file_href'])
  2590. $url = $item['file_href'];
  2591. else
  2592. $url = $context['url_to_home'].$context['url_to_root'].'files/'.str_replace(':', '/', $item['anchor']).'/'.rawurlencode($item['file_name']);
  2593. // several ways to play flash
  2594. switch(strtolower(substr(strrchr($url, '.'), 1))) {
  2595. // stream a sound file
  2596. case 'mp3':
  2597. // a flash player to stream a sound
  2598. $dewplayer_url = $context['url_to_root'].'included/browser/dewplayer.swf';
  2599. if($flashvars)
  2600. $flashvars = 'son='.$url.'&'.$flashvars;
  2601. else
  2602. $flashvars = 'son='.$url;
  2603. $output = '<div id="sound_'.$item['id'].'" class="no_print">Flash plugin or Javascript are turned off. Activate both and reload to view the object</div>'."\n";
  2604. Page::insert_script(
  2605. 'var params = {};'."\n"
  2606. .'params.base = "'.dirname($url).'/";'."\n"
  2607. .'params.quality = "high";'."\n"
  2608. .'params.wmode = "transparent";'."\n"
  2609. .'params.menu = "false";'."\n"
  2610. .'params.flashvars = "'.$flashvars.'";'."\n"
  2611. .'swfobject.embedSWF("'.$dewplayer_url.'", "sound_'.$item['id'].'", "200", "20", "6", "'.$context['url_to_home'].$context['url_to_root'].'included/browser/expressinstall.swf", false, params);'."\n"
  2612. );
  2613. return $output;
  2614. // link to file page
  2615. default:
  2616. // link label
  2617. $text = Skin::strip( $item['title']?$item['title']:str_replace('_', ' ', $item['file_name']) );
  2618. // make a link to the target page
  2619. $url = Files::get_download_url($item);
  2620. // return a complete anchor
  2621. $output =& Skin::build_link($url, $text, 'basic');
  2622. return $output;
  2623. }
  2624. // link to a user
  2625. case 'user':
  2626. // maybe an alternate title has been provided
  2627. $attributes = preg_split("/\s*,\s*/", $id, 2);
  2628. $id = $attributes[0];
  2629. // load the record from the database
  2630. if(!$item = Users::get($id))
  2631. $output = '[user='.$id.']';
  2632. else {
  2633. // ensure we have a label for this link
  2634. if(isset($attributes[1])) {
  2635. $text = $attributes[1];
  2636. $type = 'basic';
  2637. } elseif(isset($item['full_name']) && $item['full_name'])
  2638. $text = ucfirst($item['full_name']);
  2639. else
  2640. $text = ucfirst($item['nick_name']);
  2641. // make a link to the target page
  2642. $url = Users::get_permalink($item);
  2643. // return a complete anchor
  2644. $output =& Skin::build_link($url, $text, $type);
  2645. }
  2646. return $output;
  2647. // invalid type
  2648. default:
  2649. $output = '['.$type.']';
  2650. return $output;
  2651. }
  2652. }
  2653. /**
  2654. * get the value of one global parameter
  2655. *
  2656. * Parameter is taken from the global $context array, and its name has to start with
  2657. * prefix 'page_', or 'site_', for obvious security reasons.
  2658. *
  2659. * @param string name of the parameter
  2660. * @param mixed default value, if any
  2661. * @return the actual value of this parameter, else the default value, else ''
  2662. */
  2663. public static function &render_parameter($name, $default='') {
  2664. global $context;
  2665. if(!strncmp($name, 'page_', 5) && isset($context[$name])) {
  2666. $output =& $context[$name];
  2667. return $output;
  2668. }
  2669. if(!strncmp($name, 'site_', 5) && isset($context[$name])) {
  2670. $output =& $context[$name];
  2671. return $output;
  2672. }
  2673. $output = $default;
  2674. return $output;
  2675. }
  2676. /**
  2677. * render a block of code
  2678. *
  2679. * @param string the text
  2680. * @return string the rendered text
  2681. **/
  2682. public static function &render_pre($text, $variant='snippet') {
  2683. // change new lines
  2684. $text = trim(str_replace("\r", '', str_replace(array("<br>\n", "<br/>\n", "<br />\n", '<br>', '<br/>', '<br />'), "\n", $text)));
  2685. // caught from tinymce
  2686. if(preg_match('/<p>(.*)<\/p>$/s', $text, $matches)) {
  2687. $text = $matches[1];
  2688. $text = str_replace(array('&amp;', '<p>', '</p>'), array('&', '', "\n"), $text);
  2689. }
  2690. // match some php code
  2691. $explicit = FALSE;
  2692. if(preg_match('/<\?php\s/', $text))
  2693. $variant = 'php';
  2694. elseif(($variant == 'php') && !preg_match('/<\?'.'php.+'.'\?'.'>/', $text)) {
  2695. $text = '<?'.'php'."\n".$text."\n".'?'.'>';
  2696. $explicit = TRUE;
  2697. }
  2698. // highlight php code, if any
  2699. if($variant == 'php') {
  2700. // fix some chars set by wysiwig editors
  2701. $text = str_replace(array('&lt;', '&gt;', '&nbsp;', '&quot;'), array('<', '>', ' ', '"'), $text);
  2702. // wrap long lines if necessary
  2703. // $lines = explode("\n", $text);
  2704. // $text = '';
  2705. // foreach($lines as $line)
  2706. // $text .= wordwrap($line, 100, " \n", 0)."\n";
  2707. // handle newlines and indentations properly
  2708. $text = str_replace(array("\n<span", "\n</code", "\n</pre", "\n</span"), array('<span', '</code', '</pre', '</span'), Safe::highlight_string($text));
  2709. // remove explicit php prefix and suffix -- dependant of highlight_string() evolution
  2710. if($explicit)
  2711. $text = preg_replace(array('/&lt;\?php<br\s*\/>/', '/\?&gt;/'), '', $text);
  2712. // or prevent html rendering
  2713. } else
  2714. $text = str_replace(array('<', "\n"), array('&lt;', '<br/>'), $text);
  2715. // prevent additional transformations
  2716. $search = array( '[', ']', ':', '//', '##', '**', '++', '--', '__');
  2717. $replace = array( '&#91;', '&#93;', '&#58;', '&#47;&#47;', '&#35;&#35;', '&#42;&#42;', '&#43;&#43;', '&#45;&#45;', '&#95;&#95;');
  2718. $output = '<pre>'.str_replace($search, $replace, $text).'</pre>';
  2719. return $output;
  2720. }
  2721. /**
  2722. * render a compact list of recent publications
  2723. *
  2724. * The provided anchor can reference:
  2725. * - a section 'section:123'
  2726. * - a category 'category:456'
  2727. * - a user 'user:789'
  2728. * - 'self'
  2729. * - nothing
  2730. *
  2731. * @param string the anchor (e.g. 'section:123')
  2732. * @param string layout to use
  2733. * @return string the rendered text
  2734. **/
  2735. public static function &render_published($anchor='', $layout='compact') {
  2736. global $context;
  2737. // we return some text;
  2738. $text = '';
  2739. // number of items to display
  2740. $count = COMPACT_LIST_SIZE;
  2741. if($position = strrpos($anchor, ',')) {
  2742. $count = (integer)trim(substr($anchor, $position+1));
  2743. if(!$count)
  2744. $count = COMPACT_LIST_SIZE;
  2745. $anchor = trim(substr($anchor, 0, $position));
  2746. }
  2747. // scope is limited to current surfer
  2748. if(($anchor == 'self') && Surfer::get_id()) {
  2749. $anchor = 'user:'.Surfer::get_id();
  2750. // refresh on every page load
  2751. Cache::poison();
  2752. }
  2753. // scope is limited to one section
  2754. if(strpos($anchor, 'section:') === 0) {
  2755. // look at this branch of the content tree
  2756. $anchors = Sections::get_branch_at_anchor($anchor);
  2757. // query the database and layout that stuff
  2758. $text =& Articles::list_for_anchor_by('publication', $anchors, 0, $count, $layout);
  2759. // scope is limited to one category
  2760. } elseif(strpos($anchor, 'category:') === 0) {
  2761. // first level of depth
  2762. $anchors = array();
  2763. // get sections linked to this category
  2764. if($topics =& Members::list_sections_by_title_for_anchor($anchor, 0, 50, 'raw')) {
  2765. foreach($topics as $id => $not_used)
  2766. $anchors = array_merge($anchors, array('section:'.$id));
  2767. }
  2768. // second level of depth
  2769. if(count($topics) && (count($anchors) < 2000)) {
  2770. $topics = Sections::get_children_of_anchor($anchors);
  2771. $anchors = array_merge($anchors, $topics);
  2772. }
  2773. // third level of depth
  2774. if(count($topics) && (count($anchors) < 2000)) {
  2775. $topics = Sections::get_children_of_anchor($anchors);
  2776. $anchors = array_merge($anchors, $topics);
  2777. }
  2778. // fourth level of depth
  2779. if(count($topics) && (count($anchors) < 2000)) {
  2780. $topics = Sections::get_children_of_anchor($anchors);
  2781. $anchors = array_merge($anchors, $topics);
  2782. }
  2783. // fifth level of depth
  2784. if(count($topics) && (count($anchors) < 2000)) {
  2785. $topics = Sections::get_children_of_anchor($anchors);
  2786. $anchors = array_merge($anchors, $topics);
  2787. }
  2788. // the category itself is an anchor
  2789. $anchors[] = $anchor;
  2790. // ensure anchors are referenced only once
  2791. $anchors = array_unique($anchors);
  2792. // query the database and layout that stuff
  2793. $text =& Members::list_articles_by_date_for_anchor($anchors, 0, $count, $layout);
  2794. // scope is limited to one author
  2795. } elseif(strpos($anchor, 'user:') === 0)
  2796. $text =& Articles::list_for_author_by('publication', str_replace('user:', '', $anchor), 0, $count, $layout);
  2797. // consider all pages
  2798. else
  2799. $text =& Articles::list_by('publication', 0, $count, $layout);
  2800. // we have an array to format
  2801. if(is_array($text))
  2802. $text =& Skin::build_list($text, $layout);
  2803. // job done
  2804. return $text;
  2805. }
  2806. /**
  2807. * select a random page
  2808. *
  2809. * The provided anchor can reference:
  2810. * - a section 'section:123'
  2811. * - a category 'category:456'
  2812. * - a user 'user:789'
  2813. * - 'self'
  2814. * - nothing
  2815. *
  2816. * @param string the anchor (e.g. 'section:123')
  2817. * @param string layout to use
  2818. * @return string the rendered text
  2819. **/
  2820. public static function &render_random($anchor='', $layout='') {
  2821. global $context;
  2822. // we return some text;
  2823. $text = '';
  2824. // label is explicit
  2825. $label = '';
  2826. if($position = strrpos($anchor, ',')) {
  2827. $label = trim(substr($anchor, $position+1));
  2828. $anchor = trim(substr($anchor, 0, $position));
  2829. }
  2830. // scope is limited to current surfer
  2831. if(($anchor == 'self') && Surfer::get_id()) {
  2832. $anchor = 'user:'.Surfer::get_id();
  2833. // refresh on every page load
  2834. Cache::poison();
  2835. }
  2836. // scope is limited to one section
  2837. if(!strncmp($anchor, 'section:', 8)) {
  2838. // look at this branch of the content tree
  2839. $anchors = Sections::get_branch_at_anchor($anchor);
  2840. // query the database and layout that stuff
  2841. $items =& Articles::list_for_anchor_by('random', $anchors, 0, 1, 'raw');
  2842. // scope is limited to one author
  2843. } elseif(!strncmp($anchor, 'user:', 5))
  2844. $items =& Articles::list_for_author_by('random', str_replace('user:', '', $anchor), 0, 1, 'raw');
  2845. // consider all pages
  2846. else
  2847. $items =& Articles::list_by('random', 0, 1, 'raw');
  2848. // we have an array to format
  2849. if($items) {
  2850. foreach($items as $id => $item) {
  2851. // make a link to the target page
  2852. $link = Articles::get_permalink($item);
  2853. if(!$label)
  2854. $label = Skin::strip($item['title']);
  2855. $text =& Skin::build_link($link, $label, 'article');
  2856. if($layout == 'description') {
  2857. // the introduction text, if any
  2858. $text .= BR.Codes::beautify($item['introduction']);
  2859. // load overlay, if any
  2860. if(isset($item['overlay']) && $item['overlay']) {
  2861. $overlay = Overlay::load($item, 'article:'.$item['id']);
  2862. // get text related to the overlay, if any
  2863. if(is_object($overlay))
  2864. $text .= $overlay->get_text('view', $item);
  2865. }
  2866. // the description, which is the actual page body
  2867. $text .= '<div>'.Codes::beautify($item['description']).'</div>';
  2868. }
  2869. // we take only one item
  2870. break;
  2871. }
  2872. }
  2873. // job done
  2874. return $text;
  2875. }
  2876. /**
  2877. * render a compact list of hits
  2878. *
  2879. * @param string the anchor (e.g. 'section:123')
  2880. * @param string layout to use
  2881. * @return string the rendered text
  2882. **/
  2883. public static function &render_read($anchor='', $layout='hits') {
  2884. global $context;
  2885. // we return some text;
  2886. $text = '';
  2887. // number of items to display
  2888. $count = COMPACT_LIST_SIZE;
  2889. if(($position = strpos($anchor, ',')) !== FALSE) {
  2890. $count = (integer)trim(substr($anchor, $position+1));
  2891. if(!$count)
  2892. $count = COMPACT_LIST_SIZE;
  2893. $anchor = trim(substr($anchor, 0, $position));
  2894. }
  2895. // scope is limited to current surfer
  2896. if(($anchor == 'self') && Surfer::get_id()) {
  2897. $anchor = 'user:'.Surfer::get_id();
  2898. // refresh on every page load
  2899. Cache::poison();
  2900. }
  2901. // scope is limited to one section
  2902. if(strpos($anchor, 'section:') === 0) {
  2903. // look at this branch of the content tree
  2904. $anchors = Sections::get_branch_at_anchor($anchor);
  2905. // query the database and layout that stuff
  2906. $text =& Articles::list_for_anchor_by('hits', $anchors, 0, $count, $layout);
  2907. // scope is limited to pages of one surfer
  2908. } elseif(strpos($anchor, 'user:') === 0)
  2909. $text =& Articles::list_for_user_by('hits', substr($anchor, 5), 0, $count, $layout);
  2910. // consider all pages
  2911. if(!$text)
  2912. $text =& Articles::list_by('hits', 0, $count, $layout);
  2913. // we have an array to format
  2914. if(is_array($text))
  2915. $text =& Skin::build_list($text, $layout);
  2916. // job done
  2917. return $text;
  2918. }
  2919. /**
  2920. * redirect dynamically from this page to any local web address
  2921. *
  2922. * This is typically useful to have a regular yacs page redirected to a specific PHP script.
  2923. *
  2924. * @param string target link
  2925. * @return text generated during the inclusion
  2926. */
  2927. public static function &render_redirect($link) {
  2928. global $context;
  2929. // turn external links to clickable things
  2930. if(preg_match('/^(ftp:|http:|https:|www\.)/i', $link)) {
  2931. $output = '<p>'.Skin::build_link($link).'</p>';
  2932. return $output;
  2933. }
  2934. // only while viewing real pages
  2935. if(isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] != 'GET')) {
  2936. $output = '<p>'.Skin::build_link($link).'</p>';
  2937. return $output;
  2938. }
  2939. // check path to the file
  2940. while(TRUE) {
  2941. // remove leading /
  2942. if($link[0] == '/') {
  2943. $link = substr($link, 1);
  2944. continue;
  2945. }
  2946. // avoid reference to current directory
  2947. if(!strncmp($link, './', 2)) {
  2948. $link = substr($link, 2);
  2949. continue;
  2950. }
  2951. // can't go outside this instance of yacs
  2952. if(!strncmp($link, '../', 3)) {
  2953. $link = substr($link, 3);
  2954. continue;
  2955. }
  2956. break;
  2957. }
  2958. // forward to the target page
  2959. Safe::redirect($context['url_to_home'].$context['url_to_root'].$link);
  2960. }
  2961. /**
  2962. * render tweetmeme button
  2963. *
  2964. * @return string the rendered text
  2965. **/
  2966. public static function &render_retweet() {
  2967. global $context;
  2968. // we return some text --$context['self_url'] already has $context['url_to_root'] in it
  2969. Page::insert_script('tweetmeme_url = "'.$context['url_to_home'].$context['self_url'].'";');
  2970. Page::defer_script("http://tweetmeme.com/i/scripts/button.js");
  2971. // job done
  2972. return $text;
  2973. }
  2974. /**
  2975. * render a list of sections
  2976. *
  2977. * The provided anchor can reference:
  2978. * - a section 'section:123'
  2979. * - a user 'user:789'
  2980. * - 'self'
  2981. * - nothing
  2982. *
  2983. * @param string the anchor (e.g. 'section:123')
  2984. * @param string layout to use
  2985. * @return string the rendered text
  2986. **/
  2987. public static function &render_sections($anchor='', $layout='simple') {
  2988. global $context;
  2989. // we return some text;
  2990. $text = '';
  2991. // number of items to display
  2992. $count = YAHOO_LIST_SIZE;
  2993. if(($position = strpos($anchor, ',')) !== FALSE) {
  2994. $count = (integer)trim(substr($anchor, $position+1));
  2995. if(!$count)
  2996. $count = YAHOO_LIST_SIZE;
  2997. $anchor = trim(substr($anchor, 0, $position));
  2998. }
  2999. // scope is limited to current surfer
  3000. if(($anchor == 'self') && Surfer::get_id()) {
  3001. $anchor = 'user:'.Surfer::get_id();
  3002. // refresh on every page load
  3003. Cache::poison();
  3004. }
  3005. // scope is limited to one section
  3006. if(strpos($anchor, 'section:') === 0)
  3007. $text =& Sections::list_by_title_for_anchor($anchor, 0, $count, $layout);
  3008. // scope is limited to one author
  3009. elseif(strpos($anchor, 'user:') === 0)
  3010. $text =& Members::list_sections_by_title_for_anchor($anchor, 0, $count, $layout);
  3011. // consider all pages
  3012. else
  3013. $text =& Sections::list_by_title_for_anchor(NULL, 0, $count, $layout);
  3014. // we have an array to format
  3015. if(is_array($text))
  3016. $text =& Skin::build_list($text, $layout);
  3017. // job done
  3018. return $text;
  3019. }
  3020. /**
  3021. * render a table
  3022. *
  3023. * @param string the table content
  3024. * @param string the variant, if any
  3025. * @return string the rendered text
  3026. **/
  3027. public static function render_static_table($content, $variant='') {
  3028. global $context;
  3029. // we are providing inline tables
  3030. if($variant)
  3031. $variant = 'inline '.$variant;
  3032. else
  3033. $variant = 'inline';
  3034. // do we have headers to proceed?
  3035. $in_body = !preg_match('/\[body\]/i', $content);
  3036. // start at first line, except if headers have to be printed first
  3037. if($in_body)
  3038. $count = 1;
  3039. else
  3040. $count = 2;
  3041. // split lines
  3042. $rows = explode("\n", $content);
  3043. if(!is_array($rows))
  3044. return '';
  3045. // one row per line - cells are separated by |, \t, or 2 spaces
  3046. $text =& Skin::table_prefix($variant);
  3047. foreach($rows as $row) {
  3048. // skip blank lines
  3049. if(!$row)
  3050. continue;
  3051. // header row
  3052. if(!$in_body) {
  3053. if(preg_match('/\[body\]/i', $row))
  3054. $in_body = true;
  3055. else
  3056. $text .= Skin::table_row(preg_split("/([\|\t]| "." )/", $row), 'header');
  3057. // body row
  3058. } else
  3059. $text .= Skin::table_row(preg_split("/([\|\t]| "." )/", $row), $count++);
  3060. }
  3061. // return the complete table
  3062. $text .= Skin::table_suffix();
  3063. return $text;
  3064. }
  3065. /**
  3066. * render a table of links
  3067. *
  3068. * @param string the variant
  3069. * @return string the rendered text
  3070. **/
  3071. public static function &render_table_of($variant) {
  3072. global $context;
  3073. // nothing to return yet
  3074. $output = '';
  3075. // list of questions for a FAQ
  3076. if($variant == 'questions') {
  3077. // only if the table is not empty
  3078. global $codes_toq;
  3079. if(isset($codes_toq) && $codes_toq) {
  3080. // to be rendered by css, using selector .toq_box ul, etc.
  3081. $text = '<ul>'."\n";
  3082. foreach($codes_toq as $link)
  3083. $text .= '<li>'.$link.'</li>'."\n";
  3084. $text .= '</ul>'."\n";
  3085. $output =& Skin::build_box('', $text, 'toq');
  3086. }
  3087. // list of titles
  3088. } else {
  3089. // only if the table is not empty
  3090. global $codes_toc;
  3091. if(isset($codes_toc) && $codes_toc) {
  3092. // to be rendered by css, using selector .toc_box ul, etc.
  3093. // <ul>
  3094. // <li>1. link</li> 0 -> 1
  3095. // <li>1. link 1 -> 1
  3096. // <ul>
  3097. // <li>2. link</li> 1 -> 2
  3098. // <li>2. link</li> 2 -> 2
  3099. // </ul></li>
  3100. // <li>1. link 2 -> 1
  3101. // <ul>
  3102. // <li>2. link</li> 1 -> 2
  3103. // <li>2. link</li> 2 -> 2
  3104. // </ul></li>
  3105. // </ul>
  3106. $text ='';
  3107. $previous_level = 0;
  3108. foreach($codes_toc as $attributes) {
  3109. list($level, $link) = $attributes;
  3110. if($previous_level == $level)
  3111. $text .= '</li>'."\n";
  3112. else {
  3113. if($previous_level < $level) {
  3114. $text .= '<ul>';
  3115. $previous_level++;
  3116. while($previous_level < $level) {
  3117. $text .= '<li><ul>'."\n";
  3118. $previous_level++;
  3119. }
  3120. }
  3121. if($previous_level > $level) {
  3122. $text .= '</li>';
  3123. while($previous_level > $level) {
  3124. $text .= '</ul></li>'."\n";
  3125. $previous_level--;
  3126. }
  3127. }
  3128. }
  3129. $text .= '<li>'.$link;
  3130. }
  3131. if($previous_level > 0) {
  3132. $text .= '</li>';
  3133. while($previous_level > 0) {
  3134. if($previous_level > 1)
  3135. $text .= '</ul></li>'."\n";
  3136. else
  3137. $text .= '</ul>'."\n";
  3138. $previous_level--;
  3139. }
  3140. }
  3141. $output =& Skin::build_box('', $text, 'toc');
  3142. }
  3143. }
  3144. return $output;
  3145. }
  3146. /**
  3147. * render a title, a sub-title, or a question
  3148. *
  3149. * @param string the text
  3150. * @param string the variant
  3151. * @return string the rendered text
  3152. **/
  3153. public static function &render_title($text, $variant) {
  3154. global $codes_toc, $codes_toq, $context;
  3155. // remember questions
  3156. if($variant == 'question') {
  3157. $index = count($codes_toq)+1;
  3158. $id = 'question_'.$index;
  3159. $url = $context['self_url'].'#'.$id;
  3160. $codes_toq[] = Skin::build_link($url, ucfirst($text), 'basic');
  3161. $text = QUESTION_FLAG.$text;
  3162. // remember level 1 titles ([title]...[/title] or [header1]...[/header1])
  3163. } elseif($variant == 'header1') {
  3164. $index = count($codes_toc)+1;
  3165. $id = 'title_'.$index;
  3166. $url = $context['self_url'].'#'.$id;
  3167. $codes_toc[] = array(1, Skin::build_link($url, ucfirst($text), 'basic'));
  3168. // remember level 2 titles ([subtitle]...[/subtitle] or [header2]...[/header2])
  3169. } elseif($variant == 'header2') {
  3170. $index = count($codes_toc)+1;
  3171. $id = 'title_'.$index;
  3172. $url = $context['self_url'].'#'.$id;
  3173. $codes_toc[] = array(2, Skin::build_link($url, ucfirst($text), 'basic'));
  3174. // remember level 3 titles
  3175. } elseif($variant == 'header3') {
  3176. $index = count($codes_toc)+1;
  3177. $id = 'title_'.$index;
  3178. $url = $context['self_url'].'#'.$id;
  3179. $codes_toc[] = array(3, Skin::build_link($url, ucfirst($text), 'basic'));
  3180. // remember level 4 titles
  3181. } elseif($variant == 'header4') {
  3182. $index = count($codes_toc)+1;
  3183. $id = 'title_'.$index;
  3184. $url = $context['self_url'].'#'.$id;
  3185. $codes_toc[] = array(4, Skin::build_link($url, ucfirst($text), 'basic'));
  3186. // remember level 5 titles
  3187. } elseif($variant == 'header5') {
  3188. $index = count($codes_toc)+1;
  3189. $id = 'title_'.$index;
  3190. $url = $context['self_url'].'#'.$id;
  3191. $codes_toc[] = array(5, Skin::build_link($url, ucfirst($text), 'basic'));
  3192. }
  3193. // the rendered text
  3194. $output =& Skin::build_block($text, $variant, $id);
  3195. return $output;
  3196. }
  3197. /**
  3198. * render twitter profile
  3199. *
  3200. * @param string twitter id to display, plus optional parameters, if any
  3201. * @return string the rendered text
  3202. **/
  3203. public static function &render_twitter($id) {
  3204. global $context;
  3205. // up to 4 parameters: id, width, height, styles
  3206. $attributes = preg_split("/\s*,\s*/", $id, 4);
  3207. $id = $attributes[0];
  3208. // width
  3209. if(isset($attributes[1]))
  3210. $width = $attributes[1];
  3211. else
  3212. $width = 250;
  3213. // height
  3214. if(isset($attributes[2]))
  3215. $height = $attributes[2];
  3216. else
  3217. $height = 300;
  3218. // theme
  3219. if(isset($attributes[3]))
  3220. $theme = $attributes[3];
  3221. else
  3222. $theme = 'theme: { shell: {'."\n"
  3223. .' background: "#3082af",'."\n"
  3224. .' color: "#ffffff"'."\n"
  3225. .' },'."\n"
  3226. .' tweets: {'."\n"
  3227. .' background: "#ffffff",'."\n"
  3228. .' color: "#444444",'."\n"
  3229. .' links: "#1985b5"'."\n"
  3230. .' }}';
  3231. // allow multiple widgets
  3232. static $count;
  3233. if(!isset($count))
  3234. $count = 1;
  3235. else
  3236. $count++;
  3237. // we return some text --$context['self_url'] already has $context['url_to_root'] in it
  3238. $text = '<div id="twitter_'.$count.'"></div>'."\n"
  3239. .'<script type="text/javascript">'."\n"
  3240. .'$(function() { $("#twitter_'.$count.'").liveTwitter("'.$id.'", {mode: "user_timeline"}); });'."\n"
  3241. .'</script>';
  3242. // job done
  3243. return $text;
  3244. }
  3245. /**
  3246. * render twitter search
  3247. *
  3248. * @param string twitter searched keywords, plus optional parameters, if any
  3249. * @return string the rendered text
  3250. **/
  3251. public static function &render_twitter_search($id) {
  3252. global $context;
  3253. // up to 4 parameters: id, width, height, styles
  3254. $attributes = preg_split("/\s*,\s*/", $id, 4);
  3255. $id = $attributes[0];
  3256. // width
  3257. if(isset($attributes[1]))
  3258. $width = $attributes[1];
  3259. else
  3260. $width = 250;
  3261. // height
  3262. if(isset($attributes[2]))
  3263. $height = $attributes[2];
  3264. else
  3265. $height = 300;
  3266. // allow multiple widgets
  3267. static $count;
  3268. if(!isset($count))
  3269. $count = 1;
  3270. else
  3271. $count++;
  3272. // $context['self_url'] already has $context['url_to_root'] in it
  3273. $text = '<div id="tsearch_'.$count.'"></div>'."\n"
  3274. .'<script type="text/javascript">'."\n"
  3275. .'$(function() { $("#tsearch_'.$count.'").liveTwitter("'.str_replace('"', '', $id).'"); });'."\n"
  3276. .'</script>';
  3277. // job done
  3278. return $text;
  3279. }
  3280. /**
  3281. * render a compact list of recent modifications
  3282. *
  3283. * The provided anchor can reference:
  3284. * - a section 'section:123'
  3285. * - a category 'category:456'
  3286. * - a user 'user:789'
  3287. * - 'self'
  3288. * - nothing
  3289. *
  3290. * @param string the anchor (e.g. 'section:123')
  3291. * @param string layout to use
  3292. * @return string the rendered text
  3293. **/
  3294. public static function &render_updated($anchor='', $layout='compact') {
  3295. global $context;
  3296. // we return some text;
  3297. $text = '';
  3298. // number of items to display
  3299. $count = COMPACT_LIST_SIZE;
  3300. if(($position = strpos($anchor, ',')) !== FALSE) {
  3301. $count = (integer)trim(substr($anchor, $position+1));
  3302. if(!$count)
  3303. $count = COMPACT_LIST_SIZE;
  3304. $anchor = trim(substr($anchor, 0, $position));
  3305. }
  3306. // scope is limited to current surfer
  3307. if(($anchor == 'self') && Surfer::get_id()) {
  3308. $anchor = 'user:'.Surfer::get_id();
  3309. // refresh on every page load
  3310. Cache::poison();
  3311. }
  3312. // scope is limited to one section
  3313. if(strpos($anchor, 'section:') === 0) {
  3314. // look at this branch of the content tree
  3315. $anchors = Sections::get_branch_at_anchor($anchor);
  3316. // query the database and layout that stuff
  3317. $text =& Articles::list_for_anchor_by('edition', $anchors, 0, $count, $layout);
  3318. // scope is limited to one category
  3319. } elseif(strpos($anchor, 'category:') === 0) {
  3320. // first level of depth
  3321. $anchors = array();
  3322. // get sections linked to this category
  3323. if($topics =& Members::list_sections_by_title_for_anchor($anchor, 0, 50, 'raw')) {
  3324. foreach($topics as $id => $not_used)
  3325. $anchors = array_merge($anchors, array('section:'.$id));
  3326. }
  3327. // second level of depth
  3328. if(count($topics) && (count($anchors) < 2000)) {
  3329. $topics = Sections::get_children_of_anchor($anchors);
  3330. $anchors = array_merge($anchors, $topics);
  3331. }
  3332. // third level of depth
  3333. if(count($topics) && (count($anchors) < 2000)) {
  3334. $topics = Sections::get_children_of_anchor($anchors);
  3335. $anchors = array_merge($anchors, $topics);
  3336. }
  3337. // fourth level of depth
  3338. if(count($topics) && (count($anchors) < 2000)) {
  3339. $topics = Sections::get_children_of_anchor($anchors);
  3340. $anchors = array_merge($anchors, $topics);
  3341. }
  3342. // fifth level of depth
  3343. if(count($topics) && (count($anchors) < 2000)) {
  3344. $topics = Sections::get_children_of_anchor($anchors);
  3345. $anchors = array_merge($anchors, $topics);
  3346. }
  3347. // the category itself is an anchor
  3348. $anchors[] = $anchor;
  3349. // ensure anchors are referenced only once
  3350. $anchors = array_unique($anchors);
  3351. // query the database and layout that stuff
  3352. $text =& Members::list_articles_by_date_for_anchor($anchors, 0, $count, $layout);
  3353. // scope is limited to pages of one surfer
  3354. } elseif(strpos($anchor, 'user:') === 0)
  3355. $text =& Articles::list_for_user_by('edition', substr($anchor, 5), 0, $count, $layout);
  3356. // consider all pages
  3357. else
  3358. $text =& Articles::list_by('edition', 0, $count, $layout);
  3359. // we have an array to format
  3360. if(is_array($text))
  3361. $text =& Skin::build_list($text, $layout);
  3362. // job done
  3363. return $text;
  3364. }
  3365. /**
  3366. * render a compact list of users present on site
  3367. *
  3368. * @param string the anchor (e.g. 'present')
  3369. * @return string the rendered text
  3370. **/
  3371. public static function &render_users($anchor='') {
  3372. global $context;
  3373. // we return some text;
  3374. $text = '';
  3375. // number of items to display
  3376. $count = COMPACT_LIST_SIZE;
  3377. if(($position = strpos($anchor, ',')) !== FALSE) {
  3378. $count = (integer)trim(substr($anchor, $position+1));
  3379. if(!$count)
  3380. $count = COMPACT_LIST_SIZE;
  3381. $anchor = trim(substr($anchor, 0, $position));
  3382. }
  3383. // the list of users present on the site
  3384. $text = Users::list_present(0, $count, 'compact');
  3385. // also mention the total number of users present, if larger than the number of users displayed
  3386. $stat = Users::stat_present();
  3387. if($stat['count'] > $count)
  3388. $text = array_merge($text, array('_' => sprintf(i18n::ns('%d active now', '%d active now', $stat['count']), $stat['count'])));
  3389. // we have an array to format
  3390. if(is_array($text))
  3391. $text =& Skin::build_list($text, 'compact');
  3392. // job done
  3393. return $text;
  3394. }
  3395. /**
  3396. * render a compact list of voted pages
  3397. *
  3398. * @param string the anchor (e.g. 'section:123')
  3399. * @param string layout to use
  3400. * @return string the rendered text
  3401. **/
  3402. public static function &render_voted($anchor='', $layout='simple') {
  3403. global $context;
  3404. // we return some text;
  3405. $text = '';
  3406. // number of items to display
  3407. $count = COMPACT_LIST_SIZE;
  3408. if(($position = strpos($anchor, ',')) !== FALSE) {
  3409. $count = (integer)trim(substr($anchor, $position+1));
  3410. if(!$count)
  3411. $count = COMPACT_LIST_SIZE;
  3412. $anchor = trim(substr($anchor, 0, $position));
  3413. }
  3414. // scope is limited to current surfer
  3415. if(($anchor == 'self') && Surfer::get_id()) {
  3416. $anchor = 'user:'.Surfer::get_id();
  3417. // refresh on every page load
  3418. Cache::poison();
  3419. }
  3420. // scope is limited to one section
  3421. if(strpos($anchor, 'section:') === 0) {
  3422. // look at this branch of the content tree
  3423. $anchors = Sections::get_branch_at_anchor($anchor);
  3424. // query the database and layout that stuff
  3425. $text =& Articles::list_for_anchor_by('rating', $anchors, 0, $count, $layout);
  3426. // scope is limited to pages of one surfer
  3427. } elseif(strpos($anchor, 'user:') === 0)
  3428. $text =& Articles::list_for_user_by('rating', substr($anchor, 5), 0, $count, $layout);
  3429. // consider all pages
  3430. else
  3431. $text =& Articles::list_by('rating', 0, $count, $layout);
  3432. // we have an array to format
  3433. if(is_array($text))
  3434. $text =& Skin::build_list($text, $layout);
  3435. // job done
  3436. return $text;
  3437. }
  3438. /**
  3439. * render a link to Wikipedia
  3440. *
  3441. * @param string the id, with possible options or variant
  3442. * @return string the rendered text
  3443. **/
  3444. public static function &render_wikipedia($id) {
  3445. global $context;
  3446. // maybe an alternate title has been provided
  3447. $attributes = preg_split("/\s*,\s*/", $id, 2);
  3448. $id = $attributes[0];
  3449. // ensure we have a label for this link
  3450. if(isset($attributes[1]))
  3451. $text = $attributes[1];
  3452. else
  3453. $text = '';
  3454. // select the language
  3455. $language = $context['preferred_language'];
  3456. // take the navigator language if possible
  3457. if (isset($context['language']) && $context['without_language_detection']=='N')
  3458. $language = $context['language'];
  3459. // make a link to the target page
  3460. $url = 'http://'.$language.'.wikipedia.org/wiki/Special:Search?search='.preg_replace('[\s]', '_', $id);
  3461. // return a complete anchor
  3462. $output =& Skin::build_link($url, $text, 'wikipedia');
  3463. return $output;
  3464. }
  3465. /**
  3466. * remove YACS codes from a string
  3467. *
  3468. * @param string embedding YACS codes
  3469. * @param boolean FALSE to remove only only pairing codes, TRUE otherwise
  3470. * @return a purged string
  3471. */
  3472. public static function &strip($text, $suppress_all_brackets=TRUE) {
  3473. global $context;
  3474. // suppress pairing codes
  3475. $output = preg_replace('#\[(\w+?)[^\]]*\](.*?)\[\/\1\]#s', '${2}', $text);
  3476. // suppress bracketed words
  3477. if($suppress_all_brackets)
  3478. $output = trim(preg_replace('/\[(.+?)\]/s', ' ', $output));
  3479. return $output;
  3480. }
  3481. }
  3482. ?>