Big set of changes to comments, with related changes to akismet and

user modules.

* Don't delete vars when we delete a module.  This makes
  reinstalling a module a lot easier.

* Add user::lookup() as the preferred way to load a user, so that
  other modules don't delve into the user module (that'd be a
  problem when we swap out user modules)

* Notify site admins if Akismet is not fully configured

* Bundle all server variables into the comment so that if/when we
  re-check the comment, we are not using the server info from the
  site admin's request.

* Update Akismet to grab request context data from the comment

* Pre-seed comment fields if we have a logged in user.  Update
  comment::create() API to clarify it for this.

* Delete comment::update(), that's a controller function.

* Add url to User_Model

* Add author_name() author_email() and author_url() to
  Comment_Model.  It'll return the appropriate values depending
  on whether the comment was left by a logged in user or a guest.

* Use resetForm() instead of clearForm() when we reload the
  comment form after ajax submit, this way we preserve the
  pre-seeded values.

* In the user profile page, ignore blank passwords.
This commit is contained in:
Bharat Mediratta 2009-01-10 00:34:23 +00:00
parent 48e73e9081
commit a7feeb576f
16 changed files with 224 additions and 146 deletions

View File

@ -293,6 +293,7 @@ class Welcome_Controller extends Template_Controller {
function add_comments($count) {
srand(time());
$photos = ORM::factory("item")->where("type", "photo")->find_all()->as_array();
$users = ORM::factory("user")->find_all()->as_array();
if (empty($photos)) {
url::redirect("welcome");
@ -303,10 +304,12 @@ class Welcome_Controller extends Template_Controller {
}
for ($i = 0; $i < $count; $i++) {
$photo = $photos[array_rand($photos)];
comment::create(
ucfirst($this->random_phrase(rand(1, 3))),
"johndoe@example.com",
$this->random_phrase(rand(8, 500)), $photo->id);
$author = $users[array_rand($users)];
$guest_name = ucfirst($this->random_phrase(rand(1, 3)));
$guest_email = sprintf("%s@%s.com", $this->random_phrase(1), $this->random_phrase(1));
$guest_url = sprintf("http://www.%s.com", $this->random_phrase(1));
comment::create($photo, $author, $this->random_phrase(rand(8, 500)),
$guest_name, $guest_email, $guest_url);
}
url::redirect("welcome");

View File

@ -63,10 +63,12 @@ class module_Core {
$module = ORM::factory("module")->where("name", $module_name)->find();
if ($module->loaded) {
$db = Database::instance();
$db->query("DELETE FROM `vars` WHERE `module_name` = '{$module->name}';");
$db->query("DELETE FROM `graphics_rules` WHERE module_name = '{$module->name}';");
$module->delete();
// We could delete the module vars here too, but it's nice to leave them around in case the
// module gets reinstalled.
Kohana::log("debug", "$module_name: module deleted");
}
}

View File

@ -44,6 +44,7 @@ class Admin_Akismet_Controller extends Admin_Controller {
log::success("akismet", t("Akismet key changed to $new_key"));
module::set_var("akismet", "api_key", $new_key);
akismet::check_config();
url::redirect("admin/akismet");
} else {
$valid_key = false;
@ -52,6 +53,7 @@ class Admin_Akismet_Controller extends Admin_Controller {
$valid_key = module::get_var("akismet", "api_key") ? 1 : 0;
}
akismet::check_config();
$view = new Admin_View("admin.html");
$view->content = new View("admin_akismet.html");
$view->content->valid_key = $valid_key;

View File

@ -20,14 +20,6 @@
class akismet_Core {
public static $test_mode = TEST_MODE;
// Lets not send everything to Akismet
private static $white_list = array(
"HTTP_USER_AGENT",
"HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
"HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST",
"HTTP_KEEP_ALIVE", "HTTP_REFERER", "HTTP_USER_AGENT", "QUERY_STRING",
"REMOTE_ADDR", "REMOTE_HOST", "REMOTE_PORT" );
public static function get_configure_form() {
$form = new Forge("admin/akismet", "", "post");
$group = $form->group("configure_akismet")->label(t("Configure Akismet"));
@ -96,6 +88,20 @@ class akismet_Core {
return "valid" == $response->body[0];
}
public static function check_config() {
$api_key = module::get_var("akismet", "api_key");
if (empty($api_key)) {
site_status::warning(
t("Akismet is not quite ready! Please provide an <a href=\"{{url}}\">API Key</a>",
array("url" => url::site("admin/akismet"))),
"akismet_config");
} else {
site_status::clear("akismet_config");
}
}
public static function _build_verify_request($api_key) {
$base_url = url::base(false, "http");
$query_string = "key={$api_key}&blog=$base_url";
@ -114,28 +120,31 @@ class akismet_Core {
public static function _build_request($function, $comment) {
$comment_data = array();
$comment_data["user_ip"] = $comment->ip_addr;
$comment_data["permalink"] = url::site("comments/{$comment->id}");
$comment_data["HTTP_ACCEPT"] = $comment->server_http_accept;
$comment_data["HTTP_ACCEPT_ENCODING"] = $comment->server_http_accept_encoding;
$comment_data["HTTP_ACCEPT_LANGUAGE"] = $comment->server_http_accept_language;
$comment_data["HTTP_CONNECTION"] = $comment->server_http_connection;
$comment_data["HTTP_HOST"] = $comment->server_http_host;
$comment_data["HTTP_USER_AGENT"] = $comment->server_http_user_agent;
$comment_data["QUERY_STRING"] = $comment->server_query_string;
$comment_data["REMOTE_ADDR"] = $comment->server_remote_addr;
$comment_data["REMOTE_HOST"] = $comment->server_remote_host;
$comment_data["REMOTE_PORT"] = $comment->server_remote_port;
$comment_data["SERVER_HTTP_ACCEPT_CHARSET"] = $comment->server_http_accept_charset;
$comment_data["blog"] = url::base(false, "http");
$comment_data["user_agent"] = $comment->user_agent;
$comment_data["referrer"] = !empty($_SERVER["HTTP_REFERER"]) ? $_SERVER["HTTP_REFERER"] : "";
$comment_data["comment_type"] = "comment";
$comment_data["comment_author"] = $comment->author;
$comment_data["comment_author_email"] = $comment->email;
$comment_data["comment_author_url"] = $comment->url;
$comment_data["comment_author"] = $comment->author_name();
$comment_data["comment_author_email"] = $comment->author_email();
$comment_data["comment_author_url"] = $comment->author_url();
$comment_data["comment_content"] = $comment->text;
foreach (self::$white_list as $key) {
if (array_key_exists($key, $_SERVER)) {
$comment_data[$key] = $_SERVER[$key];
}
}
$comment_data["comment_type"] = "comment";
$comment_data["permalink"] = url::site("comments/{$comment->id}");
$comment_data["referrer"] = $comment->server_http_referer;
$comment_data["user_agent"] = $comment->server_http_user_agent;
$comment_data["user_ip"] = $comment->server_remote_addr;
$query_string = array();
foreach ($comment_data as $key => $data) {
if (!is_array($data)) {
$query_string[] = "$key=" . urlencode($data);
}
$query_string[] = "$key=" . urlencode($data);
}
$query_string = join("&", $query_string);

View File

@ -24,6 +24,8 @@ class akismet_installer {
if ($version == 0) {
module::set_version("akismet", 1);
}
akismet::check_config();
}
public static function uninstall() {

View File

@ -60,11 +60,12 @@ class Comments_Controller extends REST_Controller {
$form = comment::get_add_form($item);
if ($form->validate()) {
$comment = comment::create($this->input->post("author"),
$this->input->post("email"),
$this->input->post("text"),
$this->input->post("item_id"),
$this->input->post("url"));
$comment = comment::create(
$item, user::active(),
$form->add_comment->text->value,
$form->add_comment->inputs["name"]->value,
$form->add_comment->email->value,
$form->add_comment->url->value);
print json_encode(
array("result" => "success",
@ -111,11 +112,12 @@ class Comments_Controller extends REST_Controller {
$form = comment::get_edit_form($comment);
if ($form->validate()) {
$comment = comment::update($comment,
$this->input->post("author"),
$this->input->post("email"),
$this->input->post("text"),
$this->input->post("url"));
$comment->guest_name = $form->edit_comment->inputs["name"]->value;
$comment->guest_email = $form->edit_comment->email->value;
$comment->url = $form->edit_comment->url->value;
$comment->text = $form->edit_comment->text->value;
$comment->save();
module::event("comment_updated", $comment);
print json_encode(
array("result" => "success",

View File

@ -24,102 +24,95 @@
* Note: by design, this class does not do any permission checking.
*/
class comment_Core {
const SECONDS_IN_A_MINUTE = 60;
const SECONDS_IN_AN_HOUR = 3600;
const SECONDS_IN_A_DAY = 86400;
const SECONDS_IN_A_MONTH = 2629744;
const SECONDS_IN_A_YEAR = 31556926;
/**
* Create a new comment.
* @param string $author author's name
* @param string $email author's email
* @param string $text comment body
* @param integer $item_id id of parent item
* @param string $url author's url
* @param Item_MOdel $item the parent item
* @param User_Model $author the author User_Model
* @param string $text comment body
* @param string $guest_name guest's name (if the author is a guest user, default empty)
* @param string $guest_email guest's email (if the author is a guest user, default empty)
* @param string $guest_url guest's url (if the author is a guest user, default empty)
* @return Comment_Model
*/
static function create($author, $email, $text, $item_id, $url=null) {
static function create($item, $author, $text, $guest_name=null,
$guest_email=ull, $guest_url=null) {
$comment = ORM::factory("comment");
$comment->author = $author;
$comment->email = $email;
$comment->text = $text;
$comment->item_id = $item_id;
$comment->url = $url;
$comment->ip_addr = Input::instance()->ip_address();
$comment->user_agent = Kohana::$user_agent;
$comment->author_id = $author->id;
$comment->created = time();
$comment->guest_email = $guest_email;
$comment->guest_name = $guest_name;
$comment->guest_url = $guest_url;
$comment->item_id = $item->id;
$comment->text = $text;
$comment->state = "published";
// @todo Figure out how to mock up the test of the spam_filter
if (module::is_installed("spam_filter") && TEST_MODE == 0) {
try {
SpamFilter::instance()->check_comment($comment);
} catch (Exception $e) {
Kohana::log("error", print_r($e, 1));
$comment->state = "unpublished";
}
} else {
$comment->state = "published";
}
// These values are useful for spam fighting, so save them with the comment.
$input = Input::instance();
$comment->server_http_accept = $input->server("HTTP_ACCEPT");
$comment->server_http_accept_charset = $input->server("SERVER_HTTP_ACCEPT_CHARSET");
$comment->server_http_accept_encoding = $input->server("HTTP_ACCEPT_ENCODING");
$comment->server_http_accept_language = $input->server("HTTP_ACCEPT_LANGUAGE");
$comment->server_http_connection = $input->server("HTTP_CONNECTION");
$comment->server_http_host = $input->server("HTTP_HOST");
$comment->server_http_referer = $input->server("HTTP_REFERER");
$comment->server_http_user_agent = $input->server("HTTP_USER_AGENT");
$comment->server_query_string = $input->server("QUERY_STRING");
$comment->server_remote_addr = $input->server("REMOTE_ADDR");
$comment->server_remote_host = $input->server("REMOTE_HOST");
$comment->server_remote_port = $input->server("REMOTE_PORT");
$comment->save();
module::event("comment_created", $comment);
return $comment;
}
/**
* Update an existing comment.
* @param Comment_Model $comment
* @param string $author author's name
* @param string $email author's email
* @param string $text comment body
* @param string $url author's url
* @return Comment_Model
*/
static function update($comment, $author, $email, $text, $url) {
$comment->author = $author;
$comment->email = $email;
$comment->text = $text;
$comment->url = $url;
$comment->ip_addr = Input::instance()->ip_address();
$comment->user_agent = Kohana::$user_agent;
// @todo Figure out how to mock up the test of the spam_filter
if (module::is_installed("spam_filter") && TEST_MODE == 0) {
SpamFilter::instance()->check_comment($comment);
}
$comment->save();
if ($comment->saved) {
module::event("comment_updated", $comment);
}
return $comment;
}
static function get_add_form($item) {
$form = new Forge("comments", "", "post");
$group = $form->group("add_comment")->label(t("Add comment"));
$group->input("author") ->label(t("Author")) ->id("gAuthor");
$group->input("email") ->label(t("Email")) ->id("gEmail");
$group->input("url") ->label(t("Website (hidden)"))->id("gUrl");
$group->textarea("text") ->label(t("Text")) ->id("gText");
$group->input("name") ->label(t("Name")) ->id("gAuthor");
$group->input("email") ->label(t("Email (hidden)")) ->id("gEmail");
$group->input("url") ->label(t("Website (hidden)"))->id("gUrl");
$group->textarea("text")->label(t("Comment")) ->id("gText");
$group->hidden("item_id")->value($item->id);
$group->submit(t("Add"));
$form->add_rules_from(ORM::factory("comment"));
// Forge will try to reload any pre-seeded values upon validation if it's a post request, so
// force validation before seeding values.
// @todo make that an option in Forge
if (request::method() == "post") {
$form->validate();
}
$active = user::active();
if (!$active->guest) {
$group->inputs["name"]->value($active->full_name)->disabled("disabled");
$group->email->value($active->email)->disabled("disabled");
$group->url->value($active->url)->disabled("disabled");
}
return $form;
}
static function get_edit_form($comment) {
$form = new Forge("comments/{$comment->id}?_method=put", "", "post");
$group = $form->group("edit_comment")->label(t("Edit comment"));
$group->input("author") ->label(t("Author")) ->id("gAuthor")->value($comment->author);
$group->input("email") ->label(t("Email")) ->id("gEmail") ->value($comment->email);
$group->input("url") ->label(t("Website (hidden)"))->id("gUrl") ->value($comment->url);
$group->textarea("text")->label(t("Text")) ->id("gText") ->value($comment->text);
$group->input("name") ->label(t("Author")) ->id("gAuthor");
$group->input("email") ->label(t("Email (hidden)")) ->id("gEmail");
$group->input("url") ->label(t("Website (hidden)"))->id("gUrl");
$group->textarea("text")->label(t("Comment")) ->id("gText");
$group->submit(t("Edit"));
$form->add_rules_from($comment);
$group->text = $comment->text;
$author = $comment->author();
if ($author->guest) {
$group->inputs["name"]->value = $comment->guest_name;
$group->email = $comment->guest_email;
$group->url = $comment->guest_url;
} else {
$group->inputs["name"]->value($author->full_name)->disabled("disabled");
$group->email->value($author->email)->disabled("disabled");
$group->url->value($author->url)->disabled("disabled");
}
return $form;
}
}

View File

@ -24,18 +24,27 @@ class comment_installer {
if ($version == 0) {
$db->query("CREATE TABLE IF NOT EXISTS `comments` (
`id` int(9) NOT NULL auto_increment,
`author` varchar(128) default NULL,
`email` varchar(128) default NULL,
`text` text,
`author_id` int(9) default NULL,
`created` int(9) NOT NULL,
`guest_email` varchar(128) default NULL,
`guest_name` varchar(128) default NULL,
`guest_url` varchar(255) default NULL,
`id` int(9) NOT NULL auto_increment,
`item_id` int(9) NOT NULL,
`url` varchar(255) default NULL,
`server_http_accept_charset` varchar(64) default NULL,
`server_http_accept_encoding` varchar(64) default NULL,
`server_http_accept_language` varchar(64) default NULL,
`server_http_accept` varchar(64) default NULL,
`server_http_connection` varchar(64) default NULL,
`server_http_host` varchar(64) default NULL,
`server_http_referer` varchar(255) default NULL,
`server_http_user_agent` varchar(64) default NULL,
`server_query_string` varchar(64) default NULL,
`server_remote_addr` varchar(32) default NULL,
`server_remote_host` varchar(64) default NULL,
`server_remote_port` varchar(16) default NULL,
`state` char(15) default 'unpublished',
`ip_addr` char(15) default NULL,
`user_agent` varchar(255) default NULL,
`spam_signature` varchar(255) default NULL,
`spam_type` char(15) default NULL,
`text` text,
PRIMARY KEY (`id`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;");

View File

@ -15,7 +15,7 @@ function ajaxify_comment_form() {
$("#gComments .gBlockContent ul:first").append("<li>"+data+"</li>");
$("#gComments .gBlockContent ul:first li:last").hide().slideDown();
});
$("#gComments form").clearForm();
$("#gComments form").resetForm();
}
}
});

View File

@ -18,13 +18,38 @@
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Comment_Model extends ORM {
var $rules = array(
"author" => "required",
"email" => "valid_email",
"url" => "valid_url",
"text" => "required");
function item() {
return ORM::factory("item", $this->item_id);
}
function author() {
return user::lookup($this->author_id);
}
function author_name() {
$author = $this->author();
if ($author->guest) {
return $this->guest_name;
} else {
return $author->full_name;
}
}
function author_email() {
$author = $this->author();
if ($author->guest) {
return $this->guest_email;
} else {
return $author->email;
}
}
function author_url() {
$author = $this->author();
if ($author->guest) {
return $this->guest_url;
} else {
return $author->url;
}
}
}

View File

@ -30,11 +30,13 @@
<? if ($queue == "spam"): ?>
<div>
<? if ($spam_caught > 0): ?>
<p>
<?= t(array("one" => "Gallery has caught {{count}} spam for you since you installed spam filtering.",
"other" => "Gallery has caught {{count}} spam for you since you installed spam filtering."),
array("count" => $spam_caught)) ?>
</p>
<? endif ?>
<p>
<? if ($spam->count()): ?>
<?= t(array("one" => "There is currently one comment in your spam queue. You can delete it with a single click, but there is no undo operation so you may want to check the message first to make sure that it really is spam.",
@ -51,7 +53,7 @@
</p>
</div>
<? endif ?>
<form id="gBulkAction" action="#" method="post">
<label for="bulk_actions"><?= t("Bulk actions")?></label>
<select id="bulk_actions">
@ -61,7 +63,7 @@
<option><?= t("Delete")?></option>
</select>
<input type="submit" value="Apply" />
<table id="gAdminCommentsList">
<tr>
<th>
@ -90,9 +92,9 @@
</td>
<td>
<a href="#"><img src="<?= $theme->url("images/avatar.jpg") ?>"
alt="<?= $comment->author ?>"/></a><br/>
<a href="mailto:<?= $comment->email ?>"
title="<?= $comment->email ?>"> <?= $comment->author ?> </a>
alt="<?= $comment->author_name() ?>"/></a><br/>
<a href="mailto:<?= $comment->author_email() ?>"
title="<?= $comment->author_email() ?>"> <?= $comment->author_name() ?> </a>
</td>
<td>
<?= $comment->text ?>
@ -109,7 +111,7 @@
</a>
</li>
<? endif ?>
<? if ($comment->state != "published"): ?>
<li>
<a href="javascript:set_state('published',<?=$comment->id?>)">
@ -117,7 +119,7 @@
</a>
</li>
<? endif ?>
<? if ($comment->state != "spam"): ?>
<li>
<a href="javascript:set_state('spam',<?=$comment->id?>)">
@ -125,7 +127,7 @@
</a>
</li>
<? endif ?>
<li>
<a href="javascript:reply(<?=$comment->id?>)">
<?= t("Reply") ?>
@ -157,8 +159,8 @@
<? endforeach ?>
</table>
</form>
<div class="pager">
<?= $pager ?>
</div>

View File

@ -1,10 +1,12 @@
<?php defined("SYSPATH") or die("No direct script access.") ?>
<li id="gComment-<?= $comment->id; ?>">
<p class="gAuthor">
<a href="#"><img src="<?= $theme->url("images/avatar.jpg") ?>"
class="gAvatar" alt="<?= $comment->author ?>" /></a>
<a href="#">
<img src="<?= $theme->url("images/avatar.jpg") ?>"
class="gAvatar" alt="<?= $comment->author_name() ?>" />
</a>
<?= t("on ") . date("Y-M-d H:i:s", $comment->created) ?>
<a href="#"><?= $comment->author ?></a> <?= t("said") ?>
<a href="#"><?= $comment->author_name() ?></a> <?= t("said") ?>
</p>
<div>
<?= $comment->text ?>

View File

@ -2,11 +2,13 @@
<a name="comments"></a>
<ul id="gComments">
<? foreach ($comments as $comment): ?>
<li id="gComment-<?= $comment->id; ?>">
<li id="gComment-<?= $comment->id ?>">
<p class="gAuthor">
<a href="#"><img src="<?= $theme->url("images/avatar.jpg") ?>" class="gAvatar" alt="<?= $comment->author ?>" /></a>
<?= t("on ") . date("Y-M-d H:i:s", $comment->created) ?>
<a href="#"><?= $comment->author ?></a> <?= t("said") ?>
<a href="#">
<img src="<?= $theme->url("images/avatar.jpg") ?>"
class="gAvatar" alt="<?= $comment->author_name() ?>" />
</a>
<? printf(t("on %s <a href=#>%s</a> said"), date("Y-M-d H:i:s", $comment->created), $comment->author_name()) ?>
</p>
<div>
<?= $comment->text ?>

View File

@ -29,9 +29,13 @@ class Users_Controller extends REST_Controller {
$form->edit_user->password->rules("-required");
if ($form->validate()) {
// @todo: allow the user to change their name
// @todo: handle password changing gracefully
$user->full_name = $form->edit_user->full_name->value;
$user->password = $form->edit_user->password->value;
if ($form->edit_user->password->value) {
$user->password = $form->edit_user->password->value;
}
$user->email = $form->edit_user->email->value;
$user->url = $form->edit_user->url->value;
$user->save();
print json_encode(

View File

@ -31,6 +31,7 @@ class user_Core {
$group->input("full_name")->label(t("Full Name"))->id("gFullName")->value($user->full_name);
$group->password("password")->label(t("Password"))->id("gPassword");
$group->input("email")->label(t("Email"))->id("gEmail")->value($user->email);
$group->input("url")->label(t("URL"))->id("gUrl")->value($user->url);
$group->submit(t("Save"));
$form->add_rules_from($user);
return $form;
@ -45,6 +46,7 @@ class user_Core {
$group->input("full_name")->label(t("Full Name"))->id("gFullName")->value($user->full_name);
$group->password("password")->label(t("Password"))->id("gPassword");
$group->input("email")->label(t("Email"))->id("gEmail")->value($user->email);
$group->input("url")->label(t("URL"))->id("gUrl")->value($user->url);
$group->submit(t("Modify User"));
$form->add_rules_from($user);
return $form;
@ -59,6 +61,7 @@ class user_Core {
$group->input("full_name")->label(t("Full Name"))->id("gFullName");
$group->password("password")->label(t("Password"))->id("gPassword");
$group->input("email")->label(t("Email"))->id("gEmail");
$group->input("url")->label(t("URL"))->id("gUrl")->value($user->url);
$group->submit(t("Add User"));
$user = ORM::factory("user");
$form->add_rules_from($user);
@ -213,7 +216,7 @@ class user_Core {
}
/**
* Perform the post authentication processing
* Log in as a given user.
* @param object $user the user object.
*/
public static function login($user) {
@ -225,6 +228,10 @@ class user_Core {
module::event("user_login", $user);
}
/**
* Log out the active user and destroy the session.
* @param object $user the user object.
*/
public static function logout() {
$user = user::active();
if (!$user->guest) {
@ -237,6 +244,19 @@ class user_Core {
}
}
/**
* Look up a user by id.
* @param integer $id the user id
* @return User_Model the user object, or null if the id was invalid.
*/
public static function lookup($id) {
$user = model_cache::get("user", $id);
if ($user->loaded) {
return $user;
}
return null;
}
/**
* Create a hashed password using md5 plus salt.
* @param string $password plaintext password

View File

@ -33,6 +33,7 @@ class user_installer {
`email` varchar(64) default NULL,
`admin` BOOLEAN default 0,
`guest` BOOLEAN default 0,
`url` varchar(255) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY(`name`))
ENGINE=InnoDB DEFAULT CHARSET=utf8;");