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

/shared/codes.php

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