1444 lines
42 KiB
Perl
1444 lines
42 KiB
Perl
###############################################################################
|
|
# Chirpy! 0.3, a quote management system #
|
|
# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net> #
|
|
###############################################################################
|
|
# This program is free software; you can redistribute it and/or modify it #
|
|
# under the terms of the GNU General Public License as published by the Free #
|
|
# Software Foundation; either version 2 of the License, or (at your option) #
|
|
# any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
|
# more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License along #
|
|
# with this program; if not, write to the Free Software Foundation, Inc., 51 #
|
|
# Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #
|
|
###############################################################################
|
|
|
|
###############################################################################
|
|
# $Id:: UI.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
|
###############################################################################
|
|
|
|
=head1 NAME
|
|
|
|
Chirpy::UI - Abstract user interface class
|
|
|
|
=head1 TODO
|
|
|
|
A detailed description of this module's API will be available in a future
|
|
release. If you want to write your own user interface implementation, you could
|
|
try analyzing the source code of this module and its only implementation so
|
|
far, L<Chirpy::UI::WebApp>. I apologize for the inconvenience.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<Chirpy::UI::WebApp>, L<Chirpy>, L<http://chirpy.sourceforge.net/>
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2005-2007 Tim De Pauw. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free Software
|
|
Foundation; either version 2 of the License, or (at your option) any later
|
|
version.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
=cut
|
|
|
|
package Chirpy::UI;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use vars qw($VERSION);
|
|
|
|
$VERSION = '0.3';
|
|
|
|
use Chirpy 0.3;
|
|
use Chirpy::Util 0.3;
|
|
use Chirpy::UpdateChecker 0.3;
|
|
|
|
use constant START_PAGE => 1;
|
|
use constant QUOTE_BROWSER => 2;
|
|
use constant SINGLE_QUOTE => 3;
|
|
use constant RANDOM_QUOTES => 4;
|
|
use constant TOP_QUOTES => 5;
|
|
use constant BOTTOM_QUOTES => 6;
|
|
use constant QUOTES_OF_THE_WEEK => 7;
|
|
use constant QUOTE_SEARCH => 8;
|
|
use constant TAG_CLOUD => 9;
|
|
use constant STATISTICS => 10;
|
|
use constant SUBMIT_QUOTE => 11;
|
|
use constant QUOTE_RATING_UP => 12;
|
|
use constant QUOTE_RATING_DOWN => 13;
|
|
use constant REPORT_QUOTE => 14;
|
|
use constant LOGIN => 15;
|
|
use constant LOGOUT => 16;
|
|
use constant ADMINISTRATION => 17;
|
|
use constant MODERATION_QUEUE => 18;
|
|
|
|
use constant CHANGE_PASSWORD => 100;
|
|
use constant MANAGE_UNAPPROVED_QUOTES => 110;
|
|
use constant MANAGE_FLAGGED_QUOTES => 120;
|
|
use constant EDIT_QUOTE => 130;
|
|
use constant REMOVE_QUOTE => 131;
|
|
use constant ADD_NEWS => 140;
|
|
use constant EDIT_NEWS => 141;
|
|
use constant REMOVE_NEWS => 142;
|
|
use constant ADD_ACCOUNT => 150;
|
|
use constant EDIT_ACCOUNT => 151;
|
|
use constant REMOVE_ACCOUNT => 152;
|
|
use constant CHECK_FOR_UPDATE => 160;
|
|
use constant VIEW_EVENT_LOG => 170;
|
|
|
|
use constant CURRENT_PASSWORD_INVALID => -1;
|
|
use constant NEW_PASSWORD_INVALID => -2;
|
|
use constant PASSWORDS_DIFFER => -3;
|
|
|
|
# TODO: make this easily configurable one day
|
|
use constant ADMIN_PERMISSIONS => {
|
|
MANAGE_UNAPPROVED_QUOTES() => {
|
|
Chirpy::Account::USER_LEVEL_3 => 1,
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
MANAGE_FLAGGED_QUOTES() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
EDIT_QUOTE() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
REMOVE_QUOTE() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
ADD_NEWS() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
EDIT_NEWS() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
REMOVE_NEWS() => {
|
|
Chirpy::Account::USER_LEVEL_6 => 1,
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
VIEW_EVENT_LOG() => {
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
ADD_ACCOUNT() => {
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
EDIT_ACCOUNT() => {
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
REMOVE_ACCOUNT() => {
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
},
|
|
CHECK_FOR_UPDATE() => {
|
|
Chirpy::Account::USER_LEVEL_9 => 1
|
|
}
|
|
};
|
|
|
|
use constant STATISTICS_UPDATE_INTERVAL => 60 * 60;
|
|
use constant UPDATE_CHECK_INTERVAL => 7 * 24 * 60 * 60;
|
|
|
|
sub new {
|
|
my ($class, $parent, $params) = @_;
|
|
return bless {
|
|
'parent' => $parent,
|
|
'params' => $params
|
|
}, $class;
|
|
}
|
|
|
|
sub run {
|
|
my $self = shift;
|
|
my $page = $self->get_current_page();
|
|
if ($page == START_PAGE) {
|
|
$self->welcome_user(
|
|
$self->parent()->get_latest_news_items()
|
|
);
|
|
}
|
|
elsif ($page == QUOTE_BROWSER) {
|
|
my $start = $self->get_first_quote_index();
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_quotes($start)
|
|
);
|
|
}
|
|
elsif ($page == QUOTES_OF_THE_WEEK) {
|
|
my $start = $self->get_first_quote_index();
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_quotes_of_the_week($start),
|
|
);
|
|
}
|
|
elsif ($page == QUOTE_SEARCH) {
|
|
my $start = $self->get_first_quote_index();
|
|
my ($queries, $tags) = $self->get_search_instruction();
|
|
if (@$queries || @$tags) {
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_matching_quotes($start, $queries, $tags)
|
|
);
|
|
}
|
|
else {
|
|
$self->provide_quote_search_interface();
|
|
}
|
|
}
|
|
elsif ($page == SINGLE_QUOTE) {
|
|
my $quote = $self->parent()->get_quote(
|
|
$self->get_selected_quote_id());
|
|
if (defined $quote && ($quote->is_approved() || $self->moderation_queue_is_public())) {
|
|
$self->browse_quotes([ $quote ], $page);
|
|
}
|
|
else {
|
|
$self->report_inexistent_quote();
|
|
}
|
|
}
|
|
elsif ($page == RANDOM_QUOTES) {
|
|
my $quotes = $self->parent()->get_random_quotes();
|
|
(defined $quotes
|
|
? $self->browse_quotes($quotes, $page)
|
|
: $self->report_no_quotes_to_display($page));
|
|
}
|
|
elsif ($page == TOP_QUOTES) {
|
|
my $start = $self->get_first_quote_index();
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_top_quotes($start),
|
|
);
|
|
}
|
|
elsif ($page == BOTTOM_QUOTES) {
|
|
my $start = $self->get_first_quote_index();
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_bottom_quotes($start),
|
|
);
|
|
}
|
|
elsif ($page == MODERATION_QUEUE) {
|
|
if ($self->moderation_queue_is_public()) {
|
|
my $start = $self->get_first_quote_index();
|
|
$self->_browse_quotes_segmented(
|
|
$page,
|
|
$start,
|
|
$self->parent()->get_unapproved_quotes($start),
|
|
);
|
|
}
|
|
else {
|
|
$self->report_unknown_action();
|
|
}
|
|
}
|
|
elsif ($page == SUBMIT_QUOTE) {
|
|
my ($body, $notes, $tags) = $self->get_submitted_quote();
|
|
if (defined $body && $body) {
|
|
my $approved
|
|
= $self->administration_allowed(Chirpy::UI::MANAGE_UNAPPROVED_QUOTES);
|
|
$body = Chirpy::Util::clean_up_submission($body);
|
|
$notes = (defined $notes
|
|
? Chirpy::Util::clean_up_submission($notes)
|
|
: undef);
|
|
$tags = Chirpy::Util::parse_tags($tags);
|
|
my $quote = $self->parent()->add_quote(
|
|
$body,
|
|
$notes,
|
|
$approved,
|
|
$tags
|
|
);
|
|
$self->confirm_quote_submission($approved);
|
|
my $id = $quote->get_id();
|
|
$self->_log_event(Chirpy::Event::ADD_QUOTE, {
|
|
'id' => $id,
|
|
'body' => $body,
|
|
(defined $notes && length($notes) ? ('notes' => $notes) : ()),
|
|
(@$tags ? ('tags' => join(' ', sort @$tags)) : ())
|
|
});
|
|
if ($approved) {
|
|
$self->_log_event(Chirpy::Event::APPROVE_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_quote_submission_interface();
|
|
}
|
|
}
|
|
elsif ($page == QUOTE_RATING_UP || $page == QUOTE_RATING_DOWN) {
|
|
my $up = ($page == QUOTE_RATING_UP);
|
|
my $id = $self->get_selected_quote_id();
|
|
my $quote = $self->parent()->get_quote($id);
|
|
if (defined $quote
|
|
&& ($quote->is_approved() || $self->moderation_queue_is_public())) {
|
|
my $last_rating = $self->_last_rating($id);
|
|
if (($page == QUOTE_RATING_UP && $last_rating > 0)
|
|
|| ($page == QUOTE_RATING_DOWN && $last_rating < 0)) {
|
|
$self->report_quote_already_rated($id);
|
|
}
|
|
else {
|
|
my ($history, $full) = $self->_rating_history();
|
|
if (!$full) {
|
|
if ($self->quote_rating_confirmed()) {
|
|
$self->_rate_quote($id, $up, abs($last_rating), $history);
|
|
}
|
|
else {
|
|
$self->request_quote_rating_confirmation(
|
|
$quote, $up, abs($last_rating));
|
|
}
|
|
}
|
|
else {
|
|
$self->report_quote_rating_limit_excess();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$self->report_rated_quote_not_found();
|
|
}
|
|
}
|
|
elsif ($page == REPORT_QUOTE) {
|
|
my $id = $self->get_selected_quote_id();
|
|
my $quote = $self->parent()->get_quote($id);
|
|
if (defined $quote && $quote->is_approved()) {
|
|
if ($self->quote_report_confirmed()) {
|
|
$self->parent()->flag_quotes($id);
|
|
$self->confirm_quote_report($id);
|
|
$self->_log_event(Chirpy::Event::REPORT_QUOTE, { 'id' => $id });
|
|
}
|
|
else {
|
|
$self->request_quote_report_confirmation($quote);
|
|
}
|
|
}
|
|
else {
|
|
$self->report_reported_quote_not_found();
|
|
}
|
|
}
|
|
elsif ($page eq TAG_CLOUD) {
|
|
my $tag_counts = $self->parent()->get_tag_use_counts();
|
|
if (%$tag_counts) {
|
|
$self->_provide_tag_cloud($tag_counts);
|
|
}
|
|
else {
|
|
$self->report_no_tagged_quotes();
|
|
}
|
|
}
|
|
elsif ($page eq STATISTICS) {
|
|
$self->_provide_statistics();
|
|
}
|
|
elsif ($page == LOGIN) {
|
|
if ($self->attempting_login()) {
|
|
my ($username, $password)
|
|
= $self->get_supplied_username_and_password();
|
|
my $account = $self->parent()
|
|
->attempt_login($username, $password);
|
|
if (defined $account) {
|
|
$self->set_logged_in_user($account->get_id());
|
|
$self->confirm_login();
|
|
$self->_log_event(Chirpy::Event::LOGIN_SUCCESS);
|
|
}
|
|
else {
|
|
$self->report_invalid_login();
|
|
$self->_log_event(Chirpy::Event::LOGIN_FAILURE,
|
|
{ 'username' => $username });
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_login_interface();
|
|
}
|
|
}
|
|
elsif ($page == LOGOUT) {
|
|
$self->set_logged_in_user(undef);
|
|
$self->confirm_logout();
|
|
}
|
|
elsif ($page == ADMINISTRATION) {
|
|
if (defined $self->get_logged_in_user_account()) {
|
|
$self->_provide_administration_interface()
|
|
}
|
|
else {
|
|
$self->provide_login_interface();
|
|
}
|
|
}
|
|
else {
|
|
$self->report_unknown_action();
|
|
}
|
|
}
|
|
|
|
sub statistics_update_allowed {
|
|
return 1;
|
|
}
|
|
|
|
sub _provide_administration_interface {
|
|
my $self = shift;
|
|
if ($self->configuration()->get('general', 'update_check')
|
|
&& $self->administration_allowed(CHECK_FOR_UPDATE)) {
|
|
$self->_maybe_check_for_update();
|
|
}
|
|
my $page = $self->get_current_administration_page() || 0;
|
|
if ($page == CHANGE_PASSWORD) {
|
|
my $user = $self->get_logged_in_user_account();
|
|
if ($self->attempting_password_change()) {
|
|
my ($current_password, $new_password, $repeat_password)
|
|
= $self->get_supplied_passwords();
|
|
if (!defined $self->parent()
|
|
->attempt_login(
|
|
$user->get_username(), $current_password)) {
|
|
$self->provide_password_change_interface(
|
|
CURRENT_PASSWORD_INVALID);
|
|
}
|
|
elsif (!Chirpy::Util::valid_password($new_password)) {
|
|
$self->provide_password_change_interface(
|
|
NEW_PASSWORD_INVALID);
|
|
}
|
|
elsif ($new_password ne $repeat_password) {
|
|
$self->provide_password_change_interface(
|
|
PASSWORDS_DIFFER);
|
|
}
|
|
else {
|
|
$self->parent()->modify_account(
|
|
$user, undef, $new_password);
|
|
$self->confirm_password_change();
|
|
$self->_log_event(Chirpy::Event::CHANGE_PASSWORD);
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_password_change_interface();
|
|
}
|
|
}
|
|
elsif ($page == MANAGE_UNAPPROVED_QUOTES) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my ($approve, $remove, $edited)
|
|
= $self->get_quote_approval_result();
|
|
my @approve = (defined $approve && ref $approve eq 'ARRAY'
|
|
? @$approve : ());
|
|
my @remove = (defined $remove && ref $remove eq 'ARRAY'
|
|
? @$remove : ());
|
|
if (@approve) {
|
|
$self->parent()->approve_quotes(@approve);
|
|
foreach my $id (@approve) {
|
|
if (exists $edited->{$id}) {
|
|
my $quote = $self->parent()->get_quote($id);
|
|
if (defined $quote) {
|
|
my $body = $edited->{$id}->{'body'};
|
|
my $notes = $edited->{$id}->{'notes'};
|
|
my $tags = $edited->{$id}->{'tags'};
|
|
if ($body) {
|
|
$self->_modify_quote(
|
|
$quote, $body, $notes, $tags);
|
|
}
|
|
}
|
|
}
|
|
$self->_log_event(Chirpy::Event::APPROVE_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
}
|
|
if (@remove) {
|
|
$self->parent()->remove_quotes(@remove);
|
|
foreach my $id (@remove) {
|
|
$self->_log_event(Chirpy::Event::REMOVE_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
}
|
|
$self->provide_quote_approval_interface(
|
|
@approve ? \@approve : undef,
|
|
@remove ? \@remove : undef);
|
|
}
|
|
}
|
|
elsif ($page == MANAGE_FLAGGED_QUOTES) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my ($unflag, $remove) = $self->get_quote_flag_management_result();
|
|
my @unflag = (defined $unflag && ref $unflag eq 'ARRAY'
|
|
? @$unflag : ());
|
|
my @remove = (defined $remove && ref $remove eq 'ARRAY'
|
|
? @$remove : ());
|
|
if (@unflag) {
|
|
$self->parent()->unflag_quotes(@unflag);
|
|
foreach my $id (@unflag) {
|
|
$self->_log_event(Chirpy::Event::UNFLAG_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
}
|
|
if (@remove) {
|
|
$self->parent()->remove_quotes(@remove);
|
|
foreach my $id (@remove) {
|
|
$self->_log_event(Chirpy::Event::REMOVE_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
}
|
|
$self->provide_quote_flag_management_interface(
|
|
@unflag ? \@unflag : undef,
|
|
@remove ? \@remove : undef);
|
|
}
|
|
}
|
|
elsif ($page == EDIT_QUOTE) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $id = $self->get_quote_to_edit();
|
|
if ($id) {
|
|
my $quote = $self->parent()->get_quote($id);
|
|
if (defined $quote && $quote->is_approved()) {
|
|
my ($body, $notes, $tags)
|
|
= $self->get_modified_quote_information();
|
|
if ($body) {
|
|
$self->_modify_quote($quote, $body, $notes, $tags);
|
|
$self->confirm_quote_modification($quote);
|
|
}
|
|
else {
|
|
$self->provide_quote_editing_interface($quote);
|
|
}
|
|
}
|
|
else {
|
|
$self->report_quote_to_edit_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_quote_selection_for_modification_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == REMOVE_QUOTE) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $id = $self->get_quote_to_remove();
|
|
if ($id) {
|
|
my $quote = $self->parent()->get_quote($id);
|
|
if (defined $quote && $quote->is_approved()) {
|
|
if ($self->quote_removal_confirmed()) {
|
|
$self->parent()->remove_quotes($id);
|
|
$self->confirm_quote_removal();
|
|
$self->_log_event(Chirpy::Event::REMOVE_QUOTE,
|
|
{ 'id' => $id });
|
|
}
|
|
else {
|
|
$self->request_quote_removal_confirmation($quote);
|
|
}
|
|
}
|
|
else {
|
|
$self->report_quote_to_remove_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_quote_selection_for_removal_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == ADD_NEWS) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
if (my $news = $self->get_news_item_to_add()) {
|
|
my $news = Chirpy::Util::clean_up_submission($news);
|
|
my $item = $self->parent()->add_news_item(
|
|
$news,
|
|
$self->get_logged_in_user_account()
|
|
);
|
|
$self->confirm_news_submission($news);
|
|
$self->_log_event(Chirpy::Event::ADD_NEWS, {
|
|
'id' => $item->get_id(), 'body' => $news
|
|
});
|
|
}
|
|
else {
|
|
$self->provide_news_submission_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == EDIT_NEWS) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $id = $self->get_news_item_to_edit();
|
|
if ($id) {
|
|
my $item = $self->parent()->get_news_item($id);
|
|
if (defined $item) {
|
|
my ($text, $poster_id) = $self->get_modified_news_item();
|
|
if ($text) {
|
|
my $old_body = $item->get_body();
|
|
my $old_poster = $item->get_poster();
|
|
$text = Chirpy::Util::clean_up_submission($text);
|
|
$self->parent()->modify_news_item(
|
|
$item,
|
|
$text,
|
|
$self->parent()->get_account_by_id($poster_id)
|
|
);
|
|
$self->confirm_news_item_modification();
|
|
$self->_log_event(Chirpy::Event::EDIT_NEWS, {
|
|
'id' => $id,
|
|
($old_body ne $text ? ('new_body' => $text) : ()),
|
|
(!defined $old_poster
|
|
|| $old_poster->get_id() != $poster_id
|
|
? ('new_poster' => $poster_id) : ())
|
|
});
|
|
}
|
|
else {
|
|
$self->provide_news_item_editing_interface($item);
|
|
}
|
|
}
|
|
else {
|
|
$self->report_news_item_to_edit_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_news_item_selection_for_modification_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == REMOVE_NEWS) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $id = $self->get_news_item_to_remove();
|
|
if ($id) {
|
|
my $item = $self->parent()->get_news_item($id);
|
|
if (defined $item) {
|
|
$self->parent()->remove_news_items($id);
|
|
$self->confirm_news_item_removal();
|
|
$self->_log_event(Chirpy::Event::REMOVE_NEWS,
|
|
{ 'id' => $id });
|
|
}
|
|
else {
|
|
$self->report_news_item_to_remove_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_news_item_selection_for_removal_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == ADD_ACCOUNT) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my ($username, $password, $repeat_password, $level)
|
|
= $self->get_account_information_to_add();
|
|
if (defined $username) {
|
|
if (!Chirpy::Util::valid_username($username)) {
|
|
$self->report_invalid_new_username();
|
|
}
|
|
elsif ($self->parent()->username_exists($username)) {
|
|
$self->report_new_username_exists();
|
|
}
|
|
elsif (!Chirpy::Util::valid_password($password)) {
|
|
$self->report_invalid_new_password();
|
|
}
|
|
elsif ($password ne $repeat_password) {
|
|
$self->report_different_new_passwords();
|
|
}
|
|
elsif ($level <= 0) {
|
|
$self->report_invalid_new_user_level();
|
|
}
|
|
else {
|
|
my $account = $self->parent()->add_account(
|
|
$username, $password, $level);
|
|
$self->confirm_account_creation();
|
|
$self->_log_event(Chirpy::Event::ADD_ACCOUNT, {
|
|
'id' => $account->get_id(),
|
|
'username' => $username,
|
|
'level' => $level
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_account_creation_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == EDIT_ACCOUNT) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $id = $self->get_account_to_modify();
|
|
if (defined $id) {
|
|
my $account = $self->parent()->get_account_by_id($id);
|
|
if (defined $account) {
|
|
my ($username, $password, $repeat_password, $level)
|
|
= $self->get_modified_account_information();
|
|
if (defined $username
|
|
&& !Chirpy::Util::valid_username($username)) {
|
|
$self->report_invalid_modified_username();
|
|
}
|
|
elsif ($self->parent()->username_exists($username)) {
|
|
$self->report_modified_username_exists();
|
|
}
|
|
elsif (defined $password
|
|
&& !Chirpy::Util::valid_password($password)) {
|
|
$self->report_invalid_modified_password();
|
|
}
|
|
elsif (defined $password
|
|
&& $password ne $repeat_password) {
|
|
$self->report_different_modified_passwords();
|
|
}
|
|
elsif (defined $level && $level <= 0) {
|
|
$self->report_invalid_modified_user_level();
|
|
}
|
|
elsif (!defined $username && !defined $password
|
|
&& !defined $level) {
|
|
$self->report_modified_account_information_required();
|
|
}
|
|
else {
|
|
my $old_username = $account->get_username();
|
|
my $old_password = $account->get_password();
|
|
my $old_level = $account->get_level();
|
|
$self->parent()->modify_account(
|
|
$account, $username, $password, $level);
|
|
$self->confirm_account_modification();
|
|
$self->_log_event(Chirpy::Event::EDIT_ACCOUNT, {
|
|
'id' => $id,
|
|
(defined $username && $old_username ne $username
|
|
? ('new_username' => $username) : ()),
|
|
(defined $level && $old_level != $level
|
|
? ('new_level' => $level) : ()),
|
|
(defined $password
|
|
? ('password_changed' => 1) : ())
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
$self->report_account_to_modify_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_account_selection_for_modification_interface();
|
|
}
|
|
}
|
|
}
|
|
elsif ($page == REMOVE_ACCOUNT) {
|
|
if (!$self->administration_allowed($page)) {
|
|
$self->report_administration_user_level_insufficient($page);
|
|
}
|
|
else {
|
|
my $parent = $self->parent();
|
|
if ($parent->account_count() > 1) {
|
|
my $id = $self->get_account_to_remove();
|
|
if (defined $id) {
|
|
my $account = $self->parent()->get_account_by_id($id);
|
|
my $level = Chirpy::Account::USER_LEVEL_9;
|
|
if ($account->get_level() == $level
|
|
&& $parent->account_count_by_level($level) <= 1) {
|
|
$self->report_last_owner_account_removal_error();
|
|
}
|
|
elsif (defined $account) {
|
|
if ($account->get_id()
|
|
== $self->get_logged_in_user_account()->get_id()) {
|
|
$self->set_logged_in_user(undef);
|
|
}
|
|
my $username = $account->get_username();
|
|
my $level = $account->get_level();
|
|
$parent->remove_accounts($id);
|
|
$self->confirm_account_removal();
|
|
$self->_log_event(Chirpy::Event::REMOVE_ACCOUNT,
|
|
{ 'id' => $id });
|
|
}
|
|
else {
|
|
$self->report_account_to_remove_not_found();
|
|
}
|
|
}
|
|
else {
|
|
$self->provide_account_selection_for_removal_interface();
|
|
}
|
|
}
|
|
else {
|
|
$self->report_last_owner_account_removal_error();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$self->welcome_administrator();
|
|
}
|
|
}
|
|
|
|
sub _maybe_check_for_update {
|
|
my $self = shift;
|
|
my $last_check = $self->get_parameter('last_update_check');
|
|
my $now = time();
|
|
my @update_info;
|
|
my $update_check_error;
|
|
if (!defined $last_check
|
|
|| $last_check + UPDATE_CHECK_INTERVAL < $now) {
|
|
$self->set_parameter('last_update_check', $now);
|
|
my $upd_status = $self->_check_for_update();
|
|
if (defined $upd_status) {
|
|
if (ref $upd_status eq 'ARRAY') {
|
|
$self->set_parameter('update_version', $upd_status->[0]);
|
|
$self->set_parameter('update_released', $upd_status->[1]);
|
|
$self->set_parameter('update_url', $upd_status->[2]);
|
|
$self->set_parameter('update_version_current',
|
|
$Chirpy::VERSION);
|
|
@update_info = @$upd_status;
|
|
}
|
|
else {
|
|
$update_check_error = $upd_status;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
my $version = $self->get_parameter('update_version');
|
|
if ($version) {
|
|
my $version_at_check
|
|
= $self->get_parameter('update_version_current');
|
|
if ($version_at_check == $Chirpy::VERSION) {
|
|
my $date = $self->get_parameter('update_released');
|
|
my $url = $self->get_parameter('update_url');
|
|
@update_info = ($version, $date, $url);
|
|
}
|
|
else {
|
|
$self->set_parameter('update_version', undef);
|
|
$self->set_parameter('update_released', undef);
|
|
$self->set_parameter('update_url', undef);
|
|
$self->set_parameter('update_version_current', undef);
|
|
}
|
|
}
|
|
}
|
|
if (defined $update_check_error) {
|
|
$self->update_check_error($update_check_error);
|
|
}
|
|
elsif (@update_info) {
|
|
$self->update_available(@update_info);
|
|
}
|
|
}
|
|
|
|
sub _check_for_update {
|
|
my $self = shift;
|
|
my $uc = new Chirpy::UpdateChecker($self);
|
|
my $result = $uc->check_for_updates();
|
|
return undef unless ($result);
|
|
return $uc->get_error_message() if (ref $result ne 'ARRAY');
|
|
return $result;
|
|
}
|
|
|
|
sub _browse_quotes_segmented {
|
|
my ($self, $page, $start, $quotes, $leading, $trailing) = @_;
|
|
if (defined $quotes) {
|
|
my $per_page = $self->parent()->quotes_per_page();
|
|
my ($previous, $next);
|
|
if ($leading) {
|
|
$previous = $start - $per_page;
|
|
$previous = 0 if ($previous < 0);
|
|
}
|
|
if ($trailing) {
|
|
$next = $start + $per_page;
|
|
}
|
|
$self->browse_quotes($quotes, $page, $previous, $next);
|
|
}
|
|
elsif ($page == QUOTE_SEARCH) {
|
|
$self->report_no_search_results();
|
|
}
|
|
else {
|
|
$self->report_no_quotes_to_display($page);
|
|
}
|
|
}
|
|
|
|
sub _provide_tag_cloud {
|
|
my ($self, $tag_counts) = @_;
|
|
my $highest = 0;
|
|
my $lowest = undef;
|
|
foreach my $cnt (values %$tag_counts) {
|
|
$lowest = $cnt if (!defined $lowest || $cnt < $lowest);
|
|
$highest = $cnt if ($cnt > $highest);
|
|
}
|
|
my $difference = $highest - $lowest;
|
|
my @tag_info = ();
|
|
my $conf = $self->configuration();
|
|
my @tags = $conf->get('ui', 'randomize_tag_cloud')
|
|
? Chirpy::Util::shuffle_array(keys %$tag_counts)
|
|
: sort keys %$tag_counts;
|
|
my @tag_info_list;
|
|
my $factor = $conf->get('ui', 'tag_cloud_percentage_delta') || 100;
|
|
my $logarithmic = ($difference > 1
|
|
&& $conf->get('ui', 'tag_cloud_logarithmic'));
|
|
$factor /= log($difference) if ($logarithmic);
|
|
foreach my $tag (@tags) {
|
|
my $cnt = $tag_counts->{$tag};
|
|
my $delta;
|
|
if (!$difference || $cnt == $lowest) {
|
|
$delta = 0;
|
|
}
|
|
elsif ($logarithmic) {
|
|
$delta = $factor * log($cnt - $lowest);
|
|
}
|
|
else {
|
|
$delta = $factor * ($cnt - $lowest) / $difference;
|
|
}
|
|
$tag = [
|
|
$tag,
|
|
$cnt,
|
|
sprintf('%.0f', 100 + $delta)
|
|
];
|
|
}
|
|
$self->provide_tag_cloud(\@tags);
|
|
}
|
|
|
|
sub _provide_statistics {
|
|
my $self = shift;
|
|
my $stats;
|
|
require Storable;
|
|
my $file = $self->_statistics_cache_file();
|
|
my $exists = (-e $file);
|
|
my $tag_file = $file . '.tag';
|
|
if ((!$exists || (
|
|
$self->statistics_update_allowed()
|
|
&& (stat($file))[9] + STATISTICS_UPDATE_INTERVAL < time
|
|
)) && (!-e $tag_file
|
|
# If the update seems to be taking longer than the update interval itself,
|
|
# something probably went wrong. In this case, we just try again.
|
|
|| (stat($tag_file))[9] + STATISTICS_UPDATE_INTERVAL < time)) {
|
|
local *TAG;
|
|
open(TAG, '>', $tag_file) and close(TAG);
|
|
$stats = $self->_compute_statistics();
|
|
if (defined $stats) {
|
|
Storable::store($stats, $file);
|
|
}
|
|
unlink $tag_file;
|
|
}
|
|
elsif ($exists) {
|
|
$stats = Storable::retrieve($file);
|
|
}
|
|
if (defined $stats) {
|
|
$self->provide_statistics(@$stats);
|
|
}
|
|
else {
|
|
$self->report_statistics_unavailable();
|
|
}
|
|
}
|
|
|
|
sub _compute_statistics {
|
|
my $self = shift;
|
|
my $quotes = $self->parent()->get_quotes(0, 0, [ [ 'submitted', 0 ] ]);
|
|
return undef unless (@$quotes);
|
|
my $by_date = [];
|
|
my $by_hour = &_init_array(0, 24);
|
|
my $by_month = &_init_array(0, 12);
|
|
my $by_day = &_init_array(0, 31);
|
|
my $by_weekday = &_init_array(0, 7);
|
|
my $by_rating = {};
|
|
my $by_votes = {};
|
|
my $votes_by_rating = [ 0, 0 ];
|
|
my ($date, $weekday, $year, $month, $day, $prev_time);
|
|
foreach my $quote (@$quotes) {
|
|
my $time = $quote->get_date_submitted();
|
|
my $d = $self->format_date($time);
|
|
my @time = $self->get_time($time);
|
|
if (!defined($date) || $d ne $date) {
|
|
if (defined $prev_time) {
|
|
$self->_pad_statistics($prev_time, $date, $d, $by_date);
|
|
}
|
|
$prev_time = $time;
|
|
$date = $d;
|
|
$day = $time[3] - 1;
|
|
$month = $time[4];
|
|
$year = $time[5];
|
|
$weekday = $time[6];
|
|
}
|
|
&_add_statistic($date, 1, $by_date);
|
|
$by_weekday->[$weekday]++;
|
|
$by_day->[$day]++;
|
|
$by_month->[$month]++;
|
|
$by_hour->[$time[2]]++;
|
|
my $rating = $quote->get_rating();
|
|
my $votes = $quote->get_vote_count();
|
|
$by_rating->{$rating}++;
|
|
$by_votes->{$votes}++;
|
|
my $votes_down = ($votes - $rating) / 2;
|
|
my $votes_up = $votes - $votes_down;
|
|
$votes_by_rating->[0] += $votes_up;
|
|
$votes_by_rating->[1] += $votes_down;
|
|
}
|
|
return [
|
|
$by_date, $by_hour, $by_weekday, $by_day, $by_month,
|
|
&_to_sorted_array($by_rating, 1), &_to_sorted_array($by_votes, 0),
|
|
$votes_by_rating
|
|
];
|
|
}
|
|
|
|
sub _to_sorted_array {
|
|
my ($hashref, $negative) = @_;
|
|
my @keys = keys %$hashref;
|
|
my $highest = shift @keys;
|
|
$highest = abs $highest if ($negative);
|
|
foreach my $k (@keys) {
|
|
my $a = ($negative ? abs $k : $k);
|
|
if ($a > $highest) {
|
|
$highest = $a;
|
|
}
|
|
}
|
|
my @res = ();
|
|
for (my $i = ($negative ? - $highest : 0); $i <= $highest; $i++) {
|
|
push @res, [ $i, $hashref->{$i} || 0 ];
|
|
}
|
|
return \@res;
|
|
}
|
|
|
|
sub _init_array {
|
|
my ($value, $length) = @_;
|
|
my @array = ();
|
|
foreach (1..$length) {
|
|
push @array, $value;
|
|
}
|
|
return \@array;
|
|
}
|
|
|
|
sub _add_statistic {
|
|
my ($name, $value, $aref) = @_;
|
|
if (@$aref && $aref->[scalar(@$aref) - 1]->[0] eq $name) {
|
|
$aref->[scalar(@$aref) - 1]->[1] += $value;
|
|
}
|
|
else {
|
|
push @$aref, [$name, $value];
|
|
}
|
|
}
|
|
|
|
sub _pad_statistics {
|
|
my ($self, $from, $from_date, $to, $by_date) = @_;
|
|
while (1) {
|
|
my ($next_date, $next_date_time)
|
|
= $self->_next_date($from, $from_date);
|
|
last if ($next_date eq $to);
|
|
&_add_statistic($next_date, 0, $by_date);
|
|
my @time = $self->get_time($next_date_time);
|
|
$from = $next_date_time;
|
|
$from_date = $next_date;
|
|
}
|
|
}
|
|
|
|
sub _next_date {
|
|
my ($self, $time, $date) = @_;
|
|
while (1) {
|
|
$time += 23 * 60 * 60;
|
|
my $d = $self->format_date($time);
|
|
next if ($d eq $date);
|
|
return ($d, $time);
|
|
}
|
|
}
|
|
|
|
sub _last_rating {
|
|
my ($self, $id) = @_;
|
|
my @list = $self->get_rated_quotes();
|
|
foreach my $i (@list) {
|
|
next unless (abs($i) == $id);
|
|
return ($i < 0 ? -1 : 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub _rate_quote {
|
|
my ($self, $id, $up, $revert, $history) = @_;
|
|
my $parent = $self->parent();
|
|
my ($new_rating, $new_vote_count);
|
|
my @rated = $self->get_rated_quotes();
|
|
if ($revert) {
|
|
($new_rating, $new_vote_count) = ($up
|
|
? $parent->increase_quote_rating($id, 1)
|
|
: $parent->decrease_quote_rating($id, 1));
|
|
@rated = grep { abs($_) != $id } @rated;
|
|
}
|
|
else {
|
|
($new_rating, $new_vote_count) = ($up
|
|
? $parent->increase_quote_rating($id)
|
|
: $parent->decrease_quote_rating($id));
|
|
$self->set_rating_history(@$history, time);
|
|
}
|
|
$self->set_rated_quotes(@rated, ($up ? $id : -$id));
|
|
$self->confirm_quote_rating($id, $up, $new_rating, $new_vote_count);
|
|
$self->_log_event(($up
|
|
? Chirpy::Event::QUOTE_RATING_UP
|
|
: Chirpy::Event::QUOTE_RATING_DOWN),
|
|
{ 'id' => $id, 'new_rating' => $new_rating,
|
|
($revert ? ('revert' => 1) : ()) });
|
|
}
|
|
|
|
sub _rating_history {
|
|
my $self = shift;
|
|
my $conf = $self->parent()->configuration();
|
|
my $max_time = $conf->get('general', 'rating_limit_time');
|
|
my $max_size = $conf->get('general', 'rating_limit_count');
|
|
return undef if ($max_time <= 0 || $max_size <= 0);
|
|
my $time = time;
|
|
my $since = $time - $max_time;
|
|
my @history = $self->get_rating_history();
|
|
shift @history
|
|
while (@history && $history[0] < $since);
|
|
return (\@history, @history >= $max_size);
|
|
}
|
|
|
|
sub _modify_quote {
|
|
my ($self, $quote, $body, $notes, $tags) = @_;
|
|
my $id = $quote->get_id();
|
|
$body = Chirpy::Util::clean_up_submission($body);
|
|
$notes = Chirpy::Util::clean_up_submission($notes);
|
|
$tags = Chirpy::Util::parse_tags($tags);
|
|
my $old_body = $quote->get_body();
|
|
my $old_notes = $quote->get_notes();
|
|
my $old_tags = join('', sort @{$quote->get_tags()});
|
|
$self->parent()->modify_quote($quote, $body, $notes, $tags);
|
|
$tags = join(' ', sort @$tags);
|
|
$self->_log_event(Chirpy::Event::EDIT_QUOTE, {
|
|
'id' => $id,
|
|
($old_body ne $body ? ('new_body' => $body) : ()),
|
|
($old_notes ne $notes ? ('new_notes' => $notes) : ()),
|
|
($old_tags ne $tags ? ('new_tags' => $tags) : ())
|
|
});
|
|
}
|
|
|
|
sub _log_event {
|
|
my ($self, $code, $params) = @_;
|
|
$params = {} unless (defined $params);
|
|
my $info = $self->get_user_information();
|
|
while (my ($k, $v) = each %$info) {
|
|
$params->{'user:' . $k} = $v;
|
|
}
|
|
$self->parent()->log_event(
|
|
$code,
|
|
$self->get_logged_in_user_account(),
|
|
$params
|
|
);
|
|
}
|
|
|
|
sub _statistics_cache_file {
|
|
my $self = shift;
|
|
return $self->configuration()->get('general', 'base_path')
|
|
. '/cache/statistics';
|
|
}
|
|
|
|
sub moderation_queue_is_public {
|
|
my $self = shift;
|
|
return $self->configuration()->get('ui', 'moderation_queue_public');
|
|
}
|
|
|
|
sub get_news_posters {
|
|
my $self = shift;
|
|
return $self->parent()->get_accounts_by_level(
|
|
keys %{ADMIN_PERMISSIONS->{ADD_NEWS()}});
|
|
}
|
|
|
|
sub get_logged_in_user_account {
|
|
my $self = shift;
|
|
my $id = $self->get_logged_in_user();
|
|
return (defined $id
|
|
? $self->parent()->get_account_by_id($id)
|
|
: undef);
|
|
}
|
|
|
|
sub administration_allowed {
|
|
my ($self, $action) = @_;
|
|
return 0 unless exists ADMIN_PERMISSIONS->{$action};
|
|
my $user = $self->get_logged_in_user_account();
|
|
return 0 unless defined $user;
|
|
my $level = $user->get_level();
|
|
return ADMIN_PERMISSIONS->{$action}{$level};
|
|
}
|
|
|
|
sub get_parameter {
|
|
my ($self, $name) = @_;
|
|
return $self->parent()->get_parameter($name);
|
|
}
|
|
|
|
sub set_parameter {
|
|
my ($self, $name, $value) = @_;
|
|
$self->parent()->set_parameter($name, $value);
|
|
}
|
|
|
|
sub format_date_time {
|
|
my ($self, $timestamp) = @_;
|
|
return Chirpy::Util::format_date_time($timestamp,
|
|
$self->configuration()->get('ui', 'date_time_format'),
|
|
$self->configuration()->get('ui', 'use_gmt'));
|
|
}
|
|
|
|
sub format_date {
|
|
my ($self, $timestamp) = @_;
|
|
return Chirpy::Util::format_date_time($timestamp,
|
|
$self->configuration()->get('ui', 'date_format'),
|
|
$self->configuration()->get('ui', 'use_gmt'));
|
|
}
|
|
|
|
sub format_time {
|
|
my ($self, $timestamp) = @_;
|
|
return Chirpy::Util::format_date_time($timestamp,
|
|
$self->configuration()->get('ui', 'time_format'),
|
|
$self->configuration()->get('ui', 'use_gmt'));
|
|
}
|
|
|
|
sub format_month {
|
|
my ($self, $month_id) = @_;
|
|
my @months = qw(january february march april may june
|
|
july august september october november december);
|
|
my $l = $self->locale();
|
|
my $month = $month_id % 100;
|
|
my $year = $month_id - $month;
|
|
my $m = $months[$month];
|
|
my $suffix = ($year ? ' ' . (1900 + $year / 100) : '');
|
|
my $long = $l->get_string($m) . $suffix;
|
|
my $short = $l->get_string($m . '_short') . $suffix;
|
|
return ($short, $long);
|
|
}
|
|
|
|
sub get_time {
|
|
my ($self, $timestamp) = @_;
|
|
return ($self->configuration()->get('ui', 'use_gmt')
|
|
? gmtime($timestamp) : localtime($timestamp));
|
|
}
|
|
|
|
sub locale {
|
|
my $self = shift;
|
|
return $self->parent()->locale();
|
|
}
|
|
|
|
sub configuration {
|
|
my $self = shift;
|
|
return $self->parent()->configuration();
|
|
}
|
|
|
|
sub parent {
|
|
my $self = shift;
|
|
return $self->{'parent'};
|
|
}
|
|
|
|
sub param {
|
|
my ($self, $name) = @_;
|
|
return defined $self->{'params'} ? $self->{'params'}{$name} : undef;
|
|
}
|
|
|
|
*get_target_version = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_current_page = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_selected_quote_id = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_first_quote_index = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_search_instruction = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_submitted_quote = \&Chirpy::Util::abstract_method;
|
|
|
|
*attempting_login = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_supplied_username_and_password = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_rating_history = \&Chirpy::Util::abstract_method;
|
|
|
|
*set_rating_history = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_rated_quotes = \&Chirpy::Util::abstract_method;
|
|
|
|
*set_rated_quotes = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_logged_in_user = \&Chirpy::Util::abstract_method;
|
|
|
|
*set_logged_in_user = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_no_quotes_to_display = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_unknown_action = \&Chirpy::Util::abstract_method;
|
|
|
|
*welcome_user = \&Chirpy::Util::abstract_method;
|
|
|
|
*browse_quotes = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_search_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_tag_cloud = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_no_tagged_quotes = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_statistics = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_statistics_unavailable = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_no_search_results = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_inexistent_quote = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_submission_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_quote_submission = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_quote_rating = \&Chirpy::Util::abstract_method;
|
|
|
|
*quote_rating_confirmed = \&Chirpy::Util::abstract_method;
|
|
|
|
*request_quote_rating_confirmation = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_rated_quote_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_quote_already_rated = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_quote_rating_limit_excess = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_quote_report = \&Chirpy::Util::abstract_method;
|
|
|
|
*quote_report_confirmed = \&Chirpy::Util::abstract_method;
|
|
|
|
*request_quote_report_confirmation = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_reported_quote_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_login_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_login = \&Chirpy::Util::abstract_method;
|
|
|
|
*attempting_password_change = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_supplied_passwords = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_password_change_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_password_change = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_login = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_logout = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_current_administration_page = \&Chirpy::Util::abstract_method;
|
|
|
|
*welcome_administrator = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_quote_to_remove = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_quote_removal = \&Chirpy::Util::abstract_method;
|
|
|
|
*quote_removal_confirmed = \&Chirpy::Util::abstract_method;
|
|
|
|
*request_quote_removal_confirmation = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_quote_to_remove_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_selection_for_removal_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*get_quote_to_edit = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_modified_quote_information = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_quote_modification = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_editing_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_quote_to_edit_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_selection_for_modification_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_approval_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_quote_approval_result = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_flag_management_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_quote_flag_management_result = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_news_item_to_add = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_news_submission = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_news_submission_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_news_item_to_edit = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_modified_news_item = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_news_item_modification = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_news_item_to_edit_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_news_item_editing_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_news_item_selection_for_modification_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*get_news_item_to_remove = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_news_item_removal = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_news_item_to_remove_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_quote_selection_for_removal_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*get_account_information_to_add = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_new_username = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_new_username_exists = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_new_password = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_different_new_passwords = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_new_user_level = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_account_creation = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_account_creation_interface = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_account_to_modify = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_modified_account_information = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_modified_username = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_modified_username_exists = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_modified_password = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_different_modified_passwords = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_invalid_modified_user_level = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_account_modification = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_account_to_modify_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_modified_account_information_required
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_account_selection_for_modification_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*get_account_to_remove = \&Chirpy::Util::abstract_method;
|
|
|
|
*confirm_account_removal = \&Chirpy::Util::abstract_method;
|
|
|
|
*report_account_to_remove_not_found = \&Chirpy::Util::abstract_method;
|
|
|
|
*provide_account_selection_for_removal_interface
|
|
= \&Chirpy::Util::abstract_method;
|
|
|
|
*report_last_owner_account_removal_error = \&Chirpy::Util::abstract_method;
|
|
|
|
*get_user_information = \&Chirpy::Util::abstract_method;
|
|
|
|
*update_available = \&Chirpy::Util::abstract_method;
|
|
|
|
*update_check_error = \&Chirpy::Util::abstract_method;
|
|
|
|
1;
|
|
|
|
############################################################################### |