From 214aa16a7d53ec61623ab3ce1f94c6bfa8ae604f Mon Sep 17 00:00:00 2001 From: Gabriel Dunne Date: Sun, 13 May 2012 01:25:38 -0700 Subject: [PATCH] comments --- content/code/config | 2 +- content/code/mostused | 2 +- css/comments.css | 26 ++++ css/style.css | 11 +- js/prettify/prettify.css | 7 +- lib/comments.php | 118 +++++++++++++++ lib/data.php | 14 ++ lib/init.php | 7 + lib/model.php | 10 ++ lib/recaptchalib.php | 277 ++++++++++++++++++++++++++++++++++++ pages/about | 22 +-- templates/comments.html.tpl | 33 +++++ templates/entry.html.tpl | 5 + templates/head-inc.html.tpl | 14 +- templates/nav.html.tpl | 2 +- 15 files changed, 526 insertions(+), 24 deletions(-) create mode 100644 css/comments.css create mode 100644 lib/comments.php create mode 100644 lib/recaptchalib.php create mode 100644 templates/comments.html.tpl diff --git a/content/code/config b/content/code/config index 38a8b8e..b76bddf 100644 --- a/content/code/config +++ b/content/code/config @@ -1,2 +1,2 @@ -template = code + -- diff --git a/content/code/mostused b/content/code/mostused index 30944b1..2b451bf 100644 --- a/content/code/mostused +++ b/content/code/mostused @@ -29,4 +29,4 @@ example 5 ln 4 mkdir 4 ./scripts/content - \ No newline at end of file + diff --git a/css/comments.css b/css/comments.css new file mode 100644 index 0000000..61a873b --- /dev/null +++ b/css/comments.css @@ -0,0 +1,26 @@ +/* comments */ + +.comments { + margin-top:50px; + color:#d04; +} +.comments textarea { + width:315px; + height:130px; +} +.comments .error { + color:#900; +} +.comments .comment { + margin:30px 0; +} +.comments .comment p { + margin:10px 0; +} +.comments .comment .name { + font-style:italic; + color:#600; +} +.comments .comment_form { + margin-top:40px; +} diff --git a/css/style.css b/css/style.css index fc105d7..4576766 100644 --- a/css/style.css +++ b/css/style.css @@ -68,14 +68,17 @@ blockquote { } pre, code { color:#df0; - line-height:1.3em; - font-family: monaco, monospace; + line-height:1.4em; + font-size:14px; + font-family:menlo, monaco, "Lucida Console", monospace; padding:1px 5px 2px; background:#090909; - border-radius:4px; + border-radius:3px; } pre { - padding:10px 30px; + padding:10px 20px; + position:relative; + left:-20px; } table, td, tr { margin:0; diff --git a/js/prettify/prettify.css b/js/prettify/prettify.css index 24cce10..7384ce1 100644 --- a/js/prettify/prettify.css +++ b/js/prettify/prettify.css @@ -6,15 +6,12 @@ .typ { color: #93d44f; } .lit { color: #099; } .pun { color: #66d; } -.pln { color: #ffd; } +.pln { color: #df0; } .tag { color: #008; } .atn { color: #606; } .atv { color: #080; } .dec { color: #606; } -pre.prettyprint, code.prettyprint { - font-family:monaco, "Lucida Console", monospace; font-size:12px; line-height:1.5em; padding:0.2em 0.6em; background:#181818; - border-radius: 6px; -} +pre.prettyprint, code.prettyprint { } pre.prettyprint { padding:20px; } @media print { diff --git a/lib/comments.php b/lib/comments.php new file mode 100644 index 0000000..c413a0c --- /dev/null +++ b/lib/comments.php @@ -0,0 +1,118 @@ +fileInfo = $fileInfo; + $this->comments = $this->get_comments( $fileInfo ); + $this->comments_loc = get_comments_location($fileInfo); + } + + + /** + * get comments + * @param fileInfo the fileinfo object + * @return array() comments + */ + function get_comments(&$fileInfo) + { + $comments_location = get_comments_location($fileInfo); + $comments = array(); + if (is_dir($comments_location)) { + $dir_iterator = new DirectoryIterator($comments_location); + foreach ($dir_iterator as $file => $info) { + if (!$info->isDir() && !$info->isDot()) { + $contents = file_get_contents($info->getPath() . DIRECTORY_SEPARATOR . $info->getFilename()); + $comments[] = json_decode($contents, true); + } + } + } + return $comments; + } + + + function create_comments_dir( &$dir ) + { + try { + if (!mkdir($dir, 0777, true)) { + throw new Exception('Error creating comments directory'); + } + } catch (Exception $e) { + echo 'Caught exception', $e->getMessage(), "\n"; + } + } + + + function process_post_request() + { + $this->_recaptcha_post_request(); + } + + + # the response from reCAPTCHA + var $_recaptcha_resp = null; + # the error code from reCAPTCHA, if any + var $_recaptcha_error = null; + #error making comments + var $_comment_error = null; + + + function recaptcha_html() + { + global $captcha_publickey, $captcha_privatekey; + if ($this->_comment_error) + echo '
', $this->_comment_error, '
'; + echo recaptcha_get_html($captcha_publickey, $this->_recaptcha_error); + } + + + function _recaptcha_post_request() { + + global $captcha_publickey, $captcha_privatekey; + + # was there a reCAPTCHA response? + if (isset($_POST["recaptcha_response_field"])) { + + $this->_recaptcha_resp = recaptcha_check_answer ($captcha_privatekey, + $_SERVER["REMOTE_ADDR"], + $_POST["recaptcha_challenge_field"], + $_POST["recaptcha_response_field"]); + if ($this->_recaptcha_resp->is_valid) { + + $new_comment = array(); + $new_comment['name'] = !empty($_POST['name']) ? $_POST['name'] : 'anon'; + $new_comment['www'] = !empty($_POST['www']) ? $_POST['www'] : null; + $new_comment['comment'] = !empty($_POST['comment']) ? $_POST['comment'] : null; + $new_comment['timestamp'] = date('U'); + + if (!$new_comment['comment']) { + + $this->_comment_error = 'You must enter a comment.'; + + } else { + + if (!is_dir($this->comments_loc)) { + $this->create_comments_dir($this->comments_loc); + } + + # put new comment + if (!file_put_contents($this->comments_loc . date('U') . '.json', json_encode($new_comment))) { + $_comment_error = 'error creating comment'; + } + + # all comments once new one is created + $this->comments = $this->get_comments( $this->fileInfo ); + } + + } else { + # set the error code so that we can display it + $this->_recaptcha_error = $this->_recaptcha_resp->error; + } + } + } +} diff --git a/lib/data.php b/lib/data.php index 3d4a35f..08e0316 100644 --- a/lib/data.php +++ b/lib/data.php @@ -145,6 +145,9 @@ function parse_entry($fileInfo, $page = false) $f['tags'] = isset($f['config']['tags']) ? explode(" ", $f['config']['tags']) : null; $f['content'] = Markdown($content); + $f['comments_enabled'] = isset($f['config']['comments']) && $f['config']['comments']; + $f['comments'] = new Comments($fileInfo); + if ($passed_more) $f['content_short'] = Markdown($content_short); @@ -163,6 +166,17 @@ function parse_entry($fileInfo, $page = false) return $f; } +function get_comments_location ( &$fileInfo ) { + return LOCAL_ROOT . COMMENTS_DIR . get_clean_path($fileInfo) . DIRECTORY_SEPARATOR . $fileInfo->getFilename() . DIRECTORY_SEPARATOR; +} + +function get_clean_path ( &$fileInfo, $page = false ) { + if (!$page) { + return str_replace(LOCAL_ROOT . CONTENT_DIR, "", clean_slashes($fileInfo->getPath())); + } else { + return str_replace(LOCAL_ROOT . PAGE_DIR, "", clean_slashes($fileInfo->getPath())); + } +} function get_entry ( $relative_path ) { diff --git a/lib/init.php b/lib/init.php index 8703194..6ac949c 100644 --- a/lib/init.php +++ b/lib/init.php @@ -30,6 +30,7 @@ define ('SITE_TITLE', 'quilime'); define ('LOCAL_ROOT', '/home/quilime/quilime.com/'); define ('WEB_ROOT', '/'); define ('CONTENT_DIR', 'content/'); +define ('COMMENTS_DIR', 'comments/'); define ('TEMPLATE_DIR', 'templates/'); define ('PAGE_DIR', 'pages/'); define ('CONFIG_DELIMITER', '--'); @@ -42,6 +43,11 @@ $_FILE_IGNORES = array(CONFIG_FILE, '.DS_Store'); +# recaptcha for comments +require_once 'recaptchalib.php'; +// Get a key from https://www.google.com/recaptcha/admin/create +$captcha_publickey = "6Lek-MkSAAAAAAZknQQGSx9DiCqm_wAiFGytc37d"; +$captcha_privatekey = "6Lek-MkSAAAAAK4FAaPKO0Cwp-iHa0OcUaqipee4"; @@ -51,3 +57,4 @@ require_once 'output.php'; require_once 'markdown.php'; require_once 'model.php'; require_once 'view.php'; +require_once 'comments.php'; diff --git a/lib/model.php b/lib/model.php index 3cd2c00..86a5ddf 100644 --- a/lib/model.php +++ b/lib/model.php @@ -51,6 +51,11 @@ class Model // prev/next $entries = get_entries($request['dirname']); + // post request + if ($_SERVER['REQUEST_METHOD'] == 'POST') + $this->process_post_request($this->entry); + + for($i = count($entries)-1; $i>=0; $i--) { if ($this->entry['url'] == $entries[$i]['url']) { $this->entry['prev_entry'] = isset($entries[$i-1]) ? $entries[$i-1] : null; @@ -78,6 +83,11 @@ class Model } } + function process_post_request( &$entry ) { + if ($entry['comments']) + $entry['comments']->process_post_request(); + } + function has_config() { return is_file(join(array( LOCAL_ROOT, CONTENT_DIR, $this->content_request, CONFIG_FILE ), DIRECTORY_SEPARATOR )) ? 1 : 0; diff --git a/lib/recaptchalib.php b/lib/recaptchalib.php new file mode 100644 index 0000000..32c4f4d --- /dev/null +++ b/lib/recaptchalib.php @@ -0,0 +1,277 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://www.google.com/recaptcha/mailhide/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/pages/about b/pages/about index 5db10ad..76643c4 100644 --- a/pages/about +++ b/pages/about @@ -5,26 +5,26 @@ title = about quilime is an ongoing collection of projects, sketches, ideas, and process by **gabriel dunne** ([www](http://gabrieldunne.com), [email](mailto:gdunne@quilime.com)) -
- -built with plog, a static-file `p`roject`log` engine. - +quilime.com is built with plog, a static-file `p`roject`log` engine written in PHP.
+## related +[aggregate](http://media.quilime.com/aggregate/) images +[clmpr](http://clmpr.com/quilime/) links +
+ +## elsewhere +[twitter (@quilime)](http://twitter.com/quilime/) +[vimeo](http://vimeo.com/quilime/) +[github](http://github.com/quilime/) +[flickr](http://flickr.com/photos/quilime/) -## elsewhere -[clmpr.com/quilime](http://clmpr.com/quilime/) -[media.quilime.com/aggregate/](http://media.quilime.com/aggregate/) -[github.com/quilime](http://github.com/quilime/) -[flickr.com/photos/quilime](http://flickr.com/photos/quilime/) -[vimeo.com/quilime](http://vimeo.com/quilime/) -[twitter.com/quilime](http://twitter.com/quilime/) diff --git a/templates/comments.html.tpl b/templates/comments.html.tpl new file mode 100644 index 0000000..2f8cbf6 --- /dev/null +++ b/templates/comments.html.tpl @@ -0,0 +1,33 @@ + +
+

comments

+ + +
+ + comments as $comment) : ?> +
+ +
, ago
+
+ + +
+ +
+ +
+
+
+ comment
+ +
+ + recaptcha_html(); ?> + +
+ + +
+
+
diff --git a/templates/entry.html.tpl b/templates/entry.html.tpl index ae845d1..e21bca3 100644 --- a/templates/entry.html.tpl +++ b/templates/entry.html.tpl @@ -16,4 +16,9 @@ /tags: ' . implode(', ', $entry['tags']); ?> + include_template('comments.html.tpl', array('entry' => $entry)); + ?> + diff --git a/templates/head-inc.html.tpl b/templates/head-inc.html.tpl index ed48403..8a61884 100644 --- a/templates/head-inc.html.tpl +++ b/templates/head-inc.html.tpl @@ -7,6 +7,7 @@ + @@ -35,5 +36,16 @@ $(document).ready(function() { */ ?> + + + + + + + + ->>>>>> 2ae0cd949c331f83dcb05116cffdca21b2ecb871 --> */ ?> diff --git a/templates/nav.html.tpl b/templates/nav.html.tpl index 6b0b116..28e773d 100644 --- a/templates/nav.html.tpl +++ b/templates/nav.html.tpl @@ -12,7 +12,7 @@
-
  • aggregate
  • +
  • photo
  • links
  • about
  • -- 2.34.1