1<?php 2// $Id$ 3 4// Force login before action can be taken 5include '../include/login.inc'; 6include '../include/email-validation.inc'; 7include '../include/note-reasons.inc'; 8 9define("NOTES_MAIL", "php-notes@lists.php.net"); 10define("PHP_SELF", hsc($_SERVER['PHP_SELF'])); 11 12$reject_text = 13'You are receiving this email because your note posted 14to the online PHP manual has been removed by one of the editors. 15 16Read the following paragraphs carefully, because they contain 17pointers to resources better suited for requesting support or 18reporting bugs, none of which are to be included in manual notes 19because there are mechanisms and groups in place to deal with 20those issues. 21 22The user contributed notes are not an appropriate place to 23ask questions, report bugs or suggest new features; please 24use the resources listed on <http://php.net/support> 25for those purposes. This was clearly stated in the page 26you used to submit your note, please carefully re-read 27those instructions before submitting future contributions. 28 29Bug submissions and feature requests should be entered at 30<http://bugs.php.net/>. For documentation errors use the 31bug system, and classify the bug as "Documentation problem". 32Support and ways to find answers to your questions can be found 33at <http://php.net/support>. 34 35Your note has been removed from the online manual.'; 36 37db_connect(); 38 39$action = (isset($_REQUEST['action']) ? preg_replace('/[^\w\d\s_]/', '', $_REQUEST['action']) : ''); 40$id = (isset($_REQUEST['id']) ? intval($_REQUEST['id']) : ''); 41 42/*------ BEGIN SEARCH ------*/ 43if (!$action) { 44 head("user notes"); 45 46 // someting done before ? 47 if ($id) { 48 $str = 'Note #' . $id . ' has been '; 49 switch ($_GET['was']) { 50 case 'delete' : 51 case 'reject' : 52 $str .= ($_GET['was'] == 'delete') ? 'deleted' : 'rejected'; 53 $str .= ' and removed from the manual'; 54 break; 55 case 'edit' : 56 $str .= ' edited'; 57 break; 58 case 'resetall' : 59 $str .= ' reset to 0 votes.'; 60 break; 61 case 'resetup' : 62 $str .= ' reset to 0 up votes.'; 63 break; 64 case 'resetdown' : 65 $str .= ' reset to 0 down votes.'; 66 break; 67 case 'deletevotes' : 68 $str = 'The selected votes have been deleted!'; // INTENTIONALLY missing the concat operator 69 } 70 echo $str . '<br />'; 71 } 72 73 if (isset($_REQUEST['keyword']) || isset($_REQUEST["view"])) { 74 // Pagination start 75 $page = isset($_REQUEST["page"]) ? intval($_REQUEST["page"]) : 0; 76 $NextPage = isset($_REQUEST["page"]) ? intval($_REQUEST["page"]) : 0; 77 $type = isset($_REQUEST["type"]) ? intval($_REQUEST["type"]) : 0; 78 79 if($page < 0) { $page = 0; } 80 if($NextPage < 0) { $NextPage = 0; } 81 $limit = $page * 10; $page++; 82 $limitVotes = $NextPage * 25; $NextPage++; 83 $PrevPage = ($NextPage - 2) > -1 ? $NextPage - 2 : 0; 84 // Pagination end 85 86 if(isset($_REQUEST['keyword'])) { 87 $query = new Query('SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, note.*, UNIX_TIMESTAMP(note.ts) AS ts '. 88 'FROM note '. 89 'LEFT JOIN(votes) ON (note.id = votes.note_id) '. 90 'WHERE '); 91 if (is_numeric($_REQUEST['keyword'])) { 92 $search_heading = 'Search results for #' . (int) $_REQUEST['keyword']; 93 $query->add('note.id = ?', [$_REQUEST['keyword']]); 94 } elseif (substr($_REQUEST['keyword'], 0, 5) == 'sect:') { 95 $search_heading = 'Search results for <em>' . hsc($_REQUEST['keyword']) . '</em>'; 96 $section = str_replace('*', '%', substr($_REQUEST['keyword'], 5)); 97 $query->add("note.sect LIKE ? GROUP BY note.id ORDER BY note.sect, note.ts LIMIT ?int, 10", [$section, $limit]); 98 } else { 99 $search_heading = 'Search results for <em>' . hsc($_REQUEST['keyword']) . '</em>'; 100 $query->add( 101 "note.note LIKE ? GROUP BY note.id LIMIT ?int, 10", 102 ['%' . $_REQUEST['keyword'] . '%', $limit]); 103 } 104 $result = db_query_safe($query->get()); 105 } else { 106 /* Added new voting information to be included in note from votes table. */ 107 /* First notes */ 108 if ($type == 1) { 109 $search_heading = 'First notes'; 110 $result = db_query_safe("SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, note.*, UNIX_TIMESTAMP(note.ts) AS ts ". 111 "FROM note ". 112 "LEFT JOIN(votes) ON (note.id = votes.note_id) ". 113 "GROUP BY note.id ORDER BY note.id ASC LIMIT ?int, 10", [$limit]); 114 /* Minor notes */ 115 } else if ($type == 2) { 116 $search_heading = 'Minor notes'; 117 $result = db_query_safe("SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, note.*, UNIX_TIMESTAMP(note.ts) AS ts ". 118 "FROM note ". 119 "LEFT JOIN(votes) ON (note.id = votes.note_id) ". 120 "GROUP BY note.id ORDER BY LENGTH(note.note) ASC LIMIT ?int, 10", [$limit]); 121 /* Top rated notes */ 122 } else if ($type == 3) { 123 $search_heading = 'Top rated notes'; 124 $result = db_query_safe("SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, ". 125 "ROUND((SUM(votes.vote) / COUNT(votes.vote)) * 100) AS rate, ". 126 "(SUM(votes.vote) - (COUNT(votes.vote) - SUM(votes.vote))) AS arating, ". 127 "note.id, note.sect, note.user, note.note, UNIX_TIMESTAMP(note.ts) AS ts ". 128 "FROM note ". 129 "JOIN(votes) ON (note.id = votes.note_id) ". 130 "GROUP BY note.id ORDER BY arating DESC, up DESC, rate DESC, down DESC LIMIT ?int, 10", [$limit]); 131 /* Bottom rated notes */ 132 } else if ($type == 4) { 133 $search_heading = 'Bottom rated notes'; 134 $result = db_query_safe("SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, ". 135 "ROUND((SUM(votes.vote) / COUNT(votes.vote)) * 100) AS rate, ". 136 "(SUM(votes.vote) - (COUNT(votes.vote) - SUM(votes.vote))) AS arating, ". 137 "note.id, note.sect, note.user, note.note, UNIX_TIMESTAMP(note.ts) AS ts ". 138 "FROM note ". 139 "JOIN(votes) ON (note.id = votes.note_id) ". 140 "GROUP BY note.id ORDER BY arating ASC, up ASC, rate ASC, down DESC LIMIT ?int, 10", [$limit]); 141 /* Votes table view */ 142 } else if ($type == 5) { 143 $search_votes = true; // set this only to change the output between votes table and notes table 144 if (!empty($_GET['votessearch'])) { 145 if (($iprange = wildcard_ip($_GET['votessearch'])) !== false) { 146 $search = html_entity_decode($_GET['votessearch'], ENT_QUOTES, 'UTF-8'); 147 $start = $iprange[0]; 148 $end = $iprange[1]; 149 $resultCount = db_query_safe("SELECT count(votes.id) AS total_votes FROM votes JOIN (note) ON (votes.note_id = note.id) WHERE ". 150 "(hostip >= ? AND hostip <= ?) OR (ip >= ? AND ip <= ?)", [$start, $end, $start, $end]); 151 $resultCount = mysql_fetch_assoc($resultCount); 152 $resultCount = $resultCount['total_votes']; 153 $isSearch = '&votessearch=' . hsc($search); 154 $result = db_query_safe( 155 'SELECT votes.id, UNIX_TIMESTAMP(votes.ts) AS ts, votes.vote, votes.note_id, note.sect, votes.hostip, votes.ip '. 156 'FROM votes JOIN(note) ON (votes.note_id = note.id) '. 157 'WHERE (hostip >= ? AND hostip <= ?) OR (ip >= ? AND ip <= ?) '. 158 'ORDER BY votes.id DESC LIMIT ?int, 25', 159 [$start, $end, $start, $end, $limitVotes]); 160 161 } elseif (filter_var(html_entity_decode($_GET['votessearch'], ENT_QUOTES, 'UTF-8'), FILTER_VALIDATE_IP)) { 162 $searchip = (int) ip2long(filter_var(html_entity_decode($_GET['votessearch'], ENT_QUOTES, 'UTF-8'), FILTER_VALIDATE_IP)); 163 $resultCount = db_query_safe("SELECT count(votes.id) AS total_votes FROM votes JOIN(note) ON (votes.note_id = note.id) WHERE hostip = ? OR ip = ?", [$searchip, $searchip]); 164 $resultCount = mysql_fetch_assoc($resultCount); 165 $resultCount = $resultCount['total_votes']; 166 $isSearch = '&votessearch=' . hsc(long2ip($searchip)); 167 $result = db_query_safe( 168 "SELECT votes.id, UNIX_TIMESTAMP(votes.ts) AS ts, votes.vote, votes.note_id, note.sect, votes.hostip, votes.ip ". 169 "FROM votes JOIN(note) ON (votes.note_id = note.id) ". 170 "WHERE hostip = ? OR ip = ? ". 171 "ORDER BY votes.id DESC LIMIT ?int, 25", 172 [$searchip, $searchip, $limitVotes]); 173 } else { 174 $search = (int) html_entity_decode($_GET['votessearch'], ENT_QUOTES, 'UTF-8'); 175 $resultCount = db_query_safe("SELECT count(votes.id) AS total_votes FROM votes JOIN(note) ON (votes.note_id = note.id) WHERE votes.note_id = ?", [$search]); 176 $resultCount = mysql_fetch_assoc($resultCount); 177 $resultCount = $resultCount['total_votes']; 178 $isSearch = '&votessearch=' . hsc($search); 179 $result = db_query_safe( 180 "SELECT votes.id, UNIX_TIMESTAMP(votes.ts) AS ts, votes.vote, votes.note_id, note.sect, votes.hostip, votes.ip ". 181 "FROM votes JOIN(note) ON (votes.note_id = note.id) ". 182 "WHERE votes.note_id = ? ". 183 "ORDER BY votes.id DESC LIMIT ?int, 25", 184 [$search, $limitVotes]); 185 } 186 } else { 187 $isSearch = null; 188 $resultCount = db_query_safe("SELECT COUNT(votes.id) AS total_votes FROM votes JOIN(note) ON (votes.note_id = note.id)"); 189 $resultCount = mysql_fetch_assoc($resultCount); 190 $resultCount = $resultCount['total_votes']; 191 $result = db_query_safe( 192 "SELECT votes.id, UNIX_TIMESTAMP(votes.ts) AS ts, votes.vote, votes.note_id, note.sect, votes.hostip, votes.ip ". 193 "FROM votes JOIN(note) ON (votes.note_id = note.id) ". 194 "ORDER BY votes.id DESC LIMIT ?int, 25", 195 [$limitVotes]); 196 } 197 /* IPs with the most votes -- aggregated data */ 198 } elseif ($type == 6) { 199 $votes_by_ip = true; // only set this get the table for top IPs with votes 200 $result = db_query_safe( 201 "SELECT DISTINCT(votes.ip), COUNT(votes.ip) as votes, COUNT(DISTINCT(votes.note_id)) as notes, ". 202 "INET_NTOA(votes.ip) AS ip, MIN(UNIX_TIMESTAMP(votes.ts)) AS `from`, MAX(UNIX_TIMESTAMP(votes.ts)) AS `to` ". 203 "FROM votes ". 204 "JOIN (note) ON (votes.note_id = note.id) GROUP BY votes.ip ORDER BY votes DESC LIMIT 100"); 205 /* Last notes */ 206 } else { 207 $search_heading = 'Last notes'; 208 $result = db_query_safe( 209 "SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, note.*, UNIX_TIMESTAMP(note.ts) AS ts ". 210 "FROM note LEFT JOIN(votes) ON (note.id = votes.note_id) ". 211 "GROUP BY note.id ORDER BY note.id DESC LIMIT ?int, 10", 212 [$limit]); 213 } 214 } 215 216 if ($result) { 217 /* This is a special table only used for viewing the most recent votes */ 218 $t = (isset($_GET['type']) ? '&type=' . $_GET['type'] : null); 219 if (!empty($search_votes)) { 220 $from = $limitVotes + 1; 221 $to = $NextPage * 25; 222 $to = $to > $resultCount ? $resultCount : $to; 223 if ($resultCount) { 224 echo "<p><strong>Showing $from - $to of $resultCount results.</strong></p>"; 225 echo "<form method=\"POST\" action=\"" . PHP_SELF . "?action=deletevotes{$t}\" id=\"votesdeleteform\">". 226 "<table width=\"100%\">". 227 " <thead>". 228 " <tr style=\"text-align: center; background-color: #99C; font-size: 18px;\">\n". 229 " <td colspan=\"7\" width=\"100%\" style=\"padding: 5px;\"><strong>Most Recent Votes</strong></td>\n". 230 " </tr>\n". 231 " <tr style=\"background-color: #99C; 18px;\">\n". 232 " <td style=\"padding: 5px;\"><input type=\"checkbox\" id=\"votesselectall\" /></td> 233 <td style=\"padding: 5px;\"><strong>Date</strong></td> 234 <td style=\"padding: 5px;\"><strong>Vote</strong></td> 235 <td style=\"padding: 5px;\"><strong>Note ID</strong></td> 236 <td style=\"padding: 5px;\"><strong>Note Section</strong></td> 237 <td style=\"padding: 5px;\"><strong>Host IP</strong></td> 238 <td style=\"padding: 5px;\"><strong>Client IP</strong></td>\n". 239 " </tr>\n". 240 " </thead>\n". 241 " <tbody>\n"; 242 } else { 243 echo "<p><strong>No results found...</strong></p>"; 244 } 245 } 246 /* This is a special table only used for viewing top IPs by votes */ 247 if (!empty($votes_by_ip)) { 248 echo "<form method=\"POST\" action=\"" . PHP_SELF . "?action=deletevotes{$t}\" id=\"votesdeleteform\">". 249 "<table width=\"100%\">". 250 " <thead>". 251 " <tr style=\"text-align: center; background-color: #99C; font-size: 18px;\">\n". 252 " <td colspan=\"5\" width=\"100%\" style=\"padding: 5px;\"><strong>IPs With Most Votes</strong></td>\n". 253 " </tr>\n". 254 " <tr style=\"background-color: #99C; 18px;\">\n". 255 " <td style=\"padding: 5px;\"><strong>Client IP Address</strong></td> 256 <td style=\"padding: 5px;\"><strong>Number of Votes</strong></td> 257 <td style=\"padding: 5px;\"><strong>Number of Notes</strong></td> 258 <td style=\"padding: 5px;\"><strong>First Vote Cast</strong></td> 259 <td style=\"padding: 5px;\"><strong>Last Vote Cast</strong></td>\n". 260 " </tr>\n". 261 " </thead>\n". 262 " <tbody>\n"; 263 } 264 if (!empty($search_heading)) { 265 echo "<h2>$search_heading</h2>"; 266 } 267 while ($row = mysql_fetch_assoc($result)) { 268 /* 269 I had to do this because the JOIN queries will return a single row of NULL values even when no rows match. 270 So the `if (mysql_num_rows($result))` check earlier becomes useless and as such I had to replace it with this. 271 */ 272 if (mysql_num_rows($result) == 1 && !array_filter($row)) { 273 echo "<p>No results found...</p>"; 274 continue; 275 } 276 $id = isset($row['id']) ? $row['id'] : null; 277 /* This div is only available in cases where the query includes the voting info */ 278 if (isset($row['up']) && isset($row['down'])) { 279 $rating = isset($row['arating']) ? $row['arating'] : ($row['up'] - $row['down']); 280 if ($rating < 0) { 281 $rating = "<span style=\"color: red;\">$rating</span>"; 282 } elseif ($rating > 0) { 283 $rating = "<span style=\"color: green;\">$rating</span>"; 284 } else { 285 $rating = "<span style=\"color: blue;\">$rating</span>"; 286 } 287 288 if (isset($row['rate'])) { // not all queries select the rate 289 $percentage = $row['rate']; 290 } else { 291 if ($row['up'] + $row['down']) { // prevents division by zero warning 292 $percentage = round(($row['up'] / ($row['up'] +$row['down'])) * 100); 293 } else { 294 $precentage = 0; 295 } 296 } 297 $percentage = sprintf('%d%%', $percentage); 298 299 echo "<div style=\"float: right; clear: both; border: 1px solid gray; padding: 5px; background-color: lightgray;\">\n". 300 "<div style=\"display: inline-block; float: left; padding: 15px;\"><strong>Up votes</strong>: {$row['up']}</div>\n". 301 "<div style=\"display: inline-block; float: left; padding: 15px;\"><strong>Down votes</strong>: {$row['down']}</div>\n". 302 "<div style=\"display: inline-block; float: left; padding: 15px;\"><strong>Rating</strong>: $rating (<em>$percentage like this</em>)</div>\n". 303 " <div style=\"padding: 15px;\">\n". 304 " <a href=\"?action=resetall&id={$id}\">Reset all votes</a> |". 305 " <a href=\"?action=resetup&id={$id}\">Reset up votes</a> |". 306 " <a href=\"?action=resetdown&id={$id}\">Reset down votes</a> |". 307 " <a href=\"?votessearch={$id}&view=notes&type=5\">See Votes</a>\n". 308 " </div>\n". 309 "</div>\n"; 310 } 311 /* This is a special table only used for viewing the most recent votes */ 312 if (!empty($search_votes)) { 313 $row['ts'] = date('Y-m-d H:i:s', $row['ts']); 314 $row['vote'] = '<span style="color: ' . ($row['vote'] ? 'green;">+1' : 'red;">-1') . '</span>'; 315 $row['hostip'] = long2ip($row['hostip']); 316 $row['ip'] = long2ip($row['ip']); 317 $notelink = "http://php.net/{$row['sect']}#{$row['note_id']}"; 318 $sectlink = "http://php.net/{$row['sect']}"; 319 echo " <tr style=\"background-color: #F0F0F0;\">\n". 320 " <td style=\"padding: 5px;\"><input type=\"checkbox\" name=\"deletevote[]\" class=\"vdelids\" value=\"{$row['id']}\" /></td>\n". 321 " <td style=\"padding: 5px;\">{$row['ts']}</td>\n". 322 " <td style=\"padding: 5px;\">{$row['vote']}</td>\n". 323 " <td style=\"padding: 5px;\"><a href=\"$notelink\" target=\"_blank\">{$row['note_id']}</a></td>\n". 324 " <td style=\"padding: 5px;\"><a href=\"$sectlink\" target=\"_blank\">{$row['sect']}</a></td>\n". 325 " <td style=\"padding: 5px;\">{$row['hostip']}</td>\n". 326 " <td style=\"padding: 5px;\">{$row['ip']}</td>\n". 327 " </tr>\n"; 328 /* This is a special table only used for viewing top IPs by votes */ 329 } elseif(!empty($votes_by_ip)) { 330 $from = date('Y-m-d H:i:s', $row['from']); 331 $to = date('Y-m-d H:i:s', $row['to']); 332 $ip = hsc($row['ip']); 333 echo " <tr style=\"background-color: #F0F0F0;\">\n". 334 " <td style=\"padding: 5px;\"><a href=\"?view=votes&type=5&votessearch=$ip\">$ip</a></td>\n". 335 " <td style=\"padding: 5px;\">{$row['votes']}</td>\n". 336 " <td style=\"padding: 5px;\">{$row['notes']}</td>\n". 337 " <td style=\"padding: 5px;\">{$from}</td>\n". 338 " <td style=\"padding: 5px;\">{$to}</td>\n". 339 " </tr>\n"; 340 /* Everything else in search should fall through here */ 341 } else { 342 echo "<p class=\"notepreview\">",clean_note($row['note']), 343 "<br /><span class=\"author\">",date("d-M-Y h:i",$row['ts'])," ", 344 hsc($row['user']),"</span><br />", 345 "Note id: $id<br />\n", 346 "<a href=\"http://php.net/manual/en/{$row['sect']}.php#{$id}\" target=\"_blank\">http://php.net/manual/en/{$row['sect']}.php#{$id}</a><br />\n", 347 "<a href=\"https://master.php.net/note/edit/$id\" target=\"_blank\">Edit Note</a><br />"; 348 foreach ($note_del_reasons AS $reason => $text) { 349 echo '<a href="https://master.php.net/note/delete/', $id, '/', urlencode((string)$reason), '" target=\"_blank\">', 'Delete Note: ', hsc($text), "</a><br />\n"; 350 } 351 echo "<a href=\"https://master.php.net/note/delete/$id\" target=\"_blank\">Delete Note: other reason</a><br />", 352 "<a href=\"https://master.php.net/note/reject/$id\" target=\"_blank\">Reject Note</a>", 353 "</p>", 354 "<hr />"; 355 } 356 } 357 /* This is a special table only used for viewing the most recent votes */ 358 if (!empty($search_votes)) { 359 if ($resultCount) { 360 echo " </tbody>\n". 361 "</table>\n". 362 "<input type=\"submit\" name=\"deletevotes\" value=\"Delete Selected Votes\" />\n". 363 "<input type=\"hidden\" name=\"votessearch\" value=\"". 364 (isset($_GET['votessearch']) ? hsc($_GET['votessearch']) : ''). 365 "\" />". 366 "</form>\n"; 367 } 368 echo "<form method=\"GET\" action=\"" . PHP_SELF . "\">\n". 369 " <strong>Search for votes by IP address or Note ID</strong> - (<em>wild card searches are allowed e.g. 127.0.0.*</em>): ". 370 "<input type=\"text\" name=\"votessearch\" value=\"". 371 (isset($_GET['votessearch']) ? hsc($_GET['votessearch']) : ''). 372 "\" /> <input type=\"submit\" value=\"Search\" />\n". 373 "<input type=\"hidden\" name=\"view\" value=\"notes\" />\n". 374 "<input type=\"hidden\" name=\"type\" value=\"" . (isset($_GET['type']) ? hsc($_GET['type']) : 5) . "\" />\n". 375 "</form>\n"; 376 } 377 /* This is a special table only used for viewing top IPs by votes */ 378 if (!empty($votes_by_ip)) { 379 echo " </tbody>\n". 380 "</table>\n". 381 "<p>This information should only be used to determine if there are any IP addresses with an unusually high ". 382 "number of votes placed in a small timeframe to help detect spam and other potential abuse.</p>\n". 383 "<p>Also note that a <em>0.0.0.0</em> IP address indicates a client IP could not be resolved at the time of voting.</p>"; 384 } 385 if((isset($_REQUEST["view"]) || isset($_REQUEST['keyword'])) && empty($search_votes)) { 386 $keyword = isset($_REQUEST['keyword']) ? '&keyword=' . urlencode($_REQUEST['keyword']) : ''; 387 echo "<p><a href=\"?view=notes&page=$page&type=$type$keyword\">Next 10</a>"; 388 } elseif (isset($_REQUEST["view"]) && !empty($search_votes)) { 389 echo "<p>"; 390 if (isset($NextPage) && $NextPage > 1) { 391 echo "<a href=\"?view=notes&page=$PrevPage&type=$type{$isSearch}\">< Prev 25</a> "; 392 } 393 if (isset($to) && isset($resultCount) && $to < $resultCount) { 394 echo " <a href=\"?view=notes&page=$NextPage&type=$type{$isSearch}\">Next 25 ></a>"; 395 } 396 echo "</p>"; 397 } 398 } 399 } 400?> 401 402<h2>Menu</h2> 403<?php if (allow_mass_change($cuser)): ?><p><a href="<?= PHP_SELF ?>?action=mass">Mass change of sections</a></p><?php endif; ?> 404<p><a href="<?= PHP_SELF ?>?view=notes&type=0">View last 10 notes</a></p> 405<p><a href="<?= PHP_SELF ?>?view=notes&type=1">View first 10 notes</a></p> 406<p><a href="<?= PHP_SELF ?>?view=notes&type=2">View minor 10 notes</a></p> 407<p><a href="<?= PHP_SELF ?>?view=notes&type=3">View top 10 rated notes</a></p> 408<p><a href="<?= PHP_SELF ?>?view=notes&type=4">View bottom 10 rated notes</a></p> 409<p><a href="<?= PHP_SELF ?>?view=notes&type=5">View votes table</a></p> 410<p><a href="<?= PHP_SELF ?>?view=notes&type=6">IPs with the most votes</a></p> 411<p><a href="<?= PHP_SELF ?>?action=sect">Search notes within a section</a></p> 412<p><a href="<?= PHP_SELF ?>?action=voting_stats">User contributed voting statistics</a></p> 413<?php 414 foot(); 415 exit; 416} 417/*------ END SEARCH ------*/ 418 419 420if (preg_match("/^(.+)\\s+(\\d+)\$/", $action, $m)) { 421 $action = $m[1]; $id = $m[2]; 422} 423/* hack around the rewrite rules */ 424if (isset($_GET['action']) && ($_GET['action'] == 'resetall' || $_GET['action'] == 'resetup' || $_GET['action'] == 'resetdown' || $_GET['action'] == 'deletevotes')) { 425 $action = $_GET['action']; 426 $id = isset($_GET['id']) ? (int)$_GET['id'] : null; 427} 428 429switch($action) { 430case 'mass': 431 head("user notes"); 432 if (!allow_mass_change($cuser)) { 433 warn("You are not allowed to take this action!"); 434 foot(); 435 exit; 436 } 437 $step = (isset($_REQUEST["step"]) ? (int)$_REQUEST["step"] : 0); 438 $where = new Query(); 439 if (!empty($_REQUEST["old_sect"])) { 440 $where->add("sect = ?", [$_REQUEST["old_sect"]]); 441 } 442 if (!empty($_REQUEST["ids"])) { 443 if (preg_match('~^([0-9]+, *)*[0-9]+$~i', $_REQUEST["ids"])) { 444 if ($where->get() !== '') { 445 $where->add(' AND '); 446 } 447 // Safe because we checked that ids is a comma-separated list of numbers. 448 $where->add("id IN (".$_REQUEST['ids'].")"); 449 } else { 450 echo "<p><b>Incorrect format of notes IDs.</b></p>\n"; 451 $step = 0; 452 } 453 } 454 455 if ($step == 2) { 456 $query = new Query('UPDATE note SET sect = ? WHERE ', [$_REQUEST["new_sect"]]); 457 $query->addQuery($where); 458 db_query($query); 459 echo "<p>Mass change succeeded.</p>\n"; 460 } elseif ($step == 1) { 461 if (!empty($_REQUEST["new_sect"]) && $where) { 462 $query = new Query('SELECT COUNT(*) FROM note WHERE '); 463 $query->addQuery($where); 464 $result = db_query($query); 465 if (!($count = mysql_result($result, 0, 0))) { 466 echo "<p>There are no such notes.</p>\n"; 467 } else { 468 $step = 2; 469 $msg = "Are you sure to change section of <b>$count note(s)</b>"; 470 $msg .= (!empty($_REQUEST["ids"]) ? " with IDs <b>" . hsc($_REQUEST['ids']) . "</b>" : ""); 471 $msg .= (!empty($_REQUEST["old_sect"]) ? " from section <b>" . hsc($_REQUEST['old_sect']) . "</b>" : ""); 472 $msg .= " to section <b>" . hsc($_REQUEST['new_sect']) . "</b>?"; 473 echo "<p>$msg</p>\n"; 474?> 475<form action="<?= PHP_SELF; ?>?action=mass" method="post"> 476<input type="hidden" name="step" value="2"> 477<input type="hidden" name="old_sect" value="<?= hsc($_REQUEST["old_sect"]); ?>"> 478<input type="hidden" name="ids" value="<?= hsc($_REQUEST["ids"]); ?>"> 479<input type="hidden" name="new_sect" value="<?= hsc($_REQUEST["new_sect"]); ?>"> 480<input type="submit" value="Change"> 481</form> 482<?php 483 } 484 } else { 485 if (empty($_REQUEST["new_sect"])) { 486 echo "<p><b>You have to fill-in new section.</b></p>\n"; 487 } 488 if (!$where) { 489 echo "<p><b>You have to fill-in curent section or notes IDs (or both).</b></p>\n"; 490 } 491 } 492 } 493 if ($step < 2) { 494?> 495<form action="<?= PHP_SELF; ?>?action=mass" method="post"> 496<input type="hidden" name="step" value="1"> 497<p>Change section of notes which fit these criteria:</p> 498<table> 499 <tr> 500 <th align="right">Current section:</th> 501 <td><input type="text" name="old_sect" value="<?= hsc($_REQUEST["old_sect"]); ?>" size="30" maxlength="80" /> (filename without extension)</td> 502 </tr> 503 <tr> 504 <th align="right">Notes IDs:</th> 505 <td><input type="text" name="ids" value="<?= hsc($_REQUEST["ids"]); ?>" size="30" maxlength="80" /> (comma separated list)</td> 506 </tr> 507 <tr> 508 <th align="right">Move to section:</th> 509 <td><input type="text" name="new_sect" value="<?= hsc($_REQUEST["new_sect"]); ?>" size="30" maxlength="80" /></td> 510 </tr> 511 <tr> 512 <td align="center" colspan="2"> 513 <input type="submit" value="Change" /> 514 </td> 515 </tr> 516</table> 517</form> 518<?php 519 } 520 echo "<p><a href='", PHP_SELF, "'>Back to notes index</a></p>\n"; 521 foot(); 522 exit; 523case 'approve': 524 if ($id) { 525 if ($row = note_get_by_id($id)) { 526 527 if ($row['status'] != 'na') { 528 die ("Note #$id has already been approved"); 529 } 530 531 if ($row['id'] && db_query_safe("UPDATE note SET status=NULL WHERE id=?", [$id])) { 532 note_mail_on_action( 533 $cuser, 534 $id, 535 "note {$row['id']} approved from {$row['sect']} by $cuser", 536 "This note has been approved and will appear in the manual.\n\n----\n\n{$row['note']}" 537 ); 538 } 539 540 print "Note #$id has been approved and will appear in the manual"; 541 exit; 542 } 543 } 544case 'reject': 545case 'delete': 546 if ($id) { 547 if ($row = note_get_by_id($id)) { 548 if ($row['id'] && db_query_safe("DELETE note,votes FROM note LEFT JOIN (votes) ON (note.id = votes.note_id) WHERE note.id = ?", [$id])) { 549 $action_taken = ($action == "reject" ? "rejected" : "deleted"); 550 note_mail_on_action( 551 $cuser, 552 $id, 553 "note {$row['id']} $action_taken from {$row['sect']} by $cuser", 554 "Note Submitter: " . safe_email($row['user']) . 555 (isset($reason) ? "\nReason: $reason" : " ") . 556 "\n\n----\n\n{$row['note']}"); 557 if ($action == 'reject') { 558 note_mail_user($row['user'], "note $row[id] rejected and deleted from $row[sect] by notes editor $cuser",$reject_text."\n\n----- Copy of your note below -----\n\n".$row['note']); 559 } 560 } 561 562 //if we came from an email, report _something_ 563 if (isset($_GET['report'])) { 564 header('Location: user-notes.php?id=' . $id . '&was=' . $action); 565 exit; 566 } else { 567 //if not, just close the window 568 echo '<script language="javascript">window.close();</script>'; 569 } 570 exit; 571 } 572 } 573 /* falls through, with id not set. */ 574case 'preview': 575case 'edit': 576 if ($id) { 577 $note = (isset($_POST['note']) ? $_POST['note'] : null); 578 if (!isset($note) || $action == 'preview') { 579 head("user notes"); 580 } 581 582 $row = note_get_by_id($id); 583 584 $email = (isset($_POST['email']) ? html_entity_decode($_POST['email'],ENT_QUOTES) : $row['user']); 585 $sect = (isset($_POST['sect']) ? html_entity_decode($_POST['sect'],ENT_QUOTES) : $row['sect']); 586 587 if (isset($note) && $action == "edit") { 588 if (db_query_safe('UPDATE note SET note=?,user=?,sect=?,updated=NOW() WHERE id=?', [html_entity_decode($note,ENT_QUOTES), $email, $sect, $id])) { 589 note_mail_on_action( 590 $cuser, 591 $id, 592 "note {$row['id']} modified in {$row['sect']} by $cuser", 593 $note."\n\n--was--\n{$row['note']}\n\nhttp://php.net/manual/en/{$row['sect']}.php" 594 ); 595 if ($row["sect"] != $sect) { 596 note_mail_user($email, "note $id moved from $row[sect] to $sect by notes editor $cuser", "----- Copy of your note below -----\n\n".$note); 597 } 598 header('Location: user-notes.php?id=' . $id . '&was=' . $action); 599 exit; 600 } 601 } 602 603 $note = isset($note) ? $note : $row['note']; 604 605 if ($action == "preview") { 606 echo "<p class=\"notepreview\">",clean_note($note), 607 "<br /><span class=\"author\">",date("d-M-Y h:i",$row['ts'])," ", 608 hsc($email),"</span></p>"; 609 } 610?> 611<form method="post" action="<?= PHP_SELF ?>"> 612<input type="hidden" name="id" value="<?= $id ?>" /> 613<table> 614 <tr> 615 <th align="right">Section:</th> 616 <td><input type="text" name="sect" value="<?= hsc($sect) ?>" size="30" maxlength="80" /></td> 617 </tr> 618 <tr> 619 <th align="right">email:</th> 620 <td><input type="text" name="email" value="<?= hsc($email) ?>" size="30" maxlength="80" /></td> 621 </tr> 622 <tr> 623 <td colspan="2"><textarea name="note" cols="70" rows="15"><?= hsc($note) ?></textarea></td> 624 </tr> 625 <tr> 626 <td align="center" colspan="2"> 627 <input type="submit" name="action" value="edit" /> 628 <input type="submit" name="action" value="preview" /> 629 </td> 630 </tr> 631</table> 632</form> 633<?php 634 foot(); 635 exit; 636 } 637case 'resetall': 638case 'resetup': 639case 'resetdown': 640 /* Only those with privileges in allow_mass_change may use these options */ 641 head('user notes'); 642 if (!allow_mass_change($cuser)) { 643 warn("You do not have access to use this feature!"); 644 foot(); 645 exit; 646 } 647 /* Reset votes for user note -- effectively deletes votes found for that note_id in the votes table: up/down/both */ 648 if ($id) { 649 if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') { 650 /* Make sure the note has votes before we attempt to delete them */ 651 $result = db_query_safe("SELECT COUNT(id) AS id FROM votes WHERE note_id = ?", [$id]); 652 $rows = mysql_fetch_assoc($result); 653 if (!$rows['id']) { 654 echo "<p>No votes exist for Note ID ". hsc($id) ."!</p>"; 655 } else { 656 if ($action == 'resetall' && isset($_POST['resetall'])) { 657 db_query_safe('DELETE FROM votes WHERE votes.note_id = ?', [$id]); 658 /* 1 for up votes */ 659 } elseif ($action == 'resetup' && isset($_POST['resetup'])) { 660 db_query_safe('DELETE FROM votes WHERE votes.note_id = ? AND votes.vote = 1', [$id]); 661 /* 0 for down votes */ 662 } elseif ($action == 'resetdown' && isset($_POST['resetdown'])) { 663 db_query_safe('DELETE FROM votes WHERE votes.note_id = ? AND votes.vote = 0', [$id]); 664 } 665 header('Location: user-notes.php?id=' . urlencode($id) . '&was=' . urlencode($action)); 666 } 667 } else { 668 $sql = 'SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down, note.*, UNIX_TIMESTAMP(note.ts) AS ts '. 669 'FROM note '. 670 'JOIN(votes) ON (note.id = votes.note_id) '. 671 'WHERE note.id = ?'; 672 $result = db_query_safe($sql, [$id]); 673 if (mysql_num_rows($result)) { 674 $row = mysql_fetch_assoc($result); 675 $out = "<p>\nAre you sure you want to reset all votes for <strong>Note #". hsc($row['id']) ."</strong>? "; 676 if ($action == 'resetall') { 677 $out .= "This will permanently delete all <em>". hsc($row['up']) ."</em> up votes and <em>". hsc($row['down']) ."</em> down votes for this note.\n</p>\n". 678 "<form method=\"POST\" action=\"\">\n". 679 " <input type=\"submit\" value\"Yes Reset!\" name=\"resetall\" />\n". 680 "</form>\n"; 681 } elseif ($action == 'resetup') { 682 $out .= "This will permanently delete all <em>". hsc($row['up']) ."</em> up votes for this note.\n</p>\n". 683 "<form method=\"POST\" action=\"\">\n". 684 " <input type=\"submit\" value\"Yes Reset!\" name=\"resetup\" />\n". 685 "</form>\n"; 686 } elseif ($action == 'resetdown') { 687 $out .= "This will permanently delete all <em>". hsc($row['down']) ."</em> down votes for this note.\n</p>\n". 688 "<form method=\"POST\" action=\"\">\n". 689 " <input type=\"submit\" value\"Yes Reset!\" name=\"resetdown\" />\n". 690 "</form>\n"; 691 } 692 echo $out; 693 } else { 694 echo "<p>Note ". hsc($id) ." does not exist!</p>"; 695 } 696 } 697 } else { 698 echo "<p>Note id not supplied...</p>"; 699 } 700 foot(); 701 exit; 702case 'deletevotes': 703 /* Only those with privileges in allow_mass_change may use these options */ 704 if (!allow_mass_change($cuser)) { 705 die("You do not have access to use this feature!"); 706 } 707 /* Delete votes -- effectively deletes votes found in the votes table matching all supplied ids */ 708 if (empty($_POST['deletevote']) || !is_array($_POST['deletevote'])) { 709 die("No vote ids supplied!"); 710 } 711 $ids = []; 712 foreach ($_POST['deletevote'] as $id) { 713 $ids[] = (int) $id; 714 } 715 $ids = implode(',',$ids); 716 // This is safe, because $ids is an array of integers. 717 if (db_query_safe("DELETE FROM votes WHERE id IN ($ids)")) { 718 header('Location: user-notes.php?id=1&view=notes&was=' . urlencode($action) . 719 (isset($_REQUEST['type']) ? ('&type=' . urlencode($_REQUEST['type'])) : null) . 720 (isset($_REQUEST['votessearch']) ? '&votessearch=' . urlencode($_REQUEST['votessearch']) : null) 721 ); 722 } 723 exit; 724case 'sect': 725 head('user notes'); 726?> 727<h2>Search within a section</h2> 728<p> 729 You can search notes within specified section of the PHP manual using form below or 730 by prepending your query with <em>sect:</em> in regular search form (like <em>sect:book.mysql</em>). 731</p> 732<p> 733 You can use <em>*</em> as a wildcard, like <em>mysql.*</em>. Query like <em>function.json-*</em> should 734 show all notes for JSON functions (use <em>sect:function.json-*</em> in case of generic form). 735</p> 736<form method="get" action="<?= PHP_SELF ?>"> 737 <strong>Section:</strong> 738 <input type="hidden" name="action" value="sect" /> 739 <input type="text" name="query" /><br /> 740 <input type="submit" value="Search" /> 741</form> 742<?php 743 if (isset($_GET['query'])) { 744 header('Location: user-notes.php?keyword=sect:' . $_GET['query']); 745 exit; 746 } 747 748 foot(); 749 exit; 750case 'voting_stats': 751 head('user notes'); 752 /* Calculate dates */ 753 $today = strtotime('midnight'); 754 $week = !date('w') ? strtotime('midnight') : strtotime('Last Sunday'); 755 $month = strtotime('First Day of ' . date('F') . ' ' . date('Y')); 756 $yesterday = strtotime('midnight yesterday'); 757 $lastweek = !date('w') ? strtotime('midnight -1 week') : strtotime('Last Sunday -1 week'); 758 $lastmonth = strtotime('First Day of last month'); 759 /* Handle stats queries for voting here */ 760 $stats_sql = $stats = []; 761 $stats_sql['Total'] = new Query('SELECT COUNT(votes.id) AS total FROM votes'); 762 $stats_sql['Total Up'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE votes.vote = 1'); 763 $stats_sql['Total Down'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE votes.vote = 0'); 764 $stats_sql['Today'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ?', [$today]); 765 $stats_sql['This Week'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ?', [$week]); 766 $stats_sql['This Month'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ?', [$month]); 767 $stats_sql['Yesterday'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ? AND UNIX_TIMESTAMP(votes.ts) < ?', [$yesterday, $today]); 768 $stats_sql['Last Week'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ? AND UNIX_TIMESTAMP(votes.ts) < ?', [$lastweek, $week]); 769 $stats_sql['Last Month'] = new Query('SELECT COUNT(votes.id) AS total FROM votes WHERE UNIX_TIMESTAMP(votes.ts) >= ? AND UNIX_TIMESTAMP(votes.ts) < ?', [$lastmonth, $month]); 770 foreach ($stats_sql as $key => $query) { 771 $result = db_query($query); 772 $row = mysql_fetch_assoc($result); 773 $stats[$key] = $row['total']; 774 } 775 ?> 776 <h2>User contributed voting statistics</h2> 777 <div style="float: left; border: 1px solid gray; padding: 5px; background-color: #C8C8C0; margin-bottom: 20px;"> 778 <?php foreach (array_chunk($stats, 3, true) as $statset) { ?> 779 <?php foreach ($statset as $figure => $stat) { ?> 780 <div style="display: inline-block; float: left; padding: 15px; border-bottom: 1px solid white; color: #483D8B;"><strong><?= $figure ?></strong>: <?= $stat ?></div> 781 <?php } ?> 782 <p> </p> 783 <?php } ?> 784 </div> 785 786 <p style="clear: both;"><a href="<?= PHP_SELF ?>">Go back to the notes management</a></p> 787 <?php 788 // I didn't want to copy the whole menu and making it a type instead of action 789 // would mean reworking whole architecture this page has so it's an easy win tbh 790 foot(); 791break; 792 /* falls through */ 793default: 794 head('user notes'); 795 echo "<p>'$action' is not a recognized action, or no id was specified.</p>"; 796 foot(); 797} 798 799// ---------------------------------------------------------------------------------- 800 801// Use class names instead of colors 802ini_set('highlight.comment', 'comment'); 803ini_set('highlight.default', 'default'); 804ini_set('highlight.keyword', 'keyword'); 805ini_set('highlight.string', 'string'); 806ini_set('highlight.html', 'html'); 807 808// Copied over from phpweb (should be syncronised if changed) 809function clean_note($text) 810{ 811 // Highlight PHP source 812 $text = highlight_php(trim($text), TRUE); 813 814 // Turn urls into links 815 $text = preg_replace( 816 '!((mailto:|(http|ftp|nntp|news):\/\/).*?)(\s|<|\)|"|\\|\'|$)!', 817 '<a href="\1" target="_blank">\1</a>\4', 818 $text 819 ); 820 821 return $text; 822} 823 824// Highlight PHP code 825function highlight_php($code, $return = FALSE) 826{ 827 // Using OB, as highlight_string() only supports 828 // returning the result from 4.2.0 829 ob_start(); 830 highlight_string($code); 831 $highlighted = ob_get_contents(); 832 ob_end_clean(); 833 834 // Fix output to use CSS classes and wrap well 835 $highlighted = '<div class="phpcode">' . str_replace( 836 [ 837 ' ', 838 '<br />', 839 '<font color="', 840 '</font>', 841 "\n ", 842 ' ' 843 ], 844 [ 845 ' ', 846 "<br />\n", 847 '<span class="', 848 '</span>', 849 "\n ", 850 ' ' 851 ], 852 $highlighted 853 ) . '</div>'; 854 855 if ($return) { return $highlighted; } 856 else { echo $highlighted; } 857} 858 859// Send out a mail to the note submitter, with an envelope sender ignoring bounces 860function note_mail_user($mailto, $subject, $message) 861{ 862 $mailto = clean_antispam($mailto); 863 if (is_emailable_address($mailto)) { 864 mail( 865 $mailto, 866 $subject, 867 $message, 868 "From: ". NOTES_MAIL, 869 "-fbounces-ignored@php.net -O DeliveryMode=b" 870 ); 871 } 872} 873 874// Return data about a note by its ID 875function note_get_by_id($id) 876{ 877 if ($result = db_query_safe('SELECT *, UNIX_TIMESTAMP(ts) AS ts FROM note WHERE id=?', [$id])) { 878 if (!mysql_num_rows($result)) { 879 die("Note #$id doesn't exist. It has probably been deleted/rejected already."); 880 } 881 return mysql_fetch_assoc($result); 882 } 883 return FALSE; 884} 885 886// Sends out a notification to the mailing list when 887// some action is performed on a user note. 888function note_mail_on_action($user, $id, $subject, $body) 889{ 890 mail(NOTES_MAIL, $subject, $body, "From: $user@php.net\r\nIn-Reply-To: <note-$id@php.net>", "-f{$user}@php.net"); 891} 892 893// Allow some users to mass change IDs in the manual 894function allow_mass_change($user) 895{ 896 if (in_array( 897 $user, 898 [ 899 "vrana", "goba", "nlopess", "didou", "bjori", "philip", "bobby", "danbrown", "mgdm", "googleguy", "levim", 900 ] 901 ) 902 ) { 903 return TRUE; 904 } else { return FALSE; } 905} 906 907// Return safe to print version of email address 908function safe_email($mail) 909{ 910 if (in_array($mail, ["php-general@lists.php.net", "user@example.com"])) { 911 return ''; 912 } 913 elseif (preg_match("!(.+)@(.+)\.(.+)!", $mail)) { 914 return str_replace(['@', '.'], [' at ', ' dot '], $mail); 915 } 916 return $mail; 917} 918 919// Return a valid IPv4 range (as 0-indexed two element array) based on wildcard IP string 920function wildcard_ip($ip) 921{ 922 $start = explode(".", $ip); 923 if (count($start) != 4) { 924 return false; 925 } 926 foreach ($start as $part) { 927 if ($part === "*") { 928 continue; 929 } 930 if ($part > 255 || $part < 0 || !is_numeric($part)) { 931 return false; 932 } 933 } 934 $end = []; 935 foreach (array_keys($start, "*", true) as $key) { 936 $start[$key] = "0"; 937 $end[$key] = "255"; 938 } 939 foreach ($start as $key => $part) { 940 if (!isset($end[$key])) { 941 $end[$key] = $start[$key]; 942 } 943 } 944 ksort($end); 945 $start = ip2long(implode('.',$start)); 946 $end = ip2long(implode('.',$end)); 947 if ($end - $start <= 0) { 948 return false; 949 } 950 return [$start, $end]; 951} 952