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 '&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}\"> ¶</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}&page={$rredir_filename}&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}&page={$rredir_filename}&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(['-', '_'], ['-​', '_​'], $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> 405 • 406 <a href="{$edit_url}">Submit a Pull Request</a> 407 • 408 <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