xref: /web-php/include/shared-manual.inc (revision 739e1f57)
1<?php
2
3/*
4
5 This file is included directly on all manual pages,
6 and therefore is the entering point for all manual pages
7 to the website function collection. These functions display
8 the manual pages with headers and navigation.
9
10 The $PGI global variable is used to store all page related
11 information, including HTTP header related data.
12
13*/
14
15// Ensure that our environment is set up
16include_once __DIR__ . '/prepend.inc';
17
18// Set variable defaults
19$PGI = []; $SIDEBAR_DATA = '';
20
21// =============================================================================
22// User note display functions
23// =============================================================================
24
25require_once __DIR__ . '/../src/autoload.php';
26
27use phpweb\UserNotes\Sorter;
28use phpweb\UserNotes\UserNote;
29
30/**
31 * Print out all user notes for this manual page
32 *
33 * @param array<string, UserNote> $notes
34 */
35function manual_notes($notes):void {
36    // Get needed values
37    list($filename) = $GLOBALS['PGI']['this'];
38
39    // Drop file extension from the name
40    if (substr($filename, -4) == '.php') {
41        $filename = substr($filename, 0, -4);
42    }
43
44    $sorter = new Sorter();
45    $sorter->sort($notes);
46
47    // Link target to add a note to the current manual page,
48    // and it's extended form with a [+] image
49    $addnotelink = '/manual/add-note.php?sect=' . $filename .
50                   '&amp;redirect=' . $_SERVER['BASE_HREF'];
51    $addnotesnippet = make_link(
52        $addnotelink,
53        "+<small>add a note</small>",
54    );
55
56    $num_notes = count($notes);
57    $noteCountHtml = '';
58    if ($num_notes) {
59        $noteCountHtml = "<span class=\"count\">$num_notes note" . ($num_notes == 1 ? '' : 's') . "</span>";
60    }
61
62    echo <<<END_USERNOTE_HEADER
63<section id="usernotes">
64 <div class="head">
65  <span class="action">{$addnotesnippet}</span>
66  <h3 class="title">User Contributed Notes {$noteCountHtml}</h3>
67 </div>
68END_USERNOTE_HEADER;
69
70    // If we have no notes, then inform the user
71    if ($num_notes === 0) {
72        echo "\n <div class=\"note\">There are no user contributed notes for this page.</div>";
73    } else {
74        // If we have notes, print them out
75        echo '<div id="allnotes">';
76        foreach ($notes as $note) {
77            manual_note_display($note);
78        }
79        echo "</div>\n";
80        echo "<div class=\"foot\">$addnotesnippet</div>\n";
81    }
82    echo "</section>";
83}
84
85/**
86 * Get user notes from the appropriate text dump
87 *
88 * @return array<string, UserNote>
89 */
90function manual_notes_load(string $id): array
91{
92    $hash = substr(md5($id), 0, 16);
93    $notes_file = $_SERVER['DOCUMENT_ROOT'] . "/backend/notes/" .
94                  substr($hash, 0, 2) . "/$hash";
95
96    // Open the note file for reading and get the data (12KB)
97    // ..if it exists
98    if (!file_exists($notes_file)) {
99        return [];
100    }
101    $notes = [];
102    if ($fp = @fopen($notes_file, "r")) {
103        while (!feof($fp)) {
104            $line = chop(fgets($fp, 12288));
105            if ($line == "") { continue; }
106            @list($id, $sect, $rate, $ts, $user, $note, $up, $down) = explode("|", $line);
107            $notes[$id] = new UserNote($id, $sect, $rate, $ts, $user, base64_decode($note, true), (int) $up, (int) $down);
108        }
109        fclose($fp);
110    }
111    return $notes;
112}
113
114// Print out one user note entry
115function manual_note_display(UserNote $note, $voteOption = true): void
116{
117    if ($note->user) {
118        $name = "\n  <strong class=\"user\"><em>" . htmlspecialchars($note->user) . "</em></strong>";
119    } else {
120        $name = "<strong class=\"user\"><em>Anonymous</em></strong>";
121    }
122    $name = ($note->id ? "\n  <a href=\"#{$note->id}\" class=\"name\">$name</a><a class=\"genanchor\" href=\"#{$note->id}\"> &para;</a>" : "\n  $name");
123
124    // New date style will be relative time
125    $date = new DateTime("@{$note->ts}");
126    $datestr = relTime($date);
127    $fdatestr = $date->format("Y-m-d h:i");
128    $text = clean_note($note->text);
129
130    // Calculate note rating by up/down votes
131    $vote = $note->upvotes - $note->downvotes;
132    $p = floor(($note->upvotes / (($note->upvotes + $note->downvotes) ?: 1)) * 100);
133    $rate = !$p && !($note->upvotes + $note->downvotes) ? "no votes..." : "$p% like this...";
134
135    // Vote User Notes Div
136    if ($voteOption) {
137        list($redir_filename) = $GLOBALS['PGI']['this'];
138        if (substr($redir_filename, -4) == '.php') {
139            $redir_filename = substr($redir_filename, 0, -4);
140        }
141        $rredir_filename = urlencode($redir_filename);
142        $votediv = <<<VOTEDIV
143  <div class="votes">
144    <div id="Vu{$note->id}">
145    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=up" title="Vote up!" class="usernotes-voteu">up</a>
146    </div>
147    <div id="Vd{$note->id}">
148    <a href="/manual/vote-note.php?id={$note->id}&amp;page={$rredir_filename}&amp;vote=down" title="Vote down!" class="usernotes-voted">down</a>
149    </div>
150    <div class="tally" id="V{$note->id}" title="{$rate}">
151    {$vote}
152    </div>
153  </div>
154VOTEDIV;
155    } else {
156        $votediv = null;
157    }
158
159    // If the viewer is logged in, show admin options
160    if (isset($_COOKIE['IS_DEV']) && $note->id) {
161
162        $admin = "\n  <span class=\"admin\">\n   " .
163
164        make_popup_link(
165            'https://main.php.net/manage/user-notes.php?action=edit+' . $note->id,
166            '<img src="/images/notes-edit@2x.png" height="12" width="12" alt="edit note">',
167            'admin',
168            'scrollbars=yes,width=650,height=400',
169        ) . "\n   " .
170
171        make_popup_link(
172            'https://main.php.net/manage/user-notes.php?action=reject+' . $note->id,
173            '<img src="/images/notes-reject@2x.png" height="12" width="12" alt="reject note">',
174            'admin',
175            'scrollbars=no,width=300,height=200',
176        ) . "\n   " .
177
178        make_popup_link(
179            'https://main.php.net/manage/user-notes.php?action=delete+' . $note->id,
180            '<img src="/images/notes-delete@2x.png" height="12" width="12" alt="delete note">',
181            'admin',
182            'scrollbars=no,width=300,height=200',
183        ) . "\n  </span>";
184
185    } else {
186        $admin = '';
187    }
188
189    echo <<<USER_NOTE_TEXT
190
191  <div class="note" id="{$note->id}">{$votediv}{$name}{$admin}<div class="date" title="$fdatestr"><strong>{$datestr}</strong></div>
192  <div class="text" id="Hcom{$note->id}">
193{$text}
194  </div>
195 </div>
196USER_NOTE_TEXT;
197
198}
199
200function manual_navigation_breadcrumbs(array $setup) {
201    $menu = [];
202    foreach (array_reverse($setup["parents"]) as $parent) {
203        $menu[] = [
204            "title" => $parent[1],
205            "link" => $parent[0],
206        ];
207    }
208
209    // The index manual page has no parent..
210    if ($setup["up"][0]) {
211        $last_item = [
212            "title" => $setup["up"][1],
213            "link" => $setup["up"][0],
214        ];
215        $menu[] = $last_item;
216    }
217    return $menu;
218}
219
220function manual_navigation_related(array $setup) {
221    $siblings = [];
222    foreach ($setup['toc'] as $entry) {
223        $siblings[] = [
224            "title" => manual_navigation_methodname($entry[1]),
225            "link" => $entry[0],
226            "current" => $setup["this"][0] == $entry[0],
227        ];
228    }
229
230    // The index manual page has no parent..
231    if ($setup["up"][0]) {
232        $last_item = [
233            "title" => $setup["up"][1],
234            "link" => $setup["up"][0],
235        ];
236        $siblings = [array_merge($last_item, ["children" => $siblings])];
237    }
238    return $siblings;
239}
240
241function manual_navigation_deprecated(array $setup) {
242    $methods = [];
243    foreach ((array)$setup['toc_deprecated'] as $entry) {
244        $methods[] = [
245            "title" => manual_navigation_methodname($entry[1]),
246            "link" => $entry[0],
247            "current" => $setup["this"][0] == $entry[0],
248        ];
249    }
250
251    return $methods;
252}
253
254function manual_navigation_methodname($methodname) {
255    // We strip out any class prefix here, we only want method names
256    if (strpos($methodname, '::') !== false && strpos($methodname, ' ') === false) {
257        $tmp = explode('::', $methodname);
258        $methodname = $tmp[1];
259    }
260
261    // Add zero-width spaces to allow line-breaks at various characters
262    return str_replace(['-', '_'], ['-&#8203;', '_&#8203;'], $methodname);
263}
264
265// Set up variables important for this page
266// including HTTP header information
267function manual_setup($setup): void {
268    global $PGI, $MYSITE, $USERNOTES;
269    global $ACTIVE_ONLINE_LANGUAGES;
270
271    //TODO: get rid of this hack to get the related items into manual_footer
272    global $__RELATED;
273
274    if (!isset($setup["toc_deprecated"])) {
275        $setup["toc_deprecated"] = [];
276    }
277    $PGI = $setup;
278    // Set base href for this manual page
279    $base = 'manual/' . language_convert($setup['head'][1]) . "/";
280    $_SERVER['BASE_PAGE'] = $base . $setup['this'][0];
281    $_SERVER['BASE_HREF'] = $MYSITE . $_SERVER['BASE_PAGE'];
282
283    $timestamps = [
284        filemtime($_SERVER["DOCUMENT_ROOT"] . "/" . $_SERVER["BASE_PAGE"]),
285        filemtime($_SERVER["DOCUMENT_ROOT"] . "/include/prepend.inc"),
286        filemtime($_SERVER["DOCUMENT_ROOT"] . "/styles/theme-base.css"),
287    ];
288
289    // Load user note for this page
290    $filename = $PGI['this'][0];
291
292    // Drop file extension from the name
293    if (substr($filename, -4) == '.php') {
294        $filename = substr($filename, 0, -4);
295    }
296    $USERNOTES = manual_notes_load($filename);
297    if ($USERNOTES) {
298        $note = current($USERNOTES);
299        $timestamps[] = $note->ts;
300    }
301
302    $lastmod = max($timestamps);
303
304    $breadcrumbs = manual_navigation_breadcrumbs($setup);
305    $__RELATED['toc'] = manual_navigation_related($setup);
306    $__RELATED['toc_deprecated'] = manual_navigation_deprecated($setup);
307
308    $config = [
309        "current" => "docs",
310        "breadcrumbs" => $breadcrumbs,
311        "languages" => array_keys($ACTIVE_ONLINE_LANGUAGES),
312        "meta-navigation" => [
313            "contents" => $base . $setup["home"][0],
314            "index" => $base . $setup["up"][0],
315            "prev" => $base . $setup["prev"][0],
316            "next" => $base . $setup["next"][0],
317        ],
318        "lang" => $setup["head"][1],
319        "thispage" => $setup["this"][0],
320        "prev" => $setup["prev"],
321        "next" => $setup["next"],
322        "cache" => $lastmod,
323    ];
324    site_header($setup["this"][1] . " - Manual ", $config);
325
326    $languageChooser = manual_language_chooser($config['lang'], $config['thispage']);
327
328    echo <<<PAGE_TOOLS
329  <div class="page-tools">
330    <div class="change-language">
331      {$languageChooser}
332    </div>
333  </div>
334PAGE_TOOLS;
335}
336
337function manual_language_chooser($currentlang, $currentpage) {
338    global $ACTIVE_ONLINE_LANGUAGES;
339
340    // Prepare the form with all the options
341    $othersel = ' selected="selected"';
342    $out = [];
343    foreach ($ACTIVE_ONLINE_LANGUAGES as $lang => $text) {
344        $selected = '';
345        if ($lang == $currentlang) {
346            $selected = ' selected="selected"';
347            $othersel = '';
348        }
349        $out[] = "<option value='$lang/$currentpage'$selected>$text</option>";
350    }
351    $out[] = "<option value='help-translate.php'{$othersel}>Other</option>";
352    $format_options = implode("\n" . str_repeat('  ', 6), $out);
353
354    $r = <<<CHANGE_LANG
355      <form action="/manual/change.php" method="get" id="changelang" name="changelang">
356        <fieldset>
357          <label for="changelang-langs">Change language:</label>
358          <select onchange="document.changelang.submit()" name="page" id="changelang-langs">
359            {$format_options}
360          </select>
361        </fieldset>
362      </form>
363CHANGE_LANG;
364    return trim($r);
365}
366
367function manual_footer($setup): void {
368    global $USERNOTES, $__RELATED;
369
370    $id = substr($setup['this'][0], 0, -4);
371    $repo = strtolower($setup["head"][1]); // pt_BR etc.
372
373    $edit_url = "https://github.com/php/doc-{$repo}";
374    // If the documentation source information is available (generated using
375    // doc-base/configure.php and PhD) then try and make a source-specific URL.
376    if (isset($setup['source'])) {
377        $source_lang = $setup['source']['lang'];
378        if ($source_lang === $repo || $source_lang === 'base') {
379            $edit_url = "https://github.com/php/doc-{$source_lang}/blob/master/{$setup['source']['path']}";
380        }
381    }
382
383    $lastUpdate = '';
384    if (isset($setup["history"]['modified']) && $setup["history"]['modified'] !== "") {
385        $modifiedDateTime = date_create($setup["history"]['modified']);
386        if ($modifiedDateTime !== false) {
387            $lastUpdate .= "Last updated on " . date_format($modifiedDateTime,"M d, Y (H:i T)");
388            $lastUpdate .= (isset($setup["history"]['contributors'][0]) ? " by " . $setup["history"]['contributors'][0] : "") . ".";
389        }
390    }
391
392    $contributors = '';
393    if (isset($setup["history"]['contributors']) && count($setup["history"]['contributors']) > 0) {
394        $contributors = '<a href="?contributors">All contributors.</a>';
395    }
396
397    echo <<<CONTRIBUTE
398    <div class="contribute">
399      <h3 class="title">Improve This Page</h3>
400      <div>
401        $lastUpdate $contributors
402      </div>
403      <div class="edit-bug">
404        <a href="https://github.com/php/doc-base/blob/master/README.md" title="This will take you to our contribution guidelines on GitHub." target="_blank" rel="noopener noreferrer">Learn How To Improve This Page</a>
405406        <a href="{$edit_url}">Submit a Pull Request</a>
407408        <a href="https://github.com/php/doc-{$repo}/issues/new?body=From%20manual%20page:%20https:%2F%2Fphp.net%2F$id%0A%0A---">Report a Bug</a>
409      </div>
410    </div>
411CONTRIBUTE;
412
413    manual_notes($USERNOTES);
414    site_footer([
415        'related_menu' => $__RELATED['toc'],
416        'related_menu_deprecated' => $__RELATED['toc_deprecated'],
417    ]);
418}
419
420// This function takes a DateTime object and returns a formated string of the time difference relative to now
421function relTime(DateTime $date) {
422    $current = new DateTime();
423    $diff = $current->diff($date);
424    $units = ["year" => $diff->format("%y"),
425        "month" => $diff->format("%m"),
426        "day" => $diff->format("%d"),
427        "hour" => $diff->format("%h"),
428        "minute" => $diff->format("%i"),
429        "second" => $diff->format("%s"),
430    ];
431    $out = "just now...";
432    foreach ($units as $unit => $amount) {
433        if (empty($amount)) {
434            continue;
435        }
436        $out = $amount . " " . ($amount == 1 ? $unit : $unit . "s") . " ago";
437        break;
438    }
439    return $out;
440}
441
442function contributors($setup) {
443    if (!isset($_GET["contributors"])
444        || !isset($setup["history"]["contributors"])
445        || count($setup["history"]["contributors"]) < 1) {
446        return;
447    }
448
449    $contributorList = "<li>" . implode("</li><li>", $setup["history"]["contributors"]) . "</li>";
450
451    echo <<<CONTRIBUTORS
452<div class="book">
453  <h1 class="title">Output Buffering Control</h1>
454  The following have authored commits that contributed to this page:
455  <ul>
456   $contributorList
457  </ul>
458</div>
459CONTRIBUTORS;
460    manual_footer($setup);
461    exit;
462}
463