Improved styling
[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 if (!isset($_POST['captcha'])) {
365 require_once('includes/recaptchalib.php');
366 echo "<br>";
367 $resp = recaptcha_check_answer ($this->recaptcha_privatekey,
368 $_SERVER["REMOTE_ADDR"],
369 $_POST["recaptcha_challenge_field"],
370 $_POST["recaptcha_response_field"]);
371 if (!$resp->is_valid) {
372 $this->failed_captcha = true;
373 }
374 }
375 if (isset($_POST['captcha']) || $resp->is_valid) {
376 $sql = ("INSERT INTO comments (date_posted, author,
377 email, text, note)
378 VALUES(NOW(), ?, ?, ?, ?)");
379 $stmt = $this->db->prepare($sql);
380 // Checks are needed here (no blank text,
381 // and a default author / email need to be set
382 // for no-javascript users.
383 $stmt->bind_param('ssss',
384 htmlspecialchars($_POST['name']),
385 htmlspecialchars($_POST['email']),
386 htmlspecialchars($_POST['text']),
387 $this->id);
388 $stmt->execute();
389 }
390 }
391
392 private function display_note() {
393 echo "<div id=\"note\">";
394 echo "<h2><span style=\"color:grey;\">$this->year_posted/$this->month_posted/$this->day_posted/</span>$this->title</h2>";
395 echo $this->text;
396 }
397
398 private function write_navigation() {
399 echo <<<END_OF_NAVIGATION
400 <br>
401 <div id=\"navigation\">
402 <h2>
403 END_OF_NAVIGATION;
404 if (!$this->comments_enabled) {
405 $this->display_comment_link();
406 }
407 echo <<<END_OF_NAVIGATION
408 <a href="/notes/">notes</a>/
409 </h2>
410 </div>
411 END_OF_NAVIGATION;
412 }
413
414 private function display_comment_link() {
415 if ($this->number_of_comments > 0) {
416 $anchor_text = "comments ($this->number_of_comments)";
417 } else {
418 $anchor_text = "comment?";
419 }
420 if (substr($this->url, (strlen($this->url)-1), strlen($this->url)) == '/') {
421 $url = $this->url . 'comments/';
422 } else {
423 $url = $this->url . '/comments/';
424 }
425 echo "<a id=\"comment_link\" href=\"$url\">$anchor_text</a>";
426 }
427
428 private function display_comments() {
429 echo "<div id=\"comments\">";
430 $sql= "SELECT date_posted, author, email, text
431 FROM comments WHERE note = ?
432 ORDER BY date_posted DESC";
433 $result = $this->query($sql, "d", $this->id);
434 foreach ($result as $row => $entry) {
435 $date_posted = $entry['date_posted'];
436 $author = $entry['author'];
437 $email = $entry['email'];
438 $text = htmlspecialchars($entry['text']);
439 echo <<<END_OF_COMMENT
440 <h3><a href="mailto:$email">$author</a></h3>
441 $text
442 <br>
443 <br>
444 END_OF_COMMENT;
445 }
446 echo "</div>";
447 }
448
449 private function display_comment_form() {
450 $publickey = $this->recaptcha_publickey;
451 echo <<<END_CAPTCHA_STYLE
452 <script type="text/javascript">
453 Recaptcha.create("$publickey",
454 "recaptcha_div",
455 {
456 theme : 'custom',
457 custom_theme_widget: 'recaptcha_widget',
458 callback: Recaptcha.focus_response_field
459 });
460 </script>
461 END_CAPTCHA_STYLE;
462 require_once('includes/recaptchalib.php');
463 $url = $this->url . "verify";
464 echo "<form id=\"comment_form\" method=\"post\" action=\"$url\">";
465 echo <<<END_OF_FORM
466 <div id="comment">
467
468 <div id="recaptcha_div">
469 <br>
470 <h3>comment:</h3>
471 <textarea rows="10" cols="70" name="text" id="comment_text"></textarea>
472 <h3>name:</h3>
473 <input type=text name="name" id="comment_name">
474 <h3>email:</h3>
475 <input type=text name="email" id="comment_email"><br>
476 <nowiki>
477
478 <div id="recaptcha_widget">
479 <h3 class="recaptcha_only_if_image"><b>what's this say</b>?</h3>
480 <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>
481 <input type="text" id="recaptcha_response_field" name="recaptcha_response_field" />
482 <br><br>
483 <div style="float:right;position:relative;width:100px;"><div id="recaptcha_image"></div></div>
484 <br><br><br><br>
485 </div>
486 </div>
487 END_OF_FORM;
488 echo recaptcha_get_html($this->recaptcha_publickey);
489 if ($this->failed_captcha) {
490 echo <<<END_OF_FORM
491 <span style='font-weight:bold;font-family:sans-serif;color:red;margin-top:15px;'>reCAPTCHA said you're not human,</span>
492 <input class="submit" type="submit" value="try again?">
493 </form>
494 </div>
495 END_OF_FORM;
496 } else {
497 echo <<<END_OF_FORM
498 <input class="submit" type="submit" value="post comment">
499 </form>
500 </div>
501 END_OF_FORM;
502 }
503 }
504 }
505
506
507 class archive extends cms {
508
509 public function __construct() {
510 parent::__construct();
511 }
512
513 private function check_exists() {
514 $sql = "SELECT COUNT(*) FROM notes
515 WHERE url = ?";
516 $results = $this->query($sql, "s", $_GET['note']);
517 if ($results[0]["COUNT(*)"] != 1) {
518 $this->not_found();
519 }
520 }
521
522 public function display() {
523 // this really needs its own pagination...
524 // there should be a class for that.
525 $this->display_head();
526 switch (true) {
527 case (isset($_GET['year']) && !isset($_GET['month'])
528 && !isset($_GET['day'])):
529 $sql = "SELECT title, url, date_posted, text
530 FROM notes WHERE YEAR(date_posted) = ?
531 ORDER BY date_posted DESC";
532 $result = $this->query($sql, "d",
533 $_GET['year']);
534 break;
535 case (isset($_GET['year']) && isset($_GET['month'])
536 && !isset($_GET['day'])):
537 $sql = "SELECT title, url, date_posted, text
538 FROM notes WHERE YEAR(date_posted) = ?
539 AND MONTH(date_posted) = ?
540 ORDER BY date_posted DESC";
541 $result = $this->query($sql, "dd",
542 $_GET['year'], $_GET['month']);
543 break;
544 case (isset($_GET['year']) && isset($_GET['month'])
545 && isset($_GET['day'])):
546 $sql = "SELECT title, url, date_posted, text
547 FROM notes WHERE YEAR(date_posted) = ?
548 AND MONTH(date_posted) = ?
549 AND DAY(date_posted) = ?
550 ORDER BY date_posted DESC";
551 $result = $this->query($sql, "ddd",
552 $_GET['year'], $_GET['month'],
553 $_GET['day']);
554 break;
555 }
556 if (count($result) >= 1) {
557 echo "<div id=\"notes\">";
558 foreach ($result as $row => $entry) {
559 $title = $entry['title'];
560 $url = '/note/' . $entry['url'];
561 $date_posted = explode("-", $entry['date_posted']);
562 $year_posted = $date_posted[0];
563 $month_posted = $date_posted[1];
564 $datetime_posted = explode(' ', $date_posted[2]);
565 $day_posted = $datetime_posted[0];
566 echo "<div class=\"note\">";
567 echo "<h2><span style=\"color:grey;\">$year_posted/$month_posted/$day_posted/</span><a href=\"$url\">$title</a></h2>";
568 echo $entry['text'];
569 echo "</div>";
570 }
571 echo "</div>";
572 $this->write_navigation();
573 } else {
574 echo "<br>";
575 echo "<h2 style=\"font-family:sans-serif;\">sorry, nothing here</h2>";
576 echo "<pre>Empty set (0.00 sec)</pre>";
577 }
578 $this->display_close();
579 }
580
581 private function write_navigation() {
582 echo "<br>";
583 echo "<div id=\"navigation\">";
584 echo "<h2>";
585 // fill me in!
586 echo "</h2>";
587 echo "</div>";
588 }
589 }
590
591
592 class notFound extends Exception {
593 public function __construct() {
594 header("HTTP/1.0 404 Not Found");
595 ob_end_clean();
596 include("404.php");
597 exit();
598 }
599 }
600
601 class captcha extends cms {
602 public function display() {
603 $challenge = $_GET['challenge'];
604 $response = $_GET['response'];
605 $remoteip = $_SERVER['REMOTE_ADDR'];
606 $curl = curl_init('http://api-verify.recaptcha.net/verify?');
607 curl_setopt ($curl, CURLOPT_POST, 4);
608 curl_setopt ($curl, CURLOPT_POSTFIELDS, "privatekey=$this->recaptcha_privatekey&remoteip=$remoteip&challenge=$challenge&response=$response");
609 $result = curl_exec ($curl);
610 curl_close ($curl);
611 }
612 }
613
614 ## now actually do something:
615 switch (cms::determine_type()) {
616 case "index":
617 $index = new index();
618 $index->display();
619 break;
620 case "project":
621 $project = new project();
622 $project->display();
623 break;
624 case "note":
625 if (isset($_GET['comments'])) {
626 $note = new note($comments_enabled = true);
627 } else {
628 $note = new note;
629 }
630 $note->display();
631 break;
632 case "page":
633 $page = new page;
634 $page->display();
635 break;
636 case "archive":
637 $archive = new archive;
638 $archive->display();
639 break;
640 case "captcha":
641 $captcha = new captcha;
642 $captcha->display();
643 break;
644 }
645
646 ?>