Now loads new comment after posting
[dylansserver.git] / index.php
1 <?php
2
3 abstract class cms {
4 private $config_file = '/etc/dylanstestserver.ini';
5 protected $db;
6 protected $recaptcha_publickey;
7 protected $recaptcha_privatekey;
8
9 public function __construct() {
10 $config = parse_ini_file($this->config_file, true);
11 $this->db = new mysqli(
12 $config[database]['domain'],
13 $config[database]['user'],
14 $config[database]['password'],
15 $config[database]['database']);
16 if (mysqli_connect_errno()) {
17 echo "Problem connecting to database: ";
18 echo mysqli_connect_error();
19 exit();
20 }
21 $this->recaptcha_publickey = $config[recaptcha]['publickey'];
22 $this->recaptcha_privatekey = $config[recaptcha]['privatekey'];
23 ob_start();
24 }
25
26 public static function determine_type() {
27 if (isset($_GET['page']) && is_numeric($_GET['page'])) {
28 return 'page';
29 } else if (isset($_GET['year'])) {
30 return 'archive';
31 } else if (isset($_GET['note'])) {
32 return 'note';
33 } else if ($_SERVER['REQUEST_URI'] == '/') {
34 return 'index';
35 } else if (isset($_GET['project'])) {
36 return 'project';
37 } else if (isset($_GET['challenge'])) {
38 return 'captcha';
39 }
40
41 }
42
43 public function query() {
44 $args = func_get_args();
45 $statement = $this->db->prepare($args[0]);
46 $args = array_slice($args, 1);
47 call_user_func_array(array($statement, 'bind_param'), &$args);
48 $statement->execute();
49 $return = array();
50 $statement->store_result();
51 $row = array();
52 $data = $statement->result_metadata();
53 $fields = array();
54 $fields[0] = &$statement;
55 while($field = $data->fetch_field()) {
56 $fields[] = &$row[$field->name];
57 }
58 call_user_func_array("mysqli_stmt_bind_result", $fields);
59 $i = 0;
60 while ($statement->fetch()) {
61 foreach ($row as $key=>$value) $return[$i][$key] = $value;
62 $i++;
63 }
64 $statement->free_result();
65 return $return;
66 }
67
68 public function display_head($title = "dylanstestserver",
69 $home_link = "/") {
70 $scripts = "";
71 $stylesheets = "<link href=\"/includes/style.css\" rel=\"stylesheet\" type=\"text/css\">";
72 if ($this->determine_type() == "index") {
73 $scripts = "<script type=\"text/javascript\" src=\"/includes/all.js\">";
74 $home_link = "http://validator.w3.org/unicorn/check?ucn_uri=dylanstestserver.com&amp;ucn_task=conformance#";
75 } else if ($this->determine_type() == 'note') {
76 $scripts = "<script type=\"text/javascript\" src=\"http://www.google.com/recaptcha/api/js/recaptcha_ajax.js\"></script>";
77 $scripts .= "<script type=\"text/javascript\" src=\"/includes/jquery-core.js\"></script>";
78 $scripts .= "<script type=\"text/javascript\" src=\"/includes/jquery-all-components.js\"></script>";
79 $scripts .= "<script type=\"text/javascript\" src=\"/includes/ajax.js\"></script>";
80 }
81 echo <<<END_OF_HEAD
82 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
83 "http://www.w3.org/TR/html4/loose.dtd">
84
85 <html>
86 <head>
87 <meta name="generator" content=
88 "HTML Tidy for Linux (vers 25 March 2009), see www.w3.org">
89 <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
90
91 <title>$title</title>
92 <link rel="icon" href="favicon.ico" type="image/png">
93 $stylesheets
94 $scripts
95 </script>
96 </head>
97
98 <body>
99 <div id="structure">
100 <div id="banner">
101 <a href="$home_link">
102 <img src="/images/dylanstestserver.png" alt="dylanstestserver"
103 border="0"></a>
104 </div>
105
106 <div id="content">
107 END_OF_HEAD;
108 }
109
110 public function display_contact() {
111 echo <<<END_OF_CONTACT
112 <div id="contact_me"><h1><a href=
113 "mailto:dylan@psu.edu">dylan</a></h1><a href=
114 "mailto:dylan@psu.edu">@psu.edu</a>
115 </div>
116 END_OF_CONTACT;
117 }
118
119 public function display_close($show_contact = true) {
120 if ($show_contact) {
121 $this->display_contact();
122 }
123 echo <<<END_OF_CLOSE
124 </div>
125 <br>
126 <br>
127 </div>
128 </body>
129 </html>
130 END_OF_CLOSE;
131 ob_flush();
132 }
133
134 }
135
136 class blank_page extends cms {
137
138 }
139
140 class index extends cms {
141 public function display() {
142 $this->display_head();
143 $this->display_exhibits();
144 echo "<ul id=\"portfolio\" style=\"text-align:right\">";
145 $this->list_projects();
146 echo <<<OTHER_PROJECTS
147 <li>
148 <h3>things i've done for others:</h3>
149 </li>
150
151 <li><a href=
152 "http://activehamptons.com">activehamptons.com</a></li>
153
154 <li><a href=
155 "http://transfishing.com">transfishing.com</a></li>
156
157 <li>
158 <h3>something i've worked on:</h3>
159 </li>
160
161 <li><a href=
162 "http://tempositions.com">tempositions.com</a></li>
163
164 <li>
165 <h3>my repositories:</h3>
166 </li>
167
168 <li><a href=
169 "git">git://dylanstestserver.com</a></li>
170
171 <li>
172 <h3>some notes:</h3>
173 </li>
174
175 <li><a href=
176 "/notes/">here</a></li>
177
178 <li>
179 </li>
180 OTHER_PROJECTS;
181 // Because of the CSS necessary for the animations,
182 // the contact link needs to be in #portfolio to clear
183 // the floats.
184 $this->display_contact();
185 echo "</ul>";
186 $this->display_close($show_contact = false);
187 }
188
189 protected function display_exhibits() {
190 echo "<div id=\"exhibit\">";
191 $sql = "SELECT text FROM projects";
192 $result = $this->db->query($sql);
193 while ($entry = $result->fetch_object()) {
194 echo $entry->text;
195 }
196 echo "</div>";
197 }
198
199 private function list_projects() {
200 echo "<div id=\"exhibit\">";
201 echo <<<HEREDOC
202 <li>
203 <h3>my projects:</h3>
204 </li>
205 HEREDOC;
206 $sql = "SELECT title FROM projects";
207 $result = $this->db->query($sql);
208 while ($entry = $result->fetch_object()) {
209 echo "<li><a class=\"tab\" href=\"$entry->title\">$entry->title</a></li>";
210 }
211 }
212 }
213
214 class project extends index {
215 protected function display_exhibits() {
216 echo "<div id=\"exhibit\">";
217 $sql = "SELECT text FROM projects
218 WHERE title = ?";
219 $result = $this->query($sql, "s", $_GET['project']);
220 if ($result = $result[0]['text']) {
221 $text = str_replace("class=\"exhibit\"", "class=\"exhibit\" style=\"display:block;\"", $result);
222 echo $text;
223 echo "</div>";
224 } else {
225 throw new notFound();
226 }
227 }
228 }
229
230 class page extends cms {
231 private $page = 1;
232 private $offset = 0;
233 private $notes_per_page = 4;
234 private $number_of_pages = 1;
235
236 public function __construct() {
237 parent::__construct();
238 $this->page_offset();
239 }
240
241 private function page_offset() {
242 $sql = "SELECT COUNT(*) FROM notes";
243 $result = $this->db->query($sql);
244 $result = $result->fetch_array();
245 $this->number_of_pages = ceil($result[0] / $this->notes_per_page);
246 if (isset($_GET['page']) && is_numeric($_GET['page'])) {
247 $this->page = (int) $_GET['page'];
248 } else {
249 throw new notFound();
250 }
251 if ($this->page > $this->number_of_pages) {
252 throw new notFound();
253 }
254 if ($this->page < 1) {
255 throw new notFound();
256 }
257 $this->offset = ($this->page - 1) * $this->notes_per_page;
258 }
259
260 public function display() {
261 $this->display_head();
262 echo "<div id=\"notes\">";
263 $sql = "SELECT date_posted, title, url, text
264 FROM notes ORDER BY date_posted DESC
265 LIMIT ?, ?";
266 $result = $this->query($sql, "ii",
267 $this->offset,
268 $this->notes_per_page);
269 foreach ($result as $row => $entry) {
270 $title = $entry['title'];
271 $url = '/note/' . $entry['url'];
272 $date_posted = explode("-", $entry['date_posted']);
273 $year_posted = $date_posted[0];
274 $month_posted = $date_posted[1];
275 $datetime_posted = explode(' ', $date_posted[2]);
276 $day_posted = $datetime_posted[0];
277 echo "<div class=\"note\">";
278 echo "<h2><span style=\"color:grey;\">$year_posted/$month_posted/$day_posted/</span><a href=\"$url\">$title</a></h2>";
279 echo $entry['text'];
280 echo "</div>";
281 }
282 echo "</div>";
283 $this->write_navigation();
284 $this->display_close();
285 }
286
287 private function write_navigation() {
288 echo "<div id=\"navigation\">";
289 echo "<h2>";
290 if($this->page > 1){
291 $previous_page = $this->page - 1;
292 echo "<a href=\"/notes/page/$previous_page\">prev</a>";
293 }
294 if($this->page < $this->number_of_pages) {
295 $forward_page = $this->page + 1;
296 echo " <a href=\"/notes/page/$forward_page\">next</a>";
297 }
298 echo "</h2>";
299 echo "</div>";
300 }
301
302 }
303
304 class note extends cms {
305
306 private $id;
307 private $comments_enabled = false;
308 private $failed_captcha;
309 public $url;
310 public $title;
311 public $year_posted;
312 public $month_posted;
313 public $day_posted;
314 public $text;
315 public $number_of_comments;
316
317 public function __construct($comments_enabled = false) {
318 parent::__construct();
319 $this->comments_enabled = $comments_enabled;
320 $url = htmlspecialchars($_SERVER['REQUEST_URI']);
321 if (isset($_GET['verify'])) {
322 $url = substr($url, 0, (strlen($url)-6));
323 }
324 $this->url = $url;
325 $sql = "SELECT title, date_posted, text, id
326 FROM notes WHERE url = ?";
327 $result = $this->query($sql, "s",
328 $_GET['note']);
329 if ($result) {
330 $entry = $result[0];
331 $this->id = $entry["id"];
332 $this->title = $entry["title"];
333 $date_posted = explode("-", $entry["date_posted"]);
334 $this->year_posted = $date_posted[0];
335 $this->month_posted = $date_posted[1];
336 $datetime_posted = explode(' ', $date_posted[2]);
337 $this->day_posted = $datetime_posted[0];
338 $this->text = $entry["text"];
339 } else {
340 throw new notFound();
341 }
342 $sql = "SELECT COUNT(*) FROM comments
343 WHERE note = $this->id";
344 $result = $this->db->query($sql);
345 $result = $result->fetch_array();
346 $this->number_of_comments = $result[0];
347 if (isset($_GET['verify'])) {
348 $this->verify();
349 }
350 }
351
352 public function display() {
353 $this->display_head();
354 $this->display_note();
355 if ($this->comments_enabled) {
356 $this->display_comments();
357 $this->display_comment_form();
358 }
359 $this->write_navigation();
360 $this->display_close();
361 }
362
363 private function verify() {
364 var_dump($_POST['captcha']);
365 var_dump(isset($_POST['captcha']));
366 var_dump(isset($_POST['captcha']) || false);
367 if (!isset($_POST['captcha'])) {
368 require_once('includes/recaptchalib.php');
369 echo "<br>";
370 $resp = recaptcha_check_answer ($this->recaptcha_privatekey,
371 $_SERVER["REMOTE_ADDR"],
372 $_POST["recaptcha_challenge_field"],
373 $_POST["recaptcha_response_field"]);
374 if (!$resp->is_valid) {
375 $this->failed_captcha = true;
376 }
377 }
378 if (isset($_POST['captcha']) || $resp->is_valid) {
379 $sql = ("INSERT INTO comments (date_posted, author,
380 email, text, note)
381 VALUES(NOW(), ?, ?, ?, ?)");
382 $stmt = $this->db->prepare($sql);
383 // Checks are needed here (no blank text,
384 // and a default author / email need to be set
385 // for no-javascript users.
386 $stmt->bind_param('ssss',
387 htmlspecialchars($_POST['name']),
388 htmlspecialchars($_POST['email']),
389 htmlspecialchars($_POST['text']),
390 $this->id);
391 $stmt->execute();
392 }
393 }
394
395 private function display_note() {
396 echo "<div id=\"note\">";
397 echo "<h2><span style=\"color:grey;\">$this->year_posted/$this->month_posted/$this->day_posted/</span>$this->title</h2>";
398 echo $this->text;
399 }
400
401 private function write_navigation() {
402 echo <<<END_OF_NAVIGATION
403 <br>
404 <div id=\"navigation\">
405 <h2>
406 END_OF_NAVIGATION;
407 if ($this->failed_captcha) {
408 echo "<span style=\"color:red;border:1px solid black;padding:15px;\">sorry, reCAPTCHA said you're not human.</span><br><br><br>";
409 }
410 if (!$this->comments_enabled) {
411 $this->display_comment_link();
412 }
413 echo <<<END_OF_NAVIGATION
414 <a href="/notes/">notes</a>/
415 </h2>
416 </div>
417 END_OF_NAVIGATION;
418 }
419
420 private function display_comment_link() {
421 if ($this->number_of_comments > 0) {
422 $anchor_text = "comments ($this->number_of_comments)";
423 } else {
424 $anchor_text = "comment?";
425 }
426 if (substr($this->url, (strlen($this->url)-1), strlen($this->url)) == '/') {
427 $url = $this->url . 'comments/';
428 } else {
429 $url = $this->url . '/comments/';
430 }
431 echo "<a id=\"comment_link\" href=\"$url\">$anchor_text</a>";
432 }
433
434 private function display_comments() {
435 echo "<div id=\"comments\">";
436 $sql= "SELECT date_posted, author, email, text
437 FROM comments WHERE note = ?
438 ORDER BY date_posted DESC";
439 $result = $this->query($sql, "d", $this->id);
440 foreach ($result as $row => $entry) {
441 $date_posted = $entry['date_posted'];
442 $author = $entry['author'];
443 $email = $entry['email'];
444 $text = htmlspecialchars($entry['text']);
445 echo <<<END_OF_COMMENT
446 <h3><a href="mailto:$email">$author</a></h3>
447 $text
448 <br>
449 <br>
450 END_OF_COMMENT;
451 }
452 echo "</div>";
453 }
454
455 private function display_comment_form() {
456 $publickey = $this->recaptcha_publickey;
457 echo <<<END_CAPTCHA_STYLE
458 <script type="text/javascript">
459 Recaptcha.create("$publickey",
460 "recaptcha_div",
461 {
462 theme : 'custom',
463 custom_theme_widget: 'recaptcha_widget',
464 callback: Recaptcha.focus_response_field
465 });
466 </script>
467 END_CAPTCHA_STYLE;
468 require_once('includes/recaptchalib.php');
469 // Trailing slash is necessary for reloads to work
470 $url = $this->url . "verify/";
471 echo "<form id=\"comment_form\" method=\"post\" action=\"$url\">";
472 echo <<<END_OF_FORM
473 <div id="comment">
474
475 <div id="recaptcha_div">
476 <br>
477 <h3>comment:</h3>
478 <textarea rows="10" cols="70" name="text" id="comment_text"></textarea>
479 <h3>name:</h3>
480 <input type=text name="name" id="comment_name">
481 <h3>email:</h3>
482 <input type=text name="email" id="comment_email"><br>
483 <nowiki>
484
485 <div id="recaptcha_widget">
486 <h3 class="recaptcha_only_if_image"><b>what's this say</b>?</h3>
487 <h3 class="recaptcha_only_if_audio"><b>enter the numbers you hear</b>:</h3><span style="font-size:80%;">(<a href="javascript:Recaptcha.reload()">another</a>/<span class="recaptcha_only_if_image"><a href="javascript:Recaptcha.switch_type('audio')">audio</a></span>/<span class="recaptcha_only_if_audio"><a href="javascript:Recaptcha.switch_type('image')">Get an image CAPTCHA</a></span><a href="javascript:Recaptcha.showhelp()">help</a>)</span><br><br>
488 <input type="text" id="recaptcha_response_field" name="recaptcha_response_field" />
489 <br><br>
490 <div style="float:right;position:relative;width:100px;"><div id="recaptcha_image"></div></div>
491 <br><br><br><br>
492 </div>
493 END_OF_FORM;
494 echo recaptcha_get_html($this->recaptcha_publickey);
495 echo <<<END_OF_FORM
496 </div>
497
498 <input class="submit" type="submit" value="comment">
499 </form>
500 </div>
501 END_OF_FORM;
502 }
503 }
504
505
506 class archive extends cms {
507
508 public function __construct() {
509 parent::__construct();
510 }
511
512 private function check_exists() {
513 $sql = "SELECT COUNT(*) FROM notes
514 WHERE url = ?";
515 $results = $this->query($sql, "s", $_GET['note']);
516 if ($results[0]["COUNT(*)"] != 1) {
517 $this->not_found();
518 }
519 }
520
521 public function display() {
522 // this really needs its own pagination...
523 // there should be a class for that.
524 $this->display_head();
525 switch (true) {
526 case (isset($_GET['year']) && !isset($_GET['month'])
527 && !isset($_GET['day'])):
528 $sql = "SELECT title, url, date_posted, text
529 FROM notes WHERE YEAR(date_posted) = ?
530 ORDER BY date_posted DESC";
531 $result = $this->query($sql, "d",
532 $_GET['year']);
533 break;
534 case (isset($_GET['year']) && isset($_GET['month'])
535 && !isset($_GET['day'])):
536 $sql = "SELECT title, url, date_posted, text
537 FROM notes WHERE YEAR(date_posted) = ?
538 AND MONTH(date_posted) = ?
539 ORDER BY date_posted DESC";
540 $result = $this->query($sql, "dd",
541 $_GET['year'], $_GET['month']);
542 break;
543 case (isset($_GET['year']) && isset($_GET['month'])
544 && isset($_GET['day'])):
545 $sql = "SELECT title, url, date_posted, text
546 FROM notes WHERE YEAR(date_posted) = ?
547 AND MONTH(date_posted) = ?
548 AND DAY(date_posted) = ?
549 ORDER BY date_posted DESC";
550 $result = $this->query($sql, "ddd",
551 $_GET['year'], $_GET['month'],
552 $_GET['day']);
553 break;
554 }
555 if (count($result) >= 1) {
556 echo "<div id=\"notes\">";
557 foreach ($result as $row => $entry) {
558 $title = $entry['title'];
559 $url = '/note/' . $entry['url'];
560 $date_posted = explode("-", $entry['date_posted']);
561 $year_posted = $date_posted[0];
562 $month_posted = $date_posted[1];
563 $datetime_posted = explode(' ', $date_posted[2]);
564 $day_posted = $datetime_posted[0];
565 echo "<div class=\"note\">";
566 echo "<h2><span style=\"color:grey;\">$year_posted/$month_posted/$day_posted/</span><a href=\"$url\">$title</a></h2>";
567 echo $entry['text'];
568 echo "</div>";
569 }
570 echo "</div>";
571 $this->write_navigation();
572 } else {
573 echo "<br>";
574 echo "<h2 style=\"font-family:sans-serif;\">sorry, nothing here</h2>";
575 echo "<pre>Empty set (0.00 sec)</pre>";
576 }
577 $this->display_close();
578 }
579
580 private function write_navigation() {
581 echo "<br>";
582 echo "<div id=\"navigation\">";
583 echo "<h2>";
584 // fill me in!
585 echo "</h2>";
586 echo "</div>";
587 }
588 }
589
590
591 class notFound extends Exception {
592 public function __construct() {
593 header("HTTP/1.0 404 Not Found");
594 ob_end_clean();
595 include("404.php");
596 exit();
597 }
598 }
599
600 class captcha extends cms {
601 public function display() {
602 $challenge = $_GET['challenge'];
603 $response = $_GET['response'];
604 $remoteip = $_SERVER['REMOTE_ADDR'];
605 $curl = curl_init('http://api-verify.recaptcha.net/verify?');
606 curl_setopt ($curl, CURLOPT_POST, 4);
607 curl_setopt ($curl, CURLOPT_POSTFIELDS, "privatekey=$this->recaptcha_privatekey&remoteip=$remoteip&challenge=$challenge&response=$response");
608 $result = curl_exec ($curl);
609 curl_close ($curl);
610 }
611 }
612
613 ## now actually do something:
614 switch (cms::determine_type()) {
615 case "index":
616 $index = new index();
617 $index->display();
618 break;
619 case "project":
620 $project = new project();
621 $project->display();
622 break;
623 case "note":
624 if (isset($_GET['comments'])) {
625 $note = new note($comments_enabled = true);
626 } else {
627 $note = new note;
628 }
629 $note->display();
630 break;
631 case "page":
632 $page = new page;
633 $page->display();
634 break;
635 case "archive":
636 $archive = new archive;
637 $archive->display();
638 break;
639 case "captcha":
640 $captcha = new captcha;
641 $captcha->display();
642 break;
643 }
644
645 ?>