xht.lib.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. <?php /* <!-- xht.lib.php -->
  2. <!-- XML HTML Templates, 2006/12/14 -->
  3. <!-- Copyright (C) 2006 rene.haentjens@UGent.be - see note at end of text -->
  4. <!-- Released under the GNU GPL V2, see http://www.gnu.org/licenses/gpl.html -->
  5. */
  6. /**
  7. ==============================================================================
  8. * This is an XML HTML template library.
  9. * Include/require it in your code to use its functionality.
  10. *
  11. * This library defines function xht_htmlwchars & class xhtdoc with methods:
  12. * - xht_fill_template($template_name)
  13. * - xht_substitute($subtext)
  14. *
  15. * Check htt_error after defining a new xhtdoc(htt_file_contents).
  16. *
  17. * Assign xht_xmldoc (, xht_get_lang, xht_resource, xht_dbgn)
  18. * before calling the class methods.
  19. *
  20. * @package dokeos.library
  21. ==============================================================================
  22. */
  23. function xht_htmlwchars($s) // use only where ISO-8859-1 is not required!
  24. {
  25. //return ereg_replace('\[((/?(b|big|i|small|sub|sup|u))|br/)\]', '<\\1>',
  26. // str_replace('@�@', '&',
  27. // htmlspecialchars(ereg_replace('&#([0-9]+);', '@�@#\\1;', $s))));
  28. global $charset;
  29. return api_ereg_replace('\[((/?(b|big|i|small|sub|sup|u))|br/)\]', '<\\1>',
  30. str_replace('@�@', '&',
  31. htmlspecialchars(api_ereg_replace('&#([0-9]+);', '@�@#\\1;', $s), ENT_QUOTES, $charset)));
  32. // replaces htmlspecialchars for double-escaped xml chars like '&amp;#nnn;'
  33. // and when html tags <...> are represented as [...]
  34. }
  35. function xht_is_assoclist($s) // ":key1:value1,, key2:value2,, ..."
  36. {
  37. return is_string($s) && strpos($s, ',,');
  38. }
  39. function xht_explode_assoclist($s)
  40. {
  41. $result = array(); if (!xht_is_assoclist($s)) return $result;
  42. foreach (explode(',,', api_substr($s, 1)) as $keyplusvalue)
  43. if ($cp = api_strpos($keyplusvalue, api_substr($s, 0, 1)))
  44. $result[trim(api_substr($keyplusvalue, 0, $cp))] =
  45. api_substr($keyplusvalue, $cp+1);
  46. return $result;
  47. }
  48. class xhtdoc
  49. {
  50. var $htt_array; // array with HTML templates
  51. var $htt_error; // error while parsing htt_file_contents to templates
  52. var $xht_xmldoc; // XML Mini-DOM document for which templates are processed
  53. var $xht_param; // parameter array for template processing
  54. var $xht_get_lang; // user-supplied function for {-L xx-}, e.g. Dokeos get_lang
  55. var $xht_resource; // user-supplied function for '=/' in path
  56. var $xht_dbgn; // set to a positive value for debugging output
  57. var $xht_dbgo; // debugging output accumulates here
  58. var $_prev_param; // old parameter values
  59. function xht_fill_template($template_name, $cur_elem = 0)
  60. {
  61. $template_name = trim($template_name);
  62. if (!$template_name || (api_strpos($template_name, ' ') !== FALSE)) return '';
  63. if (!is_string($httext = $this->htt_array[$template_name])) return '';
  64. if ($this->xht_dbgn) $this->xht_dbgo .= '<!-- ' . XHT_LP . $template_name .
  65. ' ' . XHT_RP . $this->_show_param() . " -->\n";
  66. $prev_lpp = 0; $prev_sub = ''; $scanpos = 0;
  67. while (($rpp = api_strpos($httext, XHT_RP, $scanpos)) !== FALSE) // first -}
  68. {
  69. if (($lpp = api_strpos($httext, XHT_LP)) === FALSE) break; // no {- for -}
  70. if ($lpp > $rpp) break; // no {- preceding -}
  71. while (($next_lpp = api_strpos($httext, XHT_LP, $lpp + XHT_PL)) !== FALSE)
  72. if ($next_lpp > $rpp) break;
  73. else $lpp = $next_lpp; // find {- closest to -}
  74. $subtext = api_substr($httext, $lpp + XHT_PL, $rpp - $lpp - XHT_PL);
  75. $httext = api_substr($httext, 0, $lpp) .
  76. $this->xht_substitute($subtext, $cur_elem, XHT_LP, XHT_RP) .
  77. api_substr($httext, $rpp + XHT_PL); // substitute or leave intact
  78. if ($lpp == $prev_lpp && $subtext == $prev_sub) // prevent looping
  79. {
  80. $scanpos = $rpp + 1; $prev_lpp = 0; $prev_sub = '';
  81. }
  82. else
  83. {
  84. $prev_lpp = $lpp; $prev_sub = $subtext;
  85. }
  86. }
  87. return $httext;
  88. }
  89. function xht_substitute($subtext, $cur_elem = 0, $pre = '', $post = '')
  90. {
  91. global $charset;
  92. $regs = array(); // for use with ereg()
  93. if (!api_ereg(XHT_SUBS2, $subtext, $regs) && !api_ereg(XHT_SUBS1, $subtext, $regs))
  94. return $pre . $subtext . $post;
  95. $type = $regs[1]; $text = $regs[2]; $result = ''; $subnum = FALSE;
  96. $subtext = isset($regs[3]) ? $regs[3] : '';
  97. if ($this->xht_dbgn) // generate debugging output, with new number $subnum
  98. {
  99. $subnum = ++ $this->xht_dbgn;
  100. $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum . '+ ' .
  101. htmlspecialchars($text, ENT_QUOTES, $charset) . ' ' . XHT_RP .
  102. $this->_show_param() . " -->\n";
  103. }
  104. if ($type == 'D') // Define, e.g. {-D key value-}
  105. {
  106. // Assign the value to parameter [key]
  107. $this->xht_param[$text] = $subtext;
  108. // used to be: = $this->xht_substitute($subtext, $cur_elem);
  109. }
  110. elseif ($type == 'E') // Escape, e.g. {-E userFunction subtext-}
  111. {
  112. $result = call_user_func($text, FALSE); // get cached result, if any
  113. if ($result === FALSE) // no cached result available
  114. {
  115. $result = $this->xht_substitute($subtext, $cur_elem);
  116. $result = call_user_func($text, $result); // allow to cache
  117. }
  118. }
  119. elseif ($type == 'R') // Repeat, e.g. {-R general/title/string subtext-}
  120. {
  121. $rdepthn = 'rdepth' . (++ $this->xht_param['rdepth']);
  122. $n = 0; $this->xht_param['number'] = '0';
  123. if (is_array($a = $this->_lang($text, $cur_elem))) // repeat on array
  124. {
  125. foreach ($a as $key => $value)
  126. {
  127. $this->xht_param['number'] = (string) (++ $n);
  128. $this->xht_param[$rdepthn] = (string) ($n);
  129. $this->xht_param['key'] = $key;
  130. $this->xht_param['value'] = $value;
  131. $result .= $this->xht_substitute($subtext, $cur_elem);
  132. }
  133. }
  134. elseif (xht_is_assoclist($a)) // repeat on associative list
  135. {
  136. foreach (xht_explode_assoclist($a) as $key => $value)
  137. {
  138. $this->xht_param['number'] = (string) (++ $n);
  139. $this->xht_param[$rdepthn] = (string) ($n);
  140. $this->xht_param['key'] = $key;
  141. $this->xht_param['value'] = $value;
  142. $result .= $this->xht_substitute($subtext, $cur_elem);
  143. }
  144. }
  145. elseif (!is_object($this->xht_xmldoc))
  146. {
  147. $result = '? R Error: no XML doc has been assigned to xht_xmldoc';
  148. }
  149. else // repeat on XML elements
  150. {
  151. $sub_elems =
  152. $this->xht_xmldoc->xmd_select_elements($text, $cur_elem);
  153. foreach ($sub_elems as $subElem)
  154. {
  155. $this->xht_param['number'] = (string) (++ $n);
  156. $this->xht_param[$rdepthn] = (string) ($n);
  157. $result .= $this->xht_substitute($subtext, $subElem);
  158. }
  159. }
  160. // removed 2004/10/05: template_array (security)
  161. // added 2005/03/08: associative list (lang arrays deprecated)
  162. $this->xht_param['rdepth'] --;
  163. // As there is only one ['number'] or one set ['key'] + ['value'],
  164. // using them in nested repeats may not have the desired result.
  165. }
  166. elseif ($type == 'T') // Test, e.g. {-T key1 == key2 text-}
  167. {
  168. if (api_ereg('^(=|==|<=|<|!=|<>|>|>=) +([^ ]+) +(.*)$', $subtext, $regs))
  169. {
  170. // Comparand= parameter value if set, else languagevar value
  171. $cmp1 = isset($this->xht_param[$text]) ?
  172. $this->xht_param[$text] : $this->_lang($text, $cur_elem);
  173. $cmp3 = isset($this->xht_param[$cmp3 = $regs[2]]) ?
  174. $this->xht_param[$cmp3] : $this->_lang($cmp3, $cur_elem);
  175. $cmp = strcmp($cmp1, $cmp3); $op = ' ' . $regs[1] . ' ';
  176. if ($subnum) $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum .
  177. ' ' . htmlspecialchars($cmp1.$op.$cmp3.' = '.$cmp, ENT_QUOTES, $charset) .
  178. ' ' . XHT_RP . " -->\n"; // debugging output
  179. if ( ($cmp < 0 && api_strpos(' <= < != <> ', $op)) ||
  180. ($cmp == 0 && api_strpos(' = == <= >= ', $op)) ||
  181. ($cmp > 0 && api_strpos(' != <> > >= ', $op)) )
  182. $result = $this->xht_substitute($regs[3], $cur_elem);
  183. // else $result is empty
  184. }
  185. else
  186. {
  187. $result = $pre . $subtext . $post;
  188. }
  189. }
  190. else
  191. {
  192. if (api_strpos('CLPVX', $type) !== FALSE) // used to be always
  193. $text = $this->xht_substitute($text, $cur_elem); // nested escape
  194. if ($type == 'C') // Call, e.g. {-C SUBTEMPLATE-}
  195. $result = $this->xht_fill_template($text, $cur_elem);
  196. elseif ($type == 'H') $result = htmlspecialchars($text, ENT_QUOTES, $charset);
  197. elseif ($type == 'L') $result = $this->_lang($text, $cur_elem);
  198. elseif ($type == 'P') $result = $this->xht_param[$text];
  199. elseif ($type == 'U') $result = urlencode($text);
  200. elseif ($type == 'W') $result = xht_htmlwchars($text);
  201. elseif (!is_object($this->xht_xmldoc))
  202. {
  203. $result = '? V/X Error: no XML doc has been assigned to xht_xmldoc';
  204. }
  205. else // $type == 'V' or 'X'
  206. {
  207. if (api_ereg('^(.*)=/(.+)$', $text, $regs)) // special resource-marker
  208. {
  209. $path = $regs[1]; $text = $regs[2];
  210. if (api_substr($path, -1) == '/') $path = api_substr($path, 0, -1);
  211. if ($path) $cur_elem = $this->xht_xmldoc->
  212. xmd_select_single_element($path, $cur_elem);
  213. $cur_elem = call_user_func($this->xht_resource, $cur_elem);
  214. }
  215. $result = ($type == 'V') ?
  216. $this->xht_xmldoc->xmd_value($text, $cur_elem) :
  217. $this->xht_xmldoc->
  218. xmd_html_value($text, $cur_elem, 'xht_htmlwchars');
  219. }
  220. }
  221. if ($subnum) $this->xht_dbgo .= '<!-- ' . XHT_LP . $type . $subnum . '- ' .
  222. htmlspecialchars((api_strlen($result) <= 60) ?
  223. $result . ' ': api_substr($result, 0, 57).'...', ENT_QUOTES, $charset) . XHT_RP . " -->\n";
  224. return $result;
  225. }
  226. function xht_add_template($template_name, $httext)
  227. {
  228. $this->htt_array[$template_name] = $httext;
  229. // removed 2004/10/05: (substr($httext, 0, 6) == 'array(') ?
  230. // removed 2004/10/05: @eval('return ' . $httext . ';') : $httext;
  231. if (!$this->htt_array[$template_name])
  232. {
  233. $this->htt_error = 'template ' . $template_name .
  234. ' is empty or invalid'; return;
  235. }
  236. }
  237. function xhtdoc($htt_file_contents)
  238. {
  239. $htt_file_contents = // normalize \r (Mac) and \r\n (Windows) to \n
  240. str_replace("\r", "\n", str_replace("\r\n", "\n", $htt_file_contents));
  241. while (api_substr($htt_file_contents, -1) == "\n")
  242. $htt_file_contents = api_substr($htt_file_contents, 0, -1);
  243. $last_line = api_strrchr($htt_file_contents, "\n"); $this->htt_error = '';
  244. if (api_strlen($last_line) < 12 || api_substr($last_line, 0, 6) != "\n<!-- "
  245. || api_strlen($last_line) % 2 != 0 || api_substr($last_line, -4) != " -->")
  246. {
  247. $this->htt_error = 'last line must be of the form <!-- {--} -->';
  248. return;
  249. }
  250. define('XHT_PL', (int) (api_strlen($last_line) - 10) / 2); // Parentheses Lth
  251. define('XHT_LP', api_substr($last_line, 6, XHT_PL)); // Left Par
  252. define('XHT_RP', api_substr($last_line, 6 + XHT_PL, XHT_PL)); // Right Par
  253. if (XHT_LP == XHT_RP)
  254. {
  255. $this->htt_error =
  256. 'parentheses in last line <!-- {--} --> must not be identical';
  257. return;
  258. }
  259. $this->htt_array = array(); // named elements are arrays and strings
  260. foreach (explode("\n<!-- " . XHT_LP, "\n" . $htt_file_contents)
  261. as $name_and_text)
  262. {
  263. if (($name_length = api_strpos($name_and_text, XHT_RP . " -->\n")))
  264. {
  265. $template_name = trim(api_substr($name_and_text, 0, $name_length));
  266. if (api_strpos($template_name, ' ') !== FALSE) give_up('Template ' .
  267. $template_name . ' has a space in its name');
  268. $httext = api_substr($name_and_text, $name_length + XHT_PL + 5);
  269. while (api_substr($httext, 0, 1) == "\n") $httext = api_substr($httext, 1);
  270. while (api_substr($httext, -1) == "\n") $httext = api_substr($httext,0,-1);
  271. $this->xht_add_template($template_name, $httext);
  272. }
  273. }
  274. define('XHT_SUBS1', '^(C|H|L|P|U|V|W|X) +(.*)$'); // substitution types 1:
  275. // Call, Htmlchars, Lang, Param, Urlencode, Value, Wchars, Xvalue
  276. define('XHT_SUBS2', '^(D|E|R|T) +([^ ]+) +(.*)$'); // substitution types 2:
  277. // Define, Escape, Repeat, Test
  278. $this->xht_dbgo = '';
  279. $this->xht_param = array(0 => '0', 1 => '1',
  280. '' => '', 'empty' => '', 'rdepth' => 0);
  281. $this->_prev_param = $this->xht_param;
  282. // empty: {-R * P empty-} puts the number of subelements in {-P number-}
  283. // rdepth: current number of nested R's
  284. // rdepth1, rdepth2, ...: key or number, see R
  285. }
  286. function _show_param() // for debugging info
  287. {
  288. global $charset;
  289. $result = '';
  290. foreach ($this->xht_param as $k => $v)
  291. if ($v !== $this->_prev_param[$k])
  292. {
  293. $this->_prev_param[$k] = $v; $result .= ', ' . $k . ': ' .
  294. ((api_strlen($v) <= 20) ? $v : api_substr($v, 0, 17).'...');
  295. }
  296. return $result ? htmlspecialchars(api_substr($result, 1), ENT_QUOTES, $charset) : '';
  297. }
  298. function _lang($var, $cur_elem = 0)
  299. {
  300. $result = @call_user_func($this->xht_get_lang, $var, $cur_elem);
  301. // This is a hack for proper working of the language selectors
  302. // in the forms that are generated through templates.
  303. if ($var == 'Langs')
  304. {
  305. global $langLangs;
  306. if (isset($langLangs))
  307. {
  308. $result = $langLangs;
  309. }
  310. }
  311. return isset($result) ? $result : $var;
  312. }
  313. }
  314. /*
  315. A word of explanation...
  316. The last line of a template file (example below) defines the special markers
  317. that are used around template names such as INPUT and OPTION and around HTML
  318. escapes such as 'P value' and 'L Store', { and } in the example. You can also
  319. use markers of more than one character as long as both have the same length,
  320. e.g. <% and %>. The markers must not be equal. In templates with JavaScript
  321. or <style>, the use of { and } might be confusing; however, it does work.
  322. A template starts with a special comment line giving the name of the template
  323. and ends where the next template starts.
  324. Templates contain escapes of the form {one-letter the-rest}, e.g.
  325. {L IdentifierTip}, where the-rest can contain a nested-escape as in e.g.
  326. {R metadata/lom/general/keyword C KEYWORDTEMPLATE}.
  327. Multiple spaces count as one in many places, but not in all. Best is to use
  328. correct spacing. In particular, a T escape such as the example below requires
  329. a space at the end of the first line:
  330. {T x == y
  331. text
  332. }
  333. Names (of templates, parameters, langvars and user-functions)
  334. are case sensitive and cannot contain spaces.
  335. Templates are called with a certain currency in the XML doc, usually the root
  336. element; for an xml-path repeat, the found element is the current one.
  337. One single array with named elements and string values contains the
  338. template parameters; they are shared for all template processing.
  339. Initially, params contains
  340. 0 => '0', 1 => '1', '' => '', 'empty' => '', 'rdepth' => 0.
  341. rdepth keeps track of the current number of nested repeats. In a repeat,
  342. params number, key, value and rdepthN (N is 1, 2, ...) get assigned values.
  343. Types of escapes:
  344. C template-name/nested-escape Call a sub-template
  345. D parameter-name text Define a parameter value
  346. E user-function nested-escape Escape to user-function or do nested-escape
  347. H text HtmlSpecialChars (e.g. transform < to &lt;)
  348. L languagevar/nested-escape Language variable value (see also below)
  349. P parameter-name/nested-escape Parameter value
  350. R repeat-part nested-escape Repeat nested-escape for each ... (see below)
  351. T key1 operator key2 nested-esc Test, do nested-esc if test succeeds (id.)
  352. U text UrlEncode (e.g. transform < to %XX)
  353. V xml-path/nested-escape Value from XML document element or attribute
  354. W text xht_htmlwchars (see below)
  355. X extended-xml-path/nested-esc eXtended Value from XML (see below)
  356. nested-escape= without the special markers. Nesting with special markers is
  357. always possible and in that case the inner nesting is evaluated first.
  358. Note that as from the 2005/03/15 version, some implicit nestings are no
  359. longer possible, e.g. {H {P key}} and {D label {L Keyword}} now require
  360. the inner markers.
  361. Escape allows the caller to cache template output:
  362. user-function is called with parameter (FALSE);
  363. if it returns a string, that string is the result;
  364. if it returns FALSE, nested-escape is executed to produce a result
  365. and user-function is called again with the result, allowing it
  366. to cache the result somewhere; user-function then returns the real result.
  367. repeat-part can be:
  368. the name of a languagevar which contains an associative list such as:
  369. ":key1:value1,, key2:value2,, key3:value3" (note double ,,):
  370. nested-escape is repeated for each list element,
  371. params 'key' and 'value' refer to the current element;
  372. first character defines key-value-separator (here a colon);
  373. key cannot contain key-value-separators and is trimmed;
  374. value can contain anything except ,, and it is not trimmed;
  375. put ',,' at the end of a 0- or 1-element-list.
  376. the name of a languagevar which has an array value:
  377. nested-escape is repeated for each array element,
  378. params 'key' and 'value' refer to the current element.
  379. an xml-path to 0, 1 or more elements in the XML document:
  380. nested-escape is repeated for each element, param 'number' = 1, 2, ...
  381. test operators compare strings: = == <= < != <> > >=
  382. key1 and key2 can be parameter-name or languagevar-name.
  383. xml-path: see XML Mini-DOM; examples:
  384. organizations/@default, body/p[1] (1=first), keyword/*, node/*[-3]
  385. / * /... starts from XML document root, regardless of context
  386. other extensions: .. - + @* @. (parent, prev, next, number, name)
  387. .. stops at the root if too many
  388. -name, +name and @*name means only elements of that name
  389. xht_htmlwchars is like HtmlSpecialChars, but moreover translates:
  390. [b] to <b>, likewise for big, i, small, sub, sup, u, br, and closing tags;
  391. &amp;#nnn; to &#nnn; (double-escape for non-UTF8-channels)
  392. eXtended Value: see method xmd_html_value of the XML Mini-DOM;
  393. the values (but not the pre-, in- and suffixes) are always W-processed;
  394. extended-xml-path examples:
  395. 'keyword/string ,' generates e.g. 'kwd1,kwd2,kwd3'
  396. 'keyword/string , ' generates e.g. 'kwd1, kwd2, kwd3'
  397. '( -% keyword/string , %- )' generates e.g. '(kwd1,kwd2,kwd3)'
  398. but will generate nothing if no keywords are found in the XML doc;
  399. note that the special markers ' -% ' and ' %- ' must have the spaces.
  400. V and X escapes can have an xml-path containing '=/': this calls a user-supplied
  401. function for finding an associated element. It can e.g. be used when reading
  402. a SCORM manifest, for finding the <resource> of an <item>.
  403. L also calls a user-supplied function. Its main target is language-dependent
  404. texts, e.g. Dokeos 'get_lang'. But the current element is passed as second
  405. argument, allowing other functionality in the callback.
  406. Array templates functionality has been removed on 2004/10/05, because of a
  407. security issue when users can submit templates.
  408. --------------------------------------------------------------------------------
  409. Example template file:
  410. <!-- {HEAD} -->
  411. <style type="text/css">
  412. body {font-family: Arial}
  413. </style>
  414. <!-- {METADATA} -->
  415. <h3>{H {L Tool}: {P entry}}</h3>
  416. <table>
  417. <tr>
  418. <td>{D label {L Language}}{D tip {L LanguageTip}}{C LABEL}</td>
  419. <td><select>{D thislang {V general/language}}{R Langs C OPTION}</select></td>
  420. <td><input type="text" disabled value="urn:ugent-be:minerva."/></td>
  421. </tr>
  422. {R general/keyword C KEYWORD}
  423. </table>
  424. <!---------------------- E-n-d Of script Output ---------------------->
  425. <!-- {LABEL} -->
  426. <span title="{H {P tip}}">{H {P label}}&#xa0;:</span>
  427. <!-- {OPTION} -->
  428. <option value="{H {P key}}" {T key == thislang selected}>{H {P value}}</option>
  429. <!-- {INPUT} -->
  430. <input type="text" title="{P title}" class="wide" value="{H {P value}}"/>
  431. <!-- {KEYWORD} -->
  432. <tr>
  433. <td>{D label {L Keyword}}{D tip {L KeywordTip}}{C LABEL}</td>
  434. <td nowrap><select>{D thislang {V string/@language}}{R Langs C OPTION}</select></td>
  435. <td>{D value {X string}}{D title general/keyword[{P number}]/string}{C INPUT}</td>
  436. </tr>
  437. <!-- {} -->
  438. --------------------------------------------------------------------------------
  439. <!--
  440. This program is free software; you can redistribute it and/or
  441. modify it under the terms of the GNU General Public License
  442. as published by the Free Software Foundation; either version 2
  443. of the License, or (at your option) any later version.
  444. This program is distributed in the hope that it will be useful,
  445. but WITHOUT ANY WARRANTY; without even the implied warranty of
  446. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  447. GNU General Public License for more details.
  448. -->
  449. */
  450. ?>