850 lines
23 KiB
Perl
850 lines
23 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:: Chirpy.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
|
###############################################################################
|
|
|
|
=head1 NAME
|
|
|
|
Chirpy - Main coordination class
|
|
|
|
=head1 REQUIREMENTS
|
|
|
|
Chirpy! uses the UTF-8 character encoding, and because of that, I<Perl 5.8> is
|
|
required. A lot of systems still have Perl 5.6 and, unfortunately, Chirpy! will
|
|
not run there. This might change in future releases.
|
|
|
|
It also relies on a couple of Perl modules, most of which are part of standard
|
|
Perl distributions. The base classes require these modules:
|
|
|
|
Carp
|
|
Digest::MD5
|
|
Encode
|
|
POSIX
|
|
Storable
|
|
|
|
Additionally, the default data manager and user interface classes have their
|
|
own requirements. Before using them, please consult
|
|
L<the data manager's requirements|Chirpy::DataManager::MySQL/REQUIREMENTS>
|
|
and L<the user interface's requirements|Chirpy::UI::WebApp/REQUIREMENTS>.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Chirpy 0.3;
|
|
|
|
chirpy();
|
|
|
|
chirpy($configuration_file, $data_manager_type);
|
|
|
|
$chirpy = new Chirpy();
|
|
$chirpy->run();
|
|
|
|
$chirpy = new Chirpy($configuration_file, $data_manager_type);
|
|
$chirpy->run();
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module is Chirpy!'s main coordination class and the only one that scripts
|
|
that use it should access directly. Everything else is part of the inner
|
|
workings of Chirpy!.
|
|
|
|
An instance of this module really represents an entire Chirpy! configuration,
|
|
along with everything it uses at runtime. This means that you can have several
|
|
instances of it simultaneously and exchange information between them.
|
|
|
|
=head1 USAGE
|
|
|
|
There are two ways to use the class:
|
|
|
|
=over 4
|
|
|
|
=item Procedural Interface
|
|
|
|
The easiest way is to just run the C<chirpy()> procedure, which is exported by
|
|
default. The code below will attempt to run Chirpy! with a configuration file
|
|
located at either of the default paths F<src/chirpy.ini> and F<chirpy.ini>,
|
|
relative to the current working directory.
|
|
|
|
chirpy;
|
|
|
|
That's it! Now, if you wanted to specify your own configuration file, you would
|
|
just pass it as the first parameter, like so:
|
|
|
|
chirpy('/home/joe/chirpy.ini');
|
|
|
|
The path can also be relative, but make sure the working directory is correct.
|
|
|
|
=item Object-Oriented Interface
|
|
|
|
As you may want to distinguish between different installations, you can have
|
|
several instances of this module in the same script. Instantiating the module
|
|
is a lot like invoking C<chirpy()>, except that it doesn't create and run the
|
|
user interface instance yet.
|
|
|
|
$chirpy = new Chirpy();
|
|
|
|
$chirpy = new Chirpy('/home/joe/chirpy.ini');
|
|
|
|
If you wanted to create and run the configured user interface, you would just:
|
|
|
|
$chirpy->run();
|
|
|
|
Simple as that.
|
|
|
|
In addition, you can add a second parameter to the constructor to override the
|
|
data manager type specified in the configuration file, which can be useful for
|
|
migration:
|
|
|
|
$chirpy_old = new Chirpy('/home/joe/chirpy.ini');
|
|
$chirpy_new = new Chirpy('/home/joe/chirpy.ini', 'MyNewDataManager');
|
|
|
|
While the C<chirpy()> procedure also takes that parameter, I don't see any real
|
|
use for it.
|
|
|
|
Note that if you want to use the default configuration file path, but with an
|
|
alternate data manager, you just pass C<undef> as the first parameter:
|
|
|
|
$chirpy = new Chirpy(undef, 'MyNewDataManager');
|
|
|
|
=back
|
|
|
|
=head1 CONFIGURATION FILE
|
|
|
|
A Chirpy! configuration file is a standard INI file, so it looks a little
|
|
something like this:
|
|
|
|
[general]
|
|
title=My Little QDB
|
|
description=A place for my quotes
|
|
locale=en-US
|
|
...
|
|
|
|
[data]
|
|
type=MySQL
|
|
...
|
|
|
|
Chirpy! adds a third level of parameter nesting to this format by separating
|
|
the class and parameter name by a dot. For instance, the password for the
|
|
MySQL data manager is stored like:
|
|
|
|
[data]
|
|
mysql.password=mypassword
|
|
|
|
Now, let's go over the default configuration values.
|
|
|
|
=head2 General Section
|
|
|
|
The C<general> section configures ... general settings!
|
|
|
|
=over 4
|
|
|
|
=item base_path
|
|
|
|
The local path (on the file system) where locales, templates, etc. are stored.
|
|
Do I<not> include a trailing slash.
|
|
|
|
=item title
|
|
|
|
The title of your QDB.
|
|
|
|
=item description
|
|
|
|
A brief description of the purpose of your QDB.
|
|
|
|
=item locale
|
|
|
|
The code of the locale to use.
|
|
|
|
=item rating_limit_count
|
|
|
|
=item rating_limit_time
|
|
|
|
Limit the maximum number of votes per time frame using these two parameters.
|
|
The former sets the maximum number, the latter sets the time period in seconds.
|
|
|
|
=item quote_score_calculation_mode
|
|
|
|
Since Chirpy! 0.3, quote scores, which are used to order the quotes for the Top
|
|
and Bottom Quotes pages, are calculated using the following formula:
|
|
|
|
positive votes + 1
|
|
score = --------------------
|
|
negative votes + 1
|
|
|
|
This results in a fairly decent distribution. However, if you prefer the old
|
|
way, based on a quote's rating, i.e.
|
|
|
|
rating = positive votes - negative votes
|
|
|
|
you can set C<quote_score_calculation_mode> to C<1>. Note that the default way
|
|
corresponds with a value of C<0>; this value may correspond with a different
|
|
formula in future releases.
|
|
|
|
=back
|
|
|
|
=head2 Data Section
|
|
|
|
The C<data> section configures everything related to the data manager, or the
|
|
backend, if you will.
|
|
|
|
This section only has one default parameter, namely C<type>. It contains the
|
|
name of the data manager to use. This will be translated to
|
|
C<Chirpy::DataManager::I<Name>>, so that module will need to be installed.
|
|
|
|
Apart from that, there are parameters specific to the data manager of your
|
|
choice. Please refer to its documentation for an explanation. If you use the
|
|
default data manager, L<MySQL|Chirpy::DataManager::MySQL>, you can find the
|
|
parameters in L<its documentation|Chirpy::DataManager::MySQL/CONFIGURATION>.
|
|
|
|
=head2 UI Section
|
|
|
|
The C<ui> section configures the frontend or user interface. It includes these
|
|
parameters by default:
|
|
|
|
=over 4
|
|
|
|
=item type
|
|
|
|
Similar to the C<type> parameter under the C<data> section, this one sets the
|
|
name of the user interface module and will be translated to
|
|
C<Chirpy::UI::I<Name>>.
|
|
|
|
=item date_time_format
|
|
|
|
The string that describes the format in which to display a date along with the
|
|
time. This string is passed to the C<strftime> method of
|
|
L<the POSIX module|POSIX>.
|
|
|
|
=item date_format
|
|
|
|
Similar to the above, but for dates only.
|
|
|
|
=item time_format
|
|
|
|
Similar to the above, but for times only.
|
|
|
|
=item use_gmt
|
|
|
|
Set this parameter to 0 if you wish to display times in local time instead of
|
|
Greenwich Mean Time. For GMT, set it to 1.
|
|
|
|
=item quotes_per_page
|
|
|
|
The maximum number of quotes to display per page.
|
|
|
|
=item recent_news_items
|
|
|
|
How many news items to display on the home page.
|
|
|
|
=item moderation_queue_public
|
|
|
|
Set this to C<1> if you want to make the list of unmoderated quotes available to
|
|
the public. To hide the list from everybody except moderators, set it to 0.
|
|
|
|
=item tag_cloud_logarithmic
|
|
|
|
Set this to C<1> if you want to determine the tag cloud's font sizes using a
|
|
logarithmic algorithm instead of a linear one. Most people will probably prefer
|
|
this, as it gives better results if some of the tags are used extremely often.
|
|
|
|
=back
|
|
|
|
Apart from that, there are parameters specific to the user interface of your
|
|
choice. Please refer to its documentation for an explanation. If you use the
|
|
default user interface, L<WebApp|Chirpy::UI::WebApp>, you can find the
|
|
parameters in L<its documentation|Chirpy::UI::WebApp/CONFIGURATION>.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<Chirpy::DataManager>, L<Chirpy::UI>, L<Chirpy::Configuration>,
|
|
L<Chirpy::Locale>, 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;
|
|
|
|
require 5.008;
|
|
|
|
use strict;
|
|
use warnings;
|
|
require Exporter;
|
|
|
|
BEGIN {
|
|
use vars qw($VERSION @EXPORT @ISA $DEBUG $hires_timing);
|
|
$VERSION = '0.3';
|
|
@ISA = qw(Exporter);
|
|
@EXPORT = qw(chirpy);
|
|
eval 'use Time::HiRes qw//';
|
|
$DEBUG = 0;
|
|
$hires_timing = 1 unless ($@);
|
|
}
|
|
|
|
use constant PRODUCT_NAME => 'Chirpy!';
|
|
use constant VERSION_STRING => 'v0.3';
|
|
use constant FULL_PRODUCT_NAME => PRODUCT_NAME . ' ' . VERSION_STRING;
|
|
use constant URL => 'http://chirpy.sourceforge.net/';
|
|
|
|
use Chirpy::Configuration 0.3;
|
|
use Chirpy::Locale 0.3;
|
|
|
|
use Chirpy::Quote 0.3;
|
|
use Chirpy::NewsItem 0.3;
|
|
use Chirpy::Account 0.3;
|
|
use Chirpy::Event 0.3;
|
|
|
|
use constant USER_LEVELS => [
|
|
Chirpy::Account::USER_LEVEL_9,
|
|
Chirpy::Account::USER_LEVEL_6,
|
|
Chirpy::Account::USER_LEVEL_3
|
|
];
|
|
|
|
use Carp qw/croak confess/;
|
|
|
|
sub new {
|
|
my ($class, $configuration_file, $dm_override) = @_;
|
|
my $st = ($hires_timing ? Time::HiRes::time() : undef);
|
|
my $self = bless {}, $class;
|
|
$self->{'start_time'} = $st;
|
|
$self->{'debug_events'} = [] if ($DEBUG && $hires_timing);
|
|
unless (defined $configuration_file) {
|
|
foreach my $file (qw(src/chirpy.ini chirpy.ini)) {
|
|
next unless (-f $file);
|
|
$configuration_file = $file;
|
|
last;
|
|
}
|
|
Chirpy::die('No valid configuration file found')
|
|
unless (defined $configuration_file);
|
|
}
|
|
$self->mark_debug_event('Load configuration');
|
|
my $configuration = new Chirpy::Configuration(
|
|
defined $configuration_file ? $configuration_file : 'chirpy.ini');
|
|
$self->mark_debug_event('Configuration loaded');
|
|
$self->{'configuration'} = $configuration;
|
|
$self->mark_debug_event('Load locale');
|
|
my $locale = new Chirpy::Locale($configuration->get('general', 'base_path')
|
|
. '/locales/' . $configuration->get('general', 'locale') . '.ini');
|
|
$self->mark_debug_event('Locale loaded');
|
|
$self->{'locale'} = $locale;
|
|
my $locale_version = $locale->get_target_version();
|
|
Chirpy::die('Locale outdated: wanted target version ' . $Chirpy::VERSION
|
|
. ', got ' . $locale_version)
|
|
unless ($locale_version ge $Chirpy::VERSION);
|
|
my $dm_type = defined $dm_override
|
|
? $dm_override : $configuration->get('data', 'type');
|
|
$self->{'data_manager_type'} = $dm_type;
|
|
my $dm_params = $configuration->get_parameter_hash('data', $dm_type);
|
|
$self->mark_debug_event('Create data manager');
|
|
my $dm = &_create_data_manager($dm_type, $dm_params);
|
|
$self->mark_debug_event('Data manager created');
|
|
$self->{'data_manager'} = $dm;
|
|
my $ui_type = $configuration->get('ui', 'type');
|
|
$self->{'ui_type'} = $ui_type;
|
|
return $self;
|
|
}
|
|
|
|
sub run {
|
|
my $self = shift;
|
|
my $configuration = $self->configuration();
|
|
my $ui_type = $self->{'ui_type'};
|
|
my $ui_params = $configuration->get_parameter_hash('ui', $ui_type);
|
|
$self->mark_debug_event('Create user interface');
|
|
$self->{'ui'} = &_create_ui($ui_type, $self, $ui_params);
|
|
$self->mark_debug_event('User interface created');
|
|
$self->{'ui'}->run();
|
|
}
|
|
|
|
sub chirpy {
|
|
new Chirpy(@_)->run();
|
|
}
|
|
|
|
sub configuration {
|
|
my $self = shift;
|
|
return $self->{'configuration'};
|
|
}
|
|
|
|
sub locale {
|
|
my $self = shift;
|
|
return $self->{'locale'};
|
|
}
|
|
|
|
sub get_parameter {
|
|
my ($self, $name) = @_;
|
|
return $self->{'data_manager'}->get_parameter($name);
|
|
}
|
|
|
|
sub set_parameter {
|
|
my ($self, $name, $value) = @_;
|
|
$self->{'data_manager'}->set_parameter($name, $value);
|
|
}
|
|
|
|
sub user_level_name {
|
|
my ($self, $id) = @_;
|
|
$self->locale->get_string('user_level_' . $id);
|
|
}
|
|
|
|
sub user_levels {
|
|
return @{USER_LEVELS()};
|
|
}
|
|
|
|
sub get_quotes {
|
|
my ($self, $start, $count, $sort) = @_;
|
|
$self->mark_debug_event('Request quotes');
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'sort' => (defined $sort ? $sort : [ [ 'id', 1 ] ]),
|
|
'first' => $start,
|
|
'count' => (defined $count ? $count : $self->quotes_per_page())
|
|
});
|
|
}
|
|
|
|
sub approved_quote_count {
|
|
my $self = shift;
|
|
return $self->_data_manager()->quote_count({ 'approved' => 1 });
|
|
}
|
|
|
|
sub unapproved_quote_count {
|
|
my $self = shift;
|
|
return $self->_data_manager()->quote_count({ 'approved' => 0 });
|
|
}
|
|
|
|
sub total_quote_count {
|
|
my $self = shift;
|
|
return $self->_data_manager()->quote_count();
|
|
}
|
|
|
|
sub get_matching_quotes {
|
|
my ($self, $start, $queries, $tags) = @_;
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'contains' => $queries,
|
|
'sort' => [ [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => $self->quotes_per_page(),
|
|
'tags' => $tags
|
|
});
|
|
}
|
|
|
|
sub get_quotes_of_the_week {
|
|
my ($self, $start) = @_;
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'since' => time - 7 * 24 * 60 * 60,
|
|
'sort' => [ [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => $self->quotes_per_page()
|
|
});
|
|
}
|
|
|
|
sub get_quote {
|
|
my ($self, $id) = @_;
|
|
return undef unless (defined $id);
|
|
my $quotes = $self->_data_manager()->get_quotes({
|
|
'id' => $id
|
|
});
|
|
return undef unless (defined $quotes);
|
|
return $quotes->[0];
|
|
}
|
|
|
|
sub get_random_quotes {
|
|
my $self = shift;
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'count' => $self->quotes_per_page(),
|
|
'random' => 1
|
|
});
|
|
}
|
|
|
|
sub get_top_quotes {
|
|
my ($self, $start) = @_;
|
|
my $cm = $self->quote_score_calculation_mode();
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'sort' => [ [ ($cm == 1 ? 'rating' : 'score'), 1 ], [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => $self->quotes_per_page()
|
|
});
|
|
}
|
|
|
|
sub get_bottom_quotes {
|
|
my ($self, $start) = @_;
|
|
my $cm = $self->quote_score_calculation_mode();
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 1,
|
|
'sort' => [ [ ($cm == 1 ? 'rating' : 'score'), 0 ], [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => $self->quotes_per_page()
|
|
});
|
|
}
|
|
|
|
sub get_flagged_quotes {
|
|
my ($self, $start) = @_;
|
|
return $self->_data_manager()->get_quotes({
|
|
'flagged' => 1,
|
|
'sort' => [ [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => (defined $start ? $self->quotes_per_page() : undef)
|
|
});
|
|
}
|
|
|
|
sub get_unapproved_quotes {
|
|
my ($self, $start) = @_;
|
|
return $self->_data_manager()->get_quotes({
|
|
'approved' => 0,
|
|
'sort' => [ [ 'id', 1 ] ],
|
|
'first' => $start,
|
|
'count' => (defined $start ? $self->quotes_per_page() : undef)
|
|
});
|
|
}
|
|
|
|
sub add_quote {
|
|
my ($self, $body, $notes, $approved, $tags) = @_;
|
|
my $quote = new Chirpy::Quote(
|
|
undef,
|
|
$body,
|
|
$notes,
|
|
0,
|
|
0,
|
|
undef,
|
|
$approved,
|
|
0,
|
|
$tags
|
|
);
|
|
$self->_data_manager()->add_quote($quote);
|
|
return $quote;
|
|
}
|
|
|
|
sub modify_quote {
|
|
my ($self, $quote, $text, $notes, $tags) = @_;
|
|
Chirpy::die('Not a Chirpy::Quote')
|
|
unless (ref $quote eq 'Chirpy::Quote');
|
|
$quote->set_body(Chirpy::Util::clean_up_submission($text));
|
|
$quote->set_notes($notes
|
|
? Chirpy::Util::clean_up_submission($notes)
|
|
: undef);
|
|
$quote->set_tags($tags) if (defined $tags);
|
|
return $self->_data_manager->modify_quote($quote);
|
|
}
|
|
|
|
sub remove_quotes {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->remove_quotes(@ids);
|
|
}
|
|
|
|
sub increase_quote_rating {
|
|
my ($self, $id, $revert) = @_;
|
|
return undef unless (defined $id);
|
|
my ($rating, $votes) = $self->_data_manager()
|
|
->increase_quote_rating($id, $revert);
|
|
return ($rating, $votes);
|
|
}
|
|
|
|
sub decrease_quote_rating {
|
|
my ($self, $id, $revert) = @_;
|
|
return undef unless (defined $id);
|
|
my ($rating, $votes) = $self->_data_manager()
|
|
->decrease_quote_rating($id, $revert);
|
|
return ($rating, $votes);
|
|
}
|
|
|
|
sub get_tag_use_counts {
|
|
my $self = shift;
|
|
return $self->_data_manager()->get_tag_use_counts();
|
|
}
|
|
|
|
sub flag_quotes {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->flag_quotes(@ids);
|
|
}
|
|
|
|
sub unflag_quotes {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->unflag_quotes(@ids);
|
|
}
|
|
|
|
sub approve_quotes {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->approve_quotes(@ids);
|
|
}
|
|
|
|
sub get_news_item {
|
|
my ($self, $id) = @_;
|
|
return undef unless (defined $id);
|
|
my $items = $self->_data_manager()->get_news_items({ 'id' => $id });
|
|
return (defined $items ? $items->[0] : undef);
|
|
}
|
|
|
|
sub get_latest_news_items {
|
|
my $self = shift;
|
|
return $self->_data_manager()->get_news_items(
|
|
{ 'count' => $self->configuration()->get('ui', 'recent_news_items') });
|
|
}
|
|
|
|
sub add_news_item {
|
|
my ($self, $text, $author) = @_;
|
|
my $item = new Chirpy::NewsItem(
|
|
undef,
|
|
Chirpy::Util::clean_up_submission($text),
|
|
$author
|
|
);
|
|
$self->_data_manager()->add_news_item($item);
|
|
return $item;
|
|
}
|
|
|
|
sub modify_news_item {
|
|
my ($self, $item, $text, $poster) = @_;
|
|
Chirpy::die('Not a Chirpy::NewsItem')
|
|
unless (ref $item eq 'Chirpy::NewsItem');
|
|
$item->set_body($text);
|
|
$item->set_poster($poster);
|
|
return $self->_data_manager()->modify_news_item($item);
|
|
}
|
|
|
|
sub remove_news_items {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->remove_news_items(@ids);
|
|
}
|
|
|
|
sub get_accounts {
|
|
my $self = shift;
|
|
return $self->_data_manager()->get_accounts();
|
|
}
|
|
|
|
sub get_accounts_by_level {
|
|
my ($self, @levels) = @_;
|
|
return $self->_data_manager()->get_accounts({ 'levels' => \@levels });
|
|
}
|
|
|
|
sub get_account_by_id {
|
|
my ($self, $id) = @_;
|
|
return undef unless (defined $id);
|
|
my $accounts = $self->_data_manager()->get_accounts({ 'id' => $id });
|
|
return (defined $accounts ? $accounts->[0] : undef);
|
|
}
|
|
|
|
sub get_account_by_username {
|
|
my ($self, $username) = @_;
|
|
my $accounts = $self->_data_manager()->get_accounts(
|
|
{ 'username' => $username });
|
|
return (defined $accounts ? $accounts->[0] : undef);
|
|
}
|
|
|
|
sub account_count {
|
|
my $self = shift;
|
|
return $self->_data_manager()->account_count();
|
|
}
|
|
|
|
sub account_count_by_level {
|
|
my ($self, $level) = @_;
|
|
return $self->_data_manager()->account_count({ 'levels' => [ $level ] });
|
|
}
|
|
|
|
sub username_exists {
|
|
my ($self, $username) = @_;
|
|
return $self->_data_manager()->username_exists($username);
|
|
}
|
|
|
|
sub add_account {
|
|
my ($self, $username, $password, $level) = @_;
|
|
my $account = new Chirpy::Account(
|
|
undef,
|
|
$username,
|
|
Chirpy::Util::encrypt($password),
|
|
$level
|
|
);
|
|
$self->_data_manager()->add_account($account);
|
|
return $account;
|
|
}
|
|
|
|
sub modify_account {
|
|
my ($self, $account, $username, $password, $level) = @_;
|
|
Chirpy::die('Not a Chirpy::Account')
|
|
unless (ref $account eq 'Chirpy::Account');
|
|
if (defined $username) {
|
|
Chirpy::die('Invalid username')
|
|
unless (Chirpy::Util::valid_username($username));
|
|
$account->set_username($username);
|
|
}
|
|
if (defined $password) {
|
|
Chirpy::die('Invalid password')
|
|
unless (Chirpy::Util::valid_password($password));
|
|
$account->set_password(Chirpy::Util::encrypt($password));
|
|
}
|
|
if (defined $level) {
|
|
$account->set_level($level);
|
|
}
|
|
return $self->_data_manager()->modify_account($account);
|
|
}
|
|
|
|
sub remove_accounts {
|
|
my ($self, @ids) = @_;
|
|
return $self->_data_manager()->remove_accounts(@ids);
|
|
}
|
|
|
|
sub log_event {
|
|
my ($self, $code, $user, $data) = @_;
|
|
return $self->_data_manager()->log_event(
|
|
new Chirpy::Event(undef, undef, $code, $user, $data)
|
|
);
|
|
}
|
|
|
|
sub get_events {
|
|
my ($self, $start, $count, $desc, $code, $user, $data) = @_;
|
|
$self->mark_debug_event('Request events');
|
|
return $self->_data_manager()->get_events({
|
|
'reverse' => $desc,
|
|
'first' => $start,
|
|
'count' => $count,
|
|
'code' => $code,
|
|
'user' => $user,
|
|
'data' => $data
|
|
});
|
|
}
|
|
|
|
sub attempt_login {
|
|
my ($self, $username, $password) = @_;
|
|
my $account = $self->get_account_by_username($username);
|
|
return undef unless (defined $account);
|
|
return ($account->get_password() eq Chirpy::Util::encrypt($password)
|
|
? $account : undef);
|
|
}
|
|
|
|
sub quotes_per_page {
|
|
my ($self, $value) = @_;
|
|
$self->{'quotes_per_page'} = $value if ($value);
|
|
return $self->{'quotes_per_page'} if (defined $self->{'quotes_per_page'});
|
|
return $self->configuration()->get('ui', 'quotes_per_page');
|
|
}
|
|
|
|
sub quote_score_calculation_mode {
|
|
my $self = shift;
|
|
my $mode = $self->configuration()->get('general',
|
|
'quote_score_calculation_mode');
|
|
return (defined $mode && $mode == 1 ? 1 : 0);
|
|
}
|
|
|
|
sub timing_enabled {
|
|
return $hires_timing;
|
|
}
|
|
|
|
sub start_time {
|
|
my $self = shift;
|
|
return $self->{'start_time'};
|
|
}
|
|
|
|
sub total_time {
|
|
my $self = shift;
|
|
return ($hires_timing
|
|
? Time::HiRes::time() - $self->{'start_time'}
|
|
: undef);
|
|
}
|
|
|
|
sub set_up {
|
|
my ($self, $accounts, $news, $quotes) = @_;
|
|
$self->_data_manager()->set_up($accounts, $news, $quotes);
|
|
}
|
|
|
|
sub remove {
|
|
my $self = shift;
|
|
$self->_data_manager()->remove();
|
|
}
|
|
|
|
sub die {
|
|
my $message = shift;
|
|
$message = 'Unknown error' unless (defined $message);
|
|
if ($DEBUG) {
|
|
confess $message;
|
|
}
|
|
else {
|
|
croak $message;
|
|
}
|
|
}
|
|
|
|
sub mark_debug_event {
|
|
my ($self, $event) = @_;
|
|
if (exists $self->{'debug_events'}) {
|
|
my $now = Time::HiRes::time();
|
|
push @{$self->{'debug_events'}}, [ $now, $event ];
|
|
}
|
|
}
|
|
|
|
sub debug_events {
|
|
my $self = shift;
|
|
return $self->{'debug_events'};
|
|
}
|
|
|
|
sub _data_manager {
|
|
my $self = shift;
|
|
return $self->{'data_manager'};
|
|
}
|
|
|
|
sub _create_data_manager {
|
|
my ($type, $params) = @_;
|
|
my $dm;
|
|
eval qq{
|
|
use Chirpy::DataManager::$type;
|
|
\$dm = new Chirpy::DataManager::$type(\$params);
|
|
};
|
|
Chirpy::die('Failed to load data manager "' . $type . '": ' . $@)
|
|
if ($@ || !defined $dm);
|
|
&_check_version($dm);
|
|
return $dm;
|
|
}
|
|
|
|
sub _create_ui {
|
|
my ($type, $parent, $params) = @_;
|
|
my $ui;
|
|
eval qq{
|
|
use Chirpy::UI::$type;
|
|
\$ui = new Chirpy::UI::$type(\$parent, \$params);
|
|
};
|
|
Chirpy::die('Failed to load UI "' . $type . '": ' . $@)
|
|
if ($@ || !defined $ui);
|
|
&_check_version($ui);
|
|
return $ui;
|
|
}
|
|
|
|
sub _check_version {
|
|
my $obj = shift;
|
|
my $version = (defined $obj ? $obj->get_target_version() : undef);
|
|
Chirpy::die(ref($obj) . ' incompatible: wanted target version '
|
|
. $Chirpy::VERSION . ', got ' . $version)
|
|
unless ($version eq $Chirpy::VERSION);
|
|
}
|
|
|
|
1;
|
|
|
|
############################################################################### |