author = $author;
    $comment->email = $email;
    $comment->text = $text;
    $comment->datetime = $datetime;
    $comment->item_id = $item_id;
    $comment->save();
    module::event("comment_created", $comment);
    return $comment;
  }
  static function get_add_form($item_id) {
    $form = new Forge(url::site("comments"), "", "post");
    $group = $form->group(_("Add Comment"));
    $group->input("author") ->label(_("Author")) ->id("gAuthor");
    $group->input("email")  ->label(_("Email"))  ->id("gEmail");
    $group->textarea("text")->label(_("Text"))   ->id("gText");
    $group->hidden("item_id")->value($item_id);
    $group->submit(_("Add"));
    $form->add_rules_from(ORM::factory("comment"));
    return $form;
  }
  static function get_edit_form($comment) {
    $form = new Forge(
      url::site("comments/{$comment->id}?_method=put"), "", "post");
    $group = $form->group(_("Edit Comment"));
    $group->input("author") ->label(_("Author")) ->id("gAuthor") ->value($comment->author);
    $group->input("email")  ->label(_("Email"))  ->id("gEmail")  ->value($comment->email);
    $group->textarea("text")->label(_("Text"))   ->id("gText")   ->value($comment->text);
    $group->submit(_("Edit"));
    $form->add_rules_from($comment);
    return $form;
  }
  /**
   * @todo Refactor this into a more generic location
   */
  private static function _add_validation_rules($model_name, $form) {
    $rules = ORM::factory($model_name)->validation_rules;
    foreach ($form->inputs as $name => $input) {
      if (isset($input->inputs)) {
        comment::_add_validation_rules($model_name, $input);
      }
      if (isset($rules[$name])) {
        $input->rules($rules[$name]);
      }
    }
  }
  static function block($theme, $show_add_form=true) {
    $block = new Block;
    $block->id = "gComments";
    $block->title = _("Comments");
    $block->content = comment::get_comments($theme->item(), "html");
    if ($show_add_form) {
      $block->content .= comment::get_add_form($theme->item())->render("form.html");
    }
    return $block;
  }
  // @todo Set proper Content-Type in a central place (REST_Controller::dispatch?).
  static function get_comments($item_id) {
    $comments = ORM::factory('comment')->where('item_id', $item_id)
      ->orderby('datetime', 'asc')
      ->find_all();
    if (!$comments->count()) {
      rest::http_status(rest::BAD_REQUEST);
      return;
    }
    switch (rest::output_format()) {
    case "atom":
      rest::http_content_type(rest::XML);
      print comment::get_atom_feed($comments);
      break;
    case "xml":
      rest::http_content_type(rest::XML);
      return xml::to_xml($comments, array("comments", "comment"));
      break;
    case "json":
      rest::http_content_type(rest::JSON);
      foreach ($comments as $comment) {
        $data[] = $comment->as_array();
      }
      return json_encode($data);
    default:
      foreach ($comments as $comment) {
        $v = new View("comment.html");
        $v->comment = $comment;
        $html[] = $v;
      }
      if (!empty($html)) {
        return "
\n" . implode("\n", $html) . "
\n";
      }
    }
  }
  public static function get_atom_entry($comment) {
    $feed = new Gallery_Atom_Entry();
    $feed->updated($comment->datetime)
      ->title(sprintf(_("Comment #%d"), $comment->id))
      ->content($comment->text)
      ->author()
        ->name($comment->author)
        ->email($comment->email)
        ->uri(url::abs_site("users/$comment->id"));
    $feed->link()->related_atom("photos/$comment->item_id");
    $feed->link()->related_image("photos/$comment->item_id");
    return $feed->as_xml();
  }
  /*
   * @todo Show photo title instead of photo ID.
   * @todo Put proper user ID into author URI.
   */
  public static function get_atom_feed($comments) {
    $latest_comment = $comments[0]->datetime;
    $item_id = $comments[0]->item_id;
    /* Set up feed header. */
    $feed = new Gallery_Atom_Feed();
    $feed->title(sprintf(_("Comments on photo %d"), $item_id));
    $feed->updated($latest_comment);
    $feed->link()->related_atom("photos/$item_id");
    $feed->link()->related_image("photos/$item_id");
    /* Add individual comments. */
    foreach ($comments as $id => $comment) {
      $feed->entry()
        ->id(url::abs_site("comments/$comment->id"))
        ->updated($comment->datetime)
        ->title(sprintf(_("Comment #%d"), $comments->count() - $id))
        ->content($comment->text)
        ->author()
          ->name($comment->author)
          ->email($comment->email)
          ->uri(url::abs_site("users/$comment->id"));
    }
    return $feed->as_xml();
  }
  /**
   * Format a human-friendly message showing the amount of time elapsed since the specified
   * timestamp (e.g. 'said today', 'said yesterday', 'said 13 days ago', 'said 5 months ago').
   *
   * @todo Take into account the viewer's time zone.
   * @todo Properly pluralize strings.
   *
   * @param integer $timestamp Unix format timestamp to compare with
   * @return string user-friendly string containing the amount of time passed
   */
  static function format_elapsed_time($timestamp) {
    $now = time();
    $time_difference = $now - $timestamp;
    $date_info_now = getdate($now);
    /* Calculate the number of days, months and years elapsed since the specified timestamp. */
    $elapsed_days = round($time_difference / comment::SECONDS_IN_A_DAY);
    $elapsed_months = round($time_difference / comment::SECONDS_IN_A_MONTH);
    $elapsed_years = round($time_difference / comment::SECONDS_IN_A_YEAR);
    $seconds_since_midnight = $date_info_now['hours'] * comment::SECONDS_IN_AN_HOUR
      + $date_info_now['minutes'] * comment::SECONDS_IN_A_MINUTE + $date_info_now['seconds'];
    /* Construct message depending on how much time passed. */
    if ($elapsed_years > 0) {
      $message = sprintf(_("said %d years ago"), $elapsed_years);
    } else if ($elapsed_months > 0) {
      $message = sprintf(_("said %d months ago"), $elapsed_months);
    } else {
      if ($time_difference < $seconds_since_midnight) {
        $message = _("said today");
      } else if ($time_difference < $seconds_since_midnight + comment::SECONDS_IN_A_DAY) {
        $message = _("said yesterday");
      } else {
        $message = sprintf(_("said %d days ago"), $elapsed_days);
      }
    }
    return $message;
   }
}