xref: /web-php/include/layout.inc (revision 6c604f13)
1<?php
2
3use phpweb\I18n\Languages;
4use phpweb\Navigation\NavItem;
5
6$_SERVER['STATIC_ROOT'] = $MYSITE;
7$_SERVER['MYSITE'] = $MYSITE;
8
9// Use class names instead of colors
10ini_set('highlight.comment', 'comment');
11ini_set('highlight.default', 'default');
12ini_set('highlight.keyword', 'keyword');
13ini_set('highlight.string',  'string');
14ini_set('highlight.html',    'html');
15
16// Highlight PHP code
17function highlight_php($code, $return = false)
18{
19    $highlighted = highlight_string($code, true);
20
21    // Use this ugly hack for now to avoid code snippets with bad syntax screwing up the highlighter
22    if (strstr($highlighted, "include/layout.inc</b>")) {
23        $highlighted = '<span class="html">' . nl2br(htmlentities($code, ENT_HTML5), false) . "</span>";
24    }
25
26    // Fix output to use CSS classes and wrap well
27    $highlighted = '<div class="phpcode">' . strtr(
28        $highlighted,
29        [
30            '&nbsp;' => ' ',
31            "\n" => '',
32
33            '<span style="color: ' => '<span class="',
34        ],
35    ) . '</div>';
36
37    if ($return) { return $highlighted; }
38    echo $highlighted;
39    return null;
40}
41
42// Same as highlight_php() but does not require '<?php' in $code
43function highlight_php_trimmed($code, $return = false)
44{
45    $code = "<?php\n" . $code;
46    $highlighted_code = highlight_php($code, true);
47    $highlighted_code = preg_replace("!&lt;\?php(<br />)+!", '', $highlighted_code, 1);
48
49    if ($return) { return $highlighted_code; }
50    echo $highlighted_code;
51    return null;
52}
53
54// Resize the image using the output of make_image()
55function resize_image($img, $width = 1, $height = 1)
56{
57    // Drop width and height values from image if available
58    $str = preg_replace('!width=\"([0-9]+?)\"!i',  '', $img);
59    $str = preg_replace('!height=\"([0-9]+?)\"!i', '', $str);
60
61    // Return image with new width and height added
62    return preg_replace(
63        '!/?>$!',
64        sprintf(' height="%s" width="%s">', $height, $width),
65        $str,
66    );
67}
68
69// Return an <img> tag for a given image file available on the server
70function make_image($file, $alt = false, $align = false, $extras = false,
71                    $dir = '/images', $addsize = false)
72{
73    // If no / was provided at the start of $dir, add it
74    $webdir = $_SERVER['MYSITE'] . ($dir[0] == '/' ? '' : '/') . $dir;
75
76    // Get width and height values if possible
77    if ($addsize && ($size = @getimagesize($_SERVER['DOCUMENT_ROOT'] . "$dir/$file"))) {
78        $sizeparams = ' ' . trim($size[3]);
79    } else {
80        $sizeparams = '';
81    }
82
83    // Convert right or left alignment to CSS float,
84    // but leave other alignments intact (for now)
85    if (in_array($align, ["right", "left"], true)) {
86        $align = ' style="float: ' . $align . ';"';
87    } elseif ($align) {
88        $align = ' align="' . $align . '"';
89    } else {
90        $align = '';
91    }
92
93    // Return with image built up
94    return sprintf('<img src="%s/%s" alt="%s"%s%s%s>',
95        $webdir,
96        $file,
97        ($alt ?: ''),
98        $sizeparams,
99        $align,
100        ($extras ? ' ' . $extras : ''),
101    );
102}
103
104// Print an <img> tag out for a given file
105function print_image($file, $alt = false, $align = false, $extras = false,
106                     $dir = '/images'): void
107{
108    echo make_image($file, $alt, $align, $extras, $dir);
109}
110
111// Shortcut to usual news image printing (right floating
112// image from the news dir with an alt and an URL)
113function news_image($URL, $image, $alt, $print = true)
114{
115    $str = "<a href=\"$URL\">" . make_image("news/$image", $alt, "right") . "</a>";
116    if ($print) {
117        echo $str;
118    }
119    return $str;
120}
121
122// Return HTML code for a submit button image
123function make_submit($file, $alt = false, $align = false, $extras = false,
124                     $dir = '/images', $border = 0)
125{
126    // Get an image without size info and convert the
127    // border attribute to use CSS, as border="" is not
128    // supported on <input> elements in [X]HTML
129    $img = make_image($file, $alt, $align, $extras, $dir, false);
130    $img = str_replace(
131        "border=\"$border\"",
132        "style=\"border: {$border}px;\"",
133        $img,
134    );
135
136    // Return with ready input image
137    return '<input type="image"' . substr($img, 4);
138}
139
140// Return a hiperlink to something within the site
141function make_link(string $url, string $linktext = ''): string
142{
143    return sprintf("<a href=\"%s\">%s</a>", $url, $linktext ?: $url);
144}
145
146// make_popup_link()
147// return a hyperlink to something, within the site, that pops up a new window
148//
149function make_popup_link($url, $linktext = false, $target = false, $windowprops = "", $extras = false) {
150    return sprintf("<a href=\"%s\" target=\"%s\" onclick=\"window.open('%s','%s','%s');return false;\"%s>%s</a>",
151        htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
152        ($target ?: "_new"),
153        htmlspecialchars($url, ENT_QUOTES | ENT_IGNORE),
154        ($target ?: "_new"),
155                $windowprops,
156        ($extras ? ' ' . $extras : ''),
157        ($linktext ?: $url),
158    );
159}
160
161// Print a link for a downloadable file (including filesize)
162function download_link($file, $title): void
163{
164    $download_link = "/distributions/" . $file;
165
166    // Print out the download link
167    echo make_link($download_link, $title);
168
169    // We have a full path or a relative to the distributions dir
170    if ($tmp = strrchr($file, "/")) {
171        $local_file = substr($tmp, 1, strlen($tmp));
172    } else {
173        $local_file = "distributions/$file";
174    }
175
176    if (@file_exists($local_file . ".asc")) {
177        echo " ";
178        $sig_link = "/distributions/$file.asc";
179        echo make_link($sig_link, "(sig)");
180    }
181
182    // Try to get the size of the file
183    $size = @filesize($local_file);
184
185    // Print out size in bytes (if size is
186    // less then 1Kb, or else in Kb)
187    if ($size) {
188        echo ' [';
189        if ($size < 1024) {
190            echo number_format($size) . 'b';
191        } else {
192            echo number_format($size / 1024) . 'Kb';
193        }
194        echo ']';
195    }
196}
197
198function clean($var) {
199  return htmlspecialchars($var, ENT_QUOTES);
200}
201
202// Clean out the content of one user note for printing to HTML
203function clean_note($text)
204{
205    // Highlight PHP source
206    $text = highlight_php(trim($text), true);
207
208    // Turn urls into links
209    return preg_replace(
210        '!((mailto:|(https?|ftp|nntp|news)://).*?)(\s|<|\)|"|\\\\|\'|$)!',
211        '<a href="\1" rel="nofollow" target="_blank">\1</a>\4',
212        $text,
213    );
214}
215
216function display_errors($errors): void
217{
218    echo '<div class="errors">';
219    if (count($errors) > 1) {
220        echo "You need to do the following before your submission will be accepted:<ul>";
221        foreach ($errors as $error) {
222            echo "<li>$error</li>\n";
223        }
224        echo "</ul>";
225    }
226    else {
227        echo $errors[0];
228    }
229    echo '</div>';
230}
231
232// Displays an event. Used in event submission
233// previews and event information displays
234function display_event($event, $include_date = 1): void
235{
236    global $COUNTRIES;
237    // Current month (int)($_GET['cm'] ?: 0)
238    global $cm;
239    // Current year (int)($_GET['cy'] ?: 0)
240    global $cy;
241
242    // Weekday names array
243    for ($i = 1; $i <= 7; $i++) {
244        $days[$i] = date('l', mktime(12, 0, 0, 4, $i, 2012));
245    }
246
247    // Recurring possibilities
248    $re = [
249        1 => 'First',
250        2 => 'Second',
251        3 => 'Third',
252        4 => 'Fourth',
253        -1 => 'Last',
254        -2 => '2nd Last',
255        -3 => '3rd Last',
256    ];
257
258    if (!isset($event['start']) && isset($event['sday'])) {
259        $sday = mktime(12,0,0,$event['smonth'],$event['sday'],$event['syear']);
260    } else {
261        $sday = (isset($event['start']) && !empty($event['start'])) ? strtotime($event['start']) : 0;
262    }
263
264    if (!isset($event['end']) && isset($event['eday'])) {
265        $eday = mktime(12,0,0,$event['emonth'],$event['eday'],$event['eyear']);
266    } else {
267        $eday = (isset($event['end']) && !empty($event['end'])) ? strtotime($event['end']) : 0;
268    }
269?>
270<table border="0" cellspacing="0" cellpadding="3" width="100%" class="vevent">
271 <tr bgcolor="#dddddd"><td>
272<?php
273
274    // Print out date if needed
275    if ($include_date && (isset($event['start']))) {
276        echo "<b>", date("F j, Y", $sday), "</b>\n";
277    }
278
279    // Print link in case we have one
280    if ($event['url']) { echo '<a href="', htmlentities($event['url'], ENT_QUOTES | ENT_IGNORE, 'UTF-8'),'" class="url">'; }
281    // Print event description
282    echo "<b class='summary'>", stripslashes(htmlentities($event['sdesc'], ENT_QUOTES | ENT_IGNORE, 'UTF-8')), "</b>";
283    // End link
284    if ($event['url']) { echo "</a>"; }
285
286    // Print extra date info for recurring and multiday events
287    switch ($event['type']) {
288        case 2:
289        case 'multi':
290            $dtend = date("Y-m-d", strtotime("+1 day", $eday));
291            echo " (<abbr class='dtstart'>", date("Y-m-d",$sday), "</abbr> to <abbr class='dtend' title='$dtend'>", date("Y-m-d",$eday), "</abbr>)";
292            break;
293        case 3:
294        case 'recur':
295            $days = $re[$event['recur']] . " " . $days[$event['recur_day']];
296            if (!$cm || $cy) {
297                $cm = date("m");
298                $cy = date("Y");
299            }
300            $month = date("M", mktime(0, 0, 0, $cm, 1, $cy));
301            $dtstart = date("Y-m-d", strtotime($days . ' 0st' . $month . ' ' . $cy));
302            echo ' (Every <abbr class="dtstart" title="' . $dtstart . '">', $days, "</abbr> of the month)";
303            break;
304    }
305
306    // Event category
307    if (isset($event['category']) && $event['category']) {
308        $cat = ["unknown", "User Group Event", "Conference", "Training"];
309        echo ' [' . $cat[$event['category']] . '] ';
310    }
311
312    // Print out country information
313    echo ' (<span class="location">' , $COUNTRIES[$event['country']] , '</span>)';
314?>
315 </td></tr>
316 <tr bgcolor="#eeeeee" class="description"><td>
317<?php
318
319    // Print long description
320    echo preg_replace("/\r?\n\r?\n/", "<br><br>", trim(htmlentities($event['ldesc'],ENT_QUOTES | ENT_IGNORE, 'UTF-8')));
321    // If we have an URL, print it out
322    if ($event['url']) {
323        echo '<br><br><b>URL:</b> ',
324             '<a href="', htmlentities($event['url'], ENT_QUOTES | ENT_IGNORE, 'UTF-8'), '">',
325             htmlentities($event['url'], ENT_QUOTES | ENT_IGNORE, 'UTF-8'), '</a>';
326    }
327?>
328 </td></tr>
329</table>
330<?php
331}
332
333// Print news links for archives
334function news_archive_sidebar(): void
335{
336    global $SIDEBAR_DATA;
337    $SIDEBAR_DATA = '
338<h3 class="announcements">Archives by year</h3>
339
340';
341    for ($i = date("Y"); $i >= 1998; $i--) {
342        $pagename = "archive/$i.php";
343        $classname = ($pagename == $_SERVER['BASE_PAGE'] ? " active" : '');
344        $SIDEBAR_DATA .= "<p class='panel{$classname}'><a href=\"/{$pagename}\">{$i}</a></p>\n";
345    }
346}
347
348// Print news
349function print_news($news, $dog, $max = 5, $onlyyear = null, $return = false) {
350    $retval = [];
351    $count = 0;
352    $news = $news ?: []; // default to empty array (if no news)
353    foreach ($news as $item) {
354        $ok = false;
355
356        // Only print entries in the provided s/dog/cat/ egory
357        // If $dog is null, everything matches
358        foreach ($item["category"] as $category) {
359            if (null === $dog || in_array($category["term"], (array)$dog, true)) {
360                $ok = true;
361                $count++;
362                break;
363            }
364        }
365        if ($count > $max) {
366            break;
367        }
368        if ($ok === false) {
369            continue;
370        }
371
372        $image = "";
373        if (isset($item["newsImage"])) {
374            $image = news_image($item["newsImage"]["link"], $item["newsImage"]["content"], $item["newsImage"]["title"], false);
375        }
376
377        $id = parse_url($item["id"]);
378        $id = $id["fragment"];
379
380        // Find the permlink
381        foreach ($item["link"] as $link) {
382            if ($link["rel"] === "via") {
383                $permlink = $link["href"];
384                break;
385            }
386        }
387        if (!isset($permlink)) {
388            $permlink = "#" . $id;
389        }
390
391        $published = substr($item["published"], 0, 10);
392        $nixtimestamp = strtotime($published);
393        $newsdate = date("d M Y", $nixtimestamp);
394        if ($onlyyear && date("Y", $nixtimestamp) != $onlyyear) {
395            $count--;
396            continue;
397        }
398
399        if ($return) {
400            $retval[] = [
401                "title" => $item["title"],
402                "id" => $id,
403                "permlink" => $permlink,
404                "date" => $newsdate,
405            ];
406            continue;
407        }
408
409        echo <<<EOT
410<article class="newsItem">
411  <header>
412    <div class="newsImage">{$image}</div>
413    <h2 class="newstitle"><a id="{$id}" href="{$permlink}" rel="bookmark" class="bookmark">{$item["title"]}</a></h2>
414  </header>
415  <time class="newsdate" datetime="{$item["published"]}">{$newsdate}</time>
416  <div class="newscontent">
417    {$item["content"]}
418  </div>
419</article>
420
421EOT;
422    }
423
424    return $retval;
425}
426
427function site_header(string $title = 'Hypertext Preprocessor', array $config = []): void
428{
429    global $MYSITE, $LANG;
430
431    $meta_image_path = $MYSITE . 'images/meta-image.png';
432    $meta_description = "PHP is a popular general-purpose scripting language that powers everything from your blog to the most popular websites in the world.";
433
434    $defaults = [
435        "lang" => $LANG,
436        "current" => "",
437        "meta-navigation" => [],
438        'classes' => '',
439        'layout_span' => 9,
440        "cache" => false,
441        "headsup" => "",
442        'meta_tags' => <<<META
443<meta name="Description" content="{$meta_description}" />
444
445<meta name="twitter:card" content="summary_large_image" />
446<meta name="twitter:site" content="@official_php" />
447<meta name="twitter:title" content="PHP: Hypertext Preprocessor" />
448<meta name="twitter:description" content="{$meta_description}" />
449<meta name="twitter:creator" content="@official_php" />
450<meta name="twitter:image:src" content="{$meta_image_path}" />
451
452<meta itemprop="name" content="PHP: Hypertext Preprocessor" />
453<meta itemprop="description" content="$meta_description" />
454<meta itemprop="image" content="{$meta_image_path}" />
455
456<meta property="og:image" content="{$meta_image_path}" />
457<meta property="og:description" content="$meta_description" />
458
459<link href="https://fosstodon.org/@php" rel="me" />
460META
461    ];
462
463    $config = array_merge($defaults, $config);
464
465    $config["headsup"] = get_news_changes();
466
467    $lang = (new Languages())->convert($config["lang"]);
468    $curr = $config["current"];
469    $classes = $config['classes'];
470
471    if (isset($_COOKIE["MD"]) || isset($_GET["MD"])) {
472        $classes .= "markdown-content";
473        $config["css_overwrite"] = ["/styles/i-love-markdown.css"];
474    }
475
476    // shorturl; http://wiki.snaplog.com/short_url
477    if (isset($_SERVER['BASE_PAGE']) && $shortname = get_shortname($_SERVER["BASE_PAGE"])) {
478        $shorturl = "https://www.php.net/" . $shortname;
479    }
480
481    require __DIR__ . "/header.inc";
482}
483function site_footer(array $config = []): void
484{
485    require __DIR__ . "/footer.inc";
486}
487
488function get_nav_items(): array {
489  return [
490    new NavItem(
491      name: 'Downloads',
492      href: '/downloads.php',
493      id: 'downloads',
494    ),
495    new NavItem(
496      name: 'Documentation',
497      href: '/docs.php',
498      id: 'docs',
499    ),
500    new NavItem(
501      name: 'Get Involved',
502      href: '/get-involved.php',
503      id: 'community',
504    ),
505    new NavItem(
506      name: 'Help',
507      href: '/support.php',
508      id: 'help',
509    ),
510    new NavItem(
511      name: 'PHP 8.4',
512      href: '/releases/8.4/index.php',
513      id: 'php8',
514      image: '/images/php8/logo_php8_4.svg',
515    )
516  ];
517}
518
519function get_news_changes()
520{
521    include __DIR__ . "/pregen-news.inc";
522    $date = date_create($NEWS_ENTRIES[0]["updated"]);
523    if (isset($_COOKIE["LAST_NEWS"]) && $_COOKIE["LAST_NEWS"] >= $date->getTimestamp()) {
524        return false;
525    }
526
527    /* It is a bug when this happens.. but I don't know where it is coming from */
528    if (!isset($_SERVER["BASE_PAGE"])) {
529        return false;
530    }
531    if ($_SERVER["BASE_PAGE"] == "index.php") {
532        return false;
533    }
534
535    $date->modify("+1 week");
536    if ($date->getTimestamp() > $_SERVER["REQUEST_TIME"]) {
537        $link = preg_replace('~^(http://php.net/|https://www.php.net/)~', '/', $NEWS_ENTRIES[0]["link"][0]["href"]);
538        $title = $NEWS_ENTRIES[0]["title"];
539        return "<a href='{$link}'>{$title}</a>";
540    }
541    return false;
542}
543
544function doc_toc($lang): void {
545    $file = __DIR__ . "/../manual/$lang/toc/index.inc";
546    if (!file_exists($file)) {
547        $lang = "en"; // Fallback on english if the translation doesn't exist
548        $file = __DIR__ . "/../manual/en/toc/index.inc";
549    }
550    require __DIR__ . "/../manual/$lang/toc/index.inc";
551
552    echo "<dl>\n";
553    doc_toc_list($lang, $TOC, "getting-started");
554    doc_toc_list($lang, $TOC, "langref");
555    echo "</dl>\n";
556
557    echo "<dl>\n";
558    doc_toc_list($lang, $TOC, "security");
559    doc_toc_list($lang, $TOC, "features");
560    echo "</dl>\n";
561
562    echo "<dl>\n";
563    doc_toc_list($lang, $TOC, "funcref");
564    echo "</dl>\n";
565
566    echo "<dl>\n";
567    echo "<dt>Keyboard Shortcuts</dt>";
568    echo "<dt>?</dt>\n";
569    echo "<dd>This help</dd>\n";
570    echo "<dt>j</dt>\n";
571    echo "<dd>Next menu item</dd>\n";
572    echo "<dt>k</dt>\n";
573    echo "<dd>Previous menu item</dd>\n";
574    echo "<dt>g p</dt>\n";
575    echo "<dd>Previous man page</dd>\n";
576    echo "<dt>g n</dt>\n";
577    echo "<dd>Next man page</dd>\n";
578    echo "<dt>G</dt>\n";
579    echo "<dd>Scroll to bottom</dd>\n";
580    echo "<dt>g g</dt>\n";
581    echo "<dd>Scroll to top</dd>\n";
582    echo "<dt>g h</dt>\n";
583    echo "<dd>Goto homepage</dd>\n";
584    echo "<dt>g s</dt>\n";
585    echo "<dd>Goto search<br>(current page)</dd>\n";
586    echo "<dt>/</dt>\n";
587    echo "<dd>Focus search box</dd>\n";
588    echo "</dl>";
589
590}
591function doc_toc_list($lang, $index, $file): void {
592    include __DIR__ . "/../manual/$lang/toc/$file.inc";
593
594    doc_toc_title($lang, $index, $file);
595    foreach ($TOC as $entry) {
596        echo "\t<dd><a href='/manual/$lang/{$entry[0]}'>{$entry[1]}</a></dd>\n";
597    }
598}
599function doc_toc_title($lang, $index, $file, $elm = "dt"): void {
600    foreach ($index as $entry) {
601        if ($entry[0] == "$file.php") {
602            $link = $entry[0];
603            $title = $entry[1];
604            break;
605        }
606    }
607    echo "<$elm><a href='/manual/$lang/$link'>$title</a></$elm>\n";
608}
609