OSDN Git Service

BugTrack/2431 Topicpath title by JavaScript
[pukiwiki/pukiwiki.git] / lib / html.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // html.php
4 // Copyright
5 //   2002-2017 PukiWiki Development Team
6 //   2001-2002 Originally written by yu-ji
7 // License: GPL v2 or (at your option) any later version
8 //
9 // HTML-publishing related functions
10
11 // Show page-content
12 function catbody($title, $page, $body)
13 {
14         global $vars, $arg, $defaultpage, $whatsnew, $help_page, $hr;
15         global $attach_link, $related_link, $cantedit, $function_freeze;
16         global $search_word_color, $_msg_word, $foot_explain, $note_hr, $head_tags;
17         global $javascript, $nofollow;
18         global $_LANG, $_LINK, $_IMAGE;
19         global $auth_type, $auth_user;
20         global $html_meta_referrer_policy;
21
22         global $pkwk_dtd;     // XHTML 1.1, XHTML1.0, HTML 4.01 Transitional...
23         global $page_title;   // Title of this site
24         global $do_backup;    // Do backup or not
25         global $modifier;     // Site administrator's  web page
26         global $modifierlink; // Site administrator's name
27
28         $script = get_base_uri();
29         $enable_login = false;
30         $enable_logout = false;
31         if (AUTH_TYPE_FORM === $auth_type || AUTH_TYPE_EXTERNAL === $auth_type ||
32                 AUTH_TYPE_SAML === $auth_type) {
33                 if ($auth_user) {
34                         $enable_logout = true;
35                 } else {
36                         $enable_login = true;
37                 }
38         } else if (AUTH_TYPE_BASIC === $auth_type) {
39                 if ($auth_user) {
40                         $enable_logout = true;
41                 }
42         }
43         if (! file_exists(SKIN_FILE) || ! is_readable(SKIN_FILE))
44                 die_message('SKIN_FILE is not found');
45
46         $_LINK = $_IMAGE = array();
47
48         $_page  = isset($vars['page']) ? $vars['page'] : '';
49         $r_page = pagename_urlencode($_page);
50
51         // Canonical URL
52         $canonical_url = get_page_uri($_page, PKWK_URI_ABSOLUTE);
53
54         // Set $_LINK for skin
55         $_LINK['add']      = "$script?cmd=add&amp;page=$r_page";
56         $_LINK['backup']   = "$script?cmd=backup&amp;page=$r_page";
57         $_LINK['copy']     = "$script?plugin=template&amp;refer=$r_page";
58         $_LINK['diff']     = "$script?cmd=diff&amp;page=$r_page";
59         $_LINK['edit']     = "$script?cmd=edit&amp;page=$r_page";
60         $_LINK['filelist'] = "$script?cmd=filelist";
61         $_LINK['freeze']   = "$script?cmd=freeze&amp;page=$r_page";
62         $_LINK['help']     = get_page_uri($help_page);
63         $_LINK['list']     = "$script?cmd=list";
64         $_LINK['new']      = "$script?plugin=newpage&amp;refer=$r_page";
65         $_LINK['rdf']      = "$script?cmd=rss&amp;ver=1.0";
66         $_LINK['recent']   = get_page_uri($whatsnew);
67         $_LINK['reload']   = get_page_uri($_page);
68         $_LINK['rename']   = "$script?plugin=rename&amp;refer=$r_page";
69         $_LINK['rss']      = "$script?cmd=rss";
70         $_LINK['rss10']    = "$script?cmd=rss&amp;ver=1.0"; // Same as 'rdf'
71         $_LINK['rss20']    = "$script?cmd=rss&amp;ver=2.0";
72         $_LINK['search']   = "$script?cmd=search";
73         $_LINK['top']      = get_page_uri($defaultpage);
74         $_LINK['unfreeze'] = "$script?cmd=unfreeze&amp;page=$r_page";
75         $_LINK['upload']   = "$script?plugin=attach&amp;pcmd=upload&amp;page=$r_page";
76         $_LINK['canonical_url'] = $canonical_url;
77         $login_link = "#LOGIN_ERROR"; // dummy link that is not used
78         switch ($auth_type) {
79                 case AUTH_TYPE_FORM:
80                         $login_link = "$script?plugin=loginform&pcmd=login&page=$r_page";
81                         break;
82                 case AUTH_TYPE_EXTERNAL:
83                 case AUTH_TYPE_SAML:
84                         $login_link = get_auth_external_login_url($_page, $_LINK['reload']);
85                         break;
86         }
87         $_LINK['login']    = htmlsc($login_link);
88         $_LINK['logout']   = "$script?plugin=loginform&amp;pcmd=logout&amp;page=$r_page";
89
90         // Compat: Skins for 1.4.4 and before
91         $link_add       = & $_LINK['add'];
92         $link_new       = & $_LINK['new'];      // New!
93         $link_edit      = & $_LINK['edit'];
94         $link_diff      = & $_LINK['diff'];
95         $link_top       = & $_LINK['top'];
96         $link_list      = & $_LINK['list'];
97         $link_filelist  = & $_LINK['filelist'];
98         $link_search    = & $_LINK['search'];
99         $link_whatsnew  = & $_LINK['recent'];
100         $link_backup    = & $_LINK['backup'];
101         $link_help      = & $_LINK['help'];
102         $link_trackback = ''; // Removed (compat)
103         $link_rdf       = & $_LINK['rdf'];              // New!
104         $link_rss       = & $_LINK['rss'];
105         $link_rss10     = & $_LINK['rss10'];            // New!
106         $link_rss20     = & $_LINK['rss20'];            // New!
107         $link_freeze    = & $_LINK['freeze'];
108         $link_unfreeze  = & $_LINK['unfreeze'];
109         $link_upload    = & $_LINK['upload'];
110         $link_template  = & $_LINK['copy'];
111         $link_refer     = ''; // Removed (compat)
112         $link_rename    = & $_LINK['rename'];
113
114         // Init flags
115         $is_page = (is_pagename($_page) && ! arg_check('backup') && $_page != $whatsnew);
116         $is_read = (arg_check('read') && is_page($_page));
117         $is_freeze = is_freeze($_page);
118
119         // Last modification date (string) of the page
120         $lastmodified = $is_read ?  format_date(get_filetime($_page)) .
121                 get_passage_html_span($_page) : '';
122
123         // List of attached files to the page
124         $show_attaches = $is_read || arg_check('edit');
125         $attaches = ($attach_link && $show_attaches && exist_plugin_action('attach')) ?
126                 attach_filelist() : '';
127
128         // List of related pages
129         $related  = ($related_link && $is_read) ? make_related($_page) : '';
130
131         // List of footnotes
132         ksort($foot_explain, SORT_NUMERIC);
133         $notes = ! empty($foot_explain) ? $note_hr . join("\n", $foot_explain) : '';
134
135         // Tags will be inserted into <head></head>
136         $head_tag = ! empty($head_tags) ? join("\n", $head_tags) ."\n" : '';
137
138         // 1.3.x compat
139         // Last modification date (UNIX timestamp) of the page
140         $fmt = $is_read ? get_filetime($_page) + LOCALZONE : 0;
141
142         // Output nofollow / noindex regardless os skin file
143         if (!$is_read || $nofollow) {
144                 if (!headers_sent()) {
145                         header("X-Robots-Tag: noindex,nofollow");
146                 }
147         }
148
149         // Send Canonical URL for Search Engine Optimization
150         if ($is_read && !headers_sent()) {
151                 header("Link: <$canonical_url>; rel=\"canonical\"");
152         }
153
154         // Search words
155         if ($search_word_color && isset($vars['word'])) {
156                 $body = '<div class="small">' . $_msg_word . htmlsc($vars['word']) .
157                         '</div>' . $hr . "\n" . $body;
158
159                 // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
160                 // with array_splice(), array_flip()
161                 $words = preg_split('/\s+/', $vars['word'], -1, PREG_SPLIT_NO_EMPTY);
162                 $words = array_splice($words, 0, 10); // Max: 10 words
163                 $words = array_flip($words);
164
165                 $keys = array();
166                 foreach ($words as $word=>$id) $keys[$word] = strlen($word);
167                 arsort($keys, SORT_NUMERIC);
168                 $keys = get_search_words(array_keys($keys), TRUE);
169                 $id = 0;
170                 $patterns = '';
171                 foreach ($keys as $key=>$pattern) {
172                         if (strlen($patterns) > 0) {
173                                 $patterns .= '|';
174                         }
175                         $patterns .= '(' . $pattern . ')';
176                 }
177                 if ($pattern) {
178                         $whole_pattern  = '/' .
179                                 '<textarea[^>]*>.*?<\/textarea>' .      // Ignore textareas
180                                 '|' . '<[^>]*>' .                       // Ignore tags
181                                 '|' . '&[^;]+;' .                       // Ignore entities
182                                 '|' . '(' . $patterns . ')' .           // $matches[1]: Regex for a search word
183                                 '/sS';
184                         $body  = preg_replace_callback($whole_pattern, '_decorate_Nth_word', $body);
185                         $notes = preg_replace_callback($whole_pattern, '_decorate_Nth_word', $notes);
186                 }
187         }
188         // Embed Scripting data
189         $html_scripting_data = get_html_scripting_data($_page);
190
191         // Compat: 'HTML convert time' without time about MenuBar and skin
192         $taketime = elapsedtime();
193
194         require(SKIN_FILE);
195 }
196
197 function _decorate_Nth_word($matches)
198 {
199         // $matches[0]: including both words to skip and to decorate
200         // $matches[1]: word to decorate
201         // $matches[2+]: indicates which keyword to decorate
202         $index = -1;
203         for ($i = 2; $i < count($matches); $i++) {
204                 if (isset($matches[$i]) && $matches[$i]) {
205                         $index = $i - 2;
206                         break;
207                 }
208         }
209         if (isset($matches[1])) {
210                 // wordN highlight class: N=0...n
211                 return '<strong class="word' . $index . '">' .
212                         $matches[0] . '</strong>';
213         }
214         return $matches[0];
215 }
216
217 /**
218  * Get data used by JavaScript modules
219  */
220 function get_html_scripting_data($page)
221 {
222         global $ticket_link_sites, $plugin;
223         global $external_link_cushion_page, $external_link_cushion;
224         global $topicpath_title;
225         if (!isset($ticket_link_sites) || !is_array($ticket_link_sites)) {
226                 return '';
227         }
228         $is_utf8 = (bool)defined('PKWK_UTF8_ENABLE');
229         // Require: PHP 5.4+
230         $json_enabled = defined('JSON_UNESCAPED_UNICODE');
231         if (!$json_enabled) {
232                 $empty_data = <<<EOS
233 <div id="pukiwiki-site-properties" style="display:none;">
234 </div>
235 EOS;
236                 return $empty_data;
237         }
238         // Site basic Properties
239         $props = array(
240                 'is_utf8' => $is_utf8,
241                 'json_enabled' => $json_enabled,
242                 'base_uri_pathname' => get_base_uri(PKWK_URI_ROOT),
243                 'base_uri_absolute' => get_base_uri(PKWK_URI_ABSOLUTE)
244         );
245         $h_props = htmlsc(json_encode($props, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
246         $site_props = <<<EOS
247 <input type="hidden" class="site-props" value="$h_props" />
248 EOS;
249         $h_plugin = htmlsc($plugin);
250         $plugin_prop = <<<EOS
251 <input type="hidden" class="plugin-name" value="$h_plugin" />
252 EOS;
253
254         // AutoTicketLink
255         $filtered_ticket_link_sites = array();
256         foreach ($ticket_link_sites as $s) {
257                 if (!preg_match('/^([a-zA-Z0-9]+)([\.\-][a-zA-Z0-9]+)*$/', $s['key'])) {
258                         continue;
259                 }
260                 array_push($filtered_ticket_link_sites, $s);
261         }
262         $h_ticket_link_sites = htmlsc(json_encode($filtered_ticket_link_sites,
263                 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
264         $ticketlink_data = <<<EOS
265 <input type="hidden" class="ticketlink-def" value="$h_ticket_link_sites" />
266 EOS;
267         // External link cushion page
268         $external_link_cushion_data = '';
269         if ($external_link_cushion_page) {
270                 $h_cushion = htmlsc(json_encode($external_link_cushion,
271                         JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
272                 $external_link_cushion_data = <<<EOS
273 <input type="hidden" class="external-link-cushion" value="$h_cushion" />
274 EOS;
275         }
276         // Topicpath title
277         $topicpath_data = '';
278         if ($topicpath_title && exist_plugin('topicpath')) {
279                 $parents = plugin_topicpath_parent_links($page);
280                 $h_topicpath = htmlsc(json_encode($parents,
281                 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
282                 $topicpath_data = <<<EOS
283 <input type="hidden" class="topicpath-links" value="$h_topicpath" />
284 EOS;
285         }
286         $data = <<<EOS
287 <div id="pukiwiki-site-properties" style="display:none;">
288 $site_props
289 $plugin_prop
290 $ticketlink_data
291 $external_link_cushion_data
292 $topicpath_data
293 </div>
294 EOS;
295         return $data;
296 }
297
298 // Show 'edit' form
299 function edit_form($page, $postdata, $digest = FALSE, $b_template = TRUE)
300 {
301         global $vars, $rows, $cols, $hr, $function_freeze;
302         global $_btn_preview, $_btn_repreview, $_btn_update, $_btn_cancel, $_msg_help;
303         global $whatsnew, $_btn_template, $_btn_load, $load_template_func;
304         global $notimeupdate;
305         global $_title_list, $_label_template_pages;
306         global $_msg_edit_cancel_confirm, $_msg_edit_unloadbefore_message;
307         global $rule_page;
308
309         $script = get_base_uri();
310         // Newly generate $digest or not
311         if ($digest === FALSE) $digest = md5(join('', get_source($page)));
312
313         $refer = $template = '';
314
315         // Add plugin
316         $addtag = $add_top = '';
317         if(isset($vars['add'])) {
318                 global $_btn_addtop;
319                 $addtag  = '<input type="hidden" name="add"    value="true" />';
320                 $add_top = isset($vars['add_top']) ? ' checked="checked"' : '';
321                 $add_top = '<input type="checkbox" name="add_top" ' .
322                         'id="_edit_form_add_top" value="true"' . $add_top . ' />' . "\n" .
323                         '  <label for="_edit_form_add_top">' .
324                                 '<span class="small">' . $_btn_addtop . '</span>' .
325                         '</label>';
326         }
327
328         if($load_template_func && $b_template) {
329                 $tpage_names = array(); // Pages marked as template
330                 $template_page = ':config/Templates';
331                 $page_max = 100;
332                 foreach(get_source($template_page) as $_templates) {
333                         $m = array();
334                         if (! preg_match('#\-\s*\[\[([^\[\]]+)\]\]#', $_templates, $m)) continue;
335                         $tpage = preg_replace('#^./#', "$template_page/", $m[1]);
336                         if (! is_page($tpage)) continue;
337                         $tpage_names[] = $tpage;
338                 }
339                 $page_names = array();
340                 foreach(get_existpages() as $_page) {
341                         if ($_page == $whatsnew || check_non_list($_page) ||
342                                 !is_page_readable($_page))
343                                 continue;
344                         if (preg_match('/template/i', $_page)) {
345                                 $tpage_names[] = $_page;
346                         } else {
347                                 if (count($page_names) >= $page_max) continue;
348                                 $page_names[] = $_page;
349                         }
350                 }
351                 $tpage_names2 = array_values(array_unique($tpage_names));
352                 natcasesort($tpage_names2);
353                 natcasesort($page_names);
354                 $tpages = array(); // Template pages
355                 $npages = array(); // Normal pages
356                 foreach($tpage_names2 as $p) {
357                         $ps = htmlsc($p);
358                         $tpages[] = '   <option value="' . $ps . '">' . $ps . '</option>';
359                 }
360                 foreach($page_names as $p) {
361                         $ps = htmlsc($p);
362                         $npages[] = '   <option value="' . $ps . '">' . $ps . '</option>';
363                 }
364                 if (count($page_names) === $page_max) {
365                         $npages[] = '   <option value="">...</option>';
366                 }
367                 $s_tpages  = join("\n", $tpages);
368                 $s_npages  = join("\n", $npages);
369                 $template = <<<EOD
370   <select name="template_page">
371    <option value="">-- $_btn_template --</option>
372    <optgroup label="$_label_template_pages">
373 $s_tpages
374    </optgroup>
375    <optgroup label="$_title_list">
376 $s_npages
377    </optgroup>
378   </select>
379   <input type="submit" name="template" value="$_btn_load" accesskey="r" />
380   <br />
381 EOD;
382
383                 if (isset($vars['refer']) && $vars['refer'] != '')
384                         $refer = '[[' . strip_bracket($vars['refer']) . ']]' . "\n\n";
385         }
386
387         $r_page      = rawurlencode($page);
388         $s_page      = htmlsc($page);
389         $s_digest    = htmlsc($digest);
390         $s_postdata  = htmlsc($refer . $postdata);
391         $s_original  = isset($vars['original']) ? htmlsc($vars['original']) : $s_postdata;
392         $b_preview   = isset($vars['preview']); // TRUE when preview
393         $btn_preview = $b_preview ? $_btn_repreview : $_btn_preview;
394
395         // Checkbox 'do not change timestamp'
396         $add_notimestamp = '';
397         if ($notimeupdate != 0) {
398                 global $_btn_notchangetimestamp;
399                 $checked_time = isset($vars['notimestamp']) ? ' checked="checked"' : '';
400                 // Only for administrator
401                 if ($notimeupdate == 2) {
402                         $add_notimestamp = '   ' .
403                                 '<input type="password" name="pass" size="12" />' . "\n";
404                 }
405                 $add_notimestamp = '<input type="checkbox" name="notimestamp" ' .
406                         'id="_edit_form_notimestamp" value="true"' . $checked_time . ' />' . "\n" .
407                         '   ' . '<label for="_edit_form_notimestamp"><span class="small">' .
408                         $_btn_notchangetimestamp . '</span></label>' . "\n" .
409                         $add_notimestamp .
410                         '&nbsp;';
411         }
412
413         // 'margin-bottom', 'float:left', and 'margin-top'
414         // are for layout of 'cancel button'
415         $h_msg_edit_cancel_confirm = htmlsc($_msg_edit_cancel_confirm);
416         $h_msg_edit_unloadbefore_message = htmlsc($_msg_edit_unloadbefore_message);
417         $body = <<<EOD
418 <div class="edit_form">
419  <form action="$script" method="post" class="_plugin_edit_edit_form" style="margin-bottom:0px;">
420 $template
421   $addtag
422   <input type="hidden" name="cmd"    value="edit" />
423   <input type="hidden" name="page"   value="$s_page" />
424   <input type="hidden" name="digest" value="$s_digest" />
425   <input type="hidden" id="_msg_edit_cancel_confirm" value="$h_msg_edit_cancel_confirm" />
426   <input type="hidden" id="_msg_edit_unloadbefore_message" value="$h_msg_edit_unloadbefore_message" />
427   <textarea name="msg" rows="$rows" cols="$cols">$s_postdata</textarea>
428   <br />
429   <div style="float:left;">
430    <input type="submit" name="preview" value="$btn_preview" accesskey="p" />
431    <input type="submit" name="write"   value="$_btn_update" accesskey="s" />
432    $add_top
433    $add_notimestamp
434   </div>
435   <textarea name="original" rows="1" cols="1" style="display:none">$s_original</textarea>
436  </form>
437  <form action="$script" method="post" class="_plugin_edit_cancel" style="margin-top:0px;">
438   <input type="hidden" name="cmd"    value="edit" />
439   <input type="hidden" name="page"   value="$s_page" />
440   <input type="submit" name="cancel" value="$_btn_cancel" accesskey="c" />
441  </form>
442 </div>
443 EOD;
444
445         $body .= '<ul><li><a href="' .
446                 get_page_uri($rule_page) .
447                 '" target="_blank">' . $_msg_help . '</a></li></ul>';
448         return $body;
449 }
450
451 // Related pages
452 function make_related($page, $tag = '')
453 {
454         global $vars, $rule_related_str, $related_str;
455
456         $script = get_base_uri();
457         prepare_links_related($page);
458         $links = links_get_related($page);
459
460         if ($tag) {
461                 ksort($links, SORT_STRING);             // Page name, alphabetical order
462         } else {
463                 arsort($links, SORT_NUMERIC);   // Last modified date, newer
464         }
465
466         $_links = array();
467         foreach ($links as $page=>$lastmod) {
468                 if (check_non_list($page)) continue;
469                 $page_uri = get_page_uri($page);
470                 $s_page   = htmlsc($page);
471                 if ($tag) {
472                         $attrs = get_page_link_a_attrs($page);
473                         $_links[] = '<a href="' . $page_uri . '" class="' .
474                                 $attrs['class'] . '" data-mtime="' . $attrs['data_mtime'] .
475                                 '">' . $s_page . '</a>';
476                 } else {
477                         $mtime_span = get_passage_mtime_html_span($lastmod + LOCALZONE);
478                         $_links[] = '<a href="' . $page_uri . '">' .
479                         $s_page . '</a>' . $mtime_span;
480                 }
481         }
482         if (empty($_links)) return ''; // Nothing
483
484         if ($tag == 'p') { // From the line-head
485                 $style  = sprintf(pkwk_list_attrs_template(), 1, 1);
486                 $retval =  "\n" . '<ul' . $style . '>' . "\n" .
487                         '<li>' . join($rule_related_str, $_links) . '</li>' . "\n" .
488                         '</ul>' . "\n";
489         } else if ($tag) {
490                 $retval = join($rule_related_str, $_links);
491         } else {
492                 $retval = join($related_str, $_links);
493         }
494
495         return $retval;
496 }
497
498 function _convert_line_rule_to_regex($a)
499 {
500         return '/' . $a . '/';
501 }
502
503 // User-defined rules (convert without replacing source)
504 function make_line_rules($str)
505 {
506         global $line_rules;
507         static $pattern, $replace;
508
509         if (! isset($pattern)) {
510                 $pattern = array_map('_convert_line_rule_to_regex', array_keys($line_rules));
511                 $replace = array_values($line_rules);
512                 unset($line_rules);
513         }
514
515         return preg_replace($pattern, $replace, $str);
516 }
517
518 // Remove all HTML tags(or just anchor tags), and WikiName-speific decorations
519 function strip_htmltag($str, $all = TRUE)
520 {
521         global $_symbol_noexists;
522         static $noexists_pattern;
523
524         if (! isset($noexists_pattern))
525                 $noexists_pattern = '#<span class="noexists">([^<]*)<a[^>]+>' .
526                         preg_quote($_symbol_noexists, '#') . '</a></span>#';
527
528         // Strip Dagnling-Link decoration (Tags and "$_symbol_noexists")
529         $str = preg_replace($noexists_pattern, '$1', $str);
530
531         if ($all) {
532                 // All other HTML tags
533                 return preg_replace('#<[^>]+>#',        '', $str);
534         } else {
535                 // All other anchor-tags only
536                 return preg_replace('#<a[^>]+>|</a>#i', '', $str);
537         }
538 }
539
540 // Remove AutoLink marker with AutLink itself
541 function strip_autolink($str)
542 {
543         return preg_replace('#<!--autolink--><a [^>]+>|</a><!--/autolink-->#', '', $str);
544 }
545
546 // Make a backlink. searching-link of the page name, by the page name, for the page name
547 function make_search($page)
548 {
549         $s_page = htmlsc($page);
550         $r_page = rawurlencode($page);
551         return '<a href="' . get_base_uri() . '?plugin=related&amp;page=' . $r_page .
552                 '">' . $s_page . '</a> ';
553 }
554
555 // Make heading string (remove heading-related decorations from Wiki text)
556 function make_heading(& $str, $strip = TRUE)
557 {
558         global $NotePattern;
559
560         // Cut fixed-heading anchors
561         $id = '';
562         $matches = array();
563         if (preg_match('/^(\*{0,3})(.*?)\[#([A-Za-z][\w-]+)\](.*?)$/m', $str, $matches)) {
564                 $str = $matches[2] . $matches[4];
565                 $id  = & $matches[3];
566         } else {
567                 $str = preg_replace('/^\*{0,3}/', '', $str);
568         }
569
570         // Cut footnotes and tags
571         if ($strip === TRUE)
572                 $str = strip_htmltag(make_link(preg_replace($NotePattern, '', $str)));
573
574         return $id;
575 }
576
577 // Separate a page-name(or URL or null string) and an anchor
578 // (last one standing) without sharp
579 function anchor_explode($page, $strict_editable = FALSE)
580 {
581         $pos = strrpos($page, '#');
582         if ($pos === FALSE) return array($page, '', FALSE);
583
584         // Ignore the last sharp letter
585         if ($pos + 1 == strlen($page)) {
586                 $pos = strpos(substr($page, $pos + 1), '#');
587                 if ($pos === FALSE) return array($page, '', FALSE);
588         }
589
590         $s_page = substr($page, 0, $pos);
591         $anchor = substr($page, $pos + 1);
592
593         if($strict_editable === TRUE &&  preg_match('/^[a-z][a-f0-9]{7}$/', $anchor)) {
594                 return array ($s_page, $anchor, TRUE); // Seems fixed-anchor
595         } else {
596                 return array ($s_page, $anchor, FALSE);
597         }
598 }
599
600 // Check HTTP header()s were sent already, or
601 // there're blank lines or something out of php blocks
602 function pkwk_headers_sent()
603 {
604         if (PKWK_OPTIMISE) return;
605
606         $file = $line = '';
607         if (version_compare(PHP_VERSION, '4.3.0', '>=')) {
608                 if (headers_sent($file, $line))
609                     die('Headers already sent at ' .
610                         htmlsc($file) .
611                         ' line ' . $line . '.');
612         } else {
613                 if (headers_sent())
614                         die('Headers already sent.');
615         }
616 }
617
618 // Output common HTTP headers
619 function pkwk_common_headers()
620 {
621         global $http_response_custom_headers;
622         if (! PKWK_OPTIMISE) pkwk_headers_sent();
623         foreach ($http_response_custom_headers as $header) {
624                 header($header);
625         }
626         if(defined('PKWK_ZLIB_LOADABLE_MODULE')) {
627                 $matches = array();
628                 if(ini_get('zlib.output_compression') &&
629                     preg_match('/\b(gzip|deflate)\b/i', $_SERVER['HTTP_ACCEPT_ENCODING'], $matches)) {
630                         // Bug #29350 output_compression compresses everything _without header_ as loadable module
631                         // http://bugs.php.net/bug.php?id=29350
632                         header('Content-Encoding: ' . $matches[1]);
633                         header('Vary: Accept-Encoding');
634                 }
635         }
636 }
637
638 // DTD definitions
639 define('PKWK_DTD_XHTML_1_1',              17); // Strict only
640 define('PKWK_DTD_XHTML_1_0',              16); // Strict
641 define('PKWK_DTD_XHTML_1_0_STRICT',       16);
642 define('PKWK_DTD_XHTML_1_0_TRANSITIONAL', 15);
643 define('PKWK_DTD_XHTML_1_0_FRAMESET',     14);
644 define('PKWK_DTD_HTML_4_01',               3); // Strict
645 define('PKWK_DTD_HTML_4_01_STRICT',        3);
646 define('PKWK_DTD_HTML_4_01_TRANSITIONAL',  2);
647 define('PKWK_DTD_HTML_4_01_FRAMESET',      1);
648
649 define('PKWK_DTD_TYPE_XHTML',  1);
650 define('PKWK_DTD_TYPE_HTML',   0);
651
652 // Output HTML DTD, <html> start tag. Return content-type.
653 function pkwk_output_dtd($pkwk_dtd = PKWK_DTD_XHTML_1_1, $charset = CONTENT_CHARSET)
654 {
655         static $called;
656         if (isset($called)) die('pkwk_output_dtd() already called. Why?');
657         $called = TRUE;
658
659         $type = PKWK_DTD_TYPE_XHTML;
660         $option = '';
661         switch($pkwk_dtd){
662         case PKWK_DTD_XHTML_1_1             :
663                 $version = '1.1' ;
664                 $dtd     = 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd';
665                 break;
666         case PKWK_DTD_XHTML_1_0_STRICT      :
667                 $version = '1.0' ;
668                 $option  = 'Strict';
669                 $dtd     = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd';
670                 break;
671         case PKWK_DTD_XHTML_1_0_TRANSITIONAL:
672                 $version = '1.0' ;
673                 $option  = 'Transitional';
674                 $dtd     = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
675                 break;
676
677         case PKWK_DTD_HTML_4_01_STRICT      :
678                 $type    = PKWK_DTD_TYPE_HTML;
679                 $version = '4.01';
680                 $dtd     = 'http://www.w3.org/TR/html4/strict.dtd';
681                 break;
682         case PKWK_DTD_HTML_4_01_TRANSITIONAL:
683                 $type    = PKWK_DTD_TYPE_HTML;
684                 $version = '4.01';
685                 $option  = 'Transitional';
686                 $dtd     = 'http://www.w3.org/TR/html4/loose.dtd';
687                 break;
688
689         default: die('DTD not specified or invalid DTD');
690                 break;
691         }
692
693         $charset = htmlsc($charset);
694
695         // Output XML or not
696         if ($type == PKWK_DTD_TYPE_XHTML) echo '<?xml version="1.0" encoding="' . $charset . '" ?>' . "\n";
697
698         // Output doctype
699         echo '<!DOCTYPE html PUBLIC "-//W3C//DTD ' .
700                 ($type == PKWK_DTD_TYPE_XHTML ? 'XHTML' : 'HTML') . ' ' .
701                 $version .
702                 ($option != '' ? ' ' . $option : '') .
703                 '//EN" "' .
704                 $dtd .
705                 '">' . "\n";
706
707         // Output <html> start tag
708         echo '<html';
709         if ($type == PKWK_DTD_TYPE_XHTML) {
710                 echo ' xmlns="http://www.w3.org/1999/xhtml"'; // dir="ltr" /* LeftToRight */
711                 echo ' xml:lang="' . LANG . '"';
712                 if ($version == '1.0') echo ' lang="' . LANG . '"'; // Only XHTML 1.0
713         } else {
714                 echo ' lang="' . LANG . '"'; // HTML
715         }
716         echo '>' . "\n"; // <html>
717
718         // Return content-type (with MIME type)
719         if ($type == PKWK_DTD_TYPE_XHTML) {
720                 // NOTE: XHTML 1.1 browser will ignore http-equiv
721                 return '<meta http-equiv="content-type" content="application/xhtml+xml; charset=' . $charset . '" />' . "\n";
722         } else {
723                 return '<meta http-equiv="content-type" content="text/html; charset=' . $charset . '" />' . "\n";
724         }
725 }
726
727 /**
728  * Get template of List (ul, ol, dl) attributes
729  */
730 function pkwk_list_attrs_template() {
731         return ' class="list%d list-indent%d"';
732 }