moved qdb here because matt is lazy
This commit is contained in:
parent
fb02a3f4c5
commit
2ee07a1396
|
@ -0,0 +1,22 @@
|
|||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule ^([0-9]+|0x[0-9A-Fa-f]+|0b[01]+)$ index.cgi?id=$1 [L]
|
||||
RewriteRule ^(rss|atom)$ %{REQUEST_URI}/qotw [L,R=301]
|
||||
RewriteRule ^(rss|atom)/([a-z_]+)$ index.cgi?action=$2&output=$1 [L]
|
||||
RewriteRule ^ms/([a-z_]+)$ index.cgi?action=$1&output=ms [L]
|
||||
RewriteRule ^search/(.+) index.cgi?action=search&query=$1 [L]
|
||||
RewriteRule ^tags/([^\ ]+)$ index.cgi?action=search&query=tag:$1 [L]
|
||||
RewriteRule ^([a-z_]+)(/([a-z_]*))?$ index.cgi?action=$1&admin_action=$3 [L,QSA]
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_access.c>
|
||||
<Files chirpy.ini>
|
||||
order deny,allow
|
||||
deny from all
|
||||
</Files>
|
||||
</IfModule>
|
||||
|
||||
AuthType WebAuth
|
||||
require valid-user
|
||||
|
||||
AddHandler cgi-script .cgi
|
Binary file not shown.
|
@ -0,0 +1,111 @@
|
|||
_____________________________________________________________________________
|
||||
/ \
|
||||
| Chirpy! v0.3 2007-02-09 |
|
||||
\_____________________________________________________________________________/
|
||||
|
||||
* Added statistics page
|
||||
* Users can now revert their vote if, for example, they accidentally clicked
|
||||
on the wrong link
|
||||
* Now keeping (and displaying) vote counts per quote
|
||||
* Radically changed order of top and bottom quotes, by introducing quote
|
||||
scores: score = (positive votes + 1) / (negative votes + 1), as suggested by
|
||||
sp3000 at irc.mozilla.org #bs on 2006-10-28
|
||||
* Added metadata-based logging, including search
|
||||
* Made rating and reporting links use POST instead of GET and added
|
||||
intermediate confirmation page for non-AJAX users
|
||||
* Added microsummaries
|
||||
* You can now edit quotes before approving them
|
||||
* Added wildcard searching
|
||||
* Made tag cloud ignore unapproved quotes
|
||||
* Tag cloud can now use logarithmic calculation, to make tag use distribution
|
||||
appear more even
|
||||
* Added dynamic tag cloud pruning based on number of quotes
|
||||
* Administrative interface now only displays tabs that are available
|
||||
* Improved and simplified captcha support; added preliminary support for
|
||||
GD::SecurityImage as an alternative for Authen::Captcha; to enable captchas,
|
||||
simply webapp.captcha_provider=Authen_Captcha should suffice
|
||||
* Made pages point to their own feeds instead of QotW (where applicable)
|
||||
* Added description field in chirpy.ini, for use in feeds
|
||||
* Made quote titles in feeds more meaningful
|
||||
* Put page title in feed title instead of subtitle
|
||||
* Improved feed modification detection
|
||||
* Made templates use HTML::Template's caching feature, which is pretty fast
|
||||
* Template parameters are now global, which means you can include parameters
|
||||
from outside a loop
|
||||
* Renamed template parameters for search form; they are now always available
|
||||
and the default theme includes a search form with search results
|
||||
* Optimized template parsing a little
|
||||
* Moved administration-related subs to a separate class, speeding up non-admin
|
||||
pages somewhat
|
||||
* Extended quick style linking method: <> is now omitted and link text can be
|
||||
changed by separating it from URL by whitespace, e.g.
|
||||
Surf to <http://chirpy.sourceforge.net/ The Chirpy! web site>!
|
||||
* Simplified RSS feed: using HTML in <description> and removing
|
||||
<content:encoded> and <xhtml:body>, making feedvalidator.org like it more
|
||||
* Added tags as categories in feeds
|
||||
* Made check for expired sessions a lot faster
|
||||
* News body on start page is now divided into paragraphs; opening and closing
|
||||
tag are included, so update your templates
|
||||
* Bugfix: live rating no longer throws a JavaScript error in IE
|
||||
* Bugfix: quote rating up log entry now includes quote ID (#1493589)
|
||||
* Just for fun, quote IDs can now be in binary and hex notation too, as per
|
||||
<http://quotes.burntelectrons.org/844>
|
||||
|
||||
_____________________________________________________________________________
|
||||
/ \
|
||||
| Chirpy! v0.2 2006-05-02 |
|
||||
\_____________________________________________________________________________/
|
||||
|
||||
* Fixed SQL injection vulnerabilities
|
||||
* Fixed logging of author when editing or removing news items (#1289047)
|
||||
* Added on-the-fly gzip compression to Chirpy::UI::WebApp--webapp.enable_gzip=1
|
||||
in your configuration file enables it
|
||||
* Added optional captcha image to Chirpy::UI::WebApp's quote submission page
|
||||
* Added quote tagging
|
||||
* Made search query Google-style and added tag: prefix for searching for quotes
|
||||
with a certain tag
|
||||
* Made top and bottom quotes browsable
|
||||
* Added periodic update check (site owners only)
|
||||
* Made ui.quotes_per_page apply to random, top and bottom quotes instead of
|
||||
individual setting per page type
|
||||
* Added webapp.quotes_per_feed to set maximum number of quotes in feeds
|
||||
individually
|
||||
* Made Atom 1.0 feed valid by adding feed ID and webmaster name. Webmaster name
|
||||
must be configured as webapp.webmaster_name
|
||||
* Added quote_count method to Chirpy::DataManager and APPROVED_QUOTE_COUNT,
|
||||
UNAPPROVED_QUOTE_COUNT, and TOTAL_QUOTE_COUNT to templates
|
||||
* Added mass quote approval and unflagging to Chirpy::UI::WebApp's
|
||||
administration section
|
||||
* Changed Chirpy::DataManager's API so add_* methods set IDs
|
||||
* Added option to automatically turn URLs and e-mail addresses in quotes into
|
||||
hyperlinks
|
||||
* Made Chirpy::UI::WebApp escape all e-mail addresses to prevent spam
|
||||
* Made Chirpy::UI::WebApp replace sequences of whitespaces with   instead
|
||||
of , so the Atom feed remains valid
|
||||
* Made quote reporting require session information to prevent false positives
|
||||
from crawlers
|
||||
* Fixed sub account_count in Chirpy::DataManager::MySQL; removing accounts now
|
||||
works again
|
||||
* Optimized fetching single quote in Chirpy::DataManager::MySQL
|
||||
* Made Chirpy::UI::WebApp::Session automatically remove expired sessions every
|
||||
24 hours; util/remove_expired_sessions.pl is now obsolete
|
||||
* Fixed US English (and Dutch) locale: quote_submission_thanks_administrator is
|
||||
now quote_submission_thanks_no_approval
|
||||
* Extended feed templates with a couple of variables and added rating and
|
||||
report URLs as well as notes to the default templates
|
||||
* Replaced feed templates' CSS with legacy HTML
|
||||
* Cosmetic fixes to Chirpy::UI::WebApp's live rating system
|
||||
* Mentioned Chirpy::UI::WebApp::Session::DataManager in Chirpy::DataManager's
|
||||
documentation
|
||||
* Cosmetic fix in Account Manager: space after New Account
|
||||
* Replaced table for vertical split on start page with divs
|
||||
* No longer overriding old onunload function in style switcher
|
||||
* Added some debugging features
|
||||
* Added changelog.txt
|
||||
|
||||
_____________________________________________________________________________
|
||||
/ \
|
||||
| Chirpy! v0.1 2005-09-12 |
|
||||
\_____________________________________________________________________________/
|
||||
|
||||
* First official release
|
|
@ -0,0 +1,42 @@
|
|||
[general]
|
||||
title=CSC Quote Database
|
||||
description=Quotes from the CSC
|
||||
base_path=./src
|
||||
locale=en-US
|
||||
rating_limit_count=60
|
||||
rating_limit_time=60
|
||||
update_check=1
|
||||
|
||||
[data]
|
||||
type=MySQL
|
||||
mysql.hostname=localhost
|
||||
mysql.port=3306
|
||||
mysql.username=mimcpher
|
||||
mysql.password=oq5VuqPtNfLTFRZozDsH
|
||||
mysql.database=mimcpher
|
||||
mysql.prefix=qdb_
|
||||
|
||||
[ui]
|
||||
type=WebApp
|
||||
date_time_format=%Y-%m-%d %H:%M GMT
|
||||
date_format=%Y-%m-%d
|
||||
time_format=%H:%M GMT
|
||||
use_gmt=1
|
||||
quotes_per_page=30
|
||||
recent_news_item=3
|
||||
moderation_queue_public=1
|
||||
tac_cloud_logarithmic=1
|
||||
webapp.webmaster_name=Calum T. Dalek
|
||||
webapp.webmaster_email=calum@csclub.uwaterloo.ca
|
||||
webapp.site_url=https://csclub.uwaterloo.ca/~j3parker/pub/qdb/
|
||||
webapp.resources_url=https://csclub.uwaterloo.ca/~j3parker/pub/qdb/res
|
||||
webapp.theme=default
|
||||
webapp.welcome_text_file=welcome.html
|
||||
webapp.cookie_domain=csclub.uwaterloo.ca
|
||||
webapp.cookie_path=/~j3parker/pub/qdb
|
||||
webapp.session_expiry=+3d
|
||||
webapp.enable_short_urls=0
|
||||
webapp.enable_feeds=1
|
||||
webapp.quotes_per_feed=50
|
||||
webapp.enable_gzip=0
|
||||
webapp.enable_autolink=1
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# index.cgi #
|
||||
# Initialization script #
|
||||
###############################################################################
|
||||
# $Id:: index.cgi 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use CGI::Carp qw(fatalsToBrowser set_message);
|
||||
|
||||
BEGIN {
|
||||
unshift @INC, 'src/modules';
|
||||
set_message(sub {
|
||||
my $msg = shift;
|
||||
print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"', $/,
|
||||
'"http://www.w3.org/TR/html4/strict.dtd">', $/,
|
||||
'<html>', $/,
|
||||
'<head>', $/,
|
||||
'<meta http-equiv="Content-Type"', $/,
|
||||
'content="text/html; charset=UTF-8">', $/,
|
||||
'<title>An Error Occurred</title>', $/,
|
||||
'</head>', $/,
|
||||
'<body>', $/,
|
||||
'<h1>An Error Occurred</h1>', $/,
|
||||
'<blockquote><pre>', $msg, '</pre></blockquote>', $/,
|
||||
'<p><em>Powered by <a', $/,
|
||||
'href="http://chirpy.sourceforge.net/">Chirpy!</a></em></p>', $/,
|
||||
'</body>', $/,
|
||||
'</html>';
|
||||
exit;
|
||||
});
|
||||
}
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
chirpy('./chirpy.ini');
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,533 @@
|
|||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# INSTALLATION INSTRUCTIONS #
|
||||
###############################################################################
|
||||
|
||||
|
||||
CONTENTS
|
||||
========
|
||||
|
||||
1. INTRODUCTION
|
||||
|
||||
2. REQUIREMENTS
|
||||
|
||||
3. INSTALLATION
|
||||
3.1. Copy files
|
||||
3.2. Configure
|
||||
3.2.1. The [general] Section
|
||||
3.2.2. The [data] Section
|
||||
3.2.3. The [ui] Section
|
||||
3.3. Setup
|
||||
3.4. Import
|
||||
|
||||
4. UPGRADING
|
||||
|
||||
5. RUN CHIRPY!
|
||||
|
||||
6. NEXT STEPS
|
||||
6.1. Your Own Theme
|
||||
6.2. Your Own Locale
|
||||
6.3. Your Own Data Manager
|
||||
6.4. Your Own User Interface
|
||||
|
||||
|
||||
1. INTRODUCTION
|
||||
===============
|
||||
|
||||
Thank you for trying this Chirpy! beta version. Note that, while every feature
|
||||
contained in this product is functional unless stated otherwise, you may very
|
||||
well run into problems here and there. However, in most cases, there's probably
|
||||
an easy fix. You may find the following URLs helpful:
|
||||
|
||||
* Trackers (Bugs, Support, Feature Requests)
|
||||
http://sourceforge.net/tracker/?group_id=147270
|
||||
|
||||
* Chirpy! web site
|
||||
http://chirpy.sourceforge.net/
|
||||
|
||||
* API Specification (mainly for developers)
|
||||
http://chirpy.sourceforge.net/pod/
|
||||
|
||||
|
||||
2. REQUIREMENTS
|
||||
===============
|
||||
|
||||
To install Chirpy!, you'll need web space that gives you access to Perl 5.8 and
|
||||
a SQL server running MySQL 4.1 or higher. Those version numbers are expected
|
||||
to drop a little in an upcoming version.
|
||||
|
||||
In addition, you'll need these Perl modules:
|
||||
|
||||
Carp Data::Dumper HTML::Template
|
||||
Digest::MD5 DBD::mysql HTTP::Date
|
||||
Encode DBI URI::Escape
|
||||
POSIX Storable
|
||||
|
||||
It seems like a long list, but most of them are common and come with standard
|
||||
distributions of Perl. Other modules will need to be installed separately; you
|
||||
should probably ask your host about that. If any of these modules are missing,
|
||||
Chirpy! will not work.
|
||||
|
||||
In addition, Chirpy! uses the CGI::Carp module for verbose error reporting,
|
||||
which makes problems easier to trace. While this module is also really common,
|
||||
you can drop the dependency by removing statements containing "CGI::Carp" or
|
||||
"set_message" from the scripts.
|
||||
|
||||
|
||||
3. INSTALLATION
|
||||
===============
|
||||
|
||||
If all the requirements are met, you are ready to begin the installation of
|
||||
Chirpy!. Let's go!
|
||||
|
||||
3.1. Copy files
|
||||
---------------
|
||||
|
||||
Since you're reading this file, we'll assume that you've already extracted the
|
||||
entire installation package.
|
||||
|
||||
First, we'll take a quick look at the contents of the package. You should have
|
||||
the following directories and files in the root:
|
||||
|
||||
res/ The resources directory. Resources are public files that
|
||||
are required by themes, such as images and style sheets.
|
||||
src/ The source directory. Contains all the necessary files to
|
||||
run Chirpy!, including its modules, locales, templates and
|
||||
configuration file.
|
||||
util/ Contains some utilities that you may find useful while
|
||||
installing or using Chirpy!.
|
||||
.htaccess Has some directives that enable short URLs. Covered later
|
||||
in this document.
|
||||
changelog.txt All the stuff that's changed between Chirpy! releases.
|
||||
index.cgi The main script. Will be the only URL that is accessed
|
||||
directly by visitors.
|
||||
install.txt This document.
|
||||
license.txt The GNU General Public License, under which Chirpy! is
|
||||
distributed.
|
||||
|
||||
Now, we're going the copy the files to a public path on the web server. This is
|
||||
usually done using an FTP client.
|
||||
|
||||
As for the files in the package root directory, the only one you really need is
|
||||
index.cgi. Since this is a Perl script, it has to be inside a path where the
|
||||
server allows execution of scripts. Most servers these days will allow that
|
||||
anywhere, but yours might require that you put it inside a cgi-bin/ directory,
|
||||
which may even be outside the document root on the FTP server. This is no
|
||||
problem--just make sure it wants to execute it and not display the contents.
|
||||
|
||||
The index.cgi file needs some attributes. This is done by issueing a SITE CHMOD
|
||||
command on the FTP server. If you use a graphical FTP client, you can probably
|
||||
right-click on the file and tick off the necessary attributes. They are:
|
||||
|
||||
Owner READ WRITE EXECUTE
|
||||
Group READ - EXECUTE
|
||||
Others READ - EXECUTE
|
||||
|
||||
This translates to the string representation "rwxr-xr-x" or, numerically, 755
|
||||
on UNIX systems. On most systems, this step is essential. You will get an error
|
||||
page if you fail to change the file's attributes. Also, if you are using an FTP
|
||||
client, make sure you have uploaded index.cgi in ASCII (text) mode, not image
|
||||
(binary).
|
||||
|
||||
You may want to copy the .htaccess file from the package root as well. Make
|
||||
sure you don't confuse it with the one in the src/ directory! The one covered
|
||||
here allows you to use short URLs, which are easy to remember, using the
|
||||
mod_rewrite Apache module. To use that option, you will need to run Chirpy! on
|
||||
an Apache web server that has the mod_rewrite module. Note that, while short
|
||||
URLs will not work if mod_rewrite is not enabled, it should be safe to have the
|
||||
.htaccess file there anyway, since it checks for the module first. Moreover, it
|
||||
also attempts to block access to the configuration file, so you might want to
|
||||
have it anyway; we will cover that configuration file in a moment.
|
||||
|
||||
The res/ directory only holds static files and should reside in a public path
|
||||
on the server, like <http://www.yourserver.com/chirpy/res>. You can upload it
|
||||
anywhere, even on a different server. We'll need the URL later for the
|
||||
configuration.
|
||||
|
||||
Next up: the src/ directory. This one does not have to be publicly accessible
|
||||
and for security reasons, it really shouldn't be either. Chirpy! takes some
|
||||
precautions to disallow access to it by placing a .htaccess file in it, that
|
||||
holds code specific to the Apache web server. However, placing the directory
|
||||
outside the document root (if possible) is a MUCH safer choice.
|
||||
Alternatively, you can keep chirpy.ini, the configuration file, which we will
|
||||
cover later in this document, outside the document root and the src/ directory.
|
||||
|
||||
Inside the src/ directory, you will find a directory called cache/. Make sure
|
||||
it is empty. Also, it needs to be world-writable, i.e. its attributes must be
|
||||
set to "rwxrwxrwx" or 777.
|
||||
|
||||
The util/ directory should NOT be uploaded. You are however likely to need the
|
||||
setup.pl script inside it, but we'll get to that in a moment.
|
||||
|
||||
That covers uploading the files--almost. We'll need to upload a configuration
|
||||
file at the end of the next step.
|
||||
|
||||
3.2. Configure
|
||||
--------------
|
||||
|
||||
Chirpy! stores its configuration in a standard .INI file, a basic format which
|
||||
is common on Windows systems. In this step, we'll create such a configuration
|
||||
file.
|
||||
|
||||
By default, Chirpy! looks for chirpy.ini in the working directory (which is
|
||||
where you put index.cgi) and inside the src/ directory if it is directly inside
|
||||
the working directory. Otherwise, you will have to edit index.cgi. As explained
|
||||
above, that little bit of editing is recommended, so your configuration file
|
||||
resides outside the public domain. After all, it will contain your MySQL server
|
||||
password, and we don't want visitors to read that, now, do we?
|
||||
|
||||
Right, let's create that chirpy.ini file now. Here is a basic example:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
[general]
|
||||
title=My Little QDB
|
||||
description=A place for my quotes
|
||||
base_path=./src
|
||||
locale=en-US
|
||||
rating_limit_count=2
|
||||
rating_limit_time=60
|
||||
update_check=1
|
||||
|
||||
[data]
|
||||
type=MySQL
|
||||
mysql.hostname=localhost
|
||||
mysql.port=3306
|
||||
mysql.username=my_username
|
||||
mysql.password=my_password
|
||||
mysql.database=my_database
|
||||
mysql.prefix=chirpy_
|
||||
|
||||
[ui]
|
||||
type=WebApp
|
||||
date_time_format=%Y-%m-%d %H:%M GMT
|
||||
date_format=%Y-%m-%d
|
||||
time_format=%H:%M GMT
|
||||
use_gmt=1
|
||||
quotes_per_page=10
|
||||
recent_news_items=3
|
||||
moderation_queue_public=1
|
||||
tag_cloud_logarithmic=1
|
||||
webapp.webmaster_name=John Doe
|
||||
webapp.webmaster_email=you@yourserver.com
|
||||
webapp.site_url=http://www.yourserver.com/cgi-bin/chirpy
|
||||
webapp.resources_url=http://www.yourserver.com/chirpy/res
|
||||
webapp.theme=default
|
||||
webapp.welcome_text_file=welcome.html
|
||||
webapp.cookie_domain=yourserver.com
|
||||
webapp.cookie_path=/cgi-bin/chirpy
|
||||
webapp.session_expiry=+3d
|
||||
webapp.enable_short_urls=0
|
||||
webapp.enable_feeds=0
|
||||
webapp.quotes_per_feed=50
|
||||
webapp.enable_gzip=0
|
||||
webapp.enable_autolink=0
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
A lot of the values don't look very interesting right now, but we'll have to
|
||||
change some of the others.
|
||||
|
||||
3.2.1. The [general] Section
|
||||
----------------------------
|
||||
|
||||
title Change this value to the title you want your QDB to have.
|
||||
description Enter a brief description of the purpose of your QDB.
|
||||
base_path Enter the absolute or relative path to the src/ directory
|
||||
here. When using relative paths, this is again relative to
|
||||
the directory where index.cgi is.
|
||||
update_check Set this to 1 to tell Chirpy! to automatically check for
|
||||
updates periodically. Only site owners will be informed of
|
||||
available updates. This feature requires that libwww-perl
|
||||
(LWP) be installed; if it is not, Chirpy! will just show
|
||||
you an informative error message.
|
||||
|
||||
3.2.2. The [data] Section
|
||||
-------------------------
|
||||
|
||||
mysql.hostname Enter the name of the MySQL server here. Usually, this will
|
||||
be "localhost".
|
||||
mysql.port Enter the port the MySQL server uses. The default is 3306.
|
||||
mysql.username Enter your MySQL username. This is not necessarily the same
|
||||
as your regular username.
|
||||
mysql.password Enter your MySQL password. This is not necessarily the same
|
||||
as your regular password.
|
||||
mysql.database Enter the name of the MySQL database Chirpy! should use. If
|
||||
it does not exist, you need to create it first. Do not
|
||||
create any tables; Chirpy! will do that for you.
|
||||
mysql.prefix If you only have one MySQL database, Chirpy! can make its
|
||||
tables easy to find by prefixing their names with the text
|
||||
you enter here. The default "chirpy_" is a wise choice.
|
||||
|
||||
3.2.3. The [ui] Section
|
||||
-----------------------
|
||||
|
||||
webapp.webmaster_name
|
||||
Your name.
|
||||
webapp.webmaster_email
|
||||
Your e-mail address. Don't worry about spam, Chirpy! will
|
||||
use some fancy tricks to hide it.
|
||||
webapp.site_url The URL where you put index.cgi.
|
||||
webapp.resources_url
|
||||
The URL where you put the res/ directory.
|
||||
webapp.cookie_domain
|
||||
Essentially the domain name (without the www prefix) from
|
||||
your site's URL. This will be used to store cookies.
|
||||
webapp.cookie_path The part that comes after the domain in the site URL. This
|
||||
will also be used to store cookies.
|
||||
webapp.enable_short_urls
|
||||
Change this to 1 to enable the short URLs feature described
|
||||
above. If you get "Not Found" errors while browsing the QDB
|
||||
later, you should turn it off.
|
||||
webapp.enable_feeds Chirpy! can offer an RSS 2.0 feed and an Atom 1.0 feed of
|
||||
the Quotes of the Week, so visitors can syndicate them. If
|
||||
you want to enable those, set this to 1.
|
||||
webapp.quotes_per_feed
|
||||
This is the maximum number of quotes to include in a feed.
|
||||
In theory, Chirpy! can provide a content feed for any page,
|
||||
and since feeds do not offer "Previous"/"Next" links, this
|
||||
should be a sensible number. The default is 50.
|
||||
webapp.enable_gzip Chirpy! can greatly decrease bandwidth usage by compressing
|
||||
output on the fly if the browser supports it. Set this to 1
|
||||
to enable that. It requires the Compress::Zlib Perl module.
|
||||
webapp.enable_autolink
|
||||
Change this to 1 to automatically turn URLs and e-mail
|
||||
addresses in quote bodies into hyperlinks. This feature is
|
||||
still sort of experimental, but should work fine.
|
||||
webapp.captcha_provider
|
||||
If you wish to prevent malicious users from spamming the
|
||||
quote submission page, you will want to use captcha images.
|
||||
This parameter sets the captcha provider, which will be
|
||||
Authen_Captcha in most cases. Then Authen_Captcha provider
|
||||
relies on Authen::Captcha being installed.
|
||||
|
||||
You may want to turn the captcha feature off at first, so you can test-drive
|
||||
Chirpy!'s other features. Configuring the captcha feature should be easy in the
|
||||
case of Authen_Captcha. The alternative is to use GD_SecurityImage. Support for
|
||||
that one is preliminary for now. If you are interested in using it, please
|
||||
consult the appropriate documentation.
|
||||
|
||||
That covers the configuration file. Save it as chirpy.ini and, as stressed
|
||||
before, try to store it at a location on the server which cannot be accessed
|
||||
using a Web browser.
|
||||
|
||||
Now, we'll have to modify index.cgi a little to tell it where to find the
|
||||
configuration file. Again, it looks for chirpy.ini in the working directory and
|
||||
inside src/, but hopefully, it won't be there. So we'll just open index.cgi in
|
||||
a text editor and change the line
|
||||
|
||||
chirpy;
|
||||
|
||||
to the following:
|
||||
|
||||
chirpy('/path/to/chirpy.ini');
|
||||
|
||||
Again, you can use either an absolute path or a path relative to the working
|
||||
directory. You'll also need this path in the next step.
|
||||
|
||||
While we're editing index.cgi, there are two more things you might have to
|
||||
change. The first is the path to the modules/ directory inside src/. This path
|
||||
is stored like:
|
||||
|
||||
unshift @INC, 'src/modules';
|
||||
|
||||
If you have not placed the src/ directory in the same directory as index.cgi,
|
||||
update the path so Perl can find the Chirpy! modules.
|
||||
|
||||
The other thing you might have to change is the path to Perl itself. Most
|
||||
servers have it at /usr/bin/perl, but if yours doesn't, change the first line
|
||||
of index.cgi to "#!" followed by the exact path to Perl.
|
||||
|
||||
3.3. Setup
|
||||
----------
|
||||
|
||||
Now that all the files are there, we'll grab setup.pl from the util/ directory
|
||||
and open it in a text editor. Look for the line that reads
|
||||
|
||||
my $ch = new Chirpy();
|
||||
|
||||
and change that to
|
||||
|
||||
my $ch = new Chirpy('/path/to/chirpy.ini');
|
||||
|
||||
using the same path you entered in index.cgi. In addition, you will have to
|
||||
update the path to src/modules/ and Perl itself again, if you had to do so for
|
||||
index.cgi.
|
||||
|
||||
Now, upload setup.pl to the directory where index.cgi resides and change its
|
||||
attributes so they are the same as index.cgi's; as described above, they should
|
||||
be rwxr-xr-x (755). Again, since this is a Perl script, upload in ASCII mode!
|
||||
|
||||
Now, we're going to call your Web browser into action. Open it and surf to the
|
||||
URL where setup.pl should be now, e.g.
|
||||
|
||||
http://www.yourserver.com/cgi-bin/chirpy/setup.pl
|
||||
|
||||
That should give you a basic page, welcoming you to the setup procedure. If
|
||||
not, let's go over a few common problems ...
|
||||
|
||||
- If you get the source code of setup.pl or maybe a download window, the server
|
||||
doesn't allow execution of Perl scripts in that directory. You'll probably
|
||||
need to use the cgi-bin directory instead.
|
||||
|
||||
- If you get a Forbidden error, you probably didn't change the script's
|
||||
attributes properly, as described above.
|
||||
|
||||
- If you get a fairly verbose error that has line numbers and lots of weird
|
||||
characters and other stuff that confuses you in it, the server executed the
|
||||
script, but it crashed somewhere along the way. Something may have gone wrong
|
||||
with the upload of one or more files; upload them again. If that doesn't do
|
||||
any good, paste the error message at the Support Tracker, to which you can
|
||||
find the URL at the start of this document.
|
||||
|
||||
- If you get a generic "Internal Server Error" page, the server most likely
|
||||
failed to execute the script at an earlier stage. Some files may have gotten
|
||||
corrupted in the upload process; try uploading them again. If that doesn't
|
||||
help, go over the text above to see if you didn't miss anything. If problems
|
||||
persist, your host can give you a copy of the server's error log, which may
|
||||
tell you more. If you can get an error message from the log, you can post
|
||||
that at the Support Tracker; without it, the error will be nearly impossible
|
||||
to trace.
|
||||
|
||||
Assuming you've reached the setup page now, you get to decide if you want to
|
||||
keep your existing installation if any. If you're installing Chirpy! for the
|
||||
first time, you should choose to keep data, since it's faster. That should give
|
||||
you a page with a basic event log for the setup procedure. If it tells you the
|
||||
setup procedure has been completed, you can go to the next step now. If not,
|
||||
take a look at the error and see if you can fix things. If not, try the Support
|
||||
Tracker for assistance.
|
||||
|
||||
3.4. Import
|
||||
-----------
|
||||
|
||||
This step is optional. It only applies if you have the Rash Quote Management
|
||||
System installed and you want to migrate its data to Chirpy!. If you have no
|
||||
idea what this is about, just skip this step.
|
||||
|
||||
To import Rash's data, grab chirpy_rqms_import.php from the util/ directory.
|
||||
Open it in a text editor and edit the configuration values at the start of the
|
||||
script. Then place it in the directory where you installed Rash, so it can find
|
||||
config.php. Surf to chirpy_rqms_import.php and everything should be clear from
|
||||
there. If anything goes wrong, try the Support Tracker.
|
||||
|
||||
|
||||
4. UPGRADING
|
||||
============
|
||||
|
||||
If you are upgrading from Chirpy! version 0.1 or 0.2, you should just move the
|
||||
updated setup.pl script from the "util" directory into Chirpy!'s root directory
|
||||
for a second, make it executable, and surf to it. Chirpy! will then ask you if
|
||||
you want to perform a fresh installation or an upgrade. Now, DO *NOT* CLICK ON
|
||||
"FRESH INSTALLATION," because you would lose all your quotes, accounts, etc.
|
||||
Instead, click the "UPGRADE" button, wait for the page to load, watch Chirpy!
|
||||
inform you of the successful upgrade, and remove setup.pl again.
|
||||
|
||||
Additionally, as Chirpy! 0.3 offers quite a couple of new features, you will
|
||||
probably want to play with those. Most of them just come down to adding a line
|
||||
in the configuration file. For captchas, you will need to create a couple of
|
||||
directories; just scroll back to section 3.2, where the configuration file is
|
||||
explained--it's all there. Enjoy!
|
||||
|
||||
|
||||
5. RUN CHIRPY!
|
||||
==============
|
||||
_____________________________________________________________________________
|
||||
| |
|
||||
| !!! DON'T FORGET TO REMOVE THE SETUP SCRIPT FROM THE SERVER FIRST !!! |
|
||||
|_____________________________________________________________________________|
|
||||
|
||||
By now, the setup script should have directed you to your brand new Chirpy!
|
||||
installation already. If not, append /index.cgi to your site URL and open that
|
||||
URL. That should give you a cute little start page. You should be able to leave
|
||||
off index.cgi in the URL now--try it. If it doesn't work, you should definitely
|
||||
turn short URLs off. If something else goes wrong, you should try the Support
|
||||
Tracker; the URL is at the beginning of this file.
|
||||
|
||||
Now, you're pretty much finished. Except that you should change the default
|
||||
password. Surf to the administration section, log in as "superuser" with the
|
||||
password "password" (if you didn't poke at setup.pl already) and click on the
|
||||
"Manage Accounts" option. Select the "superuser" account and modify it.
|
||||
|
||||
That concludes the installation of Chirpy!. I hope you'll enjoy it. If you run
|
||||
into a problem or you'd like to see a new feature in the next release, surf to
|
||||
the Trackers.
|
||||
|
||||
Congratulations on a successful installation!
|
||||
|
||||
|
||||
6. NEXT STEPS
|
||||
=============
|
||||
|
||||
Now that you've got a working Chirpy! installation, you can tweak it to your
|
||||
liking. Most of this is done from the configuration file. If you want to change
|
||||
the welcome message, you just edit the file welcome.html in the src/ directory,
|
||||
which is actually a template.
|
||||
|
||||
As Chirpy! is a work in progress, you can expect a lot more documentation on
|
||||
customizing it in future releases. In the mean time, if you want to do more
|
||||
advanced tweaking, here are a few possible scenarios:
|
||||
|
||||
6.1. Your Own Theme
|
||||
-------------------
|
||||
|
||||
The easiest way to do this is to copy the existing "default" theme and modify
|
||||
it, after which you change webapp.theme in the configuration file to use the
|
||||
new theme. A theme is nothing but two directories named after it: the one in
|
||||
src/themes/ holds the templates for the theme, the one in res/ holds its
|
||||
resources. The template filenames are predefined, so you need to keep those
|
||||
intact. The templates are parsed by the HTML::Template Perl module, so you will
|
||||
probably have to look at its documentation for a second--or you could just
|
||||
learn to use it by looking at the default theme's source code. The manual page
|
||||
for HTML::Template can be found at
|
||||
|
||||
http://search.cpan.org/dist/HTML-Template/Template.pm
|
||||
|
||||
6.2. Your Own Locale
|
||||
--------------------
|
||||
|
||||
You might want Chirpy! in your own language, and you can have it too. If you
|
||||
look in the src/locales/ directory, you'll see that locales are actually just
|
||||
INI files. Each locale string, along with some basic instructions for locale
|
||||
creation, is documented in the POD, located at the URL at the start of this
|
||||
document.
|
||||
|
||||
6.3. Your Own Data Manager
|
||||
--------------------------
|
||||
|
||||
Chirpy! is extremely modular. If you'd rather have it store its data in your
|
||||
choice of database, you are free to implement the Chirpy::DataManager class.
|
||||
Its API is explained in the POD, which is available at the start of this
|
||||
document or by typing "perldoc Chirpy::DataManager" from a console or command
|
||||
prompt, if you have Perl installed.
|
||||
|
||||
6.4. Your Own User Interface
|
||||
----------------------------
|
||||
|
||||
Apart from creating a backend, you can also write your own frontend class. This
|
||||
class should implement Chirpy::UI. Unfortunately, the API documentation for it
|
||||
is not yet available. Note that if your UI would be a web site, you should
|
||||
probably just create a Chirpy::UI::WebApp theme as described in 5.1.
|
||||
|
||||
If you've created a theme, a locale, a data manager or a UI, please share your
|
||||
work with the community! E-mail it to me at ceetee@users.sourceforge.net and it
|
||||
might be included in the next Chirpy! release by default. Obviously, you would
|
||||
get credit for it.
|
||||
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,340 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
|
@ -0,0 +1,34 @@
|
|||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
Installation instructions can be found in the install.txt file, which came with
|
||||
Chirpy!. For anything else, please consult http://chirpy.sourceforge.net/.
|
||||
|
||||
Many thanks go out to the guys over at irc.mozilla.org #bs for helping me test
|
||||
and giving me lots of useful feedback, to jX for hosting the irc.mozilla.org
|
||||
QDB, the first real Chirpy! web site, and to F2O.org for providing their free
|
||||
developer webspace, ideal for testing. Peace to you all. --ct
|
||||
|
||||
Chirpy! includes the projects listed below. Please consult their documentation,
|
||||
which is also supplied with Chirpy!.
|
||||
|
||||
* ExplorerCanvas <http://excanvas.sourceforge.net/>
|
||||
* WebFX Slider <http://webfx.eae.net/dhtml/slider/slider.html>
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,39 @@
|
|||
###############################################################################
|
||||
# This file adds support for compressing static text content, such as CSS and #
|
||||
# JavaScript files, in order to reduce traffic. To use it, you must perform #
|
||||
# the following steps: #
|
||||
# #
|
||||
# 1. Move gzip.pl from the util/ directory to the root directory. If you had #
|
||||
# to move index.cgi, e.g. because it had to be inside a cgi-bin, then move #
|
||||
# gzip.pl there too. #
|
||||
# 2. Modify the line use constant CACHE_DIR => '...'; in gzip.pl, setting the #
|
||||
# cache directory to a writable path, where it can keep compressed files. #
|
||||
# By default, this is a directory called "gzip" in your already present #
|
||||
# "cache" directory. However, the path must be relative to the directory #
|
||||
# where you puth gzip.pl! #
|
||||
# 3. Change gzip.pl's attributes to rwxr-xr-x (755), like index.cgi. #
|
||||
# 4. Comment out the lines below by removing the # in front of them. #
|
||||
# 5. If gzip.pl is not in the root directory, modify its path in the line #
|
||||
# that calls it below. #
|
||||
# 6. Test! Obtain a tool that allows you to view HTTP headers and look at the #
|
||||
# HTTP headers for a .js or .css file inside the res/ directory. If the #
|
||||
# headers contain the line "Content-Encoding: gzip," the installation was #
|
||||
# successful. No luck? Here are some common explanations: #
|
||||
# a. Got an Internal Server Error? Verify that you uploaded gzip.pl in #
|
||||
# ASCII mode and that you set its attributes. If you did, obtain an #
|
||||
# error log from your host and see what that tells you. #
|
||||
# b. If the server redirected the request to the same URL, with "?nogzip" #
|
||||
# appended to it, the gzip.pl script did run, but decided compression #
|
||||
# was not possible because of an incompatibility. #
|
||||
# c. If the server neither compressed the file, nor redirected, then #
|
||||
# gzip.pl didn't get invoked at all. The server might not support the #
|
||||
# Rewrite module, or had trouble interpreting the directives below. #
|
||||
###############################################################################
|
||||
|
||||
#<IfModule mod_rewrite.c>
|
||||
#RewriteEngine On
|
||||
#RewriteCond %{HTTP:Accept-Encoding} \bgzip\b
|
||||
#RewriteCond %{QUERY_STRING} =""
|
||||
#RewriteCond %{REQUEST_FILENAME} -s
|
||||
#RewriteRule \.(css|js)$ ../gzip.pl?filename=%{REQUEST_FILENAME}&uri=%{REQUEST_URI}
|
||||
#</IfModule>
|
|
@ -0,0 +1 @@
|
|||
Options -Indexes
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# default.css #
|
||||
# "Default" color theme for the default theme #
|
||||
###############################################################################
|
||||
# $Id:: default.css 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
a:hover, a:active {
|
||||
color: #F63;
|
||||
}
|
||||
|
||||
h1 a, h1 a:link, h1 a:visited {
|
||||
background: #CFB;
|
||||
border-top-color: #D6FFC6;
|
||||
border-bottom-color: #C9F9B9;
|
||||
}
|
||||
|
||||
ul#navigation li a:hover, ul#navigation li a:active,
|
||||
ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {
|
||||
background: #FDA;
|
||||
border-color: #F0D0A0;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover h3.quote-header {
|
||||
background: #FEC;
|
||||
border-color: #FFE9C9;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-container {
|
||||
background: #FFFCEC;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {
|
||||
color: #BA9;
|
||||
border-color: #FFE9C9;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-header li a.active-tab {
|
||||
background: #ECF6EC;
|
||||
border-top-color: #C0F0B0;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-contents {
|
||||
border: 1px solid #ECF6EC;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# fish_tank.css #
|
||||
# "Fish Tank" color theme for the default theme #
|
||||
###############################################################################
|
||||
# $Id:: fish_tank.css 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
a:hover, a:active {
|
||||
color: #3F6;
|
||||
}
|
||||
|
||||
h1 a, h1 a:link, h1 a:visited {
|
||||
background: #BCF;
|
||||
border-top-color: #C6D6FF;
|
||||
border-bottom-color: #B9C9F9;
|
||||
}
|
||||
|
||||
ul#navigation li a:hover, ul#navigation li a:active,
|
||||
ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {
|
||||
background: #AFD;
|
||||
border-color: #A0F0D0;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover h3.quote-header {
|
||||
background: #CFE;
|
||||
border-color: #C9FFE9;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-container {
|
||||
background: #ECFFFC;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {
|
||||
color: #9BA;
|
||||
border-color: #C9FFE9;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-header li a.active-tab {
|
||||
background: #ECECF6;
|
||||
border-top-color: #B0C0F0;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-contents {
|
||||
border: 1px solid #ECECF6;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# grayscale.css #
|
||||
# "Grayscale" color theme for the default theme #
|
||||
###############################################################################
|
||||
# $Id:: grayscale.css 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
a:hover, a:active {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
h1 a, h1 a:link, h1 a:visited {
|
||||
background: #ECECEC;
|
||||
border-top-color: #F3F3F3;
|
||||
border-bottom-color: #E9E9E9;
|
||||
}
|
||||
|
||||
ul#navigation li a:hover, ul#navigation li a:active,
|
||||
ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {
|
||||
background: #F3F3F3;
|
||||
border-color: #ECECEC;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover h3.quote-header {
|
||||
background: #F6F6F6;
|
||||
border-color: #F0F0F0;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-container {
|
||||
background: #FCFCFC;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {
|
||||
color: #BBB;
|
||||
border-color: #F0F0F0;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-header li a.active-tab {
|
||||
background: #E9E9E9;
|
||||
border-top-color: #CCC;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-contents {
|
||||
border: 1px solid #E9E9E9;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# nineties.css #
|
||||
# "Nineties" color theme for the default theme #
|
||||
###############################################################################
|
||||
# $Id:: nineties.css 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
a:hover, a:active {
|
||||
color: #69C;
|
||||
}
|
||||
|
||||
h1 a, h1 a:link, h1 a:visited {
|
||||
background: #CEF;
|
||||
border-top-color: #D6F6FF;
|
||||
border-bottom-color: #C9E9F9;
|
||||
}
|
||||
|
||||
ul#navigation li a:hover, ul#navigation li a:active,
|
||||
ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {
|
||||
background: #FDA;
|
||||
border-color: #F0D0A0;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover h3.quote-header {
|
||||
background: #CEF;
|
||||
border-color: #C9E9FF;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-container {
|
||||
background: #ECFCFF;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {
|
||||
color: #9AB;
|
||||
border-color: #C9E9FF;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-header li a.active-tab {
|
||||
background: #E9E9E9;
|
||||
border-top-color: #CCC;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-contents {
|
||||
border: 1px solid #E9E9E9;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# spring.css #
|
||||
# "Spring" color theme for the default theme #
|
||||
###############################################################################
|
||||
# $Id:: spring.css 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
a:hover, a:active {
|
||||
color: #36F;
|
||||
}
|
||||
|
||||
h1 a, h1 a:link, h1 a:visited {
|
||||
background: #BFC;
|
||||
border-top-color: #C6FFD6;
|
||||
border-bottom-color: #B9F9C9;
|
||||
}
|
||||
|
||||
ul#navigation li a:hover, ul#navigation li a:active,
|
||||
ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {
|
||||
background: #ADF;
|
||||
border-color: #A0D0F0;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover h3.quote-header {
|
||||
background: #CEF;
|
||||
border-color: #C9E9FF;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-container {
|
||||
background: #ECFCFF;
|
||||
}
|
||||
|
||||
ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {
|
||||
color: #9AB;
|
||||
border-color: #C9E9FF;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-header li a.active-tab {
|
||||
background: #ECF6EC;
|
||||
border-top-color: #B0F0C0;
|
||||
}
|
||||
|
||||
ul.tabbed-pane-contents {
|
||||
border: 1px solid #ECF6EC;
|
||||
}
|
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# administration.js #
|
||||
# Functions specific to the administration interface #
|
||||
###############################################################################
|
||||
# $Id:: administration.js 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var eventLogTable;
|
||||
var eventLogTableBody;
|
||||
var eventLogHeaders;
|
||||
var eventLogRequest;
|
||||
var eventLogPreviousLinks = new Array();
|
||||
var eventLogNextLinks = new Array();
|
||||
var eventLogCurrentLinks = new Array();
|
||||
var eventLogURLParam = new Array();
|
||||
eventLogURLParam["start"] = 0;
|
||||
eventLogURLParam["count"] = 10;
|
||||
eventLogURLParam["asc"] = 0;
|
||||
|
||||
function editQuote (id) {
|
||||
var editLink = document.getElementById("quote-edit-" + id);
|
||||
editLink.parentNode.removeChild(editLink);
|
||||
var body = getNodeText("quote-body-" + id);
|
||||
var notes = getNodeText("quote-notes-" + id);
|
||||
var tags = getNodeText("quote-tags-" + id);
|
||||
var dataNode = document.getElementById("quote-data-" + id);
|
||||
while (dataNode.firstChild) dataNode.removeChild(dataNode.firstChild);
|
||||
var bodyArea = document.createElement("textarea");
|
||||
bodyArea.name = "body_" + id;
|
||||
bodyArea.appendChild(document.createTextNode(body));
|
||||
bodyArea.className = "body-field";
|
||||
var notesArea = document.createElement("textarea");
|
||||
notesArea.name = "notes_" + id;
|
||||
notesArea.appendChild(document.createTextNode(notes));
|
||||
notesArea.className = "notes-field";
|
||||
var tagsInput = document.createElement("input");
|
||||
tagsInput.name = "tags_" + id;
|
||||
tagsInput.value = tags;
|
||||
tagsInput.className = "tags-field";
|
||||
dataNode.appendChild(stickInDiv(bodyArea));
|
||||
dataNode.appendChild(stickInDiv(notesArea));
|
||||
dataNode.appendChild(stickInDiv(tagsInput));
|
||||
document.getElementById("a" + id + "-1").checked = true;
|
||||
}
|
||||
|
||||
function stickInDiv (node) {
|
||||
var div = document.createElement("div");
|
||||
div.className = "field-container";
|
||||
div.appendChild(node);
|
||||
return div;
|
||||
}
|
||||
|
||||
function getNodeText (id) {
|
||||
var node = document.getElementById(id);
|
||||
if (!node) return "";
|
||||
var text = "";
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
var child = node.childNodes[i];
|
||||
if (child.nodeType == 3) {
|
||||
text += child.nodeValue.replace(/ *[\r\n]/g, "");
|
||||
}
|
||||
else if (child.nodeName && child.nodeName.toLowerCase() == "br") {
|
||||
// \n breaks in IE
|
||||
text += "\r";
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function insertEventLog () {
|
||||
var node = document.getElementById("event-log-placeholder");
|
||||
if (!node || !ajaxSupported()) return;
|
||||
var div = document.createElement("div");
|
||||
var navTop = createEventLogNavigation();
|
||||
navTop.id = "event-log-navigation-top";
|
||||
var navBottom = createEventLogNavigation();
|
||||
navBottom.id = "event-log-navigation-bottom";
|
||||
div.appendChild(navTop);
|
||||
var table = document.createElement("table");
|
||||
eventLogTable = table;
|
||||
eventLogHeaders = new Array();
|
||||
table.id = "event-log-table";
|
||||
var thead = document.createElement("thead");
|
||||
var thr = document.createElement("tr");
|
||||
var cols = [ "id", "date", "username", "event" ];
|
||||
for (var i = 0; i < cols.length; i++) {
|
||||
var col = cols[i];
|
||||
var th = document.createElement("th");
|
||||
th.className = col;
|
||||
var text = eventLogLocale[col];
|
||||
var cont;
|
||||
if (col == "id") {
|
||||
cont = document.createElement("a");
|
||||
var href = document.createAttribute("href");
|
||||
var asc = (eventLogURLParam["asc"] > 0);
|
||||
href.value = getEventLogURL(true, "asc", asc ? 0 : 1);
|
||||
cont.setAttributeNode(href);
|
||||
var up = String.fromCharCode(0x25B2);
|
||||
var down = String.fromCharCode(0x25BC);
|
||||
var tn = document.createTextNode(asc ? up : down);
|
||||
cont.appendChild(tn);
|
||||
cont.onclick = function () {
|
||||
var asc = !(eventLogURLParam["asc"] > 0);
|
||||
tn.nodeValue = (asc ? up : down);
|
||||
href.value = getEventLogURL(true, "asc", asc ? 1 : 0);
|
||||
eventLogURLParam["asc"] = (asc ? 1 : 0);
|
||||
updateEventLog(true);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
else
|
||||
cont = document.createTextNode(text);
|
||||
th.appendChild(cont);
|
||||
thr.appendChild(th);
|
||||
eventLogHeaders[col] = th;
|
||||
}
|
||||
thead.appendChild(thr);
|
||||
table.appendChild(thead);
|
||||
eventLogTableBody = document.createElement("tbody");
|
||||
table.appendChild(eventLogTableBody);
|
||||
div.appendChild(table);
|
||||
div.appendChild(navBottom);
|
||||
node.parentNode.replaceChild(div, node);
|
||||
updateEventLog();
|
||||
}
|
||||
|
||||
function createEventLogNavigation () {
|
||||
var div = document.createElement("div");
|
||||
div.className = "event-log-navigation";
|
||||
var prev = document.createElement("a");
|
||||
var pHref = document.createAttribute("href");
|
||||
prev.setAttributeNode(pHref);
|
||||
prev.appendChild(document.createTextNode(
|
||||
String.fromCharCode(0x2190) + " " + eventLogLocale["previous"]));
|
||||
prev.className = "back";
|
||||
var next = document.createElement("a");
|
||||
var nHref = document.createAttribute("href");
|
||||
next.setAttributeNode(nHref);
|
||||
next.appendChild(document.createTextNode(
|
||||
eventLogLocale["next"] + " " + String.fromCharCode(0x2192)));
|
||||
next.className = "forward";
|
||||
var current = document.createElement("a");
|
||||
var cHref = document.createAttribute("href");
|
||||
current.setAttributeNode(cHref);
|
||||
current.appendChild(document.createTextNode(
|
||||
eventLogLocale["current"]));
|
||||
current.className = "current";
|
||||
div.appendChild(prev);
|
||||
div.appendChild(next);
|
||||
div.appendChild(current);
|
||||
eventLogPreviousLinks.push(prev);
|
||||
eventLogNextLinks.push(next);
|
||||
eventLogCurrentLinks.push(current);
|
||||
return div;
|
||||
}
|
||||
|
||||
function updateEventLog (reset) {
|
||||
clearEventLog();
|
||||
var tr = document.createElement("tr");
|
||||
var td = document.createElement("td");
|
||||
td.colSpan = 4;
|
||||
td.className = "loading";
|
||||
td.appendChild(document.createTextNode(eventLogLocale["loading"]));
|
||||
tr.appendChild(td);
|
||||
eventLogTableBody.appendChild(tr);
|
||||
eventLogHeaders["event"].className = "event" + (eventLogURLParam["code"] != null ? " filtered" : "");
|
||||
eventLogHeaders["username"].className = "username" + (eventLogURLParam["user"] != null ? " filtered" : "");
|
||||
if (eventLogRequest) eventLogRequest.abort();
|
||||
eventLogRequest = getAjaxObject();
|
||||
eventLogRequest.onreadystatechange = checkEventLogRequest;
|
||||
var url = getEventLogURL(reset);
|
||||
for (var i = 0; i < eventLogCurrentLinks.length; i++) {
|
||||
var link = eventLogCurrentLinks[i];
|
||||
link.getAttributeNode("href").value = url;
|
||||
}
|
||||
url += "&output=xml";
|
||||
eventLogRequest.open("GET", url, true);
|
||||
eventLogRequest.send("");
|
||||
}
|
||||
|
||||
function checkEventLogRequest () {
|
||||
if (eventLogRequest.readyState != 4 || eventLogRequest.status != 200) return;
|
||||
var xml = eventLogRequest.responseXML;
|
||||
eventLogRequest = null;
|
||||
var data = parseEventLogXML(xml);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
fillEventLog(data);
|
||||
}
|
||||
|
||||
function clearEventLog () {
|
||||
setEventLogNavigationEnabled(false, false);
|
||||
setEventLogNavigationEnabled(true, false);
|
||||
while (eventLogTableBody.firstChild)
|
||||
eventLogTableBody.removeChild(eventLogTableBody.firstChild);
|
||||
}
|
||||
|
||||
function fillEventLog (tableData) {
|
||||
clearEventLog();
|
||||
setEventLogNavigationEnabled(false, tableData["leading"]);
|
||||
setEventLogNavigationEnabled(true, tableData["trailing"]);
|
||||
var events = tableData["events"];
|
||||
var dataFilter;
|
||||
if (eventLogURLParam["data"])
|
||||
dataFilter = eventLogURLParam["data"].replace(/=.*/, "");
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
var evt = events[i];
|
||||
var data = evt["data"];
|
||||
var evenOdd = (i % 2 == 0 ? "even" : "odd");
|
||||
var firstRow = document.createElement("tr");
|
||||
firstRow.className = evenOdd;
|
||||
var idCell = document.createElement("td");
|
||||
idCell.className = "id";
|
||||
var rowSpan = 1;
|
||||
for (name in data) rowSpan++;
|
||||
idCell.rowSpan = rowSpan;
|
||||
idCell.appendChild(document.createTextNode(evt["id"]));
|
||||
firstRow.appendChild(idCell);
|
||||
var dateCell = document.createElement("td");
|
||||
dateCell.className = "date";
|
||||
dateCell.appendChild(document.createTextNode(evt["date"]));
|
||||
firstRow.appendChild(dateCell);
|
||||
var userCell = document.createElement("td");
|
||||
userCell.className = "username";
|
||||
var userID = evt["userid"];
|
||||
var username;
|
||||
if (userID == 0) {
|
||||
userCell.className += " guest";
|
||||
username = eventLogLocale["guest"];
|
||||
}
|
||||
else if ("username" in evt) {
|
||||
username = evt["username"];
|
||||
}
|
||||
else {
|
||||
userCell.className += " removed-account";
|
||||
username = "#" + userID;
|
||||
}
|
||||
var userLink = createEventLogLink("user", userID);
|
||||
userLink.appendChild(document.createTextNode(username));
|
||||
userCell.appendChild(userLink);
|
||||
firstRow.appendChild(userCell);
|
||||
var descCell = document.createElement("td");
|
||||
descCell.className = "event";
|
||||
var descLink = document.createElement("a");
|
||||
var descHref = document.createAttribute("href");
|
||||
var descLink = createEventLogLink("code", evt["code"]);
|
||||
descLink.appendChild(document.createTextNode(evt["description"]));
|
||||
descCell.appendChild(descLink);
|
||||
firstRow.appendChild(descCell);
|
||||
eventLogTableBody.appendChild(firstRow);
|
||||
for (name in data) {
|
||||
var value = data[name];
|
||||
var row = document.createElement("tr");
|
||||
row.className = evenOdd;
|
||||
var nameCell = document.createElement("td");
|
||||
nameCell.className = "property-name";
|
||||
if (dataFilter && name == dataFilter)
|
||||
nameCell.className += " filtered";
|
||||
nameCell.appendChild(document.createTextNode(name));
|
||||
var valueCell = document.createElement("td");
|
||||
valueCell.className = "property-value";
|
||||
valueCell.colSpan = 2;
|
||||
if (value.length > 1) {
|
||||
valueCell.appendChild(document.createTextNode(fixWhiteSpace(value[0])));
|
||||
for (var j = 1; j < value.length; j++) {
|
||||
valueCell.appendChild(document.createElement("br"));
|
||||
valueCell.appendChild(document.createTextNode(fixWhiteSpace(value[j])));
|
||||
}
|
||||
}
|
||||
else {
|
||||
var empty = (value.length == 0);
|
||||
var val = (!empty ? value[0].replace(/'/g, '\\\'') : "");
|
||||
var link = createEventLogLink("data", name + "=" + val);
|
||||
var text;
|
||||
if (empty) {
|
||||
link.className = "empty";
|
||||
text = eventLogLocale["empty"];
|
||||
}
|
||||
else
|
||||
text = fixWhiteSpace(value[0]);
|
||||
link.appendChild(document.createTextNode(text));
|
||||
valueCell.appendChild(link);
|
||||
}
|
||||
row.appendChild(nameCell);
|
||||
row.appendChild(valueCell);
|
||||
eventLogTableBody.appendChild(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setEventLogNavigationEnabled (next, enabled) {
|
||||
var links, className;
|
||||
if (next) {
|
||||
links = eventLogNextLinks;
|
||||
className = "forward";
|
||||
}
|
||||
else {
|
||||
links = eventLogPreviousLinks;
|
||||
className = "back";
|
||||
}
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i];
|
||||
var href = link.getAttributeNode("href");
|
||||
if (enabled) {
|
||||
link.className = className;
|
||||
var start = eventLogURLParam["start"];
|
||||
var count = eventLogURLParam["count"];
|
||||
if (!next)
|
||||
start = (start >= count ? start - count : 0);
|
||||
else
|
||||
start += count;
|
||||
link.onclick = function () {
|
||||
eventLogURLParam["start"] = start;
|
||||
updateEventLog();
|
||||
return false;
|
||||
};
|
||||
href.value = getEventLogURL(false, "start", start);
|
||||
}
|
||||
else {
|
||||
link.className = className + " inactive";
|
||||
link.onclick = function () {
|
||||
return false;
|
||||
};
|
||||
href.value = "#";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseEventLogXML (xml) {
|
||||
if (!xml || !xml.childNodes) return null;
|
||||
var leading = false;
|
||||
var trailing = false;
|
||||
var events = new Array();
|
||||
for (var i = 0; i < xml.childNodes.length; i++) {
|
||||
var root = xml.childNodes[i];
|
||||
if (root.nodeType != 1) continue;
|
||||
for (var j = 0; j < root.childNodes.length; j++) {
|
||||
var child = root.childNodes[j];
|
||||
if (child.nodeType == 1) {
|
||||
switch (child.nodeName) {
|
||||
case "event":
|
||||
events.push(parseLogEventNode(child));
|
||||
break;
|
||||
case "leading":
|
||||
leading = true;
|
||||
break;
|
||||
case "trailing":
|
||||
trailing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var data = new Array();
|
||||
data["leading"] = leading;
|
||||
data["trailing"] = trailing;
|
||||
data["events"] = events;
|
||||
return data;
|
||||
}
|
||||
|
||||
function parseLogEventNode (node) {
|
||||
var event = new Array();
|
||||
event["data"] = new Array();
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
var child = node.childNodes[i];
|
||||
if (child.nodeType != 1) continue;
|
||||
if (child.nodeName == "data") {
|
||||
var name, value;
|
||||
for (var j = 0; j < child.childNodes.length; j++) {
|
||||
var n = child.childNodes[j];
|
||||
if (n.nodeType != 1) continue;
|
||||
switch (n.nodeName) {
|
||||
case "name":
|
||||
name = n.firstChild.nodeValue;
|
||||
break;
|
||||
case "value":
|
||||
value = extractLogEventDataValue(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
event["data"][name] = value;
|
||||
}
|
||||
else {
|
||||
event[child.nodeName] = child.firstChild.nodeValue;
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
function extractLogEventDataValue (node) {
|
||||
var value = new Array();
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
var child = node.childNodes[i];
|
||||
if (child.nodeType != 3) continue;
|
||||
value.push(child.nodeValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function createEventLogLink (name, value) {
|
||||
var anchor = document.createElement("a");
|
||||
var href = document.createAttribute("href");
|
||||
href.value = getEventLogURL(true, name, value);
|
||||
anchor.setAttributeNode(href);
|
||||
anchor.onclick = function () {
|
||||
eventLogURLParam[name]
|
||||
= (eventLogURLParam[name] == value ? null : value);
|
||||
updateEventLog(true);
|
||||
return false;
|
||||
};
|
||||
return anchor;
|
||||
}
|
||||
|
||||
function getEventLogURL (reset, name, value) {
|
||||
var pairs = new Array();
|
||||
var found = false;
|
||||
for (key in eventLogURLParam) {
|
||||
var val;
|
||||
switch (key) {
|
||||
case name:
|
||||
val = (eventLogURLParam[key] == value ? null : value);
|
||||
found = true;
|
||||
break;
|
||||
case "start":
|
||||
val = (reset ? 0 : eventLogURLParam[key]);
|
||||
break;
|
||||
default:
|
||||
val = eventLogURLParam[key];
|
||||
}
|
||||
if (val != null)
|
||||
pairs.push(escape(key) + "=" + escape(val));
|
||||
}
|
||||
if (name && !found) {
|
||||
pairs.push(escape(name) + "=" + escape(value));
|
||||
}
|
||||
return eventLogURL + pairs.join("&");
|
||||
}
|
||||
|
||||
function fixWhiteSpace (text) {
|
||||
var nbsp = String.fromCharCode(160);
|
||||
return text
|
||||
.replace(/[\r\n]+/g, "")
|
||||
.replace(/^\s/, nbsp)
|
||||
.replace(/\s$/, nbsp)
|
||||
.replace(/\s{2}/g, nbsp + nbsp);
|
||||
}
|
||||
|
||||
function addOnloadFunction (f) {
|
||||
if (window.onload != null) {
|
||||
var old = window.onload;
|
||||
window.onload = function (e) {
|
||||
old(e);
|
||||
f();
|
||||
};
|
||||
}
|
||||
else {
|
||||
window.onload = f;
|
||||
}
|
||||
}
|
||||
|
||||
addOnloadFunction(insertEventLog);
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# ajax.js #
|
||||
# Facilitates access to the browser's AJAX features, if any #
|
||||
###############################################################################
|
||||
# $Id:: ajax.js 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var ajaxMethods = new Array(
|
||||
function() { return new ActiveXObject("Msxml2.XMLHTTP") },
|
||||
function() { return new ActiveXObject("Microsoft.XMLHTTP") },
|
||||
function() { return new XMLHttpRequest() }
|
||||
);
|
||||
|
||||
var ajaxMethodIndex = -1;
|
||||
|
||||
for (var i = 0; i < ajaxMethods.length; i++) {
|
||||
try {
|
||||
ajaxMethods[i]();
|
||||
ajaxMethodIndex = i;
|
||||
break;
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
function ajaxSupported () {
|
||||
return (ajaxMethodIndex >= 0);
|
||||
}
|
||||
|
||||
function getAjaxObject () {
|
||||
return (ajaxSupported()
|
||||
? ajaxMethods[ajaxMethodIndex]()
|
||||
: null);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
opensource@google.com
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,22 @@
|
|||
ExplorerCanvas
|
||||
Copyright 2006 Google Inc.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
DESCRIPTION
|
||||
|
||||
Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based
|
||||
drawing operations. ExplorerCanvas brings the same functionality to Internet
|
||||
Explorer; web developers only need to include a single script tag in their
|
||||
existing canvas webpages to enable this support.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
INSTALLATION
|
||||
|
||||
Include the ExplorerCanvas tag in the same directory as your HTML files, and
|
||||
add the following code to your page, preferably in the <head> tag.
|
||||
|
||||
<!--[if IE]><script type="text/javascript" src="excanvas.js"></script><![endif]-->
|
||||
|
||||
If you run into trouble, please look at the included example code to see how
|
||||
to best implement this
|
|
@ -0,0 +1,93 @@
|
|||
<!--
|
||||
Copyright 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>ExplorerCanvas Example 1</title>
|
||||
<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
|
||||
<script type="text/javascript">
|
||||
var canvas, ctx;
|
||||
var particles = [];
|
||||
var NUM_PARTICLES = 20;
|
||||
|
||||
function Particle() {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
|
||||
this.xvel = Math.random() * 5 - 2.5;
|
||||
this.yvel = Math.random() * 5 - 2.5;
|
||||
}
|
||||
|
||||
Particle.prototype.update = function() {
|
||||
this.x += this.xvel;
|
||||
this.y += this.yvel;
|
||||
|
||||
this.yvel += 0.1;
|
||||
|
||||
if (this.x > canvas.width || this.x < 0) {
|
||||
this.xvel = -this.xvel;
|
||||
}
|
||||
|
||||
if (this.y > canvas.height || this.y < 0) {
|
||||
this.yvel = -this.yvel;
|
||||
}
|
||||
}
|
||||
|
||||
function loop() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
for(var i = 0; i < NUM_PARTICLES; i++) {
|
||||
particles[i].update();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[i].x - particles[i].xvel,
|
||||
particles[i].y - particles[i].yvel);
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
setTimeout(loop, 10);
|
||||
}
|
||||
|
||||
function load() {
|
||||
canvas = document.getElementById("cv");
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
for(var i = 0; i < NUM_PARTICLES; i++) {
|
||||
particles[i] = new Particle();
|
||||
}
|
||||
|
||||
ctx.lineWidth = "2";
|
||||
ctx.strokeStyle = "rgb(255, 255, 255)";
|
||||
loop();
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color:black;
|
||||
margin:50px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border:1px solid #444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="load();">
|
||||
<canvas id="cv" width="400" height="300"></canvas>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,513 @@
|
|||
<!--
|
||||
Copyright 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>ExplorerCanvas Example 1</title>
|
||||
<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
|
||||
<script type="text/javascript">
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
var canvas, ctx;
|
||||
var canvasWidth, halfCanvasWidth;
|
||||
var canvasHeight, halfCanvasHeight;
|
||||
|
||||
var space; // 3D Engine
|
||||
var scene; // 3D Scene
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Space is a simple 3D system.
|
||||
*
|
||||
* Y+ = up
|
||||
* Z+ = into screen
|
||||
* X+ = right
|
||||
*/
|
||||
function Space() {
|
||||
this.m = this.createMatrixIdentity();
|
||||
this.mStack = [];
|
||||
}
|
||||
|
||||
Space.prototype.createMatrixIdentity = function() {
|
||||
return [
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies two 4x4 matricies together.
|
||||
*/
|
||||
Space.prototype.matrixMultiply = function(m1, m2) {
|
||||
var result = this.createMatrixIdentity();
|
||||
|
||||
var width = m1[0].length;
|
||||
var height = m1.length;
|
||||
|
||||
if (width != m2.length) {
|
||||
// error
|
||||
}
|
||||
|
||||
for (var x = 0; x < width; x++) {
|
||||
for (var y = 0; y < height; y++) {
|
||||
var sum = 0;
|
||||
|
||||
for (var z = 0; z < width; z++) {
|
||||
sum += m1[y][z] * m2[z][x];
|
||||
}
|
||||
|
||||
result[y][x] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a coordinate using the current transformation
|
||||
* matrix, then flattens it using the projection matrix.
|
||||
*/
|
||||
Space.prototype.flatten = function(point) {
|
||||
var p = [[point.x, point.y, point.z, 1]];
|
||||
var pm = this.matrixMultiply(p, this.m);
|
||||
|
||||
point.tx = pm[0][0];
|
||||
point.ty = pm[0][1];
|
||||
point.tz = pm[0][2];
|
||||
|
||||
// lazy projection
|
||||
point.fx = halfCanvasWidth + (canvasWidth * point.tx / point.tz);
|
||||
point.fy = halfCanvasHeight -(canvasWidth * point.ty / point.tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate (move) the current transformation matrix
|
||||
*/
|
||||
Space.prototype.translate = function(x, y, z) {
|
||||
var m = [
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[x, y, z, 1]
|
||||
];
|
||||
|
||||
this.m = this.matrixMultiply(m, this.m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the current transformation matrix. Rotations are
|
||||
* world-oriented, and occur in y,x,z order.
|
||||
*/
|
||||
Space.prototype.rotate = function(x, y, z) {
|
||||
if (y) {
|
||||
var cosY = Math.cos(y);
|
||||
var sinY = Math.sin(y);
|
||||
var rotY = [
|
||||
[cosY, 0, sinY, 0],
|
||||
[0, 1, 0, 0],
|
||||
[-sinY, 0, cosY, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
this.m = this.matrixMultiply(this.m, rotY);
|
||||
}
|
||||
|
||||
if (x) {
|
||||
var cosX = Math.cos(x);
|
||||
var sinX = Math.sin(x);
|
||||
var rotX = [
|
||||
[1, 0, 0, 0],
|
||||
[0, cosX, -sinX, 0],
|
||||
[0, sinX, cosX,0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
this.m = this.matrixMultiply(this.m, rotX);
|
||||
}
|
||||
|
||||
if (z) {
|
||||
var cosZ = Math.cos(z);
|
||||
var sinZ = Math.sin(z);
|
||||
var rotZ = [
|
||||
[cosZ, -sinZ, 0, 0],
|
||||
[sinZ, cosZ, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 1]
|
||||
];
|
||||
|
||||
this.m = this.matrixMultiply(this.m, rotZ);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the current transformation onto the stack
|
||||
*/
|
||||
Space.prototype.push = function() {
|
||||
this.mStack.push(this.m);
|
||||
this.m = [
|
||||
[this.m[0][0], this.m[0][1], this.m[0][2], this.m[0][3]],
|
||||
[this.m[1][0], this.m[1][1], this.m[1][2], this.m[1][3]],
|
||||
[this.m[2][0], this.m[2][1], this.m[2][2], this.m[2][3]],
|
||||
[this.m[3][0], this.m[3][1], this.m[3][2], this.m[3][3]]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the end off the transformation stack
|
||||
*/
|
||||
Space.prototype.pop = function() {
|
||||
this.m = this.mStack.pop();
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* A 3d coordinate
|
||||
*/
|
||||
function Point(x, y, z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
// Relative to camera coordinates
|
||||
this.tx;
|
||||
this.ty;
|
||||
this.tz;
|
||||
|
||||
// Flattened coordinates
|
||||
this.fx;
|
||||
this.fy;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Shape is made up of polygons
|
||||
*/
|
||||
function Shape() {
|
||||
this.points = [];
|
||||
this.polygons = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the shape
|
||||
*/
|
||||
Shape.prototype.draw = function(drawlist) {
|
||||
for (var i = 0; i< this.points.length; i++) {
|
||||
space.flatten(this.points[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i< this.polygons.length; i++) {
|
||||
var poly = this.polygons[i]; // convenience
|
||||
|
||||
space.flatten(poly.origin);
|
||||
|
||||
// lazy backface culling
|
||||
if (poly.normal && this.backface) {
|
||||
space.flatten(poly.normal);
|
||||
|
||||
var originDist = Math.pow(poly.origin.tx, 2)
|
||||
+ Math.pow(poly.origin.ty, 2)
|
||||
+ Math.pow(poly.origin.tz, 2);
|
||||
|
||||
var normalDist = Math.pow(poly.normal.tx, 2)
|
||||
+ Math.pow(poly.normal.ty, 2)
|
||||
+ Math.pow(poly.normal.tz, 2);
|
||||
|
||||
if(originDist > normalDist) {
|
||||
drawlist.push(poly);
|
||||
}
|
||||
} else {
|
||||
drawlist.push(poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A polygon is a connection of points in the shape object. You
|
||||
* should probably try to make them coplanar.
|
||||
*/
|
||||
function Polygon(points, normal, backface, type, color) {
|
||||
this.points = points;
|
||||
|
||||
this.origin = new Point(0, 0, 0);
|
||||
for(var i = 0; i < this.points.length; i++) {
|
||||
this.origin.x += this.points[i].x;
|
||||
this.origin.y += this.points[i].y;
|
||||
this.origin.z += this.points[i].z;
|
||||
}
|
||||
|
||||
this.origin.x /= this.points.length;
|
||||
this.origin.y /= this.points.length;
|
||||
this.origin.z /= this.points.length;
|
||||
|
||||
if (normal) {
|
||||
this.normal = new Point(this.origin.x + normal.x,
|
||||
this.origin.y + normal.y,
|
||||
this.origin.z + normal.z);
|
||||
} else {
|
||||
this.normal = null;
|
||||
}
|
||||
|
||||
this.backface = backface;
|
||||
this.type = type;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
Polygon.SOLID = 0;
|
||||
Polygon.WIRE = 1;
|
||||
|
||||
/**
|
||||
* Draws the polygon. Assumes that the points have already been
|
||||
* flattened.
|
||||
*/
|
||||
Polygon.prototype.draw = function() {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.points[0].fx, this.points[0].fy);
|
||||
|
||||
for(var i = 0; i < this.points.length; i++) {
|
||||
ctx.lineTo(this.points[i].fx, this.points[i].fy);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
|
||||
var color = this.color;
|
||||
|
||||
/*
|
||||
// Do lighting here
|
||||
lightvector = Math.abs(this.normal.x + this.normal.y);
|
||||
if(lightvector > 1) {
|
||||
lightvector = 1;
|
||||
}
|
||||
|
||||
color[0] = (color[0] * lightvector).toString();
|
||||
color[1] = (color[1] * lightvector).toString();
|
||||
color[2] = (color[2] * lightvector).toString();
|
||||
*/
|
||||
|
||||
if (color.length > 3) {
|
||||
var style = ["rgba(",
|
||||
color[0], ",",
|
||||
color[1], ",",
|
||||
color[2], ",",
|
||||
color[3], ")"].join("");
|
||||
} else {
|
||||
var style = ["rgb(",
|
||||
color[0], ",",
|
||||
color[1], ",",
|
||||
color[2], ")"].join("");
|
||||
}
|
||||
|
||||
if (this.type == Polygon.SOLID) {
|
||||
ctx.fillStyle = style;
|
||||
ctx.fill();
|
||||
} else if (this.type == Polygon.WIRE) {
|
||||
ctx.strokeStyle = style;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Scene describes the 3D environment
|
||||
*/
|
||||
function Scene() {
|
||||
this.shapes = {};
|
||||
this.camera = new Point(0, 0, 0);
|
||||
this.cameraTarget = new Point(0, 0, 0);
|
||||
this.cameraRotation = 0;
|
||||
|
||||
this.drawlist = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the world
|
||||
*/
|
||||
Scene.prototype.draw = function() {
|
||||
space.push();
|
||||
|
||||
// Camera transformation
|
||||
space.translate(
|
||||
-this.camera.x,
|
||||
-this.camera.y,
|
||||
-this.camera.z
|
||||
);
|
||||
|
||||
// Camera rotation
|
||||
var xdiff = this.cameraTarget.x - this.camera.x;
|
||||
var ydiff = this.cameraTarget.y - this.camera.y;
|
||||
var zdiff = this.cameraTarget.z - this.camera.z;
|
||||
|
||||
var xzdist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));
|
||||
|
||||
var xrot = -Math.atan2(ydiff, xzdist); // up/down rotation
|
||||
var yrot = Math.atan2(xdiff, zdiff); // left/right rotation
|
||||
|
||||
space.rotate(xrot, yrot, this.cameraRotation);
|
||||
|
||||
// Drawing
|
||||
this.drawlist = [];
|
||||
|
||||
for(var i in this.shapes) {
|
||||
this.shapes[i].draw(this.drawlist);
|
||||
}
|
||||
|
||||
// Depth sorting (warning: this is only enough to drive this demo - feel
|
||||
// free to contribute a better system).
|
||||
this.drawlist.sort(function (poly1, poly2) {
|
||||
return poly2.origin.tz - poly1.origin.tz;
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.drawlist.length; i++) {
|
||||
this.drawlist[i].draw();
|
||||
}
|
||||
|
||||
space.pop();
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
var count = 0;
|
||||
|
||||
function loop() {
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
scene.camera.x = 70*Math.sin(count);
|
||||
scene.camera.y = 70;
|
||||
scene.camera.z = 70*Math.cos(count);
|
||||
scene.cameraRotation = count / 10;
|
||||
|
||||
count += 0.01;
|
||||
scene.draw();
|
||||
}
|
||||
|
||||
function load() {
|
||||
// Init drawing system
|
||||
canvas = document.getElementById("cv");
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
canvasWidth = canvas.width;
|
||||
canvasHeight = canvas.height;
|
||||
halfCanvasWidth = canvasWidth * 0.5;
|
||||
halfCanvasHeight = canvasHeight * 0.5;
|
||||
|
||||
// Init 3D components
|
||||
space = new Space();
|
||||
scene = new Scene();
|
||||
|
||||
// Create a box shape and add it to the scene
|
||||
scene.shapes['box'] = new Shape();
|
||||
var p = scene.shapes['box'].points; // for convenience
|
||||
|
||||
p[0] = new Point(-10, -10, -10); // left bottom front
|
||||
p[1] = new Point(10, -10, -10); // right bottom front
|
||||
p[2] = new Point(10, 10, -10); // right top front
|
||||
p[3] = new Point(-10, 10, -10); // left top front
|
||||
|
||||
p[4] = new Point(-10, -10, 10); // left bottom back
|
||||
p[5] = new Point(10, -10, 10); // right bottom back
|
||||
p[6] = new Point(10, 10, 10); // right top back
|
||||
p[7] = new Point(-10, 10, 10); // left top back
|
||||
|
||||
// Back
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[0], p[1], p[2], p[3] ],
|
||||
new Point(0, 0, -1),
|
||||
true /* double-sided */,
|
||||
Polygon.SOLID,
|
||||
[255, 0, 0]
|
||||
));
|
||||
|
||||
// Front
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[4], p[5], p[6], p[7] ],
|
||||
new Point(0, 0, 1),
|
||||
true /* double-sided */,
|
||||
Polygon.SOLID,
|
||||
[0, 0, 255]
|
||||
));
|
||||
|
||||
// Top
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[2], p[3], p[7], p[6] ],
|
||||
new Point(0, 1, 0),
|
||||
false /* single-sided */,
|
||||
Polygon.WIRE,
|
||||
[0, 255, 0]
|
||||
));
|
||||
|
||||
// Transparent Top
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[2], p[3], p[7], p[6] ],
|
||||
new Point(0, 1, 0),
|
||||
false /* single-sided */,
|
||||
Polygon.SOLID,
|
||||
[0, 255, 0, 0.4]
|
||||
));
|
||||
|
||||
// Left
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[0], p[4], p[7], p[3] ],
|
||||
new Point(-1, 0, 0),
|
||||
true /* double-sided */,
|
||||
Polygon.SOLID,
|
||||
[255, 255, 0]
|
||||
));
|
||||
|
||||
// Right
|
||||
scene.shapes['box'].polygons.push(new Polygon(
|
||||
[ p[1], p[5], p[6], p[2] ],
|
||||
new Point(1, 0, 0),
|
||||
true /* double-sided */,
|
||||
Polygon.SOLID,
|
||||
[0, 255, 255]
|
||||
));
|
||||
|
||||
// Create a floor shape and add it to the scene
|
||||
scene.shapes['floor'] = new Shape();
|
||||
var p = scene.shapes['floor'].points; // for convenience
|
||||
|
||||
p[0] = new Point(-40, -10, -40);
|
||||
p[1] = new Point(-40, -10, 40);
|
||||
p[2] = new Point( 40, -10, 40);
|
||||
p[3] = new Point( 40, -10, -40);
|
||||
|
||||
// Floor
|
||||
scene.shapes['floor'].polygons.push(new Polygon(
|
||||
[ p[0], p[1], p[2], p[3] ],
|
||||
new Point(0, 1, 0),
|
||||
false /* single-sided */,
|
||||
Polygon.SOLID,
|
||||
[45, 45, 45]
|
||||
));
|
||||
|
||||
setInterval('loop()', 20);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color:black;
|
||||
margin:50px;
|
||||
text-align:center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="load();">
|
||||
<canvas id="cv" width="400" height="300"></canvas>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,284 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<!--
|
||||
|
||||
Copyright 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
|
||||
<style type="text/css">
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#image-rotator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#image-rotator .tool-bar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tool-bar button {
|
||||
margin: 0.5em 0.5em 0 0;
|
||||
}
|
||||
|
||||
#image-rotator img,
|
||||
#image-rotator canvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function sawFunc(a) {
|
||||
var PI = Math.PI;
|
||||
var PI2 = PI / 2;
|
||||
// make sure a is within 0 to PI
|
||||
a = a % PI;
|
||||
if (a < 0) {
|
||||
a += PI;
|
||||
}
|
||||
if (a < PI2) {
|
||||
return a / PI2;
|
||||
} else {
|
||||
return (PI - a) / PI2;
|
||||
}
|
||||
}
|
||||
|
||||
function easeInEaseOut(t) {
|
||||
var t2 = t * t;
|
||||
return 3 * t2 - 2 * t * t2;
|
||||
}
|
||||
|
||||
function ImageRotator(el, src, w, h) {
|
||||
this.element = el;
|
||||
this.toolBar = el.getElementsByTagName("div")[0];
|
||||
this.canvas = el.getElementsByTagName("canvas")[0];
|
||||
var images = el.getElementsByTagName("img");
|
||||
this.image = images[images.length - 1];
|
||||
var btns = el.getElementsByTagName("button");
|
||||
this.btnCw = btns[0];
|
||||
this.btnCcw = btns[1];
|
||||
var self = this;
|
||||
this.btnCcw.onclick = function () {
|
||||
self.rotateCcw();
|
||||
};
|
||||
this.btnCw.onclick = function () {
|
||||
self.rotateCw();
|
||||
};
|
||||
this.image.onload = function (e) {
|
||||
self.onImageLoad(e);
|
||||
};
|
||||
this.image.onerror = function (e) {
|
||||
self.onImageError(e);
|
||||
};
|
||||
this.image.onabort = function (e) {
|
||||
self.onImageAbort(e);
|
||||
};
|
||||
this.setImage(src, w, h);
|
||||
this.layout();
|
||||
|
||||
var onResize = function () {
|
||||
self.layout();
|
||||
};
|
||||
var onLoad = function () {
|
||||
self.onWindowLoad();
|
||||
};
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener("resize", onResize, false);
|
||||
window.addEventListener("load", onLoad, false);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent("onresize", onResize);
|
||||
window.attachEvent("onload", onLoad);
|
||||
}
|
||||
}
|
||||
|
||||
ImageRotator.prototype = {
|
||||
getLoaded: function () {
|
||||
return this.imageLoaded && this.windowLoaded;
|
||||
},
|
||||
setImage: function (src, w, h) {
|
||||
this.imageLoaded = false;
|
||||
this.image.src = src;
|
||||
this.imageWidth = w;
|
||||
this.imageHeight = h;
|
||||
},
|
||||
|
||||
layout: function () {
|
||||
var PI2 = Math.PI / 2;
|
||||
var h = this.element.clientHeight;
|
||||
var w = this.element.clientWidth;
|
||||
var th = this.toolBar.offsetHeight;
|
||||
h -= this.toolBar.offsetHeight;
|
||||
if (!this.ctx || !this.getLoaded()) {
|
||||
this.btnCw.disabled = true;
|
||||
this.btnCcw.disabled = true;
|
||||
this.canvas.style.display = "none";
|
||||
this.image.style.display = "block";
|
||||
var ratio = Math.min(w / this.imageWidth, h / this.imageHeight, 1);
|
||||
var imgW = this.imageWidth * ratio;
|
||||
var imgH = this.imageHeight * ratio;
|
||||
var y = th + (h - imgH) / 2;
|
||||
var x = (w - imgW) / 2;
|
||||
this.image.style.left = Math.round(x) + "px";
|
||||
this.image.style.top = Math.round(y) + "px";
|
||||
this.image.style.width = Math.round(imgW) + "px";
|
||||
this.image.style.height = Math.round(imgH) + "px";
|
||||
} else {
|
||||
this.btnCw.disabled = this.isAnimating_;
|
||||
this.btnCcw.disabled = this.isAnimating_;
|
||||
this.canvas.style.display = "block";
|
||||
this.image.style.display = "none";
|
||||
|
||||
this.canvas.style.left = 0 + "px";
|
||||
this.canvas.style.top = th + "px";
|
||||
this.canvas.style.width = w + "px";
|
||||
this.canvas.width = w;
|
||||
this.canvas.style.height = h + "px";
|
||||
this.canvas.height = h;
|
||||
|
||||
this.ctx.save();
|
||||
this.ctx.clearRect(0, 0, w, h);
|
||||
this.ctx.translate(w / 2, h / 2);
|
||||
this.ctx.rotate(this.rotation);
|
||||
// 0 -> 1, sin(0) = 0
|
||||
// PI / 2 -> H / W, sin(PI/2) = 1
|
||||
|
||||
// sin(PI/2) = 1 -> limit factor is w and imgH
|
||||
|
||||
var iw = this.imageWidth;
|
||||
var ih = this.imageHeight;
|
||||
var scale;
|
||||
if (iw <= w && iw <= h && ih <= h && ih <= w) {
|
||||
scale = 1;
|
||||
} else {
|
||||
var sinr = sawFunc(this.rotation);
|
||||
var cosr = sawFunc(this.rotation + PI2);
|
||||
var ratio1 = sinr * Math.min(w / ih, h / iw);
|
||||
var ratio2 = cosr * Math.min(w / iw, h / ih);
|
||||
var ratio = Math.min(1, ratio1 + ratio2);
|
||||
scale = ratio;
|
||||
}
|
||||
this.ctx.scale(scale, scale);
|
||||
this.ctx.translate(-iw / 2, -ih / 2);
|
||||
this.ctx.drawImage(this.image, 0, 0, iw, ih);
|
||||
this.ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
rotation: 0,
|
||||
animationDuration: 500,
|
||||
|
||||
rotateCcw: function () {
|
||||
if (!this.isAnimating_) {
|
||||
this.startTime_ = (new Date).valueOf();
|
||||
this.currentAngle_ = this.rotation;
|
||||
this.deltaAngle_ = Math.PI / 2;
|
||||
this.isAnimating_ = true;
|
||||
this.animCounter_ = 0;
|
||||
this.rotate_();
|
||||
}
|
||||
},
|
||||
|
||||
rotateCw: function () {
|
||||
if (!this.isAnimating_) {
|
||||
this.startTime_ = (new Date).valueOf();
|
||||
this.currentAngle_ = this.rotation;
|
||||
this.deltaAngle_ = -Math.PI / 2;
|
||||
this.isAnimating_ = true;
|
||||
this.animCounter_ = 0;
|
||||
this.rotate_();
|
||||
}
|
||||
},
|
||||
|
||||
rotate_: function () {
|
||||
if (this.isAnimating_) {
|
||||
var t = easeInEaseOut(Math.min(1, (new Date - this.startTime_) /
|
||||
this.animationDuration));
|
||||
this.rotation = t * this.deltaAngle_ + this.currentAngle_;
|
||||
if (t < 1) {
|
||||
var self = this;
|
||||
window.setTimeout(function () {
|
||||
self.rotate_();
|
||||
}, 10);
|
||||
} else {
|
||||
this.isAnimating_ = false;
|
||||
}
|
||||
this.layout();
|
||||
}
|
||||
},
|
||||
|
||||
onImageLoad: function (e) {
|
||||
this.imageLoaded = true;
|
||||
this.initCanvas();
|
||||
},
|
||||
onImageError: function (e) {
|
||||
this.imageLoaded = false;
|
||||
},
|
||||
onImageAbort: function (e) {
|
||||
this.imageLoaded = false;
|
||||
},
|
||||
onWindowLoad: function (e) {
|
||||
this.windowLoaded = true;
|
||||
this.initCanvas();
|
||||
},
|
||||
|
||||
initCanvas: function () {
|
||||
if (!this.ctx && this.getLoaded()) {
|
||||
// IE recreates the element?
|
||||
this.canvas = this.element.getElementsByTagName("canvas")[0];
|
||||
this.ctx = this.canvas.getContext("2d");
|
||||
|
||||
if (!this.ctx) {
|
||||
return;
|
||||
}
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="image-rotator">
|
||||
<div class="tool-bar">
|
||||
<button>Rotate Left</button><button>Rotate Right</button>
|
||||
</div>
|
||||
<canvas id="c"></canvas>
|
||||
<img src="" alt="">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
new ImageRotator(document.getElementById("image-rotator"),
|
||||
"ff.jpg", 608, 380);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,730 @@
|
|||
// Copyright 2006 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// TODO: Patterns
|
||||
// TODO: Radial gradient
|
||||
// TODO: Clipping paths
|
||||
// TODO: Coordsize (still need to support stretching)
|
||||
// TODO: Painting mode
|
||||
// TODO: Optimize
|
||||
// TODO: canvas width/height sets content size in moz, border size in ie
|
||||
|
||||
// only add this code if we do not already have a canvas implementation
|
||||
if (!window.CanvasRenderingContext2D) {
|
||||
|
||||
(function () {
|
||||
|
||||
// alias some functions to make (compiled) code shorter
|
||||
var m = Math;
|
||||
var mr = m.round;
|
||||
var ms = m.sin;
|
||||
var mc = m.cos;
|
||||
|
||||
var G_vmlCanvasManager_ = {
|
||||
init: function (opt_doc) {
|
||||
var doc = opt_doc || document;
|
||||
if (/MSIE/.test(navigator.userAgent) && !window.opera) {
|
||||
var self = this;
|
||||
doc.attachEvent("onreadystatechange", function () {
|
||||
self.init_(doc);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
init_: function (doc, e) {
|
||||
if (doc.readyState == "complete") {
|
||||
// create xmlns
|
||||
if (!doc.namespaces["g_vml_"]) {
|
||||
doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
|
||||
}
|
||||
|
||||
// setup default css
|
||||
var ss = doc.createStyleSheet();
|
||||
ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
|
||||
"text-align:left;}" +
|
||||
"canvas *{behavior:url(#default#VML)}";
|
||||
|
||||
// find all canvas elements
|
||||
var els = doc.getElementsByTagName("canvas");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
if (!els[i].getContext) {
|
||||
this.initElement(els[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fixElement_: function (el) {
|
||||
// in IE before version 5.5 we would need to add HTML: to the tag name
|
||||
// but we do not care about IE before version 6
|
||||
var outerHTML = el.outerHTML;
|
||||
var newEl = document.createElement(outerHTML);
|
||||
// if the tag is still open IE has created the children as siblings and
|
||||
// it has also created a tag with the name "/FOO"
|
||||
if (outerHTML.slice(-2) != "/>") {
|
||||
var tagName = "/" + el.tagName;
|
||||
var ns;
|
||||
// remove content
|
||||
while ((ns = el.nextSibling) && ns.tagName != tagName) {
|
||||
ns.removeNode();
|
||||
}
|
||||
// remove the incorrect closing tag
|
||||
if (ns) {
|
||||
ns.removeNode();
|
||||
}
|
||||
}
|
||||
el.parentNode.replaceChild(newEl, el);
|
||||
return newEl;
|
||||
},
|
||||
|
||||
/**
|
||||
* Public initializes a canvas element so that it can be used as canvas
|
||||
* element from now on. This is called automatically before the page is
|
||||
* loaded but if you are creating elements using createElement you need to
|
||||
* make sure this is called on the element.
|
||||
* @param {HTMLElement} el The canvas element to initialize.
|
||||
* @return {HTMLElement} the element that was created.
|
||||
*/
|
||||
initElement: function (el) {
|
||||
el = this.fixElement_(el);
|
||||
el.getContext = function () {
|
||||
if (this.context_) {
|
||||
return this.context_;
|
||||
}
|
||||
return this.context_ = new CanvasRenderingContext2D_(this);
|
||||
};
|
||||
|
||||
// do not use inline function because that will leak memory
|
||||
// el.attachEvent('onpropertychange', onPropertyChange)
|
||||
el.attachEvent('onresize', onResize);
|
||||
|
||||
var attrs = el.attributes;
|
||||
if (attrs.width && attrs.width.specified) {
|
||||
// TODO: use runtimeStyle and coordsize
|
||||
// el.getContext().setWidth_(attrs.width.nodeValue);
|
||||
el.style.width = attrs.width.nodeValue + "px";
|
||||
}
|
||||
if (attrs.height && attrs.height.specified) {
|
||||
// TODO: use runtimeStyle and coordsize
|
||||
// el.getContext().setHeight_(attrs.height.nodeValue);
|
||||
el.style.height = attrs.height.nodeValue + "px";
|
||||
}
|
||||
//el.getContext().setCoordsize_()
|
||||
return el;
|
||||
}
|
||||
};
|
||||
|
||||
function onPropertyChange(e) {
|
||||
// we need to watch changes to width and height
|
||||
switch (e.propertyName) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
// TODO: coordsize and size
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onResize(e) {
|
||||
var el = e.srcElement;
|
||||
if (el.firstChild) {
|
||||
el.firstChild.style.width = el.clientWidth + 'px';
|
||||
el.firstChild.style.height = el.clientHeight + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
G_vmlCanvasManager_.init();
|
||||
|
||||
// precompute "00" to "FF"
|
||||
var dec2hex = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
|
||||
}
|
||||
}
|
||||
|
||||
function createMatrixIdentity() {
|
||||
return [
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]
|
||||
];
|
||||
}
|
||||
|
||||
function matrixMultiply(m1, m2) {
|
||||
var result = createMatrixIdentity();
|
||||
|
||||
for (var x = 0; x < 3; x++) {
|
||||
for (var y = 0; y < 3; y++) {
|
||||
var sum = 0;
|
||||
|
||||
for (var z = 0; z < 3; z++) {
|
||||
sum += m1[x][z] * m2[z][y];
|
||||
}
|
||||
|
||||
result[x][y] = sum;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function copyState(o1, o2) {
|
||||
o2.fillStyle = o1.fillStyle;
|
||||
o2.lineCap = o1.lineCap;
|
||||
o2.lineJoin = o1.lineJoin;
|
||||
o2.lineWidth = o1.lineWidth;
|
||||
o2.miterLimit = o1.miterLimit;
|
||||
o2.shadowBlur = o1.shadowBlur;
|
||||
o2.shadowColor = o1.shadowColor;
|
||||
o2.shadowOffsetX = o1.shadowOffsetX;
|
||||
o2.shadowOffsetY = o1.shadowOffsetY;
|
||||
o2.strokeStyle = o1.strokeStyle;
|
||||
}
|
||||
|
||||
function processStyle(styleString) {
|
||||
var str, alpha = 1;
|
||||
|
||||
styleString = String(styleString);
|
||||
if (styleString.substring(0, 3) == "rgb") {
|
||||
var start = styleString.indexOf("(", 3);
|
||||
var end = styleString.indexOf(")", start + 1);
|
||||
var guts = styleString.substring(start + 1, end).split(",");
|
||||
|
||||
str = "#";
|
||||
for (var i = 0; i < 3; i++) {
|
||||
str += dec2hex[parseInt(guts[i])];
|
||||
}
|
||||
|
||||
if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
|
||||
alpha = guts[3];
|
||||
}
|
||||
} else {
|
||||
str = styleString;
|
||||
}
|
||||
|
||||
return [str, alpha];
|
||||
}
|
||||
|
||||
function processLineCap(lineCap) {
|
||||
switch (lineCap) {
|
||||
case "butt":
|
||||
return "flat";
|
||||
case "round":
|
||||
return "round";
|
||||
case "square":
|
||||
default:
|
||||
return "square";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class implements CanvasRenderingContext2D interface as described by
|
||||
* the WHATWG.
|
||||
* @param {HTMLElement} surfaceElement The element that the 2D context should
|
||||
* be associated with
|
||||
*/
|
||||
function CanvasRenderingContext2D_(surfaceElement) {
|
||||
this.m_ = createMatrixIdentity();
|
||||
|
||||
this.mStack_ = [];
|
||||
this.aStack_ = [];
|
||||
this.currentPath_ = [];
|
||||
|
||||
// Canvas context properties
|
||||
this.strokeStyle = "#000";
|
||||
this.fillStyle = "#ccc";
|
||||
|
||||
this.lineWidth = 1;
|
||||
this.lineJoin = "miter";
|
||||
this.lineCap = "butt";
|
||||
this.miterLimit = 10;
|
||||
this.globalAlpha = 1;
|
||||
|
||||
var el = document.createElement('div');
|
||||
el.style.width = surfaceElement.clientWidth + 'px';
|
||||
el.style.height = surfaceElement.clientHeight + 'px';
|
||||
el.style.overflow = 'hidden';
|
||||
el.style.position = 'absolute';
|
||||
surfaceElement.appendChild(el);
|
||||
|
||||
this.element_ = el;
|
||||
};
|
||||
|
||||
var contextPrototype = CanvasRenderingContext2D_.prototype;
|
||||
contextPrototype.clearRect = function() {
|
||||
this.element_.innerHTML = "";
|
||||
this.currentPath_ = [];
|
||||
};
|
||||
|
||||
contextPrototype.beginPath = function() {
|
||||
// TODO: Branch current matrix so that save/restore has no effect
|
||||
// as per safari docs.
|
||||
|
||||
this.currentPath_ = [];
|
||||
};
|
||||
|
||||
contextPrototype.moveTo = function(aX, aY) {
|
||||
this.currentPath_.push({type: "moveTo", x: aX, y: aY});
|
||||
};
|
||||
|
||||
contextPrototype.lineTo = function(aX, aY) {
|
||||
this.currentPath_.push({type: "lineTo", x: aX, y: aY});
|
||||
};
|
||||
|
||||
contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
|
||||
aCP2x, aCP2y,
|
||||
aX, aY) {
|
||||
this.currentPath_.push({type: "bezierCurveTo",
|
||||
cp1x: aCP1x,
|
||||
cp1y: aCP1y,
|
||||
cp2x: aCP2x,
|
||||
cp2y: aCP2y,
|
||||
x: aX,
|
||||
y: aY});
|
||||
};
|
||||
|
||||
contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
|
||||
// VML's qb produces different output to Firefox's
|
||||
// FF's behaviour seems to have changed in 1.5.0.1, check this
|
||||
this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
|
||||
};
|
||||
|
||||
contextPrototype.arc = function(aX, aY, aRadius,
|
||||
aStartAngle, aEndAngle, aClockwise) {
|
||||
if (!aClockwise) {
|
||||
var t = aStartAngle;
|
||||
aStartAngle = aEndAngle;
|
||||
aEndAngle = t;
|
||||
}
|
||||
|
||||
aRadius *= 10;
|
||||
|
||||
var xStart = aX + (mc(aStartAngle) * aRadius) - 5;
|
||||
var yStart = aY + (ms(aStartAngle) * aRadius) - 5;
|
||||
|
||||
var xEnd = aX + (mc(aEndAngle) * aRadius) - 5;
|
||||
var yEnd = aY + (ms(aEndAngle) * aRadius) - 5;
|
||||
|
||||
this.currentPath_.push({type: "arc",
|
||||
x: aX,
|
||||
y: aY,
|
||||
radius: aRadius,
|
||||
xStart: xStart,
|
||||
yStart: yStart,
|
||||
xEnd: xEnd,
|
||||
yEnd: yEnd});
|
||||
|
||||
};
|
||||
|
||||
contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
|
||||
this.moveTo(aX, aY);
|
||||
this.lineTo(aX + aWidth, aY);
|
||||
this.lineTo(aX + aWidth, aY + aHeight);
|
||||
this.lineTo(aX, aY + aHeight);
|
||||
this.closePath();
|
||||
};
|
||||
|
||||
contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
|
||||
// Will destroy any existing path (same as FF behaviour)
|
||||
this.beginPath();
|
||||
this.moveTo(aX, aY);
|
||||
this.lineTo(aX + aWidth, aY);
|
||||
this.lineTo(aX + aWidth, aY + aHeight);
|
||||
this.lineTo(aX, aY + aHeight);
|
||||
this.closePath();
|
||||
this.stroke();
|
||||
};
|
||||
|
||||
contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
|
||||
// Will destroy any existing path (same as FF behaviour)
|
||||
this.beginPath();
|
||||
this.moveTo(aX, aY);
|
||||
this.lineTo(aX + aWidth, aY);
|
||||
this.lineTo(aX + aWidth, aY + aHeight);
|
||||
this.lineTo(aX, aY + aHeight);
|
||||
this.closePath();
|
||||
this.fill();
|
||||
};
|
||||
|
||||
contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
|
||||
var gradient = new CanvasGradient_("gradient");
|
||||
return gradient;
|
||||
};
|
||||
|
||||
contextPrototype.createRadialGradient = function(aX0, aY0,
|
||||
aR0, aX1,
|
||||
aY1, aR1) {
|
||||
var gradient = new CanvasGradient_("gradientradial");
|
||||
gradient.radius1_ = aR0;
|
||||
gradient.radius2_ = aR1;
|
||||
gradient.focus_.x = aX0;
|
||||
gradient.focus_.y = aY0;
|
||||
return gradient;
|
||||
};
|
||||
|
||||
contextPrototype.drawImage = function (image, var_args) {
|
||||
var dx, dy, dw, dh, sx, sy, sw, sh;
|
||||
var w = image.width;
|
||||
var h = image.height;
|
||||
|
||||
if (arguments.length == 3) {
|
||||
dx = arguments[1];
|
||||
dy = arguments[2];
|
||||
sx = sy = 0;
|
||||
sw = dw = w;
|
||||
sh = dh = h;
|
||||
} else if (arguments.length == 5) {
|
||||
dx = arguments[1];
|
||||
dy = arguments[2];
|
||||
dw = arguments[3];
|
||||
dh = arguments[4];
|
||||
sx = sy = 0;
|
||||
sw = w;
|
||||
sh = h;
|
||||
} else if (arguments.length == 9) {
|
||||
sx = arguments[1];
|
||||
sy = arguments[2];
|
||||
sw = arguments[3];
|
||||
sh = arguments[4];
|
||||
dx = arguments[5];
|
||||
dy = arguments[6];
|
||||
dw = arguments[7];
|
||||
dh = arguments[8];
|
||||
} else {
|
||||
throw "Invalid number of arguments";
|
||||
}
|
||||
|
||||
var d = this.getCoords_(dx, dy);
|
||||
|
||||
var w2 = (sw / 2);
|
||||
var h2 = (sh / 2);
|
||||
|
||||
var vmlStr = [];
|
||||
|
||||
// For some reason that I've now forgotten, using divs didn't work
|
||||
vmlStr.push(' <g_vml_:group',
|
||||
' coordsize="1000,1000"',
|
||||
' coordorigin="0, 0"' ,
|
||||
' style="width:100px;height:100px;position:absolute;');
|
||||
|
||||
// If filters are necessary (rotation exists), create them
|
||||
// filters are bog-slow, so only create them if abbsolutely necessary
|
||||
// The following check doesn't account for skews (which don't exist
|
||||
// in the canvas spec (yet) anyway.
|
||||
|
||||
if (this.m_[0][0] != 1 || this.m_[0][1]) {
|
||||
var filter = [];
|
||||
|
||||
// Note the 12/21 reversal
|
||||
filter.push("M11='", this.m_[0][0], "',",
|
||||
"M12='", this.m_[1][0], "',",
|
||||
"M21='", this.m_[0][1], "',",
|
||||
"M22='", this.m_[1][1], "',",
|
||||
"Dx='", d.x, "',",
|
||||
"Dy='", d.y, "'");
|
||||
|
||||
// Bounding box calculation (need to minimize displayed area so that
|
||||
// filters don't waste time on unused pixels.
|
||||
var max = d;
|
||||
var c2 = this.getCoords_(dx+dw, dy);
|
||||
var c3 = this.getCoords_(dx, dy+dh);
|
||||
var c4 = this.getCoords_(dx+dw, dy+dh);
|
||||
|
||||
max.x = Math.max(max.x, c2.x, c3.x, c4.x);
|
||||
max.y = Math.max(max.y, c2.y, c3.y, c4.y);
|
||||
|
||||
vmlStr.push(" padding:0 ", mr(max.x), "px ", mr(max.y),
|
||||
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
|
||||
filter.join(""), ", sizingmethod='clip');")
|
||||
} else {
|
||||
vmlStr.push(" top:", d.y, "px;left:", d.x, "px;")
|
||||
}
|
||||
|
||||
vmlStr.push(' ">' ,
|
||||
'<g_vml_:image src="', image.src, '"',
|
||||
' style="width:', dw, ';',
|
||||
' height:', dh, ';"',
|
||||
' cropleft="', sx / w, '"',
|
||||
' croptop="', sy / h, '"',
|
||||
' cropright="', (w - sx - sw) / w, '"',
|
||||
' cropbottom="', (h - sy - sh) / h, '"',
|
||||
' />',
|
||||
'</g_vml_:group>');
|
||||
|
||||
this.element_.insertAdjacentHTML("BeforeEnd",
|
||||
vmlStr.join(""));
|
||||
};
|
||||
|
||||
contextPrototype.stroke = function(aFill) {
|
||||
var lineStr = [];
|
||||
var lineOpen = false;
|
||||
var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
|
||||
var color = a[0];
|
||||
var opacity = a[1] * this.globalAlpha;
|
||||
|
||||
lineStr.push('<g_vml_:shape',
|
||||
' fillcolor="', color, '"',
|
||||
' filled="', Boolean(aFill), '"',
|
||||
' style="position:absolute;width:10;height:10;"',
|
||||
' coordorigin="0 0" coordsize="100 100"',
|
||||
' stroked="', !aFill, '"',
|
||||
' strokeweight="', this.lineWidth, '"',
|
||||
' strokecolor="', color, '"',
|
||||
' path="');
|
||||
|
||||
var newSeq = false;
|
||||
var min = {x: null, y: null};
|
||||
var max = {x: null, y: null};
|
||||
|
||||
for (var i = 0; i < this.currentPath_.length; i++) {
|
||||
var p = this.currentPath_[i];
|
||||
|
||||
if (p.type == "moveTo") {
|
||||
lineStr.push(" m ");
|
||||
var c = this.getCoords_(p.x, p.y);
|
||||
lineStr.push(mr(c.x), ",", mr(c.y));
|
||||
} else if (p.type == "lineTo") {
|
||||
lineStr.push(" l ");
|
||||
var c = this.getCoords_(p.x, p.y);
|
||||
lineStr.push(mr(c.x), ",", mr(c.y));
|
||||
} else if (p.type == "close") {
|
||||
lineStr.push(" x ");
|
||||
} else if (p.type == "bezierCurveTo") {
|
||||
lineStr.push(" c ");
|
||||
var c = this.getCoords_(p.x, p.y);
|
||||
var c1 = this.getCoords_(p.cp1x, p.cp1y);
|
||||
var c2 = this.getCoords_(p.cp2x, p.cp2y);
|
||||
lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
|
||||
mr(c2.x), ",", mr(c2.y), ",",
|
||||
mr(c.x), ",", mr(c.y));
|
||||
} else if (p.type == "arc") {
|
||||
lineStr.push(" ar ");
|
||||
var c = this.getCoords_(p.x, p.y);
|
||||
var cStart = this.getCoords_(p.xStart, p.yStart);
|
||||
var cEnd = this.getCoords_(p.xEnd, p.yEnd);
|
||||
|
||||
// TODO: FIX (matricies (scale+rotation) now buggered this up)
|
||||
// VML arc also doesn't seem able to do rotated non-circular
|
||||
// arcs without parent grouping.
|
||||
var absXScale = this.m_[0][0];
|
||||
var absYScale = this.m_[1][1];
|
||||
|
||||
lineStr.push(mr(c.x - absXScale * p.radius), ",",
|
||||
mr(c.y - absYScale * p.radius), " ",
|
||||
mr(c.x + absXScale * p.radius), ",",
|
||||
mr(c.y + absYScale * p.radius), " ",
|
||||
mr(cStart.x), ",", mr(cStart.y), " ",
|
||||
mr(cEnd.x), ",", mr(cEnd.y));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Following is broken for curves due to
|
||||
// move to proper paths.
|
||||
|
||||
// Figure out dimensions so we can do gradient fills
|
||||
// properly
|
||||
if(c) {
|
||||
if (min.x == null || c.x < min.x) {
|
||||
min.x = c.x;
|
||||
}
|
||||
if (max.x == null || c.x > max.x) {
|
||||
max.x = c.x;
|
||||
}
|
||||
if (min.y == null || c.y < min.y) {
|
||||
min.y = c.y;
|
||||
}
|
||||
if (max.y == null || c.y > max.y) {
|
||||
max.y = c.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
lineStr.push(' ">');
|
||||
|
||||
if (typeof this.fillStyle == "object") {
|
||||
var focus = {x: "50%", y: "50%"};
|
||||
var width = (max.x - min.x);
|
||||
var height = (max.y - min.y);
|
||||
var dimension = (width > height) ? width : height;
|
||||
|
||||
focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
|
||||
focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
|
||||
|
||||
var colors = [];
|
||||
|
||||
// inside radius (%)
|
||||
if (this.fillStyle.type_ == "gradientradial") {
|
||||
var inside = (this.fillStyle.radius1_ / dimension * 100);
|
||||
|
||||
// percentage that outside radius exceeds inside radius
|
||||
var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
|
||||
} else {
|
||||
var inside = 0;
|
||||
var expansion = 100;
|
||||
}
|
||||
|
||||
var insidecolor = {offset: null, color: null};
|
||||
var outsidecolor = {offset: null, color: null};
|
||||
|
||||
// We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
|
||||
// won't interpret it correctly
|
||||
this.fillStyle.colors_.sort(function (cs1, cs2) {
|
||||
return cs1.offset - cs2.offset;
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.fillStyle.colors_.length; i++) {
|
||||
var fs = this.fillStyle.colors_[i];
|
||||
|
||||
colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
|
||||
|
||||
if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
|
||||
insidecolor.offset = fs.offset;
|
||||
insidecolor.color = fs.color;
|
||||
}
|
||||
|
||||
if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
|
||||
outsidecolor.offset = fs.offset;
|
||||
outsidecolor.color = fs.color;
|
||||
}
|
||||
}
|
||||
colors.pop();
|
||||
|
||||
lineStr.push('<g_vml_:fill',
|
||||
' color="', outsidecolor.color, '"',
|
||||
' color2="', insidecolor.color, '"',
|
||||
' type="', this.fillStyle.type_, '"',
|
||||
' focusposition="', focus.x, ', ', focus.y, '"',
|
||||
' colors="', colors.join(""), '"',
|
||||
' opacity="', opacity, '" />');
|
||||
} else if (aFill) {
|
||||
lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
|
||||
} else {
|
||||
lineStr.push(
|
||||
'<g_vml_:stroke',
|
||||
' opacity="', opacity,'"',
|
||||
' joinstyle="', this.lineJoin, '"',
|
||||
' miterlimit="', this.miterLimit, '"',
|
||||
' endcap="', processLineCap(this.lineCap) ,'"',
|
||||
' weight="', this.lineWidth, 'px"',
|
||||
' color="', color,'" />'
|
||||
);
|
||||
}
|
||||
|
||||
lineStr.push("</g_vml_:shape>");
|
||||
|
||||
this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
|
||||
|
||||
this.currentPath_ = [];
|
||||
};
|
||||
|
||||
contextPrototype.fill = function() {
|
||||
this.stroke(true);
|
||||
}
|
||||
|
||||
contextPrototype.closePath = function() {
|
||||
this.currentPath_.push({type: "close"});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
contextPrototype.getCoords_ = function(aX, aY) {
|
||||
return {
|
||||
x: 10 * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - 5,
|
||||
y: 10 * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - 5
|
||||
}
|
||||
};
|
||||
|
||||
contextPrototype.save = function() {
|
||||
var o = {};
|
||||
copyState(this, o);
|
||||
this.aStack_.push(o);
|
||||
this.mStack_.push(this.m_);
|
||||
this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
|
||||
};
|
||||
|
||||
contextPrototype.restore = function() {
|
||||
copyState(this.aStack_.pop(), this);
|
||||
this.m_ = this.mStack_.pop();
|
||||
};
|
||||
|
||||
contextPrototype.translate = function(aX, aY) {
|
||||
var m1 = [
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[aX, aY, 1]
|
||||
];
|
||||
|
||||
this.m_ = matrixMultiply(m1, this.m_);
|
||||
};
|
||||
|
||||
contextPrototype.rotate = function(aRot) {
|
||||
var c = mc(aRot);
|
||||
var s = ms(aRot);
|
||||
|
||||
var m1 = [
|
||||
[c, s, 0],
|
||||
[-s, c, 0],
|
||||
[0, 0, 1]
|
||||
];
|
||||
|
||||
this.m_ = matrixMultiply(m1, this.m_);
|
||||
};
|
||||
|
||||
contextPrototype.scale = function(aX, aY) {
|
||||
var m1 = [
|
||||
[aX, 0, 0],
|
||||
[0, aY, 0],
|
||||
[0, 0, 1]
|
||||
];
|
||||
|
||||
this.m_ = matrixMultiply(m1, this.m_);
|
||||
};
|
||||
|
||||
/******** STUBS ********/
|
||||
contextPrototype.clip = function() {
|
||||
// TODO: Implement
|
||||
};
|
||||
|
||||
contextPrototype.arcTo = function() {
|
||||
// TODO: Implement
|
||||
};
|
||||
|
||||
contextPrototype.createPattern = function() {
|
||||
return new CanvasPattern_;
|
||||
};
|
||||
|
||||
// Gradient / Pattern Stubs
|
||||
function CanvasGradient_(aType) {
|
||||
this.type_ = aType;
|
||||
this.radius1_ = 0;
|
||||
this.radius2_ = 0;
|
||||
this.colors_ = [];
|
||||
this.focus_ = {x: 0, y: 0};
|
||||
}
|
||||
|
||||
CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
|
||||
aColor = processStyle(aColor);
|
||||
this.colors_.push({offset: 1-aOffset, color: aColor});
|
||||
};
|
||||
|
||||
function CanvasPattern_() {}
|
||||
|
||||
// set up externs
|
||||
G_vmlCanvasManager = G_vmlCanvasManager_;
|
||||
CanvasRenderingContext2D = CanvasRenderingContext2D_;
|
||||
CanvasGradient = CanvasGradient_;
|
||||
CanvasPattern = CanvasPattern_;
|
||||
|
||||
})();
|
||||
|
||||
} // if
|
|
@ -0,0 +1,594 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# graph.js #
|
||||
# Turns graph data into nice graphs #
|
||||
###############################################################################
|
||||
# $Id:: graph.js 308 2007-02-09 01:31:25Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var graphConfig = new Array();
|
||||
graphConfig["bar_chart_values"] = 5;
|
||||
graphConfig["pie_chart_radius"] = 175;
|
||||
graphConfig["pie_chart_extrusion"] = 5;
|
||||
graphConfig["pie_chart_colors"] = new Array(
|
||||
"#DCDCDC", "#CCCCCC", "#BCBCBC", "#ACACAC"
|
||||
);
|
||||
graphConfig["pie_chart_border_width"] = 1;
|
||||
graphConfig["pie_chart_border_color"] = "#ACACAC";
|
||||
graphConfig["pie_chart_random_rotation"] = false;
|
||||
graphConfig["ogive_values"] = 5;
|
||||
graphConfig["ogive_chart_width"] = 660;
|
||||
graphConfig["ogive_chart_height"] = 360;
|
||||
graphConfig["ogive_chart_color"] = "#DCDCDC";
|
||||
graphConfig["ogive_average_color"] = "#BCBCBC";
|
||||
graphConfig["decimal_point_is_comma"] = false;
|
||||
graphConfig["average_decimal_count"] = 2;
|
||||
|
||||
function checkForGraphs () {
|
||||
var dls = document.getElementsByTagName("dl");
|
||||
for (var i = dls.length - 1; i >= 0; i--) {
|
||||
var dl = dls[i];
|
||||
var func;
|
||||
if (hasClassName(dl, "bar-chart-data")) {
|
||||
func = createBarChart;
|
||||
}
|
||||
else if (hasClassName(dl, "pie-chart-data")) {
|
||||
func = createPieChart;
|
||||
}
|
||||
else if (hasClassName(dl, "ogive-data")) {
|
||||
func = createOgive;
|
||||
}
|
||||
if (func) {
|
||||
var chartData = extractChartData(dl);
|
||||
var samples = extractChartLabelCount(dl);
|
||||
var node = func(dl, chartData, samples);
|
||||
node.id = dl.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createBarChart (sourceNode, chartData, samples) {
|
||||
var div = document.createElement("div");
|
||||
div.className = "chart bar-chart";
|
||||
var graph = document.createElement("div");
|
||||
graph.className = "bar-chart-graph";
|
||||
div.appendChild(graph);
|
||||
var max = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
var value = chartData[i][1];
|
||||
if (value > max) {
|
||||
max = value;
|
||||
}
|
||||
}
|
||||
createChartPane(div, graph, chartData, samples,
|
||||
graphConfig["bar_chart_values"], 0, max);
|
||||
var barWidth = 100 / chartData.length;
|
||||
var total = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
var data = chartData[i];
|
||||
var text = data[0];
|
||||
var value = data[1];
|
||||
var column = document.createElement("div");
|
||||
column.className = "bar-chart-column";
|
||||
column.style.left = i * barWidth + "%";
|
||||
column.style.width = barWidth + "%";
|
||||
column.title = data[0] + " "
|
||||
+ String.fromCharCode(0x2192) + " " + data[1];
|
||||
if (value > 0) {
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "bar-chart-bar";
|
||||
bar.style.height = Math.round(100 * value / max) + "%";
|
||||
var innerBar = document.createElement("div");
|
||||
innerBar.className = "bar-chart-inner-bar";
|
||||
bar.appendChild(innerBar);
|
||||
column.appendChild(bar);
|
||||
}
|
||||
graph.appendChild(column);
|
||||
total += value;
|
||||
}
|
||||
var avg = total / chartData.length;
|
||||
var avgDiv = document.createElement("div");
|
||||
avgDiv.className = "bar-chart-average-container";
|
||||
avgDiv.style.top = (100 - (100 * avg / max)) + "%";
|
||||
var stdDevTotal = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
var temp = chartData[i][1] - avg;
|
||||
stdDevTotal += temp * temp;
|
||||
}
|
||||
var stdDev = roundToDecimals(
|
||||
Math.sqrt(stdDevTotal / chartData.length),
|
||||
graphConfig["average_decimal_count"]);
|
||||
var avgText = roundToDecimals(avg, graphConfig["average_decimal_count"]);
|
||||
if (graphConfig["decimal_point_is_comma"]) {
|
||||
avgText = ("" + avgText).replace(".", ",");
|
||||
stdDev = ("" + stdDev).replace(".", ",");
|
||||
}
|
||||
var avgSpan = document.createElement("span");
|
||||
avgSpan.appendChild(document.createTextNode(avgText));
|
||||
avgSpan.className = "bar-chart-average";
|
||||
var stdDevSpan = document.createElement("span");
|
||||
stdDevSpan.appendChild(
|
||||
document.createTextNode(String.fromCharCode(0x00B1) + " " + stdDev));
|
||||
stdDevSpan.className = "bar-chart-standard-deviation";
|
||||
var avgTextDiv = document.createElement("div");
|
||||
avgTextDiv.appendChild(avgSpan);
|
||||
avgTextDiv.appendChild(document.createTextNode(" "));
|
||||
avgTextDiv.appendChild(stdDevSpan);
|
||||
avgDiv.appendChild(avgTextDiv);
|
||||
graph.appendChild(avgDiv);
|
||||
sourceNode.parentNode.replaceChild(div, sourceNode);
|
||||
return div;
|
||||
}
|
||||
|
||||
function createPieChart (sourceNode, chartData) {
|
||||
var cnv = document.createElement("canvas");
|
||||
var rad = graphConfig["pie_chart_radius"];
|
||||
var ext = graphConfig["pie_chart_extrusion"];
|
||||
var side = (rad + ext) * 2;
|
||||
cnv.width = cnv.height = side;
|
||||
cnv.style.width = cnv.style.height = side + "px";
|
||||
var graph = document.createElement("div");
|
||||
graph.appendChild(cnv);
|
||||
cnv = ensureCanvas(cnv);
|
||||
if (!cnv) {
|
||||
return createBarChart(sourceNode, chartData);
|
||||
}
|
||||
var div = document.createElement("div");
|
||||
div.className = "chart pie-chart";
|
||||
sourceNode.parentNode.replaceChild(div, sourceNode);
|
||||
graph.className = "pie-chart-graph";
|
||||
var legend = document.createElement("dl");
|
||||
legend.className = "pie-chart-legend";
|
||||
div.appendChild(graph);
|
||||
div.appendChild(legend);
|
||||
drawPieChart(cnv, legend, chartData);
|
||||
return div;
|
||||
}
|
||||
|
||||
function drawPieChart (canvas, legend, data) {
|
||||
var ctx = canvas.getContext("2d");
|
||||
var total = 0;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
total += data[i][1];
|
||||
}
|
||||
var runningTotal = 0;
|
||||
var rad = graphConfig["pie_chart_radius"];
|
||||
var ext = graphConfig["pie_chart_extrusion"];
|
||||
var xCenter = rad + ext;
|
||||
var yCenter = rad + ext;
|
||||
var radius = rad;
|
||||
var colors = graphConfig["pie_chart_colors"];
|
||||
var stroke;
|
||||
if (graphConfig["pie_chart_border_width"]) {
|
||||
ctx.strokeStyle = graphConfig["pie_chart_border_color"];
|
||||
var w = graphConfig["pie_chart_border_width"];
|
||||
ctx.lineWidth = w;
|
||||
radius -= 2 * w;
|
||||
stroke = true;
|
||||
}
|
||||
else {
|
||||
stroke = false;
|
||||
}
|
||||
var initialAngle = (graphConfig["pie_chart_random_rotation"]
|
||||
? Math.round(2 * Math.PI * Math.random())
|
||||
: - Math.PI / 2);
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var name = data[i][0];
|
||||
var value = data[i][1];
|
||||
var color = colors[i % colors.length];
|
||||
if (value > 0) {
|
||||
var startAngle = runningTotal / total * 2 * Math.PI + initialAngle;
|
||||
runningTotal += value;
|
||||
var endAngle = runningTotal / total * 2 * Math.PI + initialAngle;
|
||||
var diff = (startAngle + endAngle) / 2;
|
||||
var x = xCenter + Math.cos(diff) * ext;
|
||||
var y = yCenter + Math.sin(diff) * ext;
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.arc(x, y, radius, startAngle, endAngle, false);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
if (stroke) {
|
||||
// Safari doesn't like it when we don't recreate the path
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.arc(x, y, radius, startAngle, endAngle, false);
|
||||
ctx.lineTo(x, y);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
var block = document.createElement("div");
|
||||
block.style.backgroundColor = color;
|
||||
var dt = document.createElement("dt");
|
||||
dt.appendChild(block);
|
||||
legend.appendChild(dt);
|
||||
var dd = document.createElement("dd");
|
||||
var l = document.createElement("span");
|
||||
l.className = "label";
|
||||
l.appendChild(document.createTextNode(name));
|
||||
dd.appendChild(l);
|
||||
var v = document.createElement("span");
|
||||
v.className = "value";
|
||||
v.appendChild(document.createTextNode(value));
|
||||
dd.appendChild(v);
|
||||
var p = document.createElement("span");
|
||||
p.className = "percentage";
|
||||
p.appendChild(document.createTextNode(
|
||||
(value == 0 ? 0 : Math.round(100 * value / total)) + "%"));
|
||||
dd.appendChild(p);
|
||||
legend.appendChild(dd);
|
||||
}
|
||||
}
|
||||
|
||||
function createOgive (sourceNode, chartData, samples) {
|
||||
var cnv = document.createElement("canvas");
|
||||
cnv.width = graphConfig["ogive_chart_width"];
|
||||
cnv.height = graphConfig["ogive_chart_height"];
|
||||
cnv.style.width = graphConfig["ogive_chart_width"] + "px";
|
||||
cnv.style.height = graphConfig["ogive_chart_height"] + "px";
|
||||
var graph = document.createElement("div");
|
||||
graph.appendChild(cnv);
|
||||
cnv = ensureCanvas(cnv);
|
||||
if (!cnv) {
|
||||
var total = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
var old = chartData[i][1];
|
||||
chartData[i][1] += total;
|
||||
total += old;
|
||||
}
|
||||
return createBarChart(sourceNode, chartData, samples);
|
||||
}
|
||||
var div = document.createElement("div");
|
||||
div.className = "chart ogive";
|
||||
div.appendChild(graph);
|
||||
sourceNode.parentNode.replaceChild(div, sourceNode);
|
||||
graph.className = "ogive-graph";
|
||||
var ignoreFirst = extractOgiveIgnoreCount(sourceNode);
|
||||
var totalIgnored = 0;
|
||||
if (ignoreFirst) {
|
||||
for (var i = 0; i < ignoreFirst; i++) {
|
||||
totalIgnored += chartData[i][1];
|
||||
}
|
||||
var newChartData = new Array();
|
||||
for (var i = ignoreFirst; i < chartData.length; i++) {
|
||||
newChartData[i - ignoreFirst] = chartData[i];
|
||||
}
|
||||
chartData = newChartData;
|
||||
}
|
||||
var chartCumulData = new Array();
|
||||
var total = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
total += chartData[i][1];
|
||||
chartCumulData[i] = total;
|
||||
}
|
||||
var width = graphConfig["ogive_chart_width"];
|
||||
var max = chartCumulData[chartCumulData.length - 1];
|
||||
var xy = new Array();
|
||||
for (var i = 0; i < chartCumulData.length; i++) {
|
||||
xy[i + 1] = chartCumulData[i];
|
||||
}
|
||||
var regrParam = powerRegression(xy);
|
||||
var a = regrParam[0];
|
||||
var b = regrParam[1];
|
||||
var drawAvg;
|
||||
if (!isNaN(a) && !isNaN(b)) {
|
||||
var chartAvgData = new Array();
|
||||
for (var x = 0; x < width; x++) {
|
||||
var i = x / width * chartData.length;
|
||||
chartAvgData[x] = a * Math.pow(i, b);
|
||||
if (chartAvgData[x] > max) {
|
||||
max = chartAvgData[x];
|
||||
}
|
||||
}
|
||||
drawAvg = true;
|
||||
}
|
||||
else {
|
||||
drawAvg = false;
|
||||
}
|
||||
createChartPane(div, graph, chartData, samples,
|
||||
graphConfig["ogive_values"], 0, max, totalIgnored);
|
||||
var scale = graphConfig["ogive_chart_height"] / max;
|
||||
drawOgive(cnv, chartCumulData, false, scale);
|
||||
if (drawAvg) {
|
||||
drawOgive(cnv, chartAvgData, true, scale);
|
||||
var equation = document.createElement("div");
|
||||
equation.className = "regression-equation";
|
||||
equation.appendChild(document.createTextNode("y = "));
|
||||
var aRounded = roundToDecimals(a, 2);
|
||||
var bRounded = roundToDecimals(b, 2);
|
||||
if (graphConfig["decimal_point_is_comma"]) {
|
||||
aRounded = ("" + aRounded).replace(".", ",");
|
||||
bRounded = ("" + bRounded).replace(".", ",");
|
||||
}
|
||||
if (aRounded != 1) {
|
||||
var aTxt = document.createElement("span");
|
||||
aTxt.appendChild(document.createTextNode(aRounded));
|
||||
equation.appendChild(aTxt);
|
||||
}
|
||||
equation.appendChild(document.createTextNode("x"));
|
||||
if (bRounded != 1) {
|
||||
var bTxt = document.createElement("sup");
|
||||
bTxt.appendChild(document.createTextNode(bRounded));
|
||||
equation.appendChild(bTxt);
|
||||
}
|
||||
equation.style.position = "absolute";
|
||||
var xPos = 3/4;
|
||||
var xPad = 15;
|
||||
var yBase = Math.round(chartAvgData[Math.round(xPos * chartAvgData.length)]
|
||||
/ max * graphConfig["ogive_chart_height"]);
|
||||
/*if (b < 1) {*/
|
||||
equation.style.left = xPad + Math.round(
|
||||
xPos * graphConfig["ogive_chart_width"]) + "px";
|
||||
equation.style.bottom = yBase + "px";
|
||||
/*}
|
||||
else {
|
||||
equation.style.right = xPad + Math.round(
|
||||
(1 - xPos) * graphConfig["ogive_chart_width"]) + "px";
|
||||
equation.style.top = graphConfig["ogive_chart_height"] - yBase + "px";
|
||||
}*/
|
||||
div.appendChild(equation);
|
||||
}
|
||||
return div;
|
||||
}
|
||||
|
||||
function drawOgive (canvas, chartData, avg, yScale) {
|
||||
var ctx = canvas.getContext("2d");
|
||||
var graphWidth = graphConfig["ogive_chart_width"];
|
||||
var graphHeight = graphConfig["ogive_chart_height"];
|
||||
if (avg) {
|
||||
ctx.strokeStyle = graphConfig["ogive_average_color"];
|
||||
ctx.lineWidth = 1;
|
||||
}
|
||||
else {
|
||||
ctx.fillStyle = graphConfig["ogive_chart_color"];
|
||||
}
|
||||
var xScale = graphWidth / chartData.length;
|
||||
var x0 = 0;
|
||||
var y0 = graphHeight;
|
||||
ctx.beginPath();
|
||||
var notFirst = false;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
if (chartData[i] == null) continue;
|
||||
var x = Math.round(x0 + (i + 1) * xScale);
|
||||
var y = Math.round(y0 - chartData[i] * yScale);
|
||||
if (notFirst) {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
else {
|
||||
ctx.moveTo(x, y);
|
||||
notFirst = true;
|
||||
}
|
||||
}
|
||||
if (avg) {
|
||||
ctx.stroke();
|
||||
}
|
||||
else {
|
||||
ctx.lineTo(graphWidth, graphHeight);
|
||||
ctx.lineTo(x0, y0);
|
||||
ctx.fill();
|
||||
}
|
||||
return yScale;
|
||||
}
|
||||
|
||||
function extractChartData (dl) {
|
||||
var name, label;
|
||||
var data = new Array();
|
||||
for (var i = 0; i < dl.childNodes.length; i++) {
|
||||
var child = dl.childNodes[i];
|
||||
var eln = child.nodeName.toLowerCase();
|
||||
switch (eln) {
|
||||
case "dt":
|
||||
name = child.firstChild.nodeValue;
|
||||
label = (child.title ? child.title : null);
|
||||
break;
|
||||
case "dd":
|
||||
var value = parseInt(child.firstChild.nodeValue);
|
||||
data.push(new Array(name, value, label));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function extractChartLabelCount (dl) {
|
||||
var result = extractChartParameter(dl, "label-count");
|
||||
if (result == null) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractOgiveIgnoreCount (dl) {
|
||||
var result = extractChartParameter(dl, "ignore-first");
|
||||
if (result == null) return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractChartParameter (dl, name) {
|
||||
name += "-";
|
||||
var classNames = dl.className.split(/\s+/);
|
||||
for (var i = 0; i < classNames.length; i++) {
|
||||
var className = classNames[i];
|
||||
if (className.indexOf(name) == 0) {
|
||||
return parseInt(className.substring(name.length));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createChartPane (div, graph, chartData, samples, values, min, max, delta) {
|
||||
if (!delta) delta = 0;
|
||||
div.appendChild(createChartValues(min + delta, max + delta, values, graph, delta));
|
||||
div.appendChild(createChartLabels(chartData, samples));
|
||||
}
|
||||
|
||||
function createChartValues (min, max, count, graph, delta) {
|
||||
var values = document.createElement("div");
|
||||
values.className = "chart-values";
|
||||
var top = createChartValue(max);
|
||||
top.style.top = "0";
|
||||
top.style.marginTop = "0";
|
||||
var bottom = createChartValue(min);
|
||||
bottom.style.bottom = "0";
|
||||
bottom.style.marginBottom = "0";
|
||||
values.appendChild(top);
|
||||
var step = (max - min) / (count - 1);
|
||||
for (var i = 1; i < count - 1; i++) {
|
||||
var val = Math.round(step * i) + delta;
|
||||
var perc = 100 / (count - 1) * i + "%";
|
||||
var v = createChartValue(val);
|
||||
v.style.bottom = perc;
|
||||
values.appendChild(v);
|
||||
var line = document.createElement("div");
|
||||
line.className = "chart-line";
|
||||
line.style.top = perc;
|
||||
graph.appendChild(line);
|
||||
}
|
||||
var line = document.createElement("div");
|
||||
line.className = "chart-line";
|
||||
line.style.top = "0";
|
||||
graph.appendChild(line);
|
||||
values.appendChild(bottom);
|
||||
return values;
|
||||
}
|
||||
|
||||
function createChartValue (value) {
|
||||
var v = document.createElement("div");
|
||||
v.className = "chart-value";
|
||||
v.appendChild(document.createTextNode(Math.round(value)));
|
||||
return v;
|
||||
}
|
||||
|
||||
function createChartLabels (chartData, samples) {
|
||||
var labels = document.createElement("div");
|
||||
labels.className = "chart-labels";
|
||||
var sample = (samples > 0 && chartData.length > samples);
|
||||
var barWidth = 100 / chartData.length;
|
||||
var sampleInterval, labelWidth;
|
||||
if (sample) {
|
||||
if (chartData.length < samples * 2) {
|
||||
samples = Math.ceil(chartData.length / 2);
|
||||
}
|
||||
sampleInterval = chartData.length / samples;
|
||||
labelWidth = 100 / samples;
|
||||
var firstLabel = createChartLabel(
|
||||
chartData[0], 0, labelWidth + "%",
|
||||
"left");
|
||||
labels.appendChild(firstLabel);
|
||||
var lastLabel = createChartLabel(
|
||||
chartData[chartData.length - 1],
|
||||
(100 - labelWidth) + "%", labelWidth + "%",
|
||||
"right");
|
||||
labels.appendChild(lastLabel);
|
||||
}
|
||||
else {
|
||||
labelWidth = barWidth;
|
||||
}
|
||||
var labelCount = 0;
|
||||
for (var i = 0; i < chartData.length; i++) {
|
||||
var data = chartData[i];
|
||||
if (!sample || Math.floor((labelCount + 0.5) * sampleInterval) == i) {
|
||||
if (sample && (++labelCount == 1 || labelCount == samples)) continue;
|
||||
var left = ((i + 0.5) * barWidth - labelWidth / 2) + "%";
|
||||
var label = createChartLabel(data, left, labelWidth + "%", "center");
|
||||
labels.appendChild(label);
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
function createChartLabel (data, position, width, align) {
|
||||
var label = document.createElement("div");
|
||||
label.className = "chart-label";
|
||||
label.style.left = position;
|
||||
label.style.width = width;
|
||||
var innerLabel = document.createElement("div");
|
||||
innerLabel.className = "chart-inner-label";
|
||||
var text = (data[2] != null ? data[2] : data[0]);
|
||||
innerLabel.appendChild(document.createTextNode(text));
|
||||
innerLabel.style.textAlign = align;
|
||||
label.appendChild(innerLabel);
|
||||
return label;
|
||||
}
|
||||
|
||||
function ensureCanvas (canvas) {
|
||||
if (!canvas.getContext && useExCanvas()) {
|
||||
canvas = G_vmlCanvasManager.initElement(canvas);
|
||||
}
|
||||
return (canvas.getContext ? canvas : null);
|
||||
}
|
||||
|
||||
function useExCanvas () {
|
||||
return (typeof G_vmlCanvasManager != "undefined");
|
||||
}
|
||||
|
||||
function hasClassName (element, className) {
|
||||
return element.className.match(new RegExp("\\b" + className + "\\b"));
|
||||
}
|
||||
|
||||
function powerRegression (data) {
|
||||
var n = data.length;
|
||||
var sumX = 0;
|
||||
var sumY = 0;
|
||||
var sumXX = 0;
|
||||
var sumXY = 0;
|
||||
for (i in data) {
|
||||
var x = Math.log(i);
|
||||
var y = Math.log(data[i]);
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
sumXX += x * x;
|
||||
sumXY += x * y;
|
||||
}
|
||||
var sxx = sumXX - (sumX * sumX) / n;
|
||||
var sxy = sumXY - (sumX * sumY) / n;
|
||||
var xbar = sumX / n;
|
||||
var ybar = sumY / n;
|
||||
var b = sxy / sxx;
|
||||
var a = Math.pow(Math.exp(1), ybar - b * xbar);
|
||||
var result = new Array();
|
||||
result[0] = a;
|
||||
result[1] = b;
|
||||
return result;
|
||||
}
|
||||
|
||||
function roundToDecimals (number, decimals) {
|
||||
var factor = Math.pow(10, decimals);
|
||||
return Math.round(number * factor) / factor;
|
||||
}
|
||||
|
||||
function addOnloadFunction (f) {
|
||||
if (window.onload != null) {
|
||||
var old = window.onload;
|
||||
window.onload = function (e) {
|
||||
old(e);
|
||||
f();
|
||||
};
|
||||
}
|
||||
else {
|
||||
window.onload = f;
|
||||
}
|
||||
}
|
||||
|
||||
addOnloadFunction(checkForGraphs);
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# live_rating.js #
|
||||
# Enables rating and reporting of quotes without leaving the page #
|
||||
###############################################################################
|
||||
# $Id:: live_rating.js 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var useBlinking = true;
|
||||
var confirmationTimeout = 3000;
|
||||
var errorTimeout = 10000;
|
||||
var connectionTimeout = 30000;
|
||||
var pollingInterval = 1000;
|
||||
var okText = (/MSIE/.test(navigator.userAgent) && !window.opera
|
||||
? "OK" : String.fromCharCode(0x2713));
|
||||
|
||||
var requests = new Array();
|
||||
|
||||
function QuoteActionRequest (url, id, isReport) {
|
||||
var t = getTimestamp();
|
||||
var req = this;
|
||||
this.id = id;
|
||||
if (requests[this.id]) requests[this.id].verbose = false;
|
||||
requests[this.id] = this;
|
||||
this.url = url + "&output=xml&ignore=" + t;
|
||||
this.isReport = isReport;
|
||||
this.verbose = true;
|
||||
this.resultField = document.getElementById('quote-live-vote-result-' + id);
|
||||
this.setResult(locale["processing"], true);
|
||||
this.ajax = getAjaxObject();
|
||||
this.ajax.onreadystatechange = function() { readyStateChanged(req); };
|
||||
this.startTime = t;
|
||||
this.interval = setInterval(
|
||||
function () { checkRequestTime(req); }, pollingInterval);
|
||||
this.ajax.open("POST", this.url, true);
|
||||
this.ajax.send("");
|
||||
}
|
||||
|
||||
QuoteActionRequest.prototype.setResult = function (text, blink, timeout) {
|
||||
if (!this.verbose) return;
|
||||
if (this.fadeTimeout) {
|
||||
clearTimeout(this.fadeTimeout);
|
||||
this.fadeTimeout = null;
|
||||
}
|
||||
setText(this.resultField, text);
|
||||
if (useBlinking)
|
||||
this.resultField.style.textDecoration = blink ? "blink" : "";
|
||||
var field = this.resultField;
|
||||
if (timeout)
|
||||
this.fadeTimeout = setTimeout(
|
||||
function () { setText(field, ""); }, timeout);
|
||||
};
|
||||
|
||||
function sendRating (anchor, id) {
|
||||
if (!ajaxSupported()) return true;
|
||||
new QuoteActionRequest(anchor.href, id, false);
|
||||
var other;
|
||||
if (anchor.id.indexOf("down") >= 0) {
|
||||
other = document.getElementById(anchor.id.replace("down", "up"));
|
||||
}
|
||||
else {
|
||||
other = document.getElementById(anchor.id.replace("up", "down"));
|
||||
}
|
||||
addClassName(anchor, "casted-vote");
|
||||
removeClassName(other, "casted-vote");
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendReport (anchor, id) {
|
||||
if (!ajaxSupported()) return true;
|
||||
new QuoteActionRequest(anchor.href, id, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
function readyStateChanged (req) {
|
||||
if (req.ajax.readyState != 4) return;
|
||||
if (req.ajax.status == 200) {
|
||||
if (req.isReport)
|
||||
reportRequestCompleted(req, processXML(req.ajax.responseXML));
|
||||
else
|
||||
ratingRequestCompleted(req, processXML(req.ajax.responseXML));
|
||||
}
|
||||
else
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
removeRequest(req);
|
||||
}
|
||||
|
||||
function ratingRequestCompleted (req, result) {
|
||||
if (result) {
|
||||
switch (result["status"]) {
|
||||
case "1":
|
||||
req.setResult("");
|
||||
setText(document.getElementById('quote-rating-' + req.id),
|
||||
result["rating"]);
|
||||
var vc = document.getElementById('quote-vote-count-' + req.id);
|
||||
if (vc) setText(vc, result["votes"]);
|
||||
req.setResult(okText, false, confirmationTimeout);
|
||||
break;
|
||||
case "2":
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["already_rated_text"]);
|
||||
break;
|
||||
case "3":
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["limit_exceeded_text"]);
|
||||
break;
|
||||
case "4":
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["quote_not_found_text"]);
|
||||
break;
|
||||
case "5":
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["session_required_text"]);
|
||||
break;
|
||||
default:
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
}
|
||||
|
||||
function reportRequestCompleted (req, result) {
|
||||
if (result) {
|
||||
switch (result["status"]) {
|
||||
case "1":
|
||||
req.setResult("");
|
||||
document.getElementById('quote-' + req.id).className
|
||||
+= " flagged";
|
||||
var rep = document.getElementById('quote-report-' + req.id);
|
||||
var el = document.createElement("span");
|
||||
var at = document.createAttribute("class");
|
||||
at.value = "quote-flagged";
|
||||
el.setAttributeNode(at);
|
||||
el.appendChild(document.createTextNode(
|
||||
"[" + locale["flagged"] + "]"));
|
||||
rep.parentNode.replaceChild(el, rep);
|
||||
req.setResult(okText, false, confirmationTimeout);
|
||||
break;
|
||||
case "5":
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["session_required_text"]);
|
||||
break;
|
||||
default:
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
}
|
||||
|
||||
function removeRequest (req) {
|
||||
requests[req.id] = null;
|
||||
clearInterval(req.interval);
|
||||
}
|
||||
|
||||
function checkRequestTime (req) {
|
||||
if (getTimestamp() - req.startTime >= connectionTimeout) {
|
||||
clearInterval(req.interval);
|
||||
req.setResult(locale["error"], false, errorTimeout);
|
||||
if (req.verbose) alert(locale["timeout_text"]);
|
||||
}
|
||||
}
|
||||
|
||||
function processXML (xml) {
|
||||
if (!xml || !xml.childNodes
|
||||
|| xml.childNodes[xml.childNodes.length - 1].nodeName != "result")
|
||||
return null;
|
||||
var a = new Array();
|
||||
var root = xml.childNodes[xml.childNodes.length - 1];
|
||||
for (var i = 0; i < root.childNodes.length; i++) {
|
||||
var node = root.childNodes[i];
|
||||
a[node.nodeName] = node.firstChild.nodeValue;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function getTimestamp () {
|
||||
return (new Date()).getTime();
|
||||
}
|
||||
|
||||
function setText (element, text) {
|
||||
if (element.firstChild)
|
||||
element.firstChild.nodeValue = text;
|
||||
else
|
||||
element.appendChild(document.createTextNode(text));
|
||||
}
|
||||
|
||||
function addClassName (element, className) {
|
||||
element.className += " " + className;
|
||||
}
|
||||
|
||||
function removeClassName (element, className) {
|
||||
var c = getClassNames(element);
|
||||
if (c.length == 0) return;
|
||||
var newCN = c[0];
|
||||
for (var i = 1; i < c.length; c++) {
|
||||
if (c[i] == className) continue;
|
||||
newCN += " " + c[i];
|
||||
}
|
||||
element.className = newCN;
|
||||
}
|
||||
|
||||
function getClassNames (element) {
|
||||
if (element.className == null || element.className.length == 0)
|
||||
return new Array();
|
||||
return element.className.split(/ +/);
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
<component lightWeight="true">
|
||||
<attach event="onpropertychange" onevent="checkPropertyChange()" />
|
||||
<attach event="ondetach" onevent="restore()" />
|
||||
<script>
|
||||
//<![CDATA[
|
||||
|
||||
var doc = element.document;
|
||||
|
||||
function init() {
|
||||
updateBorderBoxWidth();
|
||||
updateBorderBoxHeight();
|
||||
}
|
||||
|
||||
function restore() {
|
||||
element.runtimeStyle.width = "";
|
||||
element.runtimeStyle.height = "";
|
||||
}
|
||||
|
||||
/* border width getters */
|
||||
function getBorderWidth(sSide) {
|
||||
if (element.currentStyle["border" + sSide + "Style"] == "none")
|
||||
return 0;
|
||||
var n = parseInt(element.currentStyle["border" + sSide + "Width"]);
|
||||
return n || 0;
|
||||
}
|
||||
|
||||
function getBorderLeftWidth() { return getBorderWidth("Left"); }
|
||||
function getBorderRightWidth() { return getBorderWidth("Right"); }
|
||||
function getBorderTopWidth() { return getBorderWidth("Top"); }
|
||||
function getBorderBottomWidth() { return getBorderWidth("Bottom"); }
|
||||
/* end border width getters */
|
||||
|
||||
/* padding getters */
|
||||
function getPadding(sSide) {
|
||||
var n = parseInt(element.currentStyle["padding" + sSide]);
|
||||
return n || 0;
|
||||
}
|
||||
|
||||
function getPaddingLeft() { return getPadding("Left"); }
|
||||
function getPaddingRight() { return getPadding("Right"); }
|
||||
function getPaddingTop() { return getPadding("Top"); }
|
||||
function getPaddingBottom() { return getPadding("Bottom"); }
|
||||
/* end padding getters */
|
||||
|
||||
function getBoxSizing() {
|
||||
var s = element.style;
|
||||
var cs = element.currentStyle
|
||||
|
||||
if (typeof s.boxSizing != "undefined" && s.boxSizing != "")
|
||||
return s.boxSizing;
|
||||
if (typeof s["box-sizing"] != "undefined" && s["box-sizing"] != "")
|
||||
return s["box-sizing"];
|
||||
if (typeof cs.boxSizing != "undefined" && cs.boxSizing != "")
|
||||
return cs.boxSizing;
|
||||
if (typeof cs["box-sizing"] != "undefined" && cs["box-sizing"] != "")
|
||||
return cs["box-sizing"];
|
||||
return getDocumentBoxSizing();
|
||||
}
|
||||
|
||||
function getDocumentBoxSizing() {
|
||||
if (doc.compatMode == null || doc.compatMode == "BackCompat")
|
||||
return "border-box";
|
||||
return "content-box"
|
||||
}
|
||||
|
||||
/* width and height setters */
|
||||
function setBorderBoxWidth(n) {
|
||||
element.runtimeStyle.width = Math.max(0, n - getBorderLeftWidth() -
|
||||
getPaddingLeft() - getPaddingRight() - getBorderRightWidth()) + "px";
|
||||
}
|
||||
|
||||
function setBorderBoxHeight(n) {
|
||||
element.runtimeStyle.height = Math.max(0, n - getBorderTopWidth() -
|
||||
getPaddingTop() - getPaddingBottom() - getBorderBottomWidth()) + "px";
|
||||
}
|
||||
|
||||
function setContentBoxWidth(n) {
|
||||
element.runtimeStyle.width = Math.max(0, n + getBorderLeftWidth() +
|
||||
getPaddingLeft() + getPaddingRight() + getBorderRightWidth()) + "px";
|
||||
}
|
||||
|
||||
function setContentBoxHeight(n) {
|
||||
element.runtimeStyle.height = Math.max(0, n + getBorderTopWidth() +
|
||||
getPaddingTop() + getPaddingBottom() + getBorderBottomWidth()) + "px";
|
||||
}
|
||||
/* end width and height setters */
|
||||
|
||||
function updateBorderBoxWidth() {
|
||||
element.runtimeStyle.width = "";
|
||||
if (getDocumentBoxSizing() == getBoxSizing())
|
||||
return;
|
||||
var csw = element.currentStyle.width;
|
||||
if (csw != "auto" && csw.indexOf("px") != -1) {
|
||||
if (getBoxSizing() == "border-box")
|
||||
setBorderBoxWidth(parseInt(csw));
|
||||
else
|
||||
setContentBoxWidth(parseInt(csw));
|
||||
}
|
||||
}
|
||||
|
||||
function updateBorderBoxHeight() {
|
||||
element.runtimeStyle.height = "";
|
||||
if (getDocumentBoxSizing() == getBoxSizing())
|
||||
return;
|
||||
var csh = element.currentStyle.height;
|
||||
if (csh != "auto" && csh.indexOf("px") != -1) {
|
||||
if (getBoxSizing() == "border-box")
|
||||
setBorderBoxHeight(parseInt(csh));
|
||||
else
|
||||
setContentBoxHeight(parseInt(csh));
|
||||
}
|
||||
}
|
||||
|
||||
function checkPropertyChange() {
|
||||
var pn = event.propertyName;
|
||||
var undef;
|
||||
|
||||
if (pn == "style.boxSizing" && element.style.boxSizing == "") {
|
||||
element.style.removeAttribute("boxSizing");
|
||||
element.runtimeStyle.boxSizing = undef;
|
||||
}
|
||||
|
||||
|
||||
switch (pn) {
|
||||
case "style.width":
|
||||
case "style.borderLeftWidth":
|
||||
case "style.borderLeftStyle":
|
||||
case "style.borderRightWidth":
|
||||
case "style.borderRightStyle":
|
||||
case "style.paddingLeft":
|
||||
case "style.paddingRight":
|
||||
updateBorderBoxWidth();
|
||||
break;
|
||||
|
||||
case "style.height":
|
||||
case "style.borderTopWidth":
|
||||
case "style.borderTopStyle":
|
||||
case "style.borderBottomWidth":
|
||||
case "style.borderBottomStyle":
|
||||
case "style.paddingTop":
|
||||
case "style.paddingBottom":
|
||||
updateBorderBoxHeight();
|
||||
break;
|
||||
|
||||
case "className":
|
||||
case "style.boxSizing":
|
||||
updateBorderBoxWidth();
|
||||
updateBorderBoxHeight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
</component>
|
|
@ -0,0 +1,132 @@
|
|||
/*----------------------------------------------------------------------------\
|
||||
| Range Class |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created by Erik Arvidsson |
|
||||
| (http://webfx.eae.net/contact.html#erik) |
|
||||
| For WebFX (http://webfx.eae.net/) |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Used to model the data used when working with sliders, scrollbars and |
|
||||
| progress bars. Based on the ideas of the javax.swing.BoundedRangeModel |
|
||||
| interface defined by Sun for Java; http://java.sun.com/products/jfc/ |
|
||||
| swingdoc-api-1.0.3/com/sun/java/swing/BoundedRangeModel.html |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Copyright (c) 2002, 2005, 2006 Erik Arvidsson |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
||||
| use this file except in compliance with the License. You may obtain a copy |
|
||||
| of the License at http://www.apache.org/licenses/LICENSE-2.0 |
|
||||
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
||||
| Unless required by applicable law or agreed to in writing, software |
|
||||
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
||||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
||||
| License for the specific language governing permissions and limitations |
|
||||
| under the License. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| 2002-10-14 | Original version released |
|
||||
| 2005-10-27 | Use Math.round instead of Math.floor |
|
||||
| 2006-05-28 | Changed license to Apache Software License 2.0. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created 2002-10-14 | All changes are in the log above. | Updated 2006-05-28 |
|
||||
\----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
function Range() {
|
||||
this._value = 0;
|
||||
this._minimum = 0;
|
||||
this._maximum = 100;
|
||||
this._extent = 0;
|
||||
|
||||
this._isChanging = false;
|
||||
}
|
||||
|
||||
Range.prototype.setValue = function (value) {
|
||||
value = Math.round(parseFloat(value));
|
||||
if (isNaN(value)) return;
|
||||
if (this._value != value) {
|
||||
if (value + this._extent > this._maximum)
|
||||
this._value = this._maximum - this._extent;
|
||||
else if (value < this._minimum)
|
||||
this._value = this._minimum;
|
||||
else
|
||||
this._value = value;
|
||||
if (!this._isChanging && typeof this.onchange == "function")
|
||||
this.onchange();
|
||||
}
|
||||
};
|
||||
|
||||
Range.prototype.getValue = function () {
|
||||
return this._value;
|
||||
};
|
||||
|
||||
Range.prototype.setExtent = function (extent) {
|
||||
if (this._extent != extent) {
|
||||
if (extent < 0)
|
||||
this._extent = 0;
|
||||
else if (this._value + extent > this._maximum)
|
||||
this._extent = this._maximum - this._value;
|
||||
else
|
||||
this._extent = extent;
|
||||
if (!this._isChanging && typeof this.onchange == "function")
|
||||
this.onchange();
|
||||
}
|
||||
};
|
||||
|
||||
Range.prototype.getExtent = function () {
|
||||
return this._extent;
|
||||
};
|
||||
|
||||
Range.prototype.setMinimum = function (minimum) {
|
||||
if (this._minimum != minimum) {
|
||||
var oldIsChanging = this._isChanging;
|
||||
this._isChanging = true;
|
||||
|
||||
this._minimum = minimum;
|
||||
|
||||
if (minimum > this._value)
|
||||
this.setValue(minimum);
|
||||
if (minimum > this._maximum) {
|
||||
this._extent = 0;
|
||||
this.setMaximum(minimum);
|
||||
this.setValue(minimum)
|
||||
}
|
||||
if (minimum + this._extent > this._maximum)
|
||||
this._extent = this._maximum - this._minimum;
|
||||
|
||||
this._isChanging = oldIsChanging;
|
||||
if (!this._isChanging && typeof this.onchange == "function")
|
||||
this.onchange();
|
||||
}
|
||||
};
|
||||
|
||||
Range.prototype.getMinimum = function () {
|
||||
return this._minimum;
|
||||
};
|
||||
|
||||
Range.prototype.setMaximum = function (maximum) {
|
||||
if (this._maximum != maximum) {
|
||||
var oldIsChanging = this._isChanging;
|
||||
this._isChanging = true;
|
||||
|
||||
this._maximum = maximum;
|
||||
|
||||
if (maximum < this._value)
|
||||
this.setValue(maximum - this._extent);
|
||||
if (maximum < this._minimum) {
|
||||
this._extent = 0;
|
||||
this.setMinimum(maximum);
|
||||
this.setValue(this._maximum);
|
||||
}
|
||||
if (maximum < this._minimum + this._extent)
|
||||
this._extent = this._maximum - this._minimum;
|
||||
if (maximum < this._value + this._extent)
|
||||
this._extent = this._maximum - this._value;
|
||||
|
||||
this._isChanging = oldIsChanging;
|
||||
if (!this._isChanging && typeof this.onchange == "function")
|
||||
this.onchange();
|
||||
}
|
||||
};
|
||||
|
||||
Range.prototype.getMaximum = function () {
|
||||
return this._maximum;
|
||||
};
|
|
@ -0,0 +1,489 @@
|
|||
/*----------------------------------------------------------------------------\
|
||||
| Slider 1.02 |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created by Erik Arvidsson |
|
||||
| (http://webfx.eae.net/contact.html#erik) |
|
||||
| For WebFX (http://webfx.eae.net/) |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| A slider control that degrades to an input control for non supported |
|
||||
| browsers. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Copyright (c) 2002, 2003, 2006 Erik Arvidsson |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
||||
| use this file except in compliance with the License. You may obtain a copy |
|
||||
| of the License at http://www.apache.org/licenses/LICENSE-2.0 |
|
||||
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
||||
| Unless required by applicable law or agreed to in writing, software |
|
||||
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
||||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
||||
| License for the specific language governing permissions and limitations |
|
||||
| under the License. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Dependencies: timer.js - an OO abstraction of timers |
|
||||
| range.js - provides the data model for the slider |
|
||||
| winclassic.css or any other css file describing the look |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| 2002-10-14 | Original version released |
|
||||
| 2003-03-27 | Added a test in the constructor for missing oElement arg |
|
||||
| 2003-11-27 | Only use mousewheel when focused |
|
||||
| 2006-05-28 | Changed license to Apache Software License 2.0. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created 2002-10-14 | All changes are in the log above. | Updated 2006-05-28 |
|
||||
\----------------------------------------------------------------------------*/
|
||||
|
||||
Slider.isSupported = typeof document.createElement != "undefined" &&
|
||||
typeof document.documentElement != "undefined" &&
|
||||
typeof document.documentElement.offsetWidth == "number";
|
||||
|
||||
|
||||
function Slider(oElement, oInput, sOrientation) {
|
||||
if (!oElement) return;
|
||||
this._orientation = sOrientation || "horizontal";
|
||||
this._range = new Range();
|
||||
this._range.setExtent(0);
|
||||
this._blockIncrement = 10;
|
||||
this._unitIncrement = 1;
|
||||
this._timer = new Timer(100);
|
||||
|
||||
|
||||
if (Slider.isSupported && oElement) {
|
||||
|
||||
this.document = oElement.ownerDocument || oElement.document;
|
||||
|
||||
this.element = oElement;
|
||||
this.element.slider = this;
|
||||
this.element.unselectable = "on";
|
||||
|
||||
// add class name tag to class name
|
||||
this.element.className = this._orientation + " " + this.classNameTag + " " + this.element.className;
|
||||
|
||||
// create line
|
||||
this.line = this.document.createElement("DIV");
|
||||
this.line.className = "line";
|
||||
this.line.unselectable = "on";
|
||||
this.line.appendChild(this.document.createElement("DIV"));
|
||||
this.element.appendChild(this.line);
|
||||
|
||||
// create handle
|
||||
this.handle = this.document.createElement("DIV");
|
||||
this.handle.className = "handle";
|
||||
this.handle.unselectable = "on";
|
||||
this.handle.appendChild(this.document.createElement("DIV"));
|
||||
this.handle.firstChild.appendChild(
|
||||
this.document.createTextNode(String.fromCharCode(160)));
|
||||
this.element.appendChild(this.handle);
|
||||
}
|
||||
|
||||
this.input = oInput;
|
||||
|
||||
// events
|
||||
var oThis = this;
|
||||
this._range.onchange = function () {
|
||||
oThis.recalculate();
|
||||
if (typeof oThis.onchange == "function")
|
||||
oThis.onchange();
|
||||
};
|
||||
|
||||
if (Slider.isSupported && oElement) {
|
||||
this.element.onfocus = Slider.eventHandlers.onfocus;
|
||||
this.element.onblur = Slider.eventHandlers.onblur;
|
||||
this.element.onmousedown = Slider.eventHandlers.onmousedown;
|
||||
this.element.onmouseover = Slider.eventHandlers.onmouseover;
|
||||
this.element.onmouseout = Slider.eventHandlers.onmouseout;
|
||||
this.element.onkeydown = Slider.eventHandlers.onkeydown;
|
||||
this.element.onkeypress = Slider.eventHandlers.onkeypress;
|
||||
this.element.onmousewheel = Slider.eventHandlers.onmousewheel;
|
||||
this.handle.onselectstart =
|
||||
this.element.onselectstart = function () { return false; };
|
||||
|
||||
this._timer.ontimer = function () {
|
||||
oThis.ontimer();
|
||||
};
|
||||
|
||||
// extra recalculate for ie
|
||||
window.setTimeout(function() {
|
||||
oThis.recalculate();
|
||||
}, 1);
|
||||
}
|
||||
else {
|
||||
this.input.onchange = function (e) {
|
||||
oThis.setValue(oThis.input.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Slider.eventHandlers = {
|
||||
|
||||
// helpers to make events a bit easier
|
||||
getEvent: function (e, el) {
|
||||
if (!e) {
|
||||
if (el)
|
||||
e = el.document.parentWindow.event;
|
||||
else
|
||||
e = window.event;
|
||||
}
|
||||
if (!e.srcElement) {
|
||||
var el = e.target;
|
||||
while (el != null && el.nodeType != 1)
|
||||
el = el.parentNode;
|
||||
e.srcElement = el;
|
||||
}
|
||||
if (typeof e.offsetX == "undefined") {
|
||||
e.offsetX = e.layerX;
|
||||
e.offsetY = e.layerY;
|
||||
}
|
||||
|
||||
return e;
|
||||
},
|
||||
|
||||
getDocument: function (e) {
|
||||
if (e.target)
|
||||
return e.target.ownerDocument;
|
||||
return e.srcElement.document;
|
||||
},
|
||||
|
||||
getSlider: function (e) {
|
||||
var el = e.target || e.srcElement;
|
||||
while (el != null && el.slider == null) {
|
||||
el = el.parentNode;
|
||||
}
|
||||
if (el)
|
||||
return el.slider;
|
||||
return null;
|
||||
},
|
||||
|
||||
getLine: function (e) {
|
||||
var el = e.target || e.srcElement;
|
||||
while (el != null && el.className != "line") {
|
||||
el = el.parentNode;
|
||||
}
|
||||
return el;
|
||||
},
|
||||
|
||||
getHandle: function (e) {
|
||||
var el = e.target || e.srcElement;
|
||||
var re = /handle/;
|
||||
while (el != null && !re.test(el.className)) {
|
||||
el = el.parentNode;
|
||||
}
|
||||
return el;
|
||||
},
|
||||
// end helpers
|
||||
|
||||
onfocus: function (e) {
|
||||
var s = this.slider;
|
||||
s._focused = true;
|
||||
s.handle.className = "handle hover";
|
||||
},
|
||||
|
||||
onblur: function (e) {
|
||||
var s = this.slider
|
||||
s._focused = false;
|
||||
s.handle.className = "handle";
|
||||
},
|
||||
|
||||
onmouseover: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var s = this.slider;
|
||||
if (e.srcElement == s.handle)
|
||||
s.handle.className = "handle hover";
|
||||
},
|
||||
|
||||
onmouseout: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var s = this.slider;
|
||||
if (e.srcElement == s.handle && !s._focused)
|
||||
s.handle.className = "handle";
|
||||
},
|
||||
|
||||
onmousedown: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var s = this.slider;
|
||||
if (s.element.focus)
|
||||
s.element.focus();
|
||||
|
||||
Slider._currentInstance = s;
|
||||
var doc = s.document;
|
||||
|
||||
if (doc.addEventListener) {
|
||||
doc.addEventListener("mousemove", Slider.eventHandlers.onmousemove, true);
|
||||
doc.addEventListener("mouseup", Slider.eventHandlers.onmouseup, true);
|
||||
}
|
||||
else if (doc.attachEvent) {
|
||||
doc.attachEvent("onmousemove", Slider.eventHandlers.onmousemove);
|
||||
doc.attachEvent("onmouseup", Slider.eventHandlers.onmouseup);
|
||||
doc.attachEvent("onlosecapture", Slider.eventHandlers.onmouseup);
|
||||
s.element.setCapture();
|
||||
}
|
||||
|
||||
if (Slider.eventHandlers.getHandle(e)) { // start drag
|
||||
Slider._sliderDragData = {
|
||||
screenX: e.screenX,
|
||||
screenY: e.screenY,
|
||||
dx: e.screenX - s.handle.offsetLeft,
|
||||
dy: e.screenY - s.handle.offsetTop,
|
||||
startValue: s.getValue(),
|
||||
slider: s
|
||||
};
|
||||
}
|
||||
else {
|
||||
var lineEl = Slider.eventHandlers.getLine(e);
|
||||
s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0);
|
||||
s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0);
|
||||
s._increasing = null;
|
||||
s.ontimer();
|
||||
}
|
||||
},
|
||||
|
||||
onmousemove: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
|
||||
if (Slider._sliderDragData) { // drag
|
||||
var s = Slider._sliderDragData.slider;
|
||||
|
||||
var boundSize = s.getMaximum() - s.getMinimum();
|
||||
var size, pos, reset;
|
||||
|
||||
if (s._orientation == "horizontal") {
|
||||
size = s.element.offsetWidth - s.handle.offsetWidth;
|
||||
pos = e.screenX - Slider._sliderDragData.dx;
|
||||
reset = Math.abs(e.screenY - Slider._sliderDragData.screenY) > 100;
|
||||
}
|
||||
else {
|
||||
size = s.element.offsetHeight - s.handle.offsetHeight;
|
||||
pos = s.element.offsetHeight - s.handle.offsetHeight -
|
||||
(e.screenY - Slider._sliderDragData.dy);
|
||||
reset = Math.abs(e.screenX - Slider._sliderDragData.screenX) > 100;
|
||||
}
|
||||
s.setValue(reset ? Slider._sliderDragData.startValue :
|
||||
s.getMinimum() + boundSize * pos / size);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var s = Slider._currentInstance;
|
||||
if (s != null) {
|
||||
var lineEl = Slider.eventHandlers.getLine(e);
|
||||
s._mouseX = e.offsetX + (lineEl ? s.line.offsetLeft : 0);
|
||||
s._mouseY = e.offsetY + (lineEl ? s.line.offsetTop : 0);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onmouseup: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var s = Slider._currentInstance;
|
||||
var doc = s.document;
|
||||
if (doc.removeEventListener) {
|
||||
doc.removeEventListener("mousemove", Slider.eventHandlers.onmousemove, true);
|
||||
doc.removeEventListener("mouseup", Slider.eventHandlers.onmouseup, true);
|
||||
}
|
||||
else if (doc.detachEvent) {
|
||||
doc.detachEvent("onmousemove", Slider.eventHandlers.onmousemove);
|
||||
doc.detachEvent("onmouseup", Slider.eventHandlers.onmouseup);
|
||||
doc.detachEvent("onlosecapture", Slider.eventHandlers.onmouseup);
|
||||
s.element.releaseCapture();
|
||||
}
|
||||
|
||||
if (Slider._sliderDragData) { // end drag
|
||||
Slider._sliderDragData = null;
|
||||
}
|
||||
else {
|
||||
s._timer.stop();
|
||||
s._increasing = null;
|
||||
}
|
||||
Slider._currentInstance = null;
|
||||
},
|
||||
|
||||
onkeydown: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
//var s = Slider.eventHandlers.getSlider(e);
|
||||
var s = this.slider;
|
||||
var kc = e.keyCode;
|
||||
switch (kc) {
|
||||
case 33: // page up
|
||||
s.setValue(s.getValue() + s.getBlockIncrement());
|
||||
break;
|
||||
case 34: // page down
|
||||
s.setValue(s.getValue() - s.getBlockIncrement());
|
||||
break;
|
||||
case 35: // end
|
||||
s.setValue(s.getOrientation() == "horizontal" ?
|
||||
s.getMaximum() :
|
||||
s.getMinimum());
|
||||
break;
|
||||
case 36: // home
|
||||
s.setValue(s.getOrientation() == "horizontal" ?
|
||||
s.getMinimum() :
|
||||
s.getMaximum());
|
||||
break;
|
||||
case 38: // up
|
||||
case 39: // right
|
||||
s.setValue(s.getValue() + s.getUnitIncrement());
|
||||
break;
|
||||
|
||||
case 37: // left
|
||||
case 40: // down
|
||||
s.setValue(s.getValue() - s.getUnitIncrement());
|
||||
break;
|
||||
}
|
||||
|
||||
if (kc >= 33 && kc <= 40) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
onkeypress: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var kc = e.keyCode;
|
||||
if (kc >= 33 && kc <= 40) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
onmousewheel: function (e) {
|
||||
e = Slider.eventHandlers.getEvent(e, this);
|
||||
var s = this.slider;
|
||||
if (s._focused) {
|
||||
s.setValue(s.getValue() + e.wheelDelta / 120 * s.getUnitIncrement());
|
||||
// windows inverts this on horizontal sliders. That does not
|
||||
// make sense to me
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
Slider.prototype.classNameTag = "dynamic-slider-control",
|
||||
|
||||
Slider.prototype.setValue = function (v) {
|
||||
this._range.setValue(v);
|
||||
this.input.value = this.getValue();
|
||||
};
|
||||
|
||||
Slider.prototype.getValue = function () {
|
||||
return this._range.getValue();
|
||||
};
|
||||
|
||||
Slider.prototype.setMinimum = function (v) {
|
||||
this._range.setMinimum(v);
|
||||
this.input.value = this.getValue();
|
||||
};
|
||||
|
||||
Slider.prototype.getMinimum = function () {
|
||||
return this._range.getMinimum();
|
||||
};
|
||||
|
||||
Slider.prototype.setMaximum = function (v) {
|
||||
this._range.setMaximum(v);
|
||||
this.input.value = this.getValue();
|
||||
};
|
||||
|
||||
Slider.prototype.getMaximum = function () {
|
||||
return this._range.getMaximum();
|
||||
};
|
||||
|
||||
Slider.prototype.setUnitIncrement = function (v) {
|
||||
this._unitIncrement = v;
|
||||
};
|
||||
|
||||
Slider.prototype.getUnitIncrement = function () {
|
||||
return this._unitIncrement;
|
||||
};
|
||||
|
||||
Slider.prototype.setBlockIncrement = function (v) {
|
||||
this._blockIncrement = v;
|
||||
};
|
||||
|
||||
Slider.prototype.getBlockIncrement = function () {
|
||||
return this._blockIncrement;
|
||||
};
|
||||
|
||||
Slider.prototype.getOrientation = function () {
|
||||
return this._orientation;
|
||||
};
|
||||
|
||||
Slider.prototype.setOrientation = function (sOrientation) {
|
||||
if (sOrientation != this._orientation) {
|
||||
if (Slider.isSupported && this.element) {
|
||||
// add class name tag to class name
|
||||
this.element.className = this.element.className.replace(this._orientation,
|
||||
sOrientation);
|
||||
}
|
||||
this._orientation = sOrientation;
|
||||
this.recalculate();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
Slider.prototype.recalculate = function() {
|
||||
if (!Slider.isSupported || !this.element) return;
|
||||
|
||||
var w = this.element.offsetWidth;
|
||||
var h = this.element.offsetHeight;
|
||||
var hw = this.handle.offsetWidth;
|
||||
var hh = this.handle.offsetHeight;
|
||||
var lw = this.line.offsetWidth;
|
||||
var lh = this.line.offsetHeight;
|
||||
|
||||
// this assumes a border-box layout
|
||||
|
||||
if (this._orientation == "horizontal") {
|
||||
this.handle.style.left = (w - hw) * (this.getValue() - this.getMinimum()) /
|
||||
(this.getMaximum() - this.getMinimum()) + "px";
|
||||
this.handle.style.top = (h - hh) / 2 + "px";
|
||||
|
||||
this.line.style.top = (h - lh) / 2 + "px";
|
||||
this.line.style.left = hw / 2 + "px";
|
||||
//this.line.style.right = hw / 2 + "px";
|
||||
this.line.style.width = Math.max(0, w - hw - 2)+ "px";
|
||||
this.line.firstChild.style.width = Math.max(0, w - hw - 4)+ "px";
|
||||
}
|
||||
else {
|
||||
this.handle.style.left = (w - hw) / 2 + "px";
|
||||
this.handle.style.top = h - hh - (h - hh) * (this.getValue() - this.getMinimum()) /
|
||||
(this.getMaximum() - this.getMinimum()) + "px";
|
||||
|
||||
this.line.style.left = (w - lw) / 2 + "px";
|
||||
this.line.style.top = hh / 2 + "px";
|
||||
this.line.style.height = Math.max(0, h - hh - 2) + "px"; //hard coded border width
|
||||
//this.line.style.bottom = hh / 2 + "px";
|
||||
this.line.firstChild.style.height = Math.max(0, h - hh - 4) + "px"; //hard coded border width
|
||||
}
|
||||
};
|
||||
|
||||
Slider.prototype.ontimer = function () {
|
||||
var hw = this.handle.offsetWidth;
|
||||
var hh = this.handle.offsetHeight;
|
||||
var hl = this.handle.offsetLeft;
|
||||
var ht = this.handle.offsetTop;
|
||||
|
||||
if (this._orientation == "horizontal") {
|
||||
if (this._mouseX > hl + hw &&
|
||||
(this._increasing == null || this._increasing)) {
|
||||
this.setValue(this.getValue() + this.getBlockIncrement());
|
||||
this._increasing = true;
|
||||
}
|
||||
else if (this._mouseX < hl &&
|
||||
(this._increasing == null || !this._increasing)) {
|
||||
this.setValue(this.getValue() - this.getBlockIncrement());
|
||||
this._increasing = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._mouseY > ht + hh &&
|
||||
(this._increasing == null || !this._increasing)) {
|
||||
this.setValue(this.getValue() - this.getBlockIncrement());
|
||||
this._increasing = false;
|
||||
}
|
||||
else if (this._mouseY < ht &&
|
||||
(this._increasing == null || this._increasing)) {
|
||||
this.setValue(this.getValue() + this.getBlockIncrement());
|
||||
this._increasing = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._timer.start();
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/*----------------------------------------------------------------------------\
|
||||
| Timer Class |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created by Erik Arvidsson |
|
||||
| (http://webfx.eae.net/contact.html#erik) |
|
||||
| For WebFX (http://webfx.eae.net/) |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Object Oriented Encapsulation of setTimeout fires ontimer when the timer |
|
||||
| is triggered. Does not work in IE 5.00 |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Copyright (c) 2002, 2006 Erik Arvidsson |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
||||
| use this file except in compliance with the License. You may obtain a copy |
|
||||
| of the License at http://www.apache.org/licenses/LICENSE-2.0 |
|
||||
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
|
||||
| Unless required by applicable law or agreed to in writing, software |
|
||||
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
||||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
||||
| License for the specific language governing permissions and limitations |
|
||||
| under the License. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| 2002-10-14 | Original version released |
|
||||
| 2006-05-28 | Changed license to Apache Software License 2.0. |
|
||||
|-----------------------------------------------------------------------------|
|
||||
| Created 2002-10-14 | All changes are in the log above. | Updated 2006-05-28 |
|
||||
\----------------------------------------------------------------------------*/
|
||||
|
||||
function Timer(nPauseTime) {
|
||||
this._pauseTime = typeof nPauseTime == "undefined" ? 1000 : nPauseTime;
|
||||
this._timer = null;
|
||||
this._isStarted = false;
|
||||
}
|
||||
|
||||
Timer.prototype.start = function () {
|
||||
if (this.isStarted())
|
||||
this.stop();
|
||||
var oThis = this;
|
||||
this._timer = window.setTimeout(function () {
|
||||
if (typeof oThis.ontimer == "function")
|
||||
oThis.ontimer();
|
||||
}, this._pauseTime);
|
||||
this._isStarted = false;
|
||||
};
|
||||
|
||||
Timer.prototype.stop = function () {
|
||||
if (this._timer != null)
|
||||
window.clearTimeout(this._timer);
|
||||
this._isStarted = false;
|
||||
};
|
||||
|
||||
Timer.prototype.isStarted = function () {
|
||||
return this._isStarted;
|
||||
};
|
||||
|
||||
Timer.prototype.getPauseTime = function () {
|
||||
return this._pauseTime;
|
||||
};
|
||||
|
||||
Timer.prototype.setPauseTime = function (nPauseTime) {
|
||||
this._pauseTime = nPauseTime;
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
/*
###############################################################################
# Chirpy!, 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 #
###############################################################################
###############################################################################
# style_switcher.js #
# Makes the user's alternate stylesheet choice persistent using a cookie #
# Largely based on http://www.alistapart.com/articles/alternate/ #
###############################################################################
# $Id:: style_switcher.js 291 2007-02-05 21:24:46Z ceetee $ #
###############################################################################
*/
function setActiveStyleSheet (title) {
var a;
var l = document.getElementsByTagName("link");
var found = false;
for (var i = 0; a = l[i]; i++) {
var t = a.getAttribute("title");
if (a.getAttribute("rel").indexOf("stylesheet") >= 0 && t) {
var d = (t != title);
a.disabled = d;
if (!d) found = true;
}
}
return found;
}
function getActiveStyleSheet () {
var a;
var l = document.getElementsByTagName("link");
for (var i = 0; a = l[i]; i++) {
var t = a.getAttribute("title");
if (a.getAttribute("rel").indexOf("stylesheet") >= 0 && t && !a.disabled)
return t;
}
return null;
}
function getPreferredStyleSheet () {
var a;
var l = document.getElementsByTagName("link");
for (var i = 0; a = l[i]; i++) {
var t = a.getAttribute("title");
if (t && a.getAttribute("rel") == "stylesheet")
return t;
}
return null;
}
function readCookie (name) {
var nameEq = name + "=";
var ca = document.cookie.split(/; */);
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
if (c.indexOf(nameEq) == 0)
return c.substring(nameEq.length, c.length);
}
return null;
}
function createCookie (name, value) {
var date = new Date();
date.setYear(1900 + date.getYear() + 1);
document.cookie = name + "=" + value + "; expires=" + date.toGMTString()
+ "; path=" + cookiePath + "; domain=" + cookieDomain;
}
function addOnunloadFunction (f) {
if (window.onunload != null) {
var old = window.onunload;
window.onunload = function (e) {
old(e);
f();
};
}
else {
window.onunload = f;
}
}
addOnunloadFunction(function () {
var style = getActiveStyleSheet();
if (style) createCookie("style", style);
});
setActiveStyleSheet(readCookie("style"))
|| setActiveStyleSheet(getPreferredStyleSheet());
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# tabbed_pane.js #
|
||||
# Emulates a tabbed pane for the administrative interface #
|
||||
###############################################################################
|
||||
# $Id:: tabbed_pane.js 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var tabs, activeTab;
|
||||
|
||||
function initializeTabbedPane (name, initialTab) {
|
||||
tabs = document.getElementById(name + '-navigation').childNodes;
|
||||
contents = document.getElementById(name + '-contents').childNodes;
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
initializeTab(tabs[i].firstChild);
|
||||
}
|
||||
setActiveTab(initialTab ? initialTab : tabs[0].firstChild);
|
||||
}
|
||||
|
||||
function initializeTab (tab) {
|
||||
tab.onclick = function() {
|
||||
displayTab(tab);
|
||||
return false;
|
||||
};
|
||||
tab.onmousedown = tab.onselectstart = function () {
|
||||
return false;
|
||||
};
|
||||
tab.removeAttribute('href');
|
||||
}
|
||||
|
||||
function displayTab (tab) {
|
||||
if (tab == activeTab) return;
|
||||
setActiveTab(tab);
|
||||
}
|
||||
|
||||
function setActiveTab (tab) {
|
||||
activeTab = tab;
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
var tab = tabs[i].firstChild;
|
||||
var active = (tab == activeTab);
|
||||
var className = active ? 'active-tab' : '';
|
||||
tab.className = className;
|
||||
document.getElementById(tab.id.substring(4)).style.display = (active ? '' : 'none');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# tag_cloud.js #
|
||||
# Allows user interaction with the tag cloud #
|
||||
###############################################################################
|
||||
# $Id:: tag_cloud.js 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
*/
|
||||
|
||||
var tagCloudUpdateTime = 5;
|
||||
|
||||
var tagUseRE = new RegExp("\\bused-(\\d+)\\b");
|
||||
var tagUseMinimum = 1;
|
||||
var tagCloudSliderLabelText;
|
||||
var tagNodes;
|
||||
|
||||
function initializeTagCloudSlider (labelPrefix) {
|
||||
var cloud = document.getElementById("tag-cloud");
|
||||
tagNodes = new Array();
|
||||
var maxUseCount = 0;
|
||||
for (var i = 0; i < cloud.childNodes.length; i++) {
|
||||
var child = cloud.childNodes[i];
|
||||
if (child.nodeName && child.nodeName.toUpperCase() == "LI") {
|
||||
var matches = child.className.match(tagUseRE);
|
||||
var cnt = parseInt(matches[1]);
|
||||
if (!tagNodes[cnt]) tagNodes[cnt] = new Array();
|
||||
tagNodes[cnt].push(child);
|
||||
if (cnt > maxUseCount) maxUseCount = cnt;
|
||||
}
|
||||
}
|
||||
var newMaxUseCount = Math.floor(maxUseCount / 4);
|
||||
if (newMaxUseCount > 1) maxUseCount = newMaxUseCount;
|
||||
var val = readCookie("tag_use");
|
||||
if (!val || val <= 0 || val > maxUseCount) val = 1;
|
||||
var container = document.createElement("div");
|
||||
container.id = "tag-cloud-slider-container";
|
||||
var sl = document.createElement("div");
|
||||
sl.id = "tag-cloud-slider";
|
||||
var form = document.createElement("form");
|
||||
form.action = "#";
|
||||
var input = document.createElement("input");
|
||||
input.id = input.name = "tag-cloud-slider-input";
|
||||
var label = document.createElement("div");
|
||||
label.id = "tag-usage-minimum";
|
||||
label.appendChild(document.createTextNode(labelPrefix + " "));
|
||||
tagCloudSliderLabelText = document.createTextNode(val);
|
||||
label.appendChild(tagCloudSliderLabelText);
|
||||
form.appendChild(input);
|
||||
sl.appendChild(form);
|
||||
container.appendChild(label);
|
||||
container.appendChild(sl);
|
||||
var placeholder = document.getElementById("tag-cloud-slider-placeholder");
|
||||
placeholder.parentNode.replaceChild(container, placeholder);
|
||||
var slider = new Slider(sl, input);
|
||||
slider.setMinimum(1);
|
||||
slider.setMaximum(maxUseCount);
|
||||
slider.setBlockIncrement(1);
|
||||
slider.setValue(val);
|
||||
slider.onchange = function () { setTagUseMinimum(slider.getValue()); };
|
||||
setTagUseMinimum(val);
|
||||
}
|
||||
|
||||
function setTagUseMinimum (min) {
|
||||
if (tagUseMinimum == min) return;
|
||||
tagCloudSliderLabelText.nodeValue = min;
|
||||
createCookie("tag_use", min);
|
||||
if (min > tagUseMinimum)
|
||||
setTagNodesVisible(tagUseMinimum, min - 1, false);
|
||||
else
|
||||
setTagNodesVisible(min, tagUseMinimum - 1, true);
|
||||
tagUseMinimum = min;
|
||||
}
|
||||
|
||||
function setTagNodesVisible (first, last, visible) {
|
||||
var disp = (visible ? "" : "none");
|
||||
for (var i = first; i <= last; i++) {
|
||||
var nodes = tagNodes[i];
|
||||
if (!nodes) continue;
|
||||
for (var j = 0; j < nodes.length; j++)
|
||||
nodes[j].style.display = disp;
|
||||
}
|
||||
}
|
||||
|
||||
function readCookie (name) {
|
||||
var nameEq = name + "=";
|
||||
var ca = document.cookie.split(/; */);
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
if (c.indexOf(nameEq) == 0)
|
||||
return c.substring(nameEq.length, c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createCookie (name, value) {
|
||||
var date = new Date();
|
||||
date.setYear(1900 + date.getYear() + 1);
|
||||
document.cookie = name + "=" + value + "; expires=" + date.toGMTString()
|
||||
+ "; path=" + cookiePath + "; domain=" + cookieDomain;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<IfModule mod_access.c>
|
||||
order deny,allow
|
||||
deny from all
|
||||
</IfModule>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,282 @@
|
|||
[properties]
|
||||
full_name=U.S. English
|
||||
author_name=Tim De Pauw
|
||||
author_email=ceetee@users.sourceforge.net
|
||||
author_uri=http://chirpy.sourceforge.net/
|
||||
version=0.3
|
||||
chirpy_version=0.3
|
||||
|
||||
[strings]
|
||||
error_title=An Error Occurred
|
||||
quote_browser=Quote Browser
|
||||
random_quotes=Random Quotes
|
||||
view_quote=View Quote
|
||||
top_quotes=Top Quotes
|
||||
bottom_quotes=Bottom Quotes
|
||||
quotes_of_the_week=Quotes of the Week
|
||||
search_for_quotes=Search for Quotes
|
||||
tag_cloud=Tag Cloud
|
||||
statistics=Statistics
|
||||
administration=Administration
|
||||
welcome=Welcome
|
||||
edit_quote=Edit Quote
|
||||
remove_quote=Remove Quote
|
||||
latest_news=Latest News
|
||||
unknown_action=The requested action is unknown.
|
||||
no_quotes=There do not appear to be any quotes to display.
|
||||
quote_browser_description=Browse Quotes
|
||||
quote_browser_short_title=Browse
|
||||
random_quotes_description=View Random Quotes
|
||||
random_quotes_short_title=Random
|
||||
top_quotes_description=View Highest Rated Quotes
|
||||
top_quotes_short_title=Top
|
||||
bottom_quotes_description=View Lowest Rated Quotes
|
||||
bottom_quotes_short_title=Bottom
|
||||
quotes_of_the_week_description=View Quotes of the Week
|
||||
quotes_of_the_week_short_title=QotW
|
||||
quote_search_description=Search for Quotes
|
||||
quote_search_short_title=Search
|
||||
tag_cloud_description=View Tag Usage
|
||||
tag_cloud_short_title=Tags
|
||||
statistics_description=View Statistics
|
||||
statistics_short_title=Stats
|
||||
quote_count_by_date=Number of Quotes per Day
|
||||
quote_count_by_hour=Number of Quotes per Hour
|
||||
quote_count_by_month=Number of Quotes per Month
|
||||
quote_count_by_day=Number of Quotes per Day of the Month
|
||||
quote_count_by_weekday=Number of Quotes per Weekday
|
||||
quote_count_by_rating=Number of Quotes per Rating
|
||||
quote_count_by_vote_count=Number of Quotes per Number of Votes
|
||||
vote_count_by_rating=Number of Votes per Rating
|
||||
submit_quote_description=Submit New Quote
|
||||
submit_quote_short_title=Submit
|
||||
moderation_queue_description=View Unmoderated Quotes
|
||||
moderation_queue_short_title=Queue
|
||||
administration_description=Administrative Interface
|
||||
administration_short_title=Admin
|
||||
login_description=Authenticate by User Account
|
||||
login_short_title=Log In
|
||||
logout_description=Clear Authentication Data
|
||||
logout_short_title=Log Out
|
||||
quote_title=Quote %1%
|
||||
quote_report_description=Report Quote
|
||||
quote_rating_up_description=Quote Rating Up
|
||||
quote_rating_down_description=Quote Rating Down
|
||||
quote_report_confirmation_request=Are you sure you would like to report the following quote?
|
||||
quote_rating_up_confirmation_request=Are you sure you would like to increase the rating of the following quote?
|
||||
quote_rating_down_confirmation_request=Are you sure you would like to decrease the rating of the following quote?
|
||||
quote_rating_description=Quote Rating
|
||||
quote_vote_count_description=Quote Vote Count
|
||||
quote_date_description=Quote Submission Date
|
||||
quote_edit_description=Edit Quote
|
||||
quote_remove_description=Remove Quote
|
||||
quote_notes_title=Notes:
|
||||
quote_tags_title=Tags:
|
||||
search_query_title=Find:
|
||||
search_button_label=Go!
|
||||
search_results=Search Results
|
||||
submit_quote=Submit Quote
|
||||
unmoderated_quotes=Unmoderated Quotes
|
||||
submission_title=Enter your quote in this field:
|
||||
notes_title=Enter additional information about the quote, if any:
|
||||
tags_title=Enter some relevant tags for the quote:
|
||||
submit_button_label=Submit Quote for Approval
|
||||
submit_button_label_no_approval=Add Quote to Database
|
||||
quote_submitted=Quote Submitted
|
||||
quote_submitted_no_approval=Quote Added
|
||||
quote_submission_thanks=Thank you for submitting a quote to our database. A site administrator will review it shortly. If it gets approved, it will appear on this web site. Fingers crossed!
|
||||
quote_submission_thanks_no_approval=Thank you for submitting a quote to our database. Because you are a site administrator, your quote does not need to be approved before it appears on the web site.
|
||||
approve_quotes=Approve Quotes
|
||||
flagged_quotes=Flagged Quotes
|
||||
manage_news=Manage News
|
||||
manage_quotes=Manage Quotes
|
||||
add_news=Add News
|
||||
edit_news=Edit News
|
||||
remove_news=Remove News
|
||||
manage_accounts=Manage Accounts
|
||||
view_event_log=View Event Log
|
||||
no_unapproved_quotes=No quotes are currently awaiting approval.
|
||||
no_flagged_quotes=No quotes are currently flagged.
|
||||
quote_rating_up_short_title=Up
|
||||
quote_rating_down_short_title=Down
|
||||
report_quote_short_title=Report
|
||||
edit=Edit
|
||||
remove=Remove
|
||||
unflag=Unflag
|
||||
flagged=Flagged
|
||||
quote_removal_confirmation=Are you sure you would like to remove this quote?
|
||||
news_removal_confirmation=Are you sure you would like to remove this news item?
|
||||
quote_rating_increased=Quote Rating Increased
|
||||
quote_rating_decreased=Quote Rating Decreased
|
||||
quote_rating_thanks=Your vote has been processed. Thank you for your input!
|
||||
quote_reported=Quote Flagged
|
||||
quote_report_thanks=Thank you for reporting the quote. A site administrator will review your report shortly.
|
||||
quote_already_rated=You have already rated this quote. You cannot rate the same quote twice.
|
||||
quote_rating_limit_exceeded=You have exceeded the maximum number of votes allowed. This system maximally allows %1% vote(s) every %2% seconds.
|
||||
login_title=Please Log In
|
||||
invalid_login_title=Invalid Login
|
||||
username_title=User Name:
|
||||
password_title=Password:
|
||||
login_button_label=Log In
|
||||
invalid_login_instructions=We were unable to log you in using the given credentials. Note that while your user name is not case sensitive, your password is. Please try to log in again.
|
||||
logged_in_as=You are currently logged in as %1% (%2%).
|
||||
change_password=Change Password
|
||||
processing=Processing
|
||||
timed_out=Timed Out
|
||||
none=None
|
||||
error=Error
|
||||
no_search_results=No Results
|
||||
no_search_results_text=Unfortunately, your search did not return any results.
|
||||
quote_not_found=Quote Not Found
|
||||
quote_not_found_text=The quote you requested does not exist in the database. You might want to try the search feature instead.
|
||||
rated_quote_not_found_text=The quote you attempted to rate could not be found in the database.
|
||||
reported_quote_not_found_text=The quote you attempted to report could not be found in the database.
|
||||
user_level_3=Moderator
|
||||
user_level_6=Administrator
|
||||
user_level_9=Owner
|
||||
password_changed=Password Changed
|
||||
password_changed_text=Your password was successfully modified. Make sure you keep it in a safe place!
|
||||
current_password_title=Current Password:
|
||||
new_password_title=New Password:
|
||||
repeat_new_password_title=Repeat New Password:
|
||||
change_password_button_label=Change Password
|
||||
change_password_current_password_invalid_text=The password you have entered is not your current password.
|
||||
change_password_new_password_invalid_text=The new password you have entered is invalid.
|
||||
change_password_passwords_differ_text=The passwords you have entered do not match. Please make sure you enter the same password twice.
|
||||
do_nothing=Do Nothing
|
||||
approve_unapproved_quote=Approve Quote
|
||||
discard_unapproved_quote=Discard Quote
|
||||
approve_all_unapproved_quotes=Approve All
|
||||
approve_all_unapproved_quotes_confirm=Are you sure you would like to approve all unapproved quotes?
|
||||
discard_all_unapproved_quotes=Discard All
|
||||
discard_all_unapproved_quotes_confirm=Are you sure you would like to discard all unapproved quotes?
|
||||
update_database=Update Database
|
||||
reset_form=Reset Form
|
||||
keep_flagged_quote=Keep Quote
|
||||
remove_flagged_quote=Remove Quote
|
||||
keep_all_flagged_quotes=Keep All
|
||||
keep_all_flagged_quotes_confirm=Are you sure you would like to keep all flagged quotes?
|
||||
remove_all_flagged_quotes=Remove All
|
||||
remove_all_flagged_quotes_confirm=Are you sure you would like to remove all flagged quotes?
|
||||
quote_removed=The selected quote was successfully removed from the database.
|
||||
quote_to_edit_not_found=The selected quote could not be found. Perhaps it had already been removed or you entered an invalid ID.
|
||||
quote_to_remove_not_found=The selected quote could not be found. Perhaps it has been removed or you entered an invalid ID.
|
||||
quote_modified=The selected quote was successfully updated.
|
||||
quote_id_title=Quote ID:
|
||||
save_quote=Save Quote
|
||||
go=Go
|
||||
news_item_added=Your news post was successfully added to the database.
|
||||
news_item_modified=The selected news item was successfully updated.
|
||||
news_item_to_edit_not_found=The selected news item could not be found. Perhaps it has been removed or you entered an invalid ID.
|
||||
news_item_removed=The selected news item was successfully removed from the database.
|
||||
news_item_to_remove_not_found=The selected news item could not be found. Perhaps it had already been removed.
|
||||
new_news_item_title=New News Item:
|
||||
add_news_item=Add News Item
|
||||
news_poster_title=Poster:
|
||||
save_news_item=Save News Item
|
||||
account_to_modify_not_found=Please select the account you would like to modify.
|
||||
account_to_remove_not_found=Please select the account you would like to remove.
|
||||
last_owner_account_removal_error=Must have at least one Owner account.
|
||||
modified_account_information_required=Please enter the new data for the account to modify.
|
||||
invalid_username=The user name you entered is invalid.
|
||||
username_exists=A user already exists by that name.
|
||||
invalid_password=The password you entered is invalid.
|
||||
different_passwords=The passwords do not match. Please make sure you enter the same password twice.
|
||||
invalid_user_level=The user level you selected is invalid.
|
||||
account_removed=The selected account has been removed.
|
||||
account_modified=The account has been updated.
|
||||
account_created=The new account has been created.
|
||||
new_account=New Account
|
||||
new_username_title=New User Name:
|
||||
new_password_title=New Password:
|
||||
repeat_new_password_title=Repeat Password:
|
||||
new_user_level_title=New User Level:
|
||||
update_accounts=Update Accounts
|
||||
remove_account=Remove Account
|
||||
no_change=No Change
|
||||
unknown=Unknown
|
||||
account_removal_confirmation=Are you sure you would like to remove the selected account? This cannot be undone.
|
||||
insufficient_administrative_privileges=You are not authorized to use this component because your administrative privileges are insufficient.
|
||||
no_tagged_quotes=There do not appear to be any quotes with tags.
|
||||
statistics_unavailable=Statistics are currently unavailable.
|
||||
tag_link_description=View Quotes Tagged %1%
|
||||
update_available=Update Available
|
||||
update_available_text=Chirpy! version %1% was released on %2%.
|
||||
update_link_text=Click here for more information.
|
||||
update_check_failed=Update Check Failed
|
||||
update_check_failed_text=Chirpy! attempted to check for updates, but failed. The reported error was:
|
||||
event_100_name=Login Success
|
||||
event_101_name=Login Failure
|
||||
event_102_name=Change Password
|
||||
event_200_name=Add Quote
|
||||
event_201_name=Edit Quote
|
||||
event_202_name=Remove Quote
|
||||
event_203_name=Quote Rating Up
|
||||
event_204_name=Quote Rating Down
|
||||
event_205_name=Report Quote
|
||||
event_206_name=Approve Quote
|
||||
event_207_name=Unflag Quote
|
||||
event_300_name=Add News
|
||||
event_301_name=Edit News
|
||||
event_302_name=Remove News
|
||||
event_400_name=Add Account
|
||||
event_401_name=Edit Account
|
||||
event_402_name=Remove Account
|
||||
date=Date
|
||||
username=Username
|
||||
event=Event
|
||||
guest=Guest
|
||||
empty=Empty
|
||||
ok=OK
|
||||
cancel=Cancel
|
||||
sunday=Sunday
|
||||
monday=Monday
|
||||
tuesday=Tuesday
|
||||
wednesday=Wednesday
|
||||
thursday=Thursday
|
||||
friday=Friday
|
||||
saturday=Saturday
|
||||
january=January
|
||||
february=February
|
||||
march=March
|
||||
april=April
|
||||
may=May
|
||||
june=June
|
||||
july=July
|
||||
august=August
|
||||
september=September
|
||||
october=October
|
||||
november=November
|
||||
december=December
|
||||
january_short=Jan
|
||||
february_short=Feb
|
||||
march_short=Mar
|
||||
april_short=Apr
|
||||
may_short=May
|
||||
june_short=Jun
|
||||
july_short=Jul
|
||||
august_short=Aug
|
||||
september_short=Sep
|
||||
october_short=Oct
|
||||
november_short=Nov
|
||||
december_short=Dec
|
||||
webapp.start_page_description=Start Page
|
||||
webapp.start_page_short_title=Home
|
||||
webapp.next_page_title=Next Page
|
||||
webapp.previous_page_title=Previous Page
|
||||
webapp.current_page_title=Current Page
|
||||
webapp.quote_link_description=Permanent Link to Quote
|
||||
webapp.footer_text=This page was generated in %1% ms by %2%.
|
||||
webapp.footer_text_no_time=This site is powered by %1%.
|
||||
webapp.manage_quote_instructions=To edit or remove a quote, just browse to it while logged in. You will then see the appropriate links next to it. Alternatively, if you know the ID of the quote you would like to edit, you can enter it below.
|
||||
webapp.remove_quote_without_viewing_confirmation=Are you sure you would like to remove this quote without viewing it first? This operation cannot be undone and is NOT recommended.
|
||||
webapp.manage_news_instructions=To edit or remove a news item, just open the home page while logged in. You will then see the appropriate links next to the items.
|
||||
webapp.session_required=The action you are attempting to take requires that session information be set. This is done by offering a cookie to your browser. Unfortunately, it seems it has failed to accept this cookie. Please review your cookie settings and try again.
|
||||
webapp.quote_rating_timed_out=The quote rating request timed out, possibly due to a connection problem. Please try again.
|
||||
webapp.captcha_code_label=Type the text you see in this image:
|
||||
webapp.captcha_image_text=Captcha
|
||||
webapp.minimum_tag_usage_count_title=Minimum Quotes:
|
||||
webapp.top_quote_prefix=Top Quote:
|
||||
webapp.bottom_quote_prefix=Bottom Quote:
|
||||
webapp.latest_quote_prefix=Latest Quote:
|
||||
webapp.latest_unmoderated_quote_prefix=Latest Unmoderated Quote:
|
|
@ -0,0 +1,850 @@
|
|||
###############################################################################
|
||||
# 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;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,172 @@
|
|||
###############################################################################
|
||||
# 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:: Account.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Account - Represents a user account
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$account = new Chirpy::Account($id, $username, $password, $level);
|
||||
|
||||
$id = $account->get_id();
|
||||
$account->set_id($id);
|
||||
|
||||
$username = $account->get_username($username);
|
||||
$account->set_username($username);
|
||||
|
||||
$password = $account->get_password();
|
||||
$account->set_password($password);
|
||||
|
||||
$level = $account->get_level();
|
||||
$account->set_level($level);
|
||||
|
||||
=head1 CONSTRAINTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item ID
|
||||
|
||||
The account ID must be a positive non-zero integer.
|
||||
|
||||
=item Username
|
||||
|
||||
The username must be valid against the C<valid_username()> function of
|
||||
L<Chirpy::Util>.
|
||||
|
||||
=item Password
|
||||
|
||||
Encryption is done I<before> invoking the constructor or the C<set_password()>
|
||||
function. The C<get_password()> function returns the I<encrypted> password.
|
||||
The password must be valid against the C<valid_password()> function and
|
||||
encrypted using the C<encrypt()> function, both part of L<Chirpy::Util>.
|
||||
|
||||
=item User Level
|
||||
|
||||
The user level must be one of the user level constants described below.
|
||||
|
||||
=back
|
||||
|
||||
=head1 USER LEVEL CONSTANTS
|
||||
|
||||
The following constants are recommended for use as user levels:
|
||||
|
||||
Chirpy::Account::USER_LEVEL_3
|
||||
Chirpy::Account::USER_LEVEL_6
|
||||
Chirpy::Account::USER_LEVEL_9
|
||||
|
||||
Note that the value of these is the integer representing the user level and
|
||||
that the constants are only for the sake of code readability.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::Account;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use constant USER_LEVEL_3 => 3;
|
||||
use constant USER_LEVEL_6 => 6;
|
||||
use constant USER_LEVEL_9 => 9;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $id, $username, $password, $level) = @_;
|
||||
my $self = {
|
||||
'id' => $id,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'level' => $level
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub get_id {
|
||||
my $self = shift;
|
||||
return $self->{'id'};
|
||||
}
|
||||
|
||||
sub set_id {
|
||||
my $self = shift;
|
||||
return ($self->{'id'} = shift);
|
||||
}
|
||||
|
||||
sub get_username {
|
||||
my $self = shift;
|
||||
return $self->{'username'};
|
||||
}
|
||||
|
||||
sub set_username {
|
||||
my $self = shift;
|
||||
return ($self->{'username'} = shift);
|
||||
}
|
||||
|
||||
sub get_password {
|
||||
my $self = shift;
|
||||
return $self->{'password'};
|
||||
}
|
||||
|
||||
sub set_password {
|
||||
my $self = shift;
|
||||
return ($self->{'password'} = shift);
|
||||
}
|
||||
|
||||
sub get_level {
|
||||
my $self = shift;
|
||||
return $self->{'level'};
|
||||
}
|
||||
|
||||
sub set_level {
|
||||
my $self = shift;
|
||||
return ($self->{'level'} = shift);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,93 @@
|
|||
###############################################################################
|
||||
# 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:: Configuration.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Configuration - Represents a configuration
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$configuration = new Chirpy::Configuration('/path/to/chirpy.ini');
|
||||
|
||||
$value = $configuration->get($section, $name);
|
||||
|
||||
$hash_ref = $configuration->get_parameter_hash($section);
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::Util::IniFile>, 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::Configuration;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION @ISA);
|
||||
|
||||
$VERSION = '0.3';
|
||||
@ISA = qw(Chirpy::Util::IniFile);
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::Util::IniFile 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $file) = @_;
|
||||
return $class->SUPER::new($file || 'chirpy.ini');
|
||||
}
|
||||
|
||||
sub get_parameter_hash {
|
||||
my ($self, $level1, $level2) = @_;
|
||||
my $level1_hash = $self->get($level1);
|
||||
return undef unless ($level1_hash);
|
||||
my %hash = ();
|
||||
my $e = quotemeta lc $level2;
|
||||
while (my ($key, $value) = each %$level1_hash) {
|
||||
next unless ($key =~ s/^$e\.//);
|
||||
$hash{$key} = $value;
|
||||
}
|
||||
return \%hash;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,569 @@
|
|||
###############################################################################
|
||||
# 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:: DataManager.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::DataManager - Abstract data manager class
|
||||
|
||||
=head1 IMPLEMENTATION
|
||||
|
||||
This section should tell you just about everything you need to know if you want
|
||||
to write your own Chirpy! data manager.
|
||||
|
||||
First of all, it must be a class that extends this abstract class, and it must
|
||||
have something along the lines of
|
||||
|
||||
use vars qw($VERSION @ISA);
|
||||
$VERSION = '0.3';
|
||||
@ISA = qw(Chirpy::DataManager);
|
||||
|
||||
All you need to do then, really, is implement this class's abstract methods.
|
||||
Unfortunately, there are quite a few of them. However, a lot of them are fairly
|
||||
trivial, as you will quickly learn. All of them are object methods.
|
||||
|
||||
=head2 Compatibility
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_target_version()
|
||||
|
||||
The return value of this function is compared to Chirpy!'s version. If the
|
||||
version numbers do not match, execution will be aborted. Hence, it needs to be
|
||||
exactly the same as the version of Chirpy! the data manager was built for.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Parameters
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_parameter($name)
|
||||
|
||||
Gets the value of a persistent parameter.
|
||||
|
||||
=item set_parameter($name, $value)
|
||||
|
||||
Sets the value of a persistent parameter.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Installation & Removal
|
||||
|
||||
=over 4
|
||||
|
||||
=item set_up($accounts, $news, $quotes)
|
||||
|
||||
Called at installation time, this method should create all necessary resources
|
||||
to use the data manager. For instance, if the module uses a database, this
|
||||
method should create the necessary tables in it.
|
||||
|
||||
In addition, the method takes 3 arguments, each either an array reference or
|
||||
C<undef>. They represent the initial data to be stored in the installation.
|
||||
C<$accounts> holds instances of L<Chirpy::Account>, C<$news> instances of
|
||||
L<Chirpy::NewsItem> and C<$quotes> instances of L<Chirpy::Quote>.
|
||||
|
||||
=item remove()
|
||||
|
||||
The exact opposite of the C<set_up()> method. Removes all applicable data.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Quotes
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_quotes($options)
|
||||
|
||||
Accepts C<$options>, a reference to a hash containing parameters defining the
|
||||
output, or C<undef>.
|
||||
|
||||
In a scalar context, returns a reference to an array of matching quotes (as
|
||||
instances of L<Chirpy::Quote>), or C<undef> if there are no matches. Otherwise,
|
||||
returns that array, along with 2 boolean values. The first is true if there
|
||||
are quotes I<before> the start of the results, the second is true if there are
|
||||
quotes I<after> the end of the results.
|
||||
|
||||
The option hash may contain any of the keys below. If multiple keys are
|
||||
present, the properties they imply must I<all> apply to the resulting quotes.
|
||||
|
||||
=over 8
|
||||
|
||||
=item id
|
||||
|
||||
ID of the quote to retrieve.
|
||||
|
||||
=item contains
|
||||
|
||||
Reference to an array of strings to find in either the quote body or notes. May
|
||||
contain wildcard characters: an asterisk (C<*>) may represent any character
|
||||
sequence, including an empty one; a question mark (C<?>) represents a single
|
||||
character. To search for the wildcard characters themselves, they may be
|
||||
prefixed with a backslash (C<\>). Consequently, backslashes themselves must
|
||||
also be prefixed with a backslash.
|
||||
|
||||
=item tags
|
||||
|
||||
Reference to an array of tags, any of which must be a tag of the quote.
|
||||
|
||||
=item approved
|
||||
|
||||
Boolean value indicating the I<approved> status of the quote. Note that a false
|
||||
value implies that the quotes must not be approved, while C<undef> cannot
|
||||
affect the results.
|
||||
|
||||
=item flagged
|
||||
|
||||
Boolean value indicating the I<flagged> status of the quote. Note that a false
|
||||
value implies that the quotes must not be flagged, while C<undef> cannot
|
||||
affect the results.
|
||||
|
||||
=item since
|
||||
|
||||
UNIX timestamp representing the earliest date allowed for the date when quotes
|
||||
were submitted.
|
||||
|
||||
=item sort
|
||||
|
||||
Properties of the quote to sort on, represented as a two-dimensional array
|
||||
reference. Here is an example:
|
||||
|
||||
[ [ 'score', 0 ], [ 'id', 1 ] ]
|
||||
|
||||
The boolean value is true if the results should be in descending order. Hence,
|
||||
the sorting instruction above means the data manager should sort by score
|
||||
first, in ascending order. If the ratings are equal, then it should sort by ID,
|
||||
in descending order.
|
||||
|
||||
The possible properties to sort on are C<id>, C<rating>, C<approved>, C<score>,
|
||||
and C<flagged>. It is assumed that sorting on the date when the quote was
|
||||
submitted has the same effect as sorting on quote ID.
|
||||
|
||||
Sorting on C<score> was introduced in Chirpy! 0.3 and is used to determine the
|
||||
top and bottom quotes. Quotes' scores are calculated as follows:
|
||||
|
||||
votes + rating
|
||||
---------------- + 1
|
||||
positive votes + 1 2
|
||||
score = -------------------- = ----------------------
|
||||
negative votes + 1 votes - rating
|
||||
---------------- + 1
|
||||
2
|
||||
|
||||
Data managers may prefer to cache this value for performance purposes.
|
||||
|
||||
=item random
|
||||
|
||||
Boolean value indicating if results should be randomly selected. Overrides the
|
||||
"sort" option.
|
||||
|
||||
=item first
|
||||
|
||||
The number of the first result to return, 0 being the first in the result list.
|
||||
|
||||
=item count
|
||||
|
||||
The number of quotes to maximally return.
|
||||
|
||||
=back
|
||||
|
||||
=item quote_count($options)
|
||||
|
||||
Returns the number of quotes in the database, either approved, unapproved, or
|
||||
both. Optionally accepts C<$options>, a reference to a hash of parameters
|
||||
defining which quotes are to be counted. Currently, C<$options> may contain only
|
||||
one parameter, namely C<approved>. If C<approved> is undefined, all quotes are
|
||||
included in the count; if it is a true value, only approved quotes are included,
|
||||
and vice versa.
|
||||
|
||||
=item add_quote($quote)
|
||||
|
||||
Adds the L<Chirpy::Quote|Chirpy::Quote> C<$quote> to the collection. Assigns an
|
||||
ID to the quote and updates the object with it. Returns a true value upon
|
||||
success.
|
||||
|
||||
The properties to be saved by this method are I<body>, I<notes>, I<approved>
|
||||
and I<tags>.
|
||||
|
||||
=item modify_quote($quote)
|
||||
|
||||
Updates the L<Chirpy::Quote|Chirpy::Quote> C<$quote> in the collection. Returns
|
||||
a true value on success.
|
||||
|
||||
The properties to be saved by this method are I<body> and I<notes>.
|
||||
|
||||
=item remove_quote($quote)
|
||||
|
||||
Removes the L<Chirpy::Quote|Chirpy::Quote> C<$quote> from the collection.
|
||||
Returns a true value on success.
|
||||
|
||||
=item remove_quotes(@ids)
|
||||
|
||||
Removes all quotes whose ID is in C<@ids> from the collection. Returns the
|
||||
number of removed quotes.
|
||||
|
||||
=item increase_quote_rating($id, $revert)
|
||||
|
||||
Increases the rating of quote number C<$id>. If C<$revert> is a true value,
|
||||
increases the rating by 2, as the user is reverting his vote; otherwise,
|
||||
increases it by 1. If the user is not reverting his vote, increases the number
|
||||
of votes for the quote by 1 as well. Returns a list containing the updated
|
||||
rating and vote count.
|
||||
|
||||
=item decrease_quote_rating($id, $revert)
|
||||
|
||||
Decreases the rating of quote number C<$id>. If C<$revert> is a true value,
|
||||
decreases the rating by 2, as the user is reverting his vote; otherwise,
|
||||
decreases it by 1. If the user is not reverting his vote, increases the number
|
||||
of votes for the quote by 1 as well. Returns a list containing the updated
|
||||
rating and vote count.
|
||||
|
||||
=item get_tag_use_counts()
|
||||
|
||||
Returns a reference to a hash, mapping tags to the number of times they were
|
||||
used. Only tags for approved quotes should be counted.
|
||||
|
||||
=item approve_quotes(@ids)
|
||||
|
||||
Sets the quotes associated with an ID in C<@ids> to I<approved>. Returns the
|
||||
number of affected quotes.
|
||||
|
||||
=item unflag_quotes(@ids)
|
||||
|
||||
Sets the quotes associated with an ID in C<@ids> to I<unflagged>. Returns the
|
||||
number of affected quotes.
|
||||
|
||||
=back
|
||||
|
||||
=head2 News Items
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_news_items($options)
|
||||
|
||||
Retrieves news items, a lot like L<get_quotes()|/get_quotes($options)>
|
||||
retrieves quotes. The possible options are now:
|
||||
|
||||
=over 8
|
||||
|
||||
=item id
|
||||
|
||||
ID of the news item to retrieve.
|
||||
|
||||
=item count
|
||||
|
||||
Maximum number of news items returned.
|
||||
|
||||
=back
|
||||
|
||||
Resulting news items are always sorted by date, newest first.
|
||||
|
||||
The function returns a reference to an array of instances of
|
||||
L<Chirpy::NewsItem>, or C<undef> if no matching news items were found.
|
||||
|
||||
=item add_news_item($news_item)
|
||||
|
||||
Adds the L<Chirpy::NewsItem|Chirpy::NewsItem> C<$news_item> to the collection.
|
||||
Assigns an ID to the news item and updates the object with it. Returns a true
|
||||
value upon success.
|
||||
|
||||
The properties to be saved by this method are I<body> and I<poster>. I<poster>
|
||||
may be C<undef> if the poster is unknown.
|
||||
|
||||
=item modify_news_item($news_item)
|
||||
|
||||
Updates the L<Chirpy::NewsItem|Chirpy::NewsItem> C<$news_item> in the
|
||||
collection. Returns a true value on success.
|
||||
|
||||
The properties to be saved by this method are I<body> and I<poster>. I<poster>
|
||||
may be C<undef> if the poster is unknown.
|
||||
|
||||
=item remove_news_item($news_item)
|
||||
|
||||
Removes the L<Chirpy::NewsItem|Chirpy::NewsItem> C<$news_item> from the
|
||||
collection. Returns a true value on success.
|
||||
|
||||
=item remove_news_items(@ids)
|
||||
|
||||
Removes all news items whose ID is in C<@ids> from the collection. Returns the
|
||||
number of removed news items.
|
||||
|
||||
=back
|
||||
|
||||
=head2 User Accounts
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_accounts($options)
|
||||
|
||||
Retrieves accounts, a lot like L<get_quotes()|/get_quotes($options)> retrieves
|
||||
quotes and L<get_news_items()|/get_news_items($options)> retrieves news items.
|
||||
The possible options are now:
|
||||
|
||||
=over 8
|
||||
|
||||
=item id
|
||||
|
||||
ID of the account to retrieve.
|
||||
|
||||
=item username
|
||||
|
||||
User name of the account to retrieve.
|
||||
|
||||
=item levels
|
||||
|
||||
Reference to an array containing allowed user levels.
|
||||
|
||||
=back
|
||||
|
||||
Resulting accounts are always sorted by user level, highest first, then by user
|
||||
name.
|
||||
|
||||
The function returns a reference to an array of instances of
|
||||
L<Chirpy::Account>, or C<undef> if no matching accounts were found.
|
||||
|
||||
=item add_account($account)
|
||||
|
||||
Adds the L<Chirpy::Account|Chirpy::Account> C<$account> to the collection.
|
||||
Assigns an ID to the account and updates the object with it. Returns a true
|
||||
value upon success.
|
||||
|
||||
The properties to be saved by this method are I<username>, I<password> and
|
||||
I<level>.
|
||||
|
||||
=item modify_account($account)
|
||||
|
||||
Updates the L<Chirpy::Account|Chirpy::Account> C<$account> in the collection.
|
||||
Returns a true value on success.
|
||||
|
||||
The properties to be saved by this method are I<username>, I<password> and
|
||||
I<level>.
|
||||
|
||||
=item remove_account($account)
|
||||
|
||||
Removes the L<Chirpy::Account|Chirpy::Account> C<$account> from the collection.
|
||||
Returns a true value on success.
|
||||
|
||||
Note that upon removal of an account, news items associated with it must be
|
||||
kept, but their author becomes unknown.
|
||||
|
||||
=item remove_accounts(@ids)
|
||||
|
||||
Removes all accounts whose ID is in C<@ids> from the collection. Returns the
|
||||
number of removed accounts.
|
||||
|
||||
Note that upon removal of an account, news items associated with it must be
|
||||
kept, but their author becomes unknown.
|
||||
|
||||
=item username_exists($username)
|
||||
|
||||
Returns a true value if the given username exists in the collection.
|
||||
|
||||
=item account_count($params)
|
||||
|
||||
Returns the current number of accounts. Takes an optional hash reference to
|
||||
retrieval options. For now, it can only contain the key C<levels>, whose value
|
||||
is a reference to an array of user levels. If this key is set, the function
|
||||
returns the number of accounts with any of those levels.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Logging
|
||||
|
||||
=over 4
|
||||
|
||||
=item get_events($options)
|
||||
|
||||
Retrieves log events, taking a hash reference like the other C<get_> functions.
|
||||
Returns results in the same fashion as L<get_quotes()|/get_quotes($options)>,
|
||||
but always sorted chronologically. This time, the available options are:
|
||||
|
||||
=over 8
|
||||
|
||||
=item code
|
||||
|
||||
Either a single event code or a reference to an array of codes to match. This
|
||||
option may be omitted.
|
||||
|
||||
=item user
|
||||
|
||||
Either a single user ID or a reference to an array of IDs to match. This option
|
||||
may be omitted. A user ID of 0 represents users who are not logged in.
|
||||
|
||||
=item first
|
||||
|
||||
The number of the first result to return, 0 being the first in the result list.
|
||||
If this option is omitted, the default value of 0 is assumed.
|
||||
|
||||
=item count
|
||||
|
||||
The number of events to maximally return. If this option is omitted, there is
|
||||
no limit on the number of results.
|
||||
|
||||
=item reverse
|
||||
|
||||
If this option has a true value, the order is reversed, so the events are in
|
||||
reverse chronological order.
|
||||
|
||||
=item data
|
||||
|
||||
May be set to a hash reference to filter the events on metadata. If any of the
|
||||
key-value pairs in the hash are equal to a metadata property of the event, it
|
||||
is considered a match. This option may be omitted.
|
||||
|
||||
=back
|
||||
|
||||
=item log_event($event)
|
||||
|
||||
Logs the L<Chirpy::Event|Chirpy::Event> C<$event>. Returns a true value on
|
||||
success.
|
||||
|
||||
The properties to be saved by this method are I<code>, I<user> and I<data>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Sessions
|
||||
|
||||
I<This step is optional.>
|
||||
|
||||
In addition, you may want to provide compatibility with
|
||||
L<Chirpy::UI::WebApp|Chirpy::UI::WebApp>'s session manager by making your data
|
||||
manager class extend L<Chirpy::UI::WebApp::Session::DataManager> as well.
|
||||
Please check L<its documentation|Chirpy::UI::WebApp::Session::DataManager> for
|
||||
instructions.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::Quote>, L<Chirpy::NewsItem>, L<Chirpy::Account>, L<Chirpy::Event>,
|
||||
L<Chirpy::DataManager::MySQL>, L<Chirpy::UI::WebApp::Session::DataManager>,
|
||||
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::DataManager;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::Util 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $params) = @_;
|
||||
return bless {
|
||||
'params' => $params
|
||||
}, $class;
|
||||
}
|
||||
|
||||
sub param {
|
||||
my ($self, $name) = @_;
|
||||
return defined $self->{'params'} ? $self->{'params'}{$name} : undef;
|
||||
}
|
||||
|
||||
*get_target_version = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*set_up = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_quote = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*quote_count = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_quotes = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*add_quote = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*modify_quote = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*increase_quote_rating = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*decrease_quote_rating = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_tag_use_counts = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*approve_quotes = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*flag_quotes = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*unflag_quotes = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_quote = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_quotes = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_news_items = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*add_news_item = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*modify_news_item = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_news_item = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_news_items = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_accounts = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*add_account = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*modify_account = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_account = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_accounts = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*username_exists = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*account_count = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_events = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*log_event = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_parameter = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*set_parameter = \&Chirpy::Util::abstract_method;
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,219 @@
|
|||
###############################################################################
|
||||
# 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:: Event.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Event - Represents a log event
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$event = new Chirpy::Event($id, $date, $code, $user, $data);
|
||||
|
||||
$id = $event->get_id();
|
||||
$event->set_id($id);
|
||||
|
||||
$date = $event->get_date();
|
||||
$event->set_date($date);
|
||||
|
||||
$code = $event->get_code();
|
||||
$event->set_code($code);
|
||||
|
||||
$user = $event->get_user();
|
||||
$event->set_user($user);
|
||||
|
||||
$data = $event->get_data();
|
||||
$event->set_data($data);
|
||||
|
||||
$event_code = Chirpy::Event::translate_code($code);
|
||||
|
||||
=head1 CONSTRAINTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item ID
|
||||
|
||||
The event ID must be a positive non-zero integer.
|
||||
|
||||
=item Date
|
||||
|
||||
The event date must be a UNIX timestamp.
|
||||
|
||||
=item Code
|
||||
|
||||
The event code must be one of the event code constants described below.
|
||||
|
||||
=item User
|
||||
|
||||
The user who was logged in at the time of the event must be an instance of
|
||||
L<Chirpy::Account>, if any.
|
||||
|
||||
=item Data
|
||||
|
||||
The event data must be a reference to a hash containing information about the
|
||||
event.
|
||||
|
||||
=back
|
||||
|
||||
=head1 EVENT CODE CONSTANTS
|
||||
|
||||
Chirpy::Event::LOGIN_SUCCESS
|
||||
Chirpy::Event::LOGIN_FAILURE
|
||||
Chirpy::Event::CHANGE_PASSWORD
|
||||
|
||||
Chirpy::Event::ADD_QUOTE
|
||||
Chirpy::Event::EDIT_QUOTE
|
||||
Chirpy::Event::REMOVE_QUOTE
|
||||
Chirpy::Event::QUOTE_RATING_UP
|
||||
Chirpy::Event::QUOTE_RATING_DOWN
|
||||
Chirpy::Event::REPORT_QUOTE
|
||||
Chirpy::Event::APPROVE_QUOTE
|
||||
Chirpy::Event::UNFLAG_QUOTE
|
||||
|
||||
Chirpy::Event::ADD_NEWS
|
||||
Chirpy::Event::EDIT_NEWS
|
||||
Chirpy::Event::REMOVE_NEWS
|
||||
|
||||
Chirpy::Event::ADD_ACCOUNT
|
||||
Chirpy::Event::EDIT_ACCOUNT
|
||||
Chirpy::Event::REMOVE_ACCOUNT
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::Event;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use constant LOGIN_SUCCESS => 100;
|
||||
use constant LOGIN_FAILURE => 101;
|
||||
use constant CHANGE_PASSWORD => 102;
|
||||
|
||||
use constant ADD_QUOTE => 200;
|
||||
use constant EDIT_QUOTE => 201;
|
||||
use constant REMOVE_QUOTE => 202;
|
||||
use constant QUOTE_RATING_UP => 203;
|
||||
use constant QUOTE_RATING_DOWN => 204;
|
||||
use constant REPORT_QUOTE => 205;
|
||||
use constant APPROVE_QUOTE => 206;
|
||||
use constant UNFLAG_QUOTE => 207;
|
||||
|
||||
use constant ADD_NEWS => 300;
|
||||
use constant EDIT_NEWS => 301;
|
||||
use constant REMOVE_NEWS => 302;
|
||||
|
||||
use constant ADD_ACCOUNT => 400;
|
||||
use constant EDIT_ACCOUNT => 401;
|
||||
use constant REMOVE_ACCOUNT => 402;
|
||||
|
||||
use vars qw($VERSION $CODES);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $id, $date, $code, $user, $data) = @_;
|
||||
my $self = {
|
||||
'id' => $id,
|
||||
'date' => $date,
|
||||
'code' => $code,
|
||||
'user' => $user,
|
||||
'data' => $data
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub get_id {
|
||||
my $self = shift;
|
||||
return $self->{'id'};
|
||||
}
|
||||
|
||||
sub set_id {
|
||||
my $self = shift;
|
||||
return ($self->{'id'} = shift);
|
||||
}
|
||||
|
||||
sub get_date {
|
||||
my $self = shift;
|
||||
return $self->{'date'};
|
||||
}
|
||||
|
||||
sub set_date {
|
||||
my $self = shift;
|
||||
return ($self->{'date'} = shift);
|
||||
}
|
||||
|
||||
sub get_code {
|
||||
my $self = shift;
|
||||
return $self->{'code'};
|
||||
}
|
||||
|
||||
sub set_code {
|
||||
my $self = shift;
|
||||
return ($self->{'code'} = shift);
|
||||
}
|
||||
|
||||
sub get_user {
|
||||
my $self = shift;
|
||||
return $self->{'user'};
|
||||
}
|
||||
|
||||
sub set_user {
|
||||
my $self = shift;
|
||||
return ($self->{'user'} = shift);
|
||||
}
|
||||
|
||||
sub get_data {
|
||||
my $self = shift;
|
||||
return $self->{'data'};
|
||||
}
|
||||
|
||||
sub set_data {
|
||||
my $self = shift;
|
||||
return ($self->{'data'} = shift);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,153 @@
|
|||
###############################################################################
|
||||
# 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:: NewsItem.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::NewsItem - Represents a news item
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$item = new Chirpy::NewsItem($id, $body, $poster, $date);
|
||||
|
||||
$id = $item->get_id();
|
||||
$item->set_id($id);
|
||||
|
||||
$body = $item->get_body();
|
||||
$item->set_body($body);
|
||||
|
||||
$poster = $item->get_poster();
|
||||
$item->set_poster($poster);
|
||||
|
||||
$date = $item->get_date();
|
||||
$item->set_date($date);
|
||||
|
||||
=head1 CONSTRAINTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item ID
|
||||
|
||||
The news item ID must be a positive non-zero integer.
|
||||
|
||||
=item Body
|
||||
|
||||
The news item body can be any text string.
|
||||
|
||||
=item Poster
|
||||
|
||||
The poster of the news item must be an instance of L<Chirpy::Account>, if any.
|
||||
|
||||
=item Date
|
||||
|
||||
The date when the news item was posted must be a UNIX timestamp.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::NewsItem;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $id, $body, $poster, $date) = @_;
|
||||
my $self = {
|
||||
'id' => $id,
|
||||
'body' => $body,
|
||||
'poster' => $poster,
|
||||
'date' => $date
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub get_id {
|
||||
my $self = shift;
|
||||
return $self->{'id'};
|
||||
}
|
||||
|
||||
sub set_id {
|
||||
my $self = shift;
|
||||
return ($self->{'id'} = shift);
|
||||
}
|
||||
|
||||
sub get_body {
|
||||
my $self = shift;
|
||||
return $self->{'body'};
|
||||
}
|
||||
|
||||
sub set_body {
|
||||
my $self = shift;
|
||||
return ($self->{'body'} = shift);
|
||||
}
|
||||
|
||||
sub get_poster {
|
||||
my $self = shift;
|
||||
return $self->{'poster'};
|
||||
}
|
||||
|
||||
sub set_poster {
|
||||
my $self = shift;
|
||||
return ($self->{'poster'} = shift);
|
||||
}
|
||||
|
||||
sub get_date {
|
||||
my $self = shift;
|
||||
return $self->{'date'};
|
||||
}
|
||||
|
||||
sub set_date {
|
||||
my $self = shift;
|
||||
return ($self->{'date'} = shift);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,254 @@
|
|||
###############################################################################
|
||||
# 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:: Quote.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Quote - Represents a quote
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$quote = new Chirpy::Quote(
|
||||
$id, $body, $notes, $rating, $vote_count,
|
||||
$submitted, $approved, $flagged, $tags);
|
||||
|
||||
$id = $quote->get_id();
|
||||
$quote->set_id($id);
|
||||
|
||||
$body = $quote->get_body();
|
||||
$quote->set_body($body);
|
||||
|
||||
$notes = $quote->get_notes();
|
||||
$quote->set_notes($notes);
|
||||
|
||||
$rating = $quote->get_rating();
|
||||
$quote->set_rating($rating);
|
||||
|
||||
$vote_count = $quote->get_vote_count();
|
||||
$quote->set_vote_count($vote_count);
|
||||
|
||||
$submitted = $quote->get_date_submitted();
|
||||
$quote->set_date_submitted($submitted);
|
||||
|
||||
$approved = $quote->is_approved();
|
||||
$quote->set_approved($approved);
|
||||
|
||||
$flagged = $quote->is_flagged();
|
||||
$quote->set_flagged($flagged);
|
||||
|
||||
$tags = $quote->get_tags();
|
||||
$quote->set_tags($tags);
|
||||
$quote->add_tag($tag);
|
||||
$quote->remove_tag($tag);
|
||||
|
||||
=head1 CONSTRAINTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item ID
|
||||
|
||||
The quote ID must be a positive non-zero integer.
|
||||
|
||||
=item Body
|
||||
|
||||
The quote body can be any text string.
|
||||
|
||||
=item Notes
|
||||
|
||||
The quote notes can be any text string, if any.
|
||||
|
||||
=item Rating
|
||||
|
||||
The quote rating must be an integer.
|
||||
|
||||
=item Vote Count
|
||||
|
||||
The vote count must be a positive integer; a value of zero is allowed.
|
||||
|
||||
=item Submitted
|
||||
|
||||
The date when the quote was submitted must be a UNIX timestamp.
|
||||
|
||||
=item Approved
|
||||
|
||||
The quote approval status is 1 if the quote has been approved, 0 if it has not.
|
||||
|
||||
=item Flagged
|
||||
|
||||
The quote flag status is 1 if the quote has been reported, 0 if it has not.
|
||||
|
||||
=item Tags
|
||||
|
||||
Tags must be passed as a reference to an array of strings, each of which
|
||||
constructed of lowercase non-whitespace characters.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::Quote;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $id, $body, $notes,
|
||||
$rating, $vote_count, $submitted, $approved, $flagged, $tags) = @_;
|
||||
my $self = {
|
||||
'id' => $id,
|
||||
'body' => $body,
|
||||
'notes' => (defined $notes && $notes ne '' ? $notes : undef),
|
||||
'rating' => $rating,
|
||||
'vote_count' => $vote_count,
|
||||
'submitted' => $submitted,
|
||||
'approved' => $approved,
|
||||
'flagged' => $flagged,
|
||||
'tags' => (ref $tags eq 'ARRAY' ? [ sort @$tags ] : [])
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub get_id {
|
||||
my $self = shift;
|
||||
return $self->{'id'};
|
||||
}
|
||||
|
||||
sub set_id {
|
||||
my $self = shift;
|
||||
return ($self->{'id'} = shift);
|
||||
}
|
||||
|
||||
sub get_body {
|
||||
my $self = shift;
|
||||
return $self->{'body'};
|
||||
}
|
||||
|
||||
sub set_body {
|
||||
my $self = shift;
|
||||
return ($self->{'body'} = shift);
|
||||
}
|
||||
|
||||
sub get_notes {
|
||||
my $self = shift;
|
||||
return $self->{'notes'};
|
||||
}
|
||||
|
||||
sub set_notes {
|
||||
my ($self, $notes) = @_;
|
||||
return ($self->{'notes'}
|
||||
= (defined $notes && $notes ne '' ? $notes : undef));
|
||||
}
|
||||
|
||||
sub get_rating {
|
||||
my $self = shift;
|
||||
return $self->{'rating'};
|
||||
}
|
||||
|
||||
sub set_vote_count {
|
||||
my $self = shift;
|
||||
return ($self->{'vote_count'} = shift);
|
||||
}
|
||||
|
||||
sub get_vote_count {
|
||||
my $self = shift;
|
||||
return $self->{'vote_count'};
|
||||
}
|
||||
|
||||
sub set_rating {
|
||||
my $self = shift;
|
||||
return ($self->{'rating'} = shift);
|
||||
}
|
||||
|
||||
sub get_date_submitted {
|
||||
my $self = shift;
|
||||
return $self->{'submitted'};
|
||||
}
|
||||
|
||||
sub set_date_submitted {
|
||||
my $self = shift;
|
||||
return ($self->{'submitted'} = shift);
|
||||
}
|
||||
|
||||
sub is_approved {
|
||||
my $self = shift;
|
||||
return $self->{'approved'};
|
||||
}
|
||||
|
||||
sub set_approved {
|
||||
my $self = shift;
|
||||
return ($self->{'approved'} = shift);
|
||||
}
|
||||
|
||||
sub is_flagged {
|
||||
my $self = shift;
|
||||
return $self->{'flagged'};
|
||||
}
|
||||
|
||||
sub set_flagged {
|
||||
my $self = shift;
|
||||
return ($self->{'flagged'} = shift);
|
||||
}
|
||||
|
||||
sub get_tags {
|
||||
my $self = shift;
|
||||
return $self->{'tags'};
|
||||
}
|
||||
|
||||
sub set_tags {
|
||||
my $self = shift;
|
||||
return ($self->{'tags'} = shift);
|
||||
}
|
||||
|
||||
*get_approved = \&is_approved;
|
||||
|
||||
*get_flagged = \&is_flagged;
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,962 @@
|
|||
###############################################################################
|
||||
# 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:: Administration.pm 305 2007-02-09 01:06:56Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Administration - Administration-section related routines
|
||||
of L<Chirpy::UI::WebApp>
|
||||
|
||||
=head1 TODO
|
||||
|
||||
Make this template-based and avoid inline calls to parents.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::UI::WebApp>, L<Chirpy::UI>, 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::WebApp::Administration;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::UI::WebApp 0.3;
|
||||
use Chirpy::Event 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
return bless { 'parent' => $parent }, $class;
|
||||
}
|
||||
|
||||
sub parent {
|
||||
my $self = shift;
|
||||
return $self->{'parent'};
|
||||
}
|
||||
|
||||
sub output {
|
||||
my ($self, %params) = @_;
|
||||
my $event_log_allowed = $self->parent()->administration_allowed(
|
||||
Chirpy::UI::VIEW_EVENT_LOG);
|
||||
if ($event_log_allowed && $self->parent()->_wants_xml()) {
|
||||
$self->_serve_event_log_table_data();
|
||||
return;
|
||||
}
|
||||
my $template = $self->parent()->_load_template('administration');
|
||||
my $locale = $self->parent()->locale();
|
||||
my ($upd_url, $upd_text);
|
||||
if (my $update = $self->parent()->{'available_update'}) {
|
||||
$template->param('UPDATE_AVAILABLE'
|
||||
=> &_text_to_xhtml($locale->get_string('update_available')));
|
||||
$template->param('UPDATE_AVAILABLE_TEXT' => &_text_to_xhtml(
|
||||
$locale->get_string('update_available_text',
|
||||
$update->[0], $update->[1])));
|
||||
$template->param('UPDATE_LINK_TEXT'
|
||||
=> &_text_to_xhtml($locale->get_string('update_link_text')));
|
||||
$template->param('UPDATE_URL' => &_text_to_xhtml($update->[2]));
|
||||
}
|
||||
elsif (my $errmsg = $self->parent()->{'update_check_error'}) {
|
||||
$template->param('UPDATE_CHECK_FAILED'
|
||||
=> &_text_to_xhtml($locale->get_string('update_check_failed')));
|
||||
$template->param('UPDATE_CHECK_FAILED_TEXT' => &_text_to_xhtml(
|
||||
$locale->get_string('update_check_failed_text')));
|
||||
$template->param('UPDATE_CHECK_ERROR_MESSAGE'
|
||||
=> &_text_to_xhtml($errmsg));
|
||||
}
|
||||
$template->param(
|
||||
'PAGE_TITLE' => &_text_to_xhtml(
|
||||
$locale->get_string('administration')),
|
||||
'APPROVE_QUOTES' => &_text_to_xhtml(
|
||||
$locale->get_string('approve_quotes')),
|
||||
'APPROVE_QUOTES_HTML'
|
||||
=> sub { return $self->get_approve_quotes_html(%params) },
|
||||
'APPROVE_QUOTES_ALLOWED'
|
||||
=> $self->parent()->administration_allowed(Chirpy::UI::MANAGE_UNAPPROVED_QUOTES),
|
||||
'APPROVE_QUOTES_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'FLAGGED_QUOTES' => &_text_to_xhtml(
|
||||
$locale->get_string('flagged_quotes')),
|
||||
'FLAGGED_QUOTES_HTML'
|
||||
=> sub { return $self->get_flagged_quotes_html(%params) },
|
||||
'FLAGGED_QUOTES_ALLOWED'
|
||||
=> $self->parent()->administration_allowed(Chirpy::UI::MANAGE_FLAGGED_QUOTES),
|
||||
'FLAGGED_QUOTES_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'MANAGE_QUOTES' => &_text_to_xhtml(
|
||||
$locale->get_string('manage_quotes')),
|
||||
'MANAGE_QUOTES_HTML'
|
||||
=> sub { return $self->get_manage_quotes_html(%params) },
|
||||
'MANAGE_QUOTES_ALLOWED'
|
||||
=> $self->parent()->administration_allowed(Chirpy::UI::EDIT_QUOTE)
|
||||
&& $self->parent()->administration_allowed(Chirpy::UI::REMOVE_QUOTE),
|
||||
'MANAGE_QUOTES_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'MANAGE_NEWS' => &_text_to_xhtml(
|
||||
$locale->get_string('manage_news')),
|
||||
'MANAGE_NEWS_HTML'
|
||||
=> sub { return $self->get_manage_news_html(%params) },
|
||||
'MANAGE_NEWS_ALLOWED'
|
||||
=> $self->parent()->administration_allowed(Chirpy::UI::ADD_NEWS)
|
||||
&& $self->parent()->administration_allowed(Chirpy::UI::EDIT_NEWS)
|
||||
&& $self->parent()->administration_allowed(Chirpy::UI::REMOVE_NEWS),
|
||||
'MANAGE_NEWS_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'MANAGE_ACCOUNTS' => &_text_to_xhtml(
|
||||
$locale->get_string('manage_accounts')),
|
||||
'MANAGE_ACCOUNTS_HTML'
|
||||
=> sub { return $self->get_manage_accounts_html(%params) },
|
||||
'MANAGE_ACCOUNTS_ALLOWED'
|
||||
=> $self->parent()->administration_allowed(Chirpy::UI::ADD_ACCOUNT)
|
||||
&& $self->parent()->administration_allowed(Chirpy::UI::EDIT_ACCOUNT)
|
||||
&& $self->parent()->administration_allowed(Chirpy::UI::REMOVE_ACCOUNT),
|
||||
'MANAGE_ACCOUNTS_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'VIEW_EVENT_LOG' => &_text_to_xhtml(
|
||||
$locale->get_string('view_event_log')),
|
||||
'VIEW_EVENT_LOG_HTML'
|
||||
=> sub { return $self->get_event_log_html(%params) },
|
||||
'VIEW_EVENT_LOG_ALLOWED'
|
||||
=> $event_log_allowed,
|
||||
'VIEW_EVENT_LOG_NOT_ALLOWED_HTML'
|
||||
=> sub { return $self->get_access_disallowed_html() },
|
||||
'CHANGE_PASSWORD' => &_text_to_xhtml(
|
||||
$locale->get_string('change_password')),
|
||||
'CHANGE_PASSWORD_HTML'
|
||||
=> sub { return $self->get_change_password_html(%params) },
|
||||
'ACTION_IS_' . $self->parent()->_admin_action() => 1
|
||||
);
|
||||
$self->parent()->_output_template($template);
|
||||
}
|
||||
|
||||
sub get_approve_quotes_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
my $quotes = $self->parent()->parent()->get_unapproved_quotes();
|
||||
if (defined $quotes) {
|
||||
my $html = '<script type="text/javascript" src="'
|
||||
. &_text_to_xhtml($self->parent()->_resources_url())
|
||||
. '/js/administration.js"></script>' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'MANAGE_UNAPPROVED_QUOTES'},
|
||||
1
|
||||
) . '">' . $/
|
||||
. '<ul id="unapproved-quotes-list" class="quote-list">' . $/;
|
||||
foreach my $quote (@$quotes) {
|
||||
my $id = $quote->get_id();
|
||||
my $notes = $quote->get_notes();
|
||||
my $tags = $quote->get_tags();
|
||||
$html .= '<li>' . $/
|
||||
. '<div class="quote-container">' . $/
|
||||
. '<h3 class="quote-header">'
|
||||
. '<span class="quote-id">#' . $quote->get_id() . '</span> '
|
||||
. ($self->parent()->moderation_queue_is_public()
|
||||
? '<span class="quote-rating">'
|
||||
. Chirpy::Util::format_quote_rating($quote->get_rating())
|
||||
. '</span>'
|
||||
. '<span class="quote-vote-count">/<span>'
|
||||
. $quote->get_vote_count()
|
||||
. '</span></span> '
|
||||
: '')
|
||||
. '<span class="quote-date">'
|
||||
. $self->parent()->format_date_time($quote->get_date_submitted())
|
||||
. '</span>' . $/
|
||||
. '<a href="javascript:editQuote(' . $id . ');" '
|
||||
. 'class="quote-edit" id="quote-edit-' . $id . '">['
|
||||
. &_text_to_xhtml($locale->get_string('edit'))
|
||||
. ']</a>'
|
||||
. '</h3>' . $/
|
||||
. '<div class="quote-data" id="quote-data-' . $id . '">' . $/
|
||||
. '<blockquote class="quote-body">' . $/
|
||||
. '<p id="quote-body-' . $id . '">' . &_text_to_xhtml($quote->get_body())
|
||||
. '</p>' . $/
|
||||
. '</blockquote>' . $/
|
||||
. (defined $notes || @$tags ?
|
||||
'<div class="quote-footer">' . (defined $notes
|
||||
? '<div class="quote-notes">' . $/
|
||||
. '<p><em class="quote-notes-title">Notes:</em>' . $/
|
||||
. '<span id="quote-notes-' . $id . '">'
|
||||
. &_text_to_xhtml($notes)
|
||||
. '</span></p>' . $/
|
||||
. '</div>' . $/
|
||||
: '')
|
||||
. (@$tags
|
||||
? '<div class="quote-tags">' . $/
|
||||
. '<p><em class="quote-tags-title">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('quote_tags_title'))
|
||||
. '</em>' . $/
|
||||
. '<span id="quote-tags-' . $id . '">'
|
||||
. &_text_to_xhtml(join(' ', @$tags))
|
||||
. '</span></p>' . $/
|
||||
. '</div>' . $/
|
||||
: '')
|
||||
. '</div>' : '')
|
||||
. '</div>' . $/
|
||||
. '</div>' . $/
|
||||
. '<div class="approval-options">' . $/
|
||||
. '<input type="radio" class="approve-do-nothing-rb" name="action_' . $id
|
||||
. '" value="0" id="a' . $id . '-0" '
|
||||
. 'checked="checked" /> <label '
|
||||
. 'for="a' . $id . '-0">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('do_nothing'))
|
||||
. '</label>' . $/
|
||||
. '<input type="radio" class="approve-rb" name="action_' . $id
|
||||
. '" value="1" id="a' . $id . '-1" /> <label '
|
||||
. 'for="a' . $id . '-1">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('approve_unapproved_quote'))
|
||||
. '</label>' . $/
|
||||
. '<input type="radio" class="discard-rb" name="action_' . $id
|
||||
. '" value="2" id="a' . $id . '-2" /> <label '
|
||||
. 'for="a' . $id . '-2">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('discard_unapproved_quote'))
|
||||
. '</label>' . $/
|
||||
. '</div>' . $/
|
||||
. '</li>' . $/;
|
||||
}
|
||||
$html .= '</ul>' . $/
|
||||
. '<div id="approve-submit-container">' . $/
|
||||
. '<div id="mass-approve-container">' . $/
|
||||
. '<input type="button" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('approve_all_unapproved_quotes'))
|
||||
. '" id="approve-all-button" onclick="if (confirm(\''
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('approve_all_unapproved_quotes_confirm'))
|
||||
. '\')) { var a = document.getElementsByTagName(\'input\'); '
|
||||
. 'for (var i = 0; i < a.length; i++) '
|
||||
. 'if (a[i].className == \'approve-rb\') a[i].checked = true; '
|
||||
. 'submit(); }" />' . $/
|
||||
. '<input type="button" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('discard_all_unapproved_quotes'))
|
||||
. '" id="discard-all-button" onclick="if (confirm(\''
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('discard_all_unapproved_quotes_confirm'))
|
||||
. '\')) { var a = document.getElementsByTagName(\'input\'); '
|
||||
. 'for (var i = 0; i < a.length; i++) '
|
||||
. 'if (a[i].className == \'discard-rb\') a[i].checked = true; '
|
||||
. 'submit(); }" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('update_database'))
|
||||
. '" id="approve-submit-button" />' . $/
|
||||
. '<input type="reset" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('reset_form'))
|
||||
. '" id="approve-reset-button" onclick="'
|
||||
. 'var a = document.getElementsByTagName(\'input\'); '
|
||||
. 'for (var i = 0; i < a.length; i++) '
|
||||
. 'if (a[i].className == \'approve-do-nothing-rb\') a[i].checked = true; '
|
||||
. 'return false;" />' . $/
|
||||
. '</div>' . $/
|
||||
. '</form>';
|
||||
return $html;
|
||||
}
|
||||
else {
|
||||
return '<p>' . &_text_to_xhtml(
|
||||
$locale->get_string('no_unapproved_quotes')) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
sub get_flagged_quotes_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
my $quotes = $self->parent()->parent()->get_flagged_quotes();
|
||||
if (defined $quotes) {
|
||||
my $html = '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'MANAGE_FLAGGED_QUOTES'},
|
||||
1
|
||||
) . '">' . $/
|
||||
. '<ul id="flagged-quotes-list" class="quote-list">' . $/;
|
||||
foreach my $quote (@$quotes) {
|
||||
my $id = $quote->get_id();
|
||||
my $notes = $quote->get_notes();
|
||||
my $tags = $quote->get_tags();
|
||||
$html .= '<li>' . $/
|
||||
. '<div class="quote-container">' . $/
|
||||
. '<h3 class="quote-header">'
|
||||
. '<span class="quote-id">#' . $quote->get_id() . '</span> '
|
||||
. '<span class="quote-rating">'
|
||||
. Chirpy::Util::format_quote_rating($quote->get_rating())
|
||||
. '</span>'
|
||||
. '<span class="quote-vote-count">/<span>'
|
||||
. $quote->get_vote_count()
|
||||
. '</span></span> '
|
||||
. '<span class="quote-date">'
|
||||
. $self->parent()->format_date_time($quote->get_date_submitted())
|
||||
. '</span>'
|
||||
. '</h3>' . $/
|
||||
. '<blockquote class="quote-body">' . $/
|
||||
. '<p>' . &_text_to_xhtml($quote->get_body())
|
||||
. '</p>' . $/
|
||||
. '</blockquote>' . $/
|
||||
. (defined $notes || @$tags ?
|
||||
'<div class="quote-footer">' . (defined $notes
|
||||
? '<div class="quote-notes">' . $/
|
||||
. '<p><em class="quote-notes-title">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('quote_notes_title'))
|
||||
. '</em>' . $/
|
||||
. &_text_to_xhtml($notes)
|
||||
. '</p>' . $/
|
||||
. '</div>' . $/
|
||||
: '')
|
||||
. (@$tags
|
||||
? '<div class="quote-tags">' . $/
|
||||
. '<p><em class="quote-tags-title">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('quote_tags_title'))
|
||||
. '</em>' . $/
|
||||
. &_text_to_xhtml(join(' ', @$tags))
|
||||
. '</p>' . $/
|
||||
. '</div>' . $/
|
||||
: '')
|
||||
. '</div>' : '')
|
||||
. '</div>' . $/
|
||||
. '<div class="flag-options">' . $/
|
||||
. '<input type="radio" name="action_' . $id
|
||||
. '" value="0" id="a' . $id . '-0" '
|
||||
. 'checked="checked" /> <label '
|
||||
. 'for="a' . $id . '-0">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('do_nothing'))
|
||||
. '</label>' . $/
|
||||
. '<input type="radio" class="keep-rb" name="action_' . $id
|
||||
. '" value="1" id="a' . $id . '-1" /> <label '
|
||||
. 'for="a' . $id . '-1">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('keep_flagged_quote'))
|
||||
. '</label>' . $/
|
||||
. '<input type="radio" class="remove-rb" name="action_' . $id
|
||||
. '" value="2" id="a' . $id . '-2" /> <label '
|
||||
. 'for="a' . $id . '-2">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('remove_flagged_quote'))
|
||||
. '</label>' . $/
|
||||
. '</div>' . $/
|
||||
. '</li>' . $/;
|
||||
}
|
||||
$html .= '</ul>' . $/
|
||||
. '<div id="flag-submit-container">' . $/
|
||||
. '<div id="mass-unflag-container">' . $/
|
||||
. '<input type="button" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('keep_all_flagged_quotes'))
|
||||
. '" id="keep-all-button" onclick="if (confirm(\''
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('keep_all_flagged_quotes_confirm'))
|
||||
. '\')) { var a = document.getElementsByTagName(\'input\'); '
|
||||
. 'for (var i = 0; i < a.length; i++) '
|
||||
. 'if (a[i].className == \'keep-rb\') a[i].checked = true; '
|
||||
. 'submit(); }" />' . $/
|
||||
. '<input type="button" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('remove_all_flagged_quotes'))
|
||||
. '" id="discard-all-button" onclick="if (confirm(\''
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('remove_all_flagged_quotes_confirm'))
|
||||
. '\')) { var a = document.getElementsByTagName(\'input\'); '
|
||||
. 'for (var i = 0; i < a.length; i++) '
|
||||
. 'if (a[i].className == \'remove-rb\') a[i].checked = true; '
|
||||
. 'submit(); }" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('update_database'))
|
||||
. '" id="flag-submit-button" />' . $/
|
||||
. '<input type="reset" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('reset_form'))
|
||||
. '" id="flag-reset-button" />' . $/
|
||||
. '</div>' . $/
|
||||
. '</form>';
|
||||
return $html;
|
||||
}
|
||||
else {
|
||||
return '<p>' . &_text_to_xhtml(
|
||||
$locale->get_string('no_flagged_quotes')) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
sub get_manage_quotes_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
if (my $quote = $params{'edit_quote'}) {
|
||||
my $notes = $quote->get_notes();
|
||||
my $tags = $quote->get_tags();
|
||||
return '<div id="edit-quote-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_QUOTE'},
|
||||
1,
|
||||
'id' => $quote->get_id()
|
||||
) . '"><div id="quote-container">' . $/
|
||||
. '<label for="quote-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('submission_title'))
|
||||
. '</label>' . $/
|
||||
. '<textarea name="quote" id="quote-field"' . $/
|
||||
. 'rows="8" cols="80">'
|
||||
. Chirpy::Util::encode_xml_entities($quote->get_body())
|
||||
. '</textarea></div>' . $/
|
||||
. '<div id="notes-container">' . $/
|
||||
. '<label for="notes-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('notes_title'))
|
||||
. '</label>' . $/
|
||||
. '<textarea name="notes" id="notes-field"' . $/
|
||||
. 'rows="3" cols="80">'
|
||||
. (defined $notes ? Chirpy::Util::encode_xml_entities($notes) : '')
|
||||
. '</textarea></div>' . $/
|
||||
. '<div id="tags-container">' . $/
|
||||
. '<label for="tags-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('tags_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="text" name="tags" value="'
|
||||
. (@$tags ? &_text_to_xhtml(join(' ', @$tags)) : '')
|
||||
. '" id="tags-field" /></div>' . $/
|
||||
. '<div id="edit-quote-submit-container">' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml($locale->get_string('save_quote'))
|
||||
. '" id="edit-quote-submit-button" />' . $/
|
||||
. '<input type="reset" value="'
|
||||
. &_text_to_xhtml($locale->get_string('reset_form'))
|
||||
. '" id="edit-quote-reset-button" />' . $/
|
||||
. '</div></form></div>';
|
||||
}
|
||||
if (my $quote = $params{'confirm_quote_removal'}) {
|
||||
my ($body, $notes, $tags) = $self->parent()->_format_quote($quote);
|
||||
return '<div id="quote-removal-confirmation-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'REMOVE_QUOTE'},
|
||||
1,
|
||||
'id' => $quote->get_id()
|
||||
) . '">' . $/
|
||||
. '<p id="quote-removal-confirmation-request">'
|
||||
. &_text_to_xhtml($locale->get_string('quote_removal_confirmation'))
|
||||
. '</p>' . $/
|
||||
. '<blockquote class="quote-body"><p>'
|
||||
. $body . '</p></blockquote>' . $/
|
||||
. '<div id="quote-removal-confirmation-submit-container">' . $/
|
||||
. '<input type="submit" name="confirm" value="'
|
||||
. &_text_to_xhtml($locale->get_string('remove_quote'))
|
||||
. '" id="quote-removal-confirmation-submit-button" />' . $/
|
||||
. '<input type="button" value="'
|
||||
. &_text_to_xhtml($locale->get_string('cancel'))
|
||||
. '" id="quote-removal-confirmation-cancel-button"'
|
||||
. ' onclick="history.go(-1);" />' . $/
|
||||
. '</div></form></div>';
|
||||
}
|
||||
my $result;
|
||||
if ($params{'quote_removed'}) {
|
||||
$result = $locale->get_string('quote_removed');
|
||||
}
|
||||
elsif ($params{'quote_to_edit_not_found'}) {
|
||||
$result = $locale->get_string('quote_to_edit_not_found');
|
||||
}
|
||||
elsif ($params{'quote_to_remove_not_found'}) {
|
||||
$result = $locale->get_string('quote_to_remove_not_found');
|
||||
}
|
||||
elsif ($params{'quote_modified'}) {
|
||||
$result = $locale->get_string('quote_modified');
|
||||
}
|
||||
return (defined $result
|
||||
? '<p id="manage-quotes-result">'
|
||||
. &_text_to_xhtml($result) . '</p>'
|
||||
: '')
|
||||
. '<p id="manage-quote-instructions">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('webapp.manage_quote_instructions'))
|
||||
. '</p>' . $/
|
||||
. '<div id="quick-manage-quote-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(undef, 1)
|
||||
. '" onsubmit="return (!document.getElementById'
|
||||
. '(\'quote-remove\').checked || confirm("'
|
||||
. &_text_to_xhtml($locale->get_string(
|
||||
'webapp.remove_quote_without_viewing_confirmation'))
|
||||
. '"))">' . $/
|
||||
. '<div id="quick-manage-quote-id-container"><label' . $/
|
||||
. 'for="quick-manage-quote-id-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('quote_id_title')) . '</label>' . $/
|
||||
. '<input name="id" id="quick-manage-quote-id-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="quick-manage-quote-options">' . $/
|
||||
. '<input type="radio" name="admin_action" value="'
|
||||
. Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_QUOTE'} . '"' . $/
|
||||
. 'id="quote-edit" checked="checked" /> <label for="quote-edit">'
|
||||
. &_text_to_xhtml($locale->get_string('edit'))
|
||||
. '</label>' . $/
|
||||
. '<input type="radio" name="admin_action" value="'
|
||||
. Chirpy::UI::WebApp::ADMIN_ACTIONS->{'REMOVE_QUOTE'} . '"' . $/
|
||||
. 'id="quote-remove" /> <label for="quote-remove">'
|
||||
. &_text_to_xhtml($locale->get_string('remove'))
|
||||
. '</label></div>' . $/
|
||||
. '<div id="quick-manage-quote-submit">'
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml($locale->get_string('go'))
|
||||
. '→" /></div>' . $/
|
||||
. '</form></div>';
|
||||
}
|
||||
|
||||
sub get_manage_news_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
if (my $item = $params{'edit_news_item'}) {
|
||||
my $html = '<div id="edit-news-item-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'EDIT_NEWS'},
|
||||
1,
|
||||
'id' => $item->get_id())
|
||||
. '">' . $/
|
||||
. '<div id="news-item-container">' . $/
|
||||
. '<textarea name="body" id="news-item-field"' . $/
|
||||
. 'rows="8" cols="80">'
|
||||
. Chirpy::Util::encode_xml_entities($item->get_body())
|
||||
. '</textarea></div>' . $/
|
||||
. '<div id="news-item-poster-container">' . $/
|
||||
. '<label for="news-item-poster-select">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('news_poster_title'))
|
||||
. '</label>' . $/
|
||||
. '<select name="poster" id="news-item-poster-select">' . $/
|
||||
. '<option>('
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('unknown'))
|
||||
. ')</option>' . $/;
|
||||
my $posters = $self->parent()->get_news_posters();
|
||||
if (defined $posters) {
|
||||
foreach my $account (@$posters) {
|
||||
my $p = $item->get_poster();
|
||||
my $level = $account->get_level();
|
||||
$html .= '<option value="' . $account->get_id() . '"'
|
||||
. (defined $p && $account->get_id() == $p->get_id()
|
||||
? ' selected="selected"' : '')
|
||||
. ' class="user-level-' . $level . '">'
|
||||
. $account->get_username()
|
||||
. ' <' . $self->parent()->parent()->user_level_name($level)
|
||||
. '></option>' . $/;
|
||||
}
|
||||
}
|
||||
return $html . '</select>'
|
||||
. '</div>' . $/
|
||||
. '<div id="edit-news-item-submit-container">' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('save_news_item'))
|
||||
. '" id="edit-news-item-submit-button" />' . $/
|
||||
. '<input type="reset" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('reset_form'))
|
||||
. '"' . $/
|
||||
. 'id="edit-news-item-reset-button" />' . $/
|
||||
. '</div></form></div>';
|
||||
}
|
||||
my $result;
|
||||
if ($params{'news_item_added'}) {
|
||||
$result = $locale->get_string('news_item_added');
|
||||
}
|
||||
elsif ($params{'news_item_modified'}) {
|
||||
$result = $locale->get_string('news_item_modified');
|
||||
}
|
||||
elsif ($params{'news_item_to_edit_not_found'}) {
|
||||
$result = $locale->get_string('news_item_to_edit_not_found');
|
||||
}
|
||||
elsif ($params{'news_item_removed'}) {
|
||||
$result = $locale->get_string('news_item_removed');
|
||||
}
|
||||
elsif ($params{'news_item_to_remove_not_found'}) {
|
||||
$result = $locale->get_string('news_item_to_remove_not_found');
|
||||
}
|
||||
return (defined $result
|
||||
? '<p id="manage-news-items-result">'
|
||||
. &_text_to_xhtml($result) . '</p>'
|
||||
: '')
|
||||
. '<div id="post-news-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'ADD_NEWS'},
|
||||
1
|
||||
) . '">' . $/
|
||||
. '<div id="news-container">' . $/
|
||||
. '<label for="news-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('new_news_item_title'))
|
||||
. '</label>' . $/
|
||||
. '<textarea name="news" id="news-field" rows="8" cols="80">'
|
||||
. '</textarea>' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="submit-news-container">' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml($locale->get_string('add_news_item'))
|
||||
. '" id="submit-news-button" />' . $/
|
||||
. '<input type="reset" value="'
|
||||
. &_text_to_xhtml($locale->get_string('reset_form'))
|
||||
. '" id="reset-news-button" />' . $/
|
||||
. '</div>' . $/
|
||||
. '</form>' . $/
|
||||
. '</div>' . $/
|
||||
. '<p id="news-instructions">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('webapp.manage_news_instructions'))
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
sub get_manage_accounts_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
my $status_message;
|
||||
if ($params{'account_to_modify_not_found'}) {
|
||||
$status_message = $locale->get_string('account_to_modify_not_found');
|
||||
}
|
||||
elsif ($params{'account_to_remove_not_found'}) {
|
||||
$status_message = $locale->get_string('account_to_remove_not_found');
|
||||
}
|
||||
elsif ($params{'last_owner_account_removal'}) {
|
||||
$status_message = $locale->get_string(
|
||||
'last_owner_account_removal_error');
|
||||
}
|
||||
elsif ($params{'modified_account_information_required'}) {
|
||||
$status_message = $locale->get_string(
|
||||
'modified_account_information_required');
|
||||
}
|
||||
elsif ($params{'invalid_username'}) {
|
||||
$status_message = $locale->get_string('invalid_username');
|
||||
}
|
||||
elsif ($params{'username_exists'}) {
|
||||
$status_message = $locale->get_string('username_exists');
|
||||
}
|
||||
elsif ($params{'invalid_password'}) {
|
||||
$status_message = $locale->get_string('invalid_password');
|
||||
}
|
||||
elsif ($params{'different_passwords'}) {
|
||||
$status_message = $locale->get_string('different_passwords');
|
||||
}
|
||||
elsif ($params{'invalid_user_level'}) {
|
||||
$status_message = $locale->get_string('invalid_user_level');
|
||||
}
|
||||
elsif ($params{'account_removed'}) {
|
||||
$status_message = $locale->get_string('account_removed');
|
||||
}
|
||||
elsif ($params{'account_modified'}) {
|
||||
$status_message = $locale->get_string('account_modified');
|
||||
}
|
||||
elsif ($params{'account_created'}) {
|
||||
$status_message = $locale->get_string('account_created');
|
||||
}
|
||||
my $html = '<form method="post" action="'
|
||||
. $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'ADD_ACCOUNT'},
|
||||
1
|
||||
) . '">' . $/
|
||||
. '<div id="account-manager">' . $/
|
||||
. '<div id="username-select-container">' . $/
|
||||
. '<select name="id" id="username-select" size="16">' . $/
|
||||
. '<option value="-1" id="username-new-user" selected="selected">'
|
||||
. '<< '
|
||||
. &_text_to_xhtml($locale->get_string('new_account'))
|
||||
. ' >></option>' . $/;
|
||||
my $users = $self->parent()->parent()->get_accounts();
|
||||
if (defined $users) {
|
||||
foreach my $user (@$users) {
|
||||
$html .= '<option value="' . $user->get_id()
|
||||
. '" class="user-level-' . $user->get_level()
|
||||
. '">' . $user->get_username() . ' <'
|
||||
. &_text_to_xhtml(
|
||||
$self->parent()->parent()->user_level_name($user->get_level()))
|
||||
. '></option>' . $/;
|
||||
}
|
||||
}
|
||||
$html .= '</select>' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="username-container">' . $/
|
||||
. '<label for="username-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('new_username_title'))
|
||||
. '</label>' . $/
|
||||
. '<input name="new_username" id="username-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="password-container">' . $/
|
||||
. '<label for="password-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('new_password_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="password" name="new_password" '
|
||||
. 'id="password-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="repeat-password-container">' . $/
|
||||
. '<label for="repeat-password-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('repeat_new_password_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="password" name="new_password_repeat" '
|
||||
. 'id="repeat-password-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="level-container">' . $/
|
||||
. '<label for="level-select">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('new_user_level_title'))
|
||||
. '</label>' . $/
|
||||
. '<select name="new_level" id="level-select" size="1">' . $/
|
||||
. '<option value="-1" selected="selected"><'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('no_change'))
|
||||
. '></option>' . $/;
|
||||
foreach my $level ($self->parent()->parent()->user_levels()) {
|
||||
$html .= '<option value="' . $level . '" class="user-level-'
|
||||
. $level . '">' . &_text_to_xhtml(
|
||||
$self->parent()->parent()->user_level_name($level))
|
||||
. ' <' . $level . '></option>' . $/;
|
||||
}
|
||||
$html .= '</select>' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="account-submit-container">' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml($locale->get_string('update_accounts'))
|
||||
. '" id="account-submit-button" />' . $/
|
||||
. '<input type="submit" name="account_remove" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('remove_account'))
|
||||
. '" id="modify-account-remove-button" onclick="return confirm("'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('account_removal_confirmation'))
|
||||
. '")" />' . $/
|
||||
. '</div>' . $/
|
||||
. (defined $status_message
|
||||
? '<div id="account-manager-result">' . $/
|
||||
. &_text_to_xhtml($status_message)
|
||||
. $/ . '</div>' . $/
|
||||
: '')
|
||||
. '</div>' . $/
|
||||
. '<div style="clear: both;"></div>' . $/
|
||||
. '</form>' . $/;
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub get_event_log_html {
|
||||
my $self = shift;
|
||||
my $locale = $self->parent()->locale();
|
||||
my $resurl = $self->parent()->_resources_url();
|
||||
my $url = $self->parent()->_url(
|
||||
Chirpy::UI::WebApp::ADMIN_ACTIONS->{'VIEW_EVENT_LOG'},
|
||||
1);
|
||||
$url .= ($url =~ /\?/ ? '&' : '?');
|
||||
my $html = '<script type="text/javascript" src="'
|
||||
. $resurl . '/js/ajax.js"></script>' . $/
|
||||
. '<script type="text/javascript" src="'
|
||||
. $resurl . '/js/administration.js"></script>' . $/
|
||||
. '<script type="text/javascript">' . $/
|
||||
. 'var eventLogURL = "' . $url . '";' . $/
|
||||
. 'var eventLogLocale = new Array();' . $/;
|
||||
foreach my $col (qw(id date username event empty)) {
|
||||
$html .= 'eventLogLocale["' . $col . '"] = "'
|
||||
. &_text_to_xhtml($locale->get_string($col)) . '";' . $/;
|
||||
}
|
||||
$html .= 'eventLogLocale["previous"] = "' . &_text_to_xhtml(
|
||||
$locale->get_string('webapp.previous_page_title')) . '";' . $/
|
||||
. 'eventLogLocale["next"] = "' . &_text_to_xhtml(
|
||||
$locale->get_string('webapp.next_page_title')) . '";' . $/
|
||||
. 'eventLogLocale["current"] = "' . &_text_to_xhtml(
|
||||
$locale->get_string('webapp.current_page_title')) . '";' . $/
|
||||
. 'eventLogLocale["loading"] = "' . &_text_to_xhtml(
|
||||
$locale->get_string('processing')) . '";' . $/
|
||||
. 'eventLogLocale["guest"] = "' . &_text_to_xhtml(
|
||||
$locale->get_string('guest')) . '";' . $/;
|
||||
foreach my $id (qw/code data user asc/) {
|
||||
my $val = $self->parent()->_cgi_param($id);
|
||||
next unless (defined $val);
|
||||
$html .= 'eventLogURLParam["' . $id . '"] = "'
|
||||
. &_text_to_xhtml($val) . '";' . $/;
|
||||
}
|
||||
$html .= '</script>' . $/
|
||||
. '<div id="event-log-placeholder"></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub _serve_event_log_table_data {
|
||||
my $self = shift;
|
||||
my $locale = $self->parent()->locale();
|
||||
my $count = $self->parent()->_cgi_param('count');
|
||||
$count = (defined $count ? int $count : undef);
|
||||
my $start = $self->parent()->_cgi_param('start');
|
||||
$start = 0 unless (defined $start && $start >= 0);
|
||||
my $desc = ($self->parent()->_cgi_param('asc') ? 0 : 1);
|
||||
my $user = $self->parent()->_cgi_param('user');
|
||||
$user = undef if (defined $user && $user !~ /^\d+$/);
|
||||
my $event = $self->parent()->_cgi_param('code');
|
||||
$event = undef if (defined $event && $event !~ /^\d+$/);
|
||||
my $filter = $self->parent()->_cgi_param('data');
|
||||
if (defined $filter && $filter =~ /^([^=]+)=(.*)$/s) {
|
||||
$filter = { $1 => $2 };
|
||||
}
|
||||
else {
|
||||
$filter = undef;
|
||||
}
|
||||
my ($events, $leading, $trailing) = $self->parent()->parent()->get_events(
|
||||
$start, $count, $desc, $event, $user, $filter);
|
||||
my @events = ();
|
||||
foreach my $event (@$events) {
|
||||
my $id = $event->get_id();
|
||||
my $date = $self->parent()->format_date_time($event->get_date());
|
||||
my $user = $event->get_user();
|
||||
my $username;
|
||||
if (defined $user) {
|
||||
my $acct = $self->parent()->parent()->get_account_by_id($user);
|
||||
if (defined $acct) {
|
||||
$username = $acct->get_username();
|
||||
}
|
||||
}
|
||||
my $description = &_text_to_xhtml($locale->get_string(
|
||||
'event_' . $event->get_code() . '_name'));
|
||||
my $data = $event->get_data();
|
||||
my $result = {
|
||||
'id' => $id,
|
||||
'date' => $date,
|
||||
(defined $username ? ('username' => $username) : ()),
|
||||
'userid' => (defined $user ? $user : 0),
|
||||
'description' => $description,
|
||||
'code' => $event->get_code()
|
||||
};
|
||||
my @data = ();
|
||||
foreach my $key (sort keys %$data) {
|
||||
my $value = $data->{$key};
|
||||
push @data, {
|
||||
'name' => &_text_to_xhtml($key),
|
||||
'value' => &_text_to_xhtml($value)
|
||||
};
|
||||
}
|
||||
$result->{'data'} = \@data;
|
||||
push @events, $result;
|
||||
}
|
||||
$self->parent()->_output_xml('result', {
|
||||
'event' => \@events,
|
||||
($leading ? ('leading' => 'true') : ()),
|
||||
($trailing ? ('trailing' => 'true') : ())
|
||||
});
|
||||
}
|
||||
|
||||
sub get_access_disallowed_html {
|
||||
my $self = shift;
|
||||
return '<p id="insufficient-administrator-privileges-notification">'
|
||||
. &_text_to_xhtml($self->parent()->locale()->get_string(
|
||||
'insufficient_administrative_privileges'))
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
sub get_change_password_html {
|
||||
my ($self, %params) = @_;
|
||||
my $locale = $self->parent()->locale();
|
||||
if ($params{'password_changed'}) {
|
||||
return '<div id="change-password-result">' . $/
|
||||
. '<p>' . $locale->get_string('password_changed_text') . '</p>' . $/
|
||||
. '</div>';
|
||||
}
|
||||
my $html = '';
|
||||
if (my $error = $params{'password_change_error'}) {
|
||||
$html = '<div id="change-password-error">' . $/ . '<p>'
|
||||
. &_text_to_xhtml($locale->get_string(
|
||||
$error == Chirpy::UI::NEW_PASSWORD_INVALID
|
||||
? 'change_password_new_password_invalid_text'
|
||||
: ($error == Chirpy::UI::PASSWORDS_DIFFER
|
||||
? 'change_password_passwords_differ_text'
|
||||
: ($error == Chirpy::UI::CURRENT_PASSWORD_INVALID
|
||||
? 'change_password_current_password_invalid_text'
|
||||
: undef))))
|
||||
. '</p>' . $/ . '</div>' . $/;
|
||||
}
|
||||
return $html . '<div id="change-password-form">' . $/
|
||||
. '<form method="post" action="'
|
||||
. $self->parent()->_url(Chirpy::UI::WebApp::ADMIN_ACTIONS->{'CHANGE_PASSWORD'}, 1)
|
||||
. '">' . $/
|
||||
. '<div id="current-password-container">' . $/
|
||||
. '<label for="current-password-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('current_password_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="password" name="current_password" '
|
||||
. 'id="current-password-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="new-password-container">' . $/
|
||||
. '<label for="new-password-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('new_password_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="password" name="new_password" '
|
||||
. 'id="new-password-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="repeat-new-password-container">' . $/
|
||||
. '<label for="repeat-new-password-field">'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('repeat_new_password_title'))
|
||||
. '</label>' . $/
|
||||
. '<input type="password" name="repeat_new_password" '
|
||||
. 'id="repeat-new-password-field" />' . $/
|
||||
. '</div>' . $/
|
||||
. '<div id="submit-container">' . $/
|
||||
. '<input type="submit" value="'
|
||||
. &_text_to_xhtml(
|
||||
$locale->get_string('change_password_button_label'))
|
||||
. '" id="change-password-button" />' . $/
|
||||
. '</div>' . $/
|
||||
. '</form>' . $/
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
*_text_to_xhtml = \&Chirpy::UI::WebApp::_text_to_xhtml;
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,117 @@
|
|||
###############################################################################
|
||||
# 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:: Captcha.pm 292 2007-02-05 21:28:55Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Captcha - Captcha provider interface
|
||||
|
||||
=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::WebApp::Captcha;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::Util 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $hash) = @_;
|
||||
my $self = {
|
||||
'parent' => $parent,
|
||||
'hash' => $hash
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub parent {
|
||||
my $self = shift;
|
||||
return $self->{'parent'};
|
||||
}
|
||||
|
||||
sub hash {
|
||||
my ($self, $hash) = @_;
|
||||
$self->{'hash'} = $hash if (defined $hash);
|
||||
return $self->{'hash'};
|
||||
}
|
||||
|
||||
sub data_path {
|
||||
my $self = shift;
|
||||
my $path = $self->parent()->configuration()->get('general', 'base_path')
|
||||
. '/cache/captcha';
|
||||
Chirpy::Util::ensure_writable_directory($path);
|
||||
return $path;
|
||||
}
|
||||
|
||||
sub base_path {
|
||||
my $self = shift;
|
||||
my $path = $self->param('captcha_path');
|
||||
return $path if (defined $path);
|
||||
return $self->parent()->configuration()->get('general', 'base_path')
|
||||
. '/../res/captcha';
|
||||
}
|
||||
|
||||
sub base_url {
|
||||
my $self = shift;
|
||||
my $url = $self->param('captcha_url');
|
||||
return $url if (defined $url);
|
||||
return $self->param('site_url') . '/res/captcha';
|
||||
}
|
||||
|
||||
sub param {
|
||||
my ($self, $name) = @_;
|
||||
return $self->parent()->param($name);
|
||||
}
|
||||
|
||||
*create = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*verify = \&Chirpy::Util::abstract_method;
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,132 @@
|
|||
###############################################################################
|
||||
# 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:: Authen_Captcha.pm 293 2007-02-05 22:33:34Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Captcha::Authen_Captcha - Captcha provider interface using
|
||||
L<Authen::Captcha>
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
This module uses the following parameters from your configuration file:
|
||||
|
||||
=over 4
|
||||
|
||||
=item webapp.authen_captcha_source_image_path
|
||||
|
||||
The physical path to the source images to be used by L<Authen::Captcha>.
|
||||
|
||||
=item webapp.authen_captcha_character_width
|
||||
|
||||
The pixel width of each character in a captcha image.
|
||||
|
||||
=item webapp.authen_captcha_character_height
|
||||
|
||||
The pixel height of each character in a captcha image.
|
||||
|
||||
=item webapp.authen_captcha_code_length
|
||||
|
||||
The number of characters in the captcha code.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::UI::WebApp::Captcha>, 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::WebApp::Captcha::Authen_Captcha;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION @ISA);
|
||||
|
||||
$VERSION = '0.3';
|
||||
@ISA = qw(Chirpy::UI::WebApp::Captcha);
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::UI::WebApp::Captcha 0.3;
|
||||
use Authen::Captcha;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->{'ac'} = new Authen::Captcha(
|
||||
'data_folder' => $self->data_path(),
|
||||
'output_folder' => $self->base_path()
|
||||
);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my ($self, $expire) = @_;
|
||||
my $ac = $self->{'ac'};
|
||||
$ac->expire($expire);
|
||||
my $length = $self->param('authen_captcha_code_length') || 4;
|
||||
my $imgpath = $self->param('authen_captcha_source_image_path');
|
||||
my $width = $self->param('authen_captcha_character_width');
|
||||
my $height = $self->param('authen_captcha_character_height');
|
||||
my $set_dimensions = ($width && $height);
|
||||
unless ($set_dimensions) {
|
||||
$width = 25;
|
||||
$height = 35;
|
||||
}
|
||||
if ($imgpath && -d $imgpath) {
|
||||
$ac->images_folder($imgpath);
|
||||
if ($set_dimensions) {
|
||||
$ac->width($width);
|
||||
$ac->height($height);
|
||||
}
|
||||
}
|
||||
my $hash = $ac->generate_code($length);
|
||||
my $imgurl = $self->base_url() . '/' . $hash . '.png';
|
||||
return ($hash, $imgurl, $length * $width, $height);
|
||||
}
|
||||
|
||||
sub verify {
|
||||
my ($self, $code) = @_;
|
||||
return ($self->{'ac'}->check_code($code, $self->hash()) == 1);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,278 @@
|
|||
###############################################################################
|
||||
# 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:: GD_SecurityImage.pm 301 2007-02-06 20:20:50Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Captcha::GD_SecurityImage - Captcha provider interface
|
||||
using L<GD::SecurityImage>
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
The following parameters from your configuration file affect the behavior of
|
||||
this module. Please see L<GD::SecurityImage's documentation|GD::SecurityImage>
|
||||
for a detailed explanation. Setting a value is optional for every parameter.
|
||||
|
||||
=over 4
|
||||
|
||||
=item webapp.gd_securityimage_width
|
||||
|
||||
=item webapp.gd_securityimage_height
|
||||
|
||||
=item webapp.gd_securityimage_ptsize
|
||||
|
||||
=item webapp.gd_securityimage_lines
|
||||
|
||||
=item webapp.gd_securityimage_font
|
||||
|
||||
=item webapp.gd_securityimage_gd_font
|
||||
|
||||
=item webapp.gd_securityimage_bgcolor
|
||||
|
||||
=item webapp.gd_securityimage_send_ctobg
|
||||
|
||||
=item webapp.gd_securityimage_frame
|
||||
|
||||
=item webapp.gd_securityimage_scramble
|
||||
|
||||
=item webapp.gd_securityimage_angle
|
||||
|
||||
=item webapp.gd_securityimage_thickness
|
||||
|
||||
=item webapp.gd_securityimage_rndmax
|
||||
|
||||
=item webapp.gd_securityimage_rnd_data
|
||||
|
||||
=item webapp.gd_securityimage_method
|
||||
|
||||
=item webapp.gd_securityimage_style
|
||||
|
||||
=item webapp.gd_securityimage_text_color
|
||||
|
||||
=item webapp.gd_securityimage_line_color
|
||||
|
||||
=item webapp.gd_securityimage_particle_density
|
||||
|
||||
=item webapp.gd_securityimage_particle_maxdots
|
||||
|
||||
=back
|
||||
|
||||
The value for C<rnd_data> should simply be a sequence of characters to use.
|
||||
Colors can only be passed as their hex values.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
This implementation is preliminary. You might have to set quite a few parameters
|
||||
to get it in a usable state.
|
||||
|
||||
If you have previously used C<Authen_Captcha> as a captcha provider, this module
|
||||
should adapt its stored captcha information flawlessly. Therefore,
|
||||
theoretically, you can switch back and forth between the two without any major
|
||||
problems.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::UI::WebApp::Captcha>, 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::WebApp::Captcha::GD_SecurityImage;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION @ISA);
|
||||
|
||||
$VERSION = '0.3';
|
||||
@ISA = qw(Chirpy::UI::WebApp::Captcha);
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::UI::WebApp::Captcha 0.3;
|
||||
use GD::SecurityImage;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
|
||||
sub create {
|
||||
my ($self, $expire) = @_;
|
||||
my ($image, $hash) = $self->_generate();
|
||||
$self->_write_img($hash, $image);
|
||||
$self->_add_entry($hash, $expire);
|
||||
my $url = $self->_img_url($hash);
|
||||
my $width = $self->param('gd_securityimage_width');
|
||||
my $height = $self->param('gd_securityimage_height');
|
||||
return ($hash, $url, $width, $height);
|
||||
}
|
||||
|
||||
sub verify {
|
||||
my ($self, $code) = @_;
|
||||
my $hash = $self->hash();
|
||||
return 0 unless (defined $hash);
|
||||
return $self->_check_entry($hash, $code);
|
||||
}
|
||||
|
||||
sub _add_entry {
|
||||
my ($self, $hash, $expire) = @_;
|
||||
my ($list, $update) = $self->_get_list();
|
||||
my $list_file = $self->_list_file();
|
||||
local *LIST;
|
||||
if ($update) {
|
||||
open(LIST, '>', $list_file)
|
||||
or Chirpy::die('Failed to open "' . $list . '" for writing: ' . $!);
|
||||
foreach my $line (@$list) {
|
||||
print LIST $line, $/;
|
||||
}
|
||||
}
|
||||
else {
|
||||
open(LIST, '>>', $list_file)
|
||||
or Chirpy::die('Failed to open "' . $list . '" for writing: ' . $!);
|
||||
}
|
||||
print LIST $expire, '::', $hash, $/;
|
||||
close(LIST);
|
||||
}
|
||||
|
||||
sub _check_entry {
|
||||
my ($self, $hash, $code) = @_;
|
||||
my ($list, $update, $found) = $self->_get_list($hash);
|
||||
$self->_write_list($list) if ($update);
|
||||
return ($found && md5_hex($code) eq $hash);
|
||||
}
|
||||
|
||||
sub _get_list {
|
||||
my ($self, $hash) = @_;
|
||||
my $list_file = $self->_list_file();
|
||||
return ([], 0, 0) unless (-f $list_file);
|
||||
my @list = ();
|
||||
my $update = 0;
|
||||
my $found = 0;
|
||||
my $now = time();
|
||||
local *LIST;
|
||||
open(LIST, '<', $list_file)
|
||||
or Chirpy::die('Failed to read from "' . $list_file . '": ' . $!);
|
||||
while (<LIST>) {
|
||||
chomp;
|
||||
my ($exp, $h) = split /::/;
|
||||
if ($exp < $now) {
|
||||
$update = 1;
|
||||
unlink $self->_img_path($h);
|
||||
}
|
||||
elsif (defined $hash && $hash eq $h) {
|
||||
$found = 1;
|
||||
$update = 1;
|
||||
unlink $self->_img_path($h);
|
||||
}
|
||||
else {
|
||||
push @list, $_;
|
||||
}
|
||||
}
|
||||
close(LIST);
|
||||
return (\@list, $update, $found);
|
||||
}
|
||||
|
||||
sub _write_list {
|
||||
my ($self, $list) = @_;
|
||||
my $list_file = $self->_list_file();
|
||||
open(LIST, '>', $list_file)
|
||||
or Chirpy::die('Failed to open "' . $list_file . '" for writing: ' . $!);
|
||||
foreach my $line (@$list) {
|
||||
print LIST $line, $/;
|
||||
}
|
||||
close LIST;
|
||||
}
|
||||
|
||||
sub _generate {
|
||||
my $self = shift;
|
||||
my $gdsi = $self->_gdsi();
|
||||
my $method = $self->param('gd_securityimage_method');
|
||||
my $style = $self->param('gd_securityimage_style');
|
||||
my $text_color = $self->param('gd_securityimage_text_color');
|
||||
my $line_color = $self->param('gd_securityimage_line_color');
|
||||
my $density = $self->param('gd_securityimage_particle_density');
|
||||
my $maxdots = $self->param('gd_securityimage_particle_maxdots');
|
||||
my ($image, $type, $rnd) = $gdsi->random()
|
||||
->create($method, $style, $text_color, $line_color)
|
||||
->particle($density, $maxdots)
|
||||
->out('force' => 'png', 'compress' => 1);
|
||||
my $hash = md5_hex($rnd);
|
||||
return ($image, $hash);
|
||||
}
|
||||
|
||||
sub _gdsi {
|
||||
my $self = shift;
|
||||
my @params = qw(width height ptsize lines font gd_font bgcolor send_ctobg
|
||||
frame scramble angle thickness rndmax);
|
||||
my %config = ();
|
||||
foreach my $param (@params) {
|
||||
my $value = $self->param('gd_securityimage_' . $param);
|
||||
$config{$param} = $value if (defined $value);
|
||||
}
|
||||
my $rnd_data = $self->param('gd_securityimage_rnd_data');
|
||||
if (defined $rnd_data) {
|
||||
$config{'rnd_data'} = [ split(//, $rnd_data) ];
|
||||
}
|
||||
return new GD::SecurityImage(%config);
|
||||
}
|
||||
|
||||
sub _write_img {
|
||||
my ($self, $hash, $image) = @_;
|
||||
my $path = $self->_img_path($hash);
|
||||
local *FILE;
|
||||
open(FILE, '>', $path)
|
||||
or Chirpy::die('Failed to open "' . $path . '" for writing: ' . $!);
|
||||
binmode FILE;
|
||||
print FILE $image;
|
||||
close FILE;
|
||||
}
|
||||
|
||||
sub _list_file {
|
||||
my $self = shift;
|
||||
return $self->data_path() . '/codes.txt';
|
||||
}
|
||||
|
||||
sub _img_path {
|
||||
my ($self, $hash) = @_;
|
||||
return $self->base_path() . '/' . $hash . '.png';
|
||||
}
|
||||
|
||||
sub _img_url {
|
||||
my ($self, $hash) = @_;
|
||||
return $self->base_url() . '/' . $hash . '.png';
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,231 @@
|
|||
###############################################################################
|
||||
# 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:: Session.pm 298 2007-02-06 17:08:08Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Session - Basic CGI session class
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::UI::WebApp::Session::DataManager>, 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::WebApp::Session;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION $NAME);
|
||||
|
||||
$VERSION = '0.3';
|
||||
$NAME = 'sid';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $create) = @_;
|
||||
|
||||
$create = 0 unless (defined $create);
|
||||
|
||||
my $dm = $parent->parent()->_data_manager();
|
||||
my $class_name = 'Chirpy::UI::WebApp::Session::DataManager';
|
||||
Chirpy::die('Data manager must implement ' . $class_name)
|
||||
unless (UNIVERSAL::isa($dm, $class_name));
|
||||
|
||||
$dm->remove_expired_sessions_if_necessary();
|
||||
|
||||
my $self = {
|
||||
'dm' => $dm,
|
||||
'data' => undef,
|
||||
'ro' => 0
|
||||
};
|
||||
bless $self, $class;
|
||||
|
||||
my $time = time();
|
||||
my $expire = $parent->param('session_expiry');
|
||||
$expire = ($expire ? &_parse_time($expire) : 3 * 24 * 60 * 60);
|
||||
|
||||
my $cgi = $parent->{'cgi'};
|
||||
my $ip = $cgi->remote_addr();
|
||||
if ($create) {
|
||||
my $sid = &_generate_id();
|
||||
$self->{'data'} = {
|
||||
'_SESSION_ID' => $sid,
|
||||
'_SESSION_CTIME' => $time,
|
||||
'_SESSION_ATIME' => $time,
|
||||
'_SESSION_ETIME' => $expire,
|
||||
'_SESSION_REMOTE_ADDR' => $ip
|
||||
};
|
||||
$dm->add_session($sid, $self->data());
|
||||
return $self;
|
||||
}
|
||||
else {
|
||||
my $sid = $cgi->cookie(-name => $NAME);
|
||||
if (&_valid_id($sid)) {
|
||||
my @result = $dm->get_sessions($sid);
|
||||
$self->{'data'} = $result[0] if (@result);
|
||||
if ($self->id()) {
|
||||
my $exp = $self->expire();
|
||||
if ($exp && $self->atime() + $exp < $time) {
|
||||
$self->delete();
|
||||
}
|
||||
elsif ($self->remote_addr() eq $ip) {
|
||||
return $self;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
unless ($self->read_only()) {
|
||||
$self->atime(time);
|
||||
$self->update();
|
||||
}
|
||||
}
|
||||
|
||||
sub param {
|
||||
my ($self, %params) = @_;
|
||||
my $name;
|
||||
Chirpy::die('Parameter name required') unless ($name = $params{'-name'});
|
||||
if (exists $params{'-value'}) {
|
||||
$self->{'data'}->{$name} = $params{'-value'};
|
||||
}
|
||||
return $self->{'data'}->{$name};
|
||||
}
|
||||
|
||||
sub delete {
|
||||
my $self = shift;
|
||||
my $id = $self->id();
|
||||
$self->{'dm'}->remove_sessions($self->id());
|
||||
$self->{'data'} = {};
|
||||
}
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
return $self->{'data'};
|
||||
}
|
||||
|
||||
sub id {
|
||||
my $self = shift;
|
||||
return $self->param(-name => '_SESSION_ID');
|
||||
}
|
||||
|
||||
sub ctime {
|
||||
my $self = shift;
|
||||
return $self->param(-name => '_SESSION_CTIME');
|
||||
}
|
||||
|
||||
sub atime {
|
||||
my ($self, $value) = @_;
|
||||
return (defined $value
|
||||
? $self->param(-name => '_SESSION_ATIME', -value => $value)
|
||||
: $self->param(-name => '_SESSION_ATIME'));
|
||||
}
|
||||
|
||||
sub expire {
|
||||
my ($self, $value) = @_;
|
||||
return (defined $value
|
||||
? $self->param(-name => '_SESSION_ETIME', -value => $value)
|
||||
: $self->param(-name => '_SESSION_ETIME'));
|
||||
}
|
||||
|
||||
sub remote_addr {
|
||||
my $self = shift;
|
||||
return $self->param(-name => '_SESSION_REMOTE_ADDR');
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
$self->{'dm'}->modify_session($self->id(), $self->data());
|
||||
}
|
||||
|
||||
sub read_only {
|
||||
my ($self, $value) = @_;
|
||||
if (defined $value) {
|
||||
$self->{'ro'} = $value;
|
||||
}
|
||||
return $self->{'ro'};
|
||||
}
|
||||
|
||||
sub _generate_id {
|
||||
require Digest::MD5;
|
||||
return Digest::MD5::md5_hex(time() . $$ . rand 9999);
|
||||
}
|
||||
|
||||
sub _valid_id {
|
||||
my $id = shift;
|
||||
return (defined $id && $id =~ /^[0-9a-f]{32}$/);
|
||||
}
|
||||
|
||||
sub _parse_time {
|
||||
my $time = shift;
|
||||
return $time if ($time =~ /^\d+$/);
|
||||
if ($time =~ /^([+-]?\d+)([smhdMy])$/) {
|
||||
my ($number, $unit) = ($1, $2);
|
||||
if ($unit eq 'y') {
|
||||
return $number * 365 * 24 * 60 * 60;
|
||||
}
|
||||
elsif ($unit eq 'M') {
|
||||
return $number * 30 * 24 * 60 * 60;
|
||||
}
|
||||
elsif ($unit eq 'd') {
|
||||
return $number * 24 * 60 * 60;
|
||||
}
|
||||
elsif ($unit eq 'h') {
|
||||
return $number * 60 * 60;
|
||||
}
|
||||
elsif ($unit eq 'm') {
|
||||
return $number * 60;
|
||||
}
|
||||
else {
|
||||
return $number;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,157 @@
|
|||
###############################################################################
|
||||
# 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:: DataManager.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UI::WebApp::Session::DataManager - Abstract data manager class specific
|
||||
to L<Chirpy::UI::WebApp::Session|Chirpy::UI::WebApp::Session>
|
||||
|
||||
=head1 USAGE
|
||||
|
||||
This class is required by L<Chirpy::UI::WebApp|Chirpy::UI::WebApp>'s session
|
||||
manager L<Chirpy::UI::WebApp::Session|Chirpy::UI::WebApp::Session>. It is an
|
||||
abstract class representing a data manager for session information.
|
||||
|
||||
If you wish to create an implementation of this class, the easiest way is to
|
||||
extend an existing L<Chirpy::DataManager|Chirpy::DataManager> implementation
|
||||
with this class's methods.
|
||||
|
||||
The class also has two non-abstract object method, namely
|
||||
C<remove_expired_sessions()> and C<remove_expired_sessions_if_necessary()>. The
|
||||
former returns a list containing the IDs of the sessions that have expired and
|
||||
have consequently been removed. The latter does the same, but only every 24
|
||||
hours; otherwise, it returns C<undef>.
|
||||
|
||||
=head1 IMPLEMENTATION
|
||||
|
||||
If you want to make your L<Chirpy::DataManager|Chirpy::DataManager> compatible
|
||||
with L<Chirpy::UI::WebApp::Session|Chirpy::UI::WebApp::Session>, all you need
|
||||
to do is implement a few extra object methods for creating, retrieving,
|
||||
updating and deleting sessions. You will probably also have to extend your
|
||||
C<set_up()> and C<remove()> methods accordingly. The extra methods to implement
|
||||
are as follows:
|
||||
|
||||
=over 4
|
||||
|
||||
=item add_session($id, $data)
|
||||
|
||||
Stores the session with ID C<$id> and session data C<$data>. C<$data> is a hash
|
||||
reference, so you will probably have to serialize it. How you do that is up to
|
||||
you, but L<Data::Dumper> makes it easy. Returns a true value upon success.
|
||||
|
||||
=item get_sessions(@ids)
|
||||
|
||||
Returns a list containing the data hash for each session whose ID is contained
|
||||
in C<@ids>, or all sessions if C<@ids> is empty. If no sessions are found,
|
||||
returns an empty list (and I<not> C<undef>).
|
||||
|
||||
=item modify_session($id, $data)
|
||||
|
||||
Updates the existing session with ID C<$id> with the data from the hash
|
||||
referred to by C<$data>.
|
||||
|
||||
=item remove_sessions(@ids)
|
||||
|
||||
Removes all sessions with an ID contained in C<@ids> from the system. Returns
|
||||
the number of removed sessions.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::UI::WebApp::Session>, L<Chirpy::DataManager::MySQL>,
|
||||
L<Chirpy::DataManager>, 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::WebApp::Session::DataManager;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
use constant CLEANUP_INTERVAL => 24 * 60 * 60;
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::Util 0.3;
|
||||
|
||||
sub remove_expired_sessions {
|
||||
my $self = shift;
|
||||
my @sessions = $self->get_sessions();
|
||||
return () unless (@sessions);
|
||||
my @remove = ();
|
||||
my $time = time;
|
||||
foreach my $data (@sessions) {
|
||||
if (my $exp = $data->{'_SESSION_ETIME'}) {
|
||||
my $at = $data->{'_SESSION_ATIME'};
|
||||
next unless ($exp + $at < $time);
|
||||
push @remove, $data->{'_SESSION_ID'};
|
||||
}
|
||||
}
|
||||
return () unless (@remove);
|
||||
$self->remove_sessions(@remove);
|
||||
return @remove;
|
||||
}
|
||||
|
||||
sub remove_expired_sessions_if_necessary {
|
||||
my $self = shift;
|
||||
my $now = time();
|
||||
my $last_cleanup = $self->get_parameter('last_session_cleanup');
|
||||
if (!defined $last_cleanup || $last_cleanup + CLEANUP_INTERVAL < $now) {
|
||||
$self->set_parameter('last_session_cleanup', $now);
|
||||
return $self->remove_expired_sessions();
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
*add_session = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*get_sessions = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*modify_session = \&Chirpy::Util::abstract_method;
|
||||
|
||||
*remove_sessions = \&Chirpy::Util::abstract_method;
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,146 @@
|
|||
###############################################################################
|
||||
# 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:: UpdateChecker.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::UpdateChecker - Update checker
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::UpdateChecker;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
use constant UPDATE_URL => Chirpy::URL . 'update/';
|
||||
use constant TIMEOUT => 5;
|
||||
use constant KEY_VERSION_NUMBER => 'VersionNumber';
|
||||
use constant KEY_RELEASE_DATE => 'ReleaseDate';
|
||||
use constant KEY_DETAIL_URL => 'DetailURL';
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
eval 'use LWP::UserAgent';
|
||||
my ($ua, $error);
|
||||
if ($@) {
|
||||
$error = 'LWP::UserAgent not available';
|
||||
}
|
||||
else {
|
||||
$ua = new LWP::UserAgent();
|
||||
$ua->timeout(TIMEOUT);
|
||||
$ua->env_proxy();
|
||||
}
|
||||
my $self = {
|
||||
'parent' => $parent,
|
||||
'ua' => $ua,
|
||||
'error' => $error
|
||||
};
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
sub check_for_updates {
|
||||
my $self = shift;
|
||||
return 1 unless ($self->{'ua'});
|
||||
my $info = $self->get_version_information();
|
||||
if (ref $info ne 'ARRAY') {
|
||||
$self->{'error'} = $info;
|
||||
return 1;
|
||||
}
|
||||
if (shift(@$info)) {
|
||||
return $info;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub get_error_message {
|
||||
my $self = shift;
|
||||
return $self->{'error'};
|
||||
}
|
||||
|
||||
sub get_version_information {
|
||||
my $self = shift;
|
||||
my $ua = $self->{'ua'};
|
||||
my $url = $self->{'parent'}->configuration()->get('ui', 'webapp.site_url');
|
||||
if (defined $url) {
|
||||
eval 'use URI::Escape';
|
||||
$url = ($@ ? undef : URI::Escape::uri_escape($url));
|
||||
}
|
||||
$url = UPDATE_URL . '?version=' . $Chirpy::VERSION
|
||||
. (defined $url ? '&url=' . $url : '');
|
||||
my $response = $ua->get($url);
|
||||
if ($response->is_success) {
|
||||
my $xml = $response->content;
|
||||
my %info = ();
|
||||
if ($xml =~ m{
|
||||
<CurrentVersion(?:\s+newer="(.*?))?">\s*(.*?)\s*</CurrentVersion>
|
||||
}sx) {
|
||||
my ($newer, $node) = ($1, $2);
|
||||
$newer = (defined $newer && lc $newer eq 'true');
|
||||
my $pattern = join('|', map { quotemeta }
|
||||
(KEY_VERSION_NUMBER, KEY_RELEASE_DATE, KEY_DETAIL_URL));
|
||||
while ($node =~ m!<($pattern)>\s*(.*?)\s*</\1>!sg) {
|
||||
$info{$1} = $2;
|
||||
}
|
||||
$info{KEY_DETAIL_URL} =~ s/&/&/g;
|
||||
return [
|
||||
$newer,
|
||||
$info{KEY_VERSION_NUMBER()},
|
||||
$info{KEY_RELEASE_DATE()},
|
||||
$info{KEY_DETAIL_URL()}
|
||||
];
|
||||
}
|
||||
return 'Unknown response from update server';
|
||||
}
|
||||
return $response->status_line;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,249 @@
|
|||
###############################################################################
|
||||
# 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:: Util.pm 302 2007-02-08 03:07:19Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Util - Static utility class
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item valid_username($username)
|
||||
|
||||
Returns whether or not the given username is valid. A username is valid if it
|
||||
is minimally 2, maximally 32 characters long. The characters can be letters,
|
||||
numbers, underscores or dashes.
|
||||
|
||||
=item valid_password($password)
|
||||
|
||||
Returns whether or not the given password is valid. A password is valid if it
|
||||
is minimally 4, maximally 256 characters long. The characters can be letters,
|
||||
numbers, underscores or dashes.
|
||||
|
||||
=item clean_up_submission($string)
|
||||
|
||||
Performs various cleanup operations on the given string, which is assumed to be
|
||||
filled in by the user somewhere. The operations include removal of leading and
|
||||
trailing whitespaces, and trimming down sequences of more than 2 line feeds.
|
||||
|
||||
=item parse_tags($tags)
|
||||
|
||||
Parses the string C<$tags> into an array of valid tags and returns a reference
|
||||
to it. The array may be empty.
|
||||
|
||||
=item encrypt($string)
|
||||
|
||||
Encrypts the given string using the MD5 algorithm. This function is used for
|
||||
password encryption.
|
||||
|
||||
=item format_quote_rating($rating)
|
||||
|
||||
Returns a string representation of the given rating, i.e. the number prepended
|
||||
with the Unicode representation of its sign.
|
||||
|
||||
=item format_date_time($timestamp, $format, $gmt)
|
||||
|
||||
Formats C<$timestamp> using L<the POSIX module|POSIX>'s C<strftime()> function
|
||||
and C<$format> as the format. Returns Greenwich Mean Time if C<$gmt> is true.
|
||||
|
||||
=item encode_xml_entities($string)
|
||||
|
||||
Returns C<$string> with the character entities defined in XML encoded. The
|
||||
entities and their respective codes are:
|
||||
|
||||
Character Entity
|
||||
========= ======
|
||||
& &
|
||||
" "
|
||||
< <
|
||||
> >
|
||||
|
||||
=item decode_utf8($string)
|
||||
|
||||
Returns the given string with UTF-8 characters decoded.
|
||||
|
||||
=back
|
||||
|
||||
=head1 PROCEDURES
|
||||
|
||||
=over 4
|
||||
|
||||
=item ensure_writable_directory($path)
|
||||
|
||||
Attempts to make the directory specified by C<$path> writable, creating it if it
|
||||
does not exist. If, upon completion, C<$path> does represent a writable
|
||||
directory, execution is aborted.
|
||||
|
||||
=item abstract_method()
|
||||
|
||||
Aborts execution immediately, stating that the method is abstract and must be
|
||||
implemented. Hence, if you have an abstract method C<my_abstract_method()>, you
|
||||
may define it as follows:
|
||||
|
||||
*my_abstract_method = \&Chirpy::Util::abstract_method;
|
||||
|
||||
Invoking it will then cause a fatal error unless it is overridden in the module
|
||||
implementation.
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::Util;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
use POSIX qw(strftime);
|
||||
|
||||
sub valid_username {
|
||||
my $string = shift;
|
||||
return ($string =~ /^[a-zA-Z0-9_\-]{2,32}$/);
|
||||
}
|
||||
|
||||
sub valid_password {
|
||||
my $string = shift;
|
||||
return ($string =~ /^[a-zA-Z0-9_\-]{4,256}$/);
|
||||
}
|
||||
|
||||
sub clean_up_submission {
|
||||
my $text = shift;
|
||||
for ($text) {
|
||||
s/\r\n?/\n/g;
|
||||
s/^\s+//;
|
||||
s/\s+$//;
|
||||
s/\n{3,}/\n\n/g;
|
||||
s/\t/ /g;
|
||||
# Remove low ASCII chars (\12 = \n)
|
||||
s/[\0-\11\13-\37]//g;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
sub parse_tags {
|
||||
my $tags = shift;
|
||||
return [] unless (defined $tags && $tags ne '');
|
||||
$tags = lc $tags;
|
||||
my %tags = ();
|
||||
foreach my $tag (split(/[\s;,]+/, $tags)) {
|
||||
next if (length($tag) < 2);
|
||||
$tags{$tag} = 1;
|
||||
}
|
||||
return [ keys %tags ];
|
||||
}
|
||||
|
||||
sub encrypt {
|
||||
require Digest::MD5;
|
||||
return Digest::MD5::md5_hex(shift);
|
||||
}
|
||||
|
||||
sub format_quote_rating {
|
||||
my $rating = shift;
|
||||
return ($rating
|
||||
? ($rating < 0 ? "\x{2212}" . (-$rating) : '+' . $rating)
|
||||
: '0');
|
||||
}
|
||||
|
||||
sub format_date_time {
|
||||
my ($timestamp, $format, $gmt) = @_;
|
||||
return strftime($format, ($gmt
|
||||
? gmtime($timestamp) : localtime($timestamp)));
|
||||
}
|
||||
|
||||
sub encode_xml_entities {
|
||||
require HTML::Entities;
|
||||
my $str = shift;
|
||||
return HTML::Entities::encode($str, '<>&"');
|
||||
}
|
||||
|
||||
sub decode_utf8 {
|
||||
require Encode;
|
||||
return Encode::decode('utf8', shift);
|
||||
}
|
||||
|
||||
sub shuffle_array {
|
||||
my @array = @_;
|
||||
my $len = scalar @array;
|
||||
for (my $i = 0; $i < $len; $i++) {
|
||||
my $j = int rand $len;
|
||||
($array[$i], $array[$j]) = ($array[$j], $array[$i]);
|
||||
}
|
||||
return @array;
|
||||
}
|
||||
|
||||
sub ensure_writable_directory {
|
||||
my $path = shift;
|
||||
if (-e $path) {
|
||||
if (-d $path) {
|
||||
if (!-w $path) {
|
||||
chmod 0777, $path;
|
||||
if (!-w $path) {
|
||||
Chirpy::die('Directory "' . $path . '" not writable');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Chirpy::die('Path "' . $path . '" must be a directory');
|
||||
}
|
||||
}
|
||||
else {
|
||||
mkdir $path
|
||||
or die('Cannot create directory "' . $path . '": ' . $!);
|
||||
}
|
||||
}
|
||||
|
||||
sub abstract_method {
|
||||
Chirpy::die('Abstract method must be implemented');
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,150 @@
|
|||
###############################################################################
|
||||
# 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:: IniFile.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Util::IniFile - Load from and save to an INI file
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$inifile = new Chirpy::Util::IniFile('/path/to/inifile.ini');
|
||||
|
||||
$value = $inifile->get($section, $name);
|
||||
|
||||
$inifile->set($section, $name, $value);
|
||||
|
||||
undef $inifile;
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The C<undef $inifile;> in the above example is not necessary to trigger an
|
||||
update of the file's contents. The file is updated as soon as the object goes
|
||||
out of scope (but only if the C<set()> function has been called).
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Chirpy::Configuration>, L<Chirpy::Locale>, 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::Util::IniFile;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $file, $create) = @_;
|
||||
my $self = {
|
||||
'contents' => {},
|
||||
'filename' => $file
|
||||
};
|
||||
if (!defined $file) {
|
||||
Chirpy::die('No filename specified');
|
||||
}
|
||||
if ($create) {
|
||||
$self->{'modified'} = 1;
|
||||
}
|
||||
elsif (!-f $file) {
|
||||
Chirpy::die('File "' . $file . '" does not exist');
|
||||
}
|
||||
local *FILE;
|
||||
open(FILE, '<:utf8', $file)
|
||||
or Chirpy::die('Failed to read from ' . $file . ': ' . $!);
|
||||
my $section;
|
||||
while (<FILE>) {
|
||||
chomp;
|
||||
next if (/^;/);
|
||||
if (/^\s*\[([^\]]+)\]\s*$/) {
|
||||
$section = $1;
|
||||
}
|
||||
elsif (defined($section) && /^([^=]+)=(.*)/) {
|
||||
$self->{'contents'}{$section}{$1} = $2;
|
||||
}
|
||||
}
|
||||
close FILE;
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
return unless ($self->{'modified'});
|
||||
local *FILE;
|
||||
open(FILE, '>:utf8', $self->{'filename'})
|
||||
or Chirpy::die('Failed to write to ' . $self->{'filename'}
|
||||
. ': ' . $!);
|
||||
print FILE '; Automatically generated by ', __PACKAGE__, $/,
|
||||
'; ', (my $str = gmtime()), $/;
|
||||
foreach my $section (sort { lc($a) cmp lc($b) }
|
||||
keys %{$self->{'contents'}}) {
|
||||
print FILE $/, '[', $section, ']', $/;
|
||||
foreach my $name (sort { lc($a) cmp lc($b) }
|
||||
keys %{$self->{'contents'}{$section}}) {
|
||||
print FILE $name, '=',
|
||||
$self->{'contents'}{$section}{$name}, $/;
|
||||
}
|
||||
}
|
||||
close FILE;
|
||||
}
|
||||
|
||||
sub get {
|
||||
my ($self, $section, $name) = @_;
|
||||
return (defined $name
|
||||
? $self->{'contents'}{$section}{$name}
|
||||
: $self->{'contents'}{$section});
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $section, $name, $value) = @_;
|
||||
$self->{'contents'}{$section} = {}
|
||||
if (!exists($self->{'contents'}{$section}));
|
||||
$self->{'contents'}{$section}{$name} = $value;
|
||||
$self->{'modified'} = 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,172 @@
|
|||
###############################################################################
|
||||
# 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:: Account.pm 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Chirpy::Account - Represents a user account
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
$account = new Chirpy::Account($id, $username, $password, $level);
|
||||
|
||||
$id = $account->get_id();
|
||||
$account->set_id($id);
|
||||
|
||||
$username = $account->get_username($username);
|
||||
$account->set_username($username);
|
||||
|
||||
$password = $account->get_password();
|
||||
$account->set_password($password);
|
||||
|
||||
$level = $account->get_level();
|
||||
$account->set_level($level);
|
||||
|
||||
=head1 CONSTRAINTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item ID
|
||||
|
||||
The account ID must be a positive non-zero integer.
|
||||
|
||||
=item Username
|
||||
|
||||
The username must be valid against the C<valid_username()> function of
|
||||
L<Chirpy::Util>.
|
||||
|
||||
=item Password
|
||||
|
||||
Encryption is done I<before> invoking the constructor or the C<set_password()>
|
||||
function. The C<get_password()> function returns the I<encrypted> password.
|
||||
The password must be valid against the C<valid_password()> function and
|
||||
encrypted using the C<encrypt()> function, both part of L<Chirpy::Util>.
|
||||
|
||||
=item User Level
|
||||
|
||||
The user level must be one of the user level constants described below.
|
||||
|
||||
=back
|
||||
|
||||
=head1 USER LEVEL CONSTANTS
|
||||
|
||||
The following constants are recommended for use as user levels:
|
||||
|
||||
Chirpy::Account::USER_LEVEL_3
|
||||
Chirpy::Account::USER_LEVEL_6
|
||||
Chirpy::Account::USER_LEVEL_9
|
||||
|
||||
Note that the value of these is the integer representing the user level and
|
||||
that the constants are only for the sake of code readability.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Tim De Pauw E<lt>ceetee@users.sourceforge.netE<gt>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
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::Account;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use constant USER_LEVEL_3 => 3;
|
||||
use constant USER_LEVEL_6 => 6;
|
||||
use constant USER_LEVEL_9 => 9;
|
||||
|
||||
use vars qw($VERSION);
|
||||
|
||||
$VERSION = '0.3';
|
||||
|
||||
use Chirpy 0.3;
|
||||
|
||||
sub new {
|
||||
my ($class, $id, $username, $password, $level) = @_;
|
||||
my $self = {
|
||||
'id' => $id,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'level' => $level
|
||||
};
|
||||
return bless($self, $class);
|
||||
}
|
||||
|
||||
sub get_id {
|
||||
my $self = shift;
|
||||
return $self->{'id'};
|
||||
}
|
||||
|
||||
sub set_id {
|
||||
my $self = shift;
|
||||
return ($self->{'id'} = shift);
|
||||
}
|
||||
|
||||
sub get_username {
|
||||
my $self = shift;
|
||||
return $self->{'username'};
|
||||
}
|
||||
|
||||
sub set_username {
|
||||
my $self = shift;
|
||||
return ($self->{'username'} = shift);
|
||||
}
|
||||
|
||||
sub get_password {
|
||||
my $self = shift;
|
||||
return $self->{'password'};
|
||||
}
|
||||
|
||||
sub set_password {
|
||||
my $self = shift;
|
||||
return ($self->{'password'} = shift);
|
||||
}
|
||||
|
||||
sub get_level {
|
||||
my $self = shift;
|
||||
return $self->{'level'};
|
||||
}
|
||||
|
||||
sub set_level {
|
||||
my $self = shift;
|
||||
return ($self->{'level'} = shift);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,9 @@
|
|||
</div>
|
||||
<div id="footer">
|
||||
<TMPL_IF NAME="LOGGED_IN">
|
||||
<TMPL_VAR NAME="LOGGED_IN_NOTICE"><br/>
|
||||
</TMPL_IF>
|
||||
<TMPL_VAR NAME="FOOTER_TEXT">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<link rel="shortcut icon" type="image/x-icon"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/cite.ico" />
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/default.css"/>
|
||||
<link rel="stylesheet" type="text/css" title="Chirpy! Default"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/styles/default.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" title="Spring"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/styles/spring.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" title="Fish Tank"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/styles/fish_tank.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" title="Grayscale"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/styles/grayscale.css"/>
|
||||
<link rel="alternate stylesheet" type="text/css" title="Nineties"
|
||||
href="<TMPL_VAR NAME="RESOURCES_URL">/css/styles/nineties.css"/>
|
||||
<TMPL_LOOP NAME="FEEDS">
|
||||
<link rel="alternate" type="<TMPL_VAR NAME="FEED_MIME_TYPE">"
|
||||
title="<TMPL_VAR NAME="FEED_TITLE">" href="<TMPL_VAR NAME="FEED_URL">" />
|
||||
</TMPL_LOOP>
|
||||
<TMPL_LOOP NAME="MICROSUMMARIES">
|
||||
<link rel="microsummary" href="<TMPL_VAR NAME="MICROSUMMARY_URL">" type="text/plain" />
|
||||
</TMPL_LOOP>
|
||||
<TMPL_IF NAME="START_URL"><link rel="start" href="<TMPL_VAR NAME="START_URL">" /></TMPL_IF>
|
||||
<TMPL_IF NAME="PREVIOUS_URL"><link rel="prev" href="<TMPL_VAR NAME="PREVIOUS_URL">" /></TMPL_IF>
|
||||
<TMPL_IF NAME="NEXT_URL"><link rel="next" href="<TMPL_VAR NAME="NEXT_URL">" /></TMPL_IF>
|
||||
<script type="text/javascript">var cookieDomain = "<TMPL_VAR NAME="COOKIE_DOMAIN">", cookiePath = "<TMPL_VAR NAME="COOKIE_PATH">";</script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/style_switcher.js"></script>
|
||||
<title><TMPL_VAR NAME="SITE_TITLE">: <TMPL_VAR NAME="PAGE_TITLE"></title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="header">
|
||||
<a href="<TMPL_VAR NAME="SITE_URL">"><TMPL_VAR NAME="SITE_TITLE"></a>
|
||||
</h1>
|
||||
<ul id="navigation"><li><a
|
||||
href="<TMPL_VAR NAME="START_PAGE_URL">" title="<TMPL_VAR NAME="START_PAGE_DESCRIPTION">"><TMPL_VAR NAME="START_PAGE_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="QUOTE_BROWSER_URL">" title="<TMPL_VAR NAME="QUOTE_BROWSER_DESCRIPTION">"><TMPL_VAR NAME="QUOTE_BROWSER_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="RANDOM_QUOTES_URL">" title="<TMPL_VAR NAME="RANDOM_QUOTES_DESCRIPTION">"><TMPL_VAR NAME="RANDOM_QUOTES_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="TOP_QUOTES_URL">" title="<TMPL_VAR NAME="TOP_QUOTES_DESCRIPTION">"><TMPL_VAR NAME="TOP_QUOTES_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="BOTTOM_QUOTES_URL">" title="<TMPL_VAR NAME="BOTTOM_QUOTES_DESCRIPTION">"><TMPL_VAR NAME="BOTTOM_QUOTES_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="QUOTE_SEARCH_URL">" title="<TMPL_VAR NAME="QUOTE_SEARCH_DESCRIPTION">"><TMPL_VAR NAME="QUOTE_SEARCH_SHORT_TITLE"></a></li><TMPL_IF NAME="MODERATION_QUEUE_PUBLIC"><li><a
|
||||
href="<TMPL_VAR NAME="MODERATION_QUEUE_URL">" title="<TMPL_VAR NAME="MODERATION_QUEUE_DESCRIPTION">"><TMPL_VAR NAME="MODERATION_QUEUE_SHORT_TITLE"></a></li></TMPL_IF><li><a
|
||||
href="<TMPL_VAR NAME="TAG_CLOUD_URL">" title="<TMPL_VAR NAME="TAG_CLOUD_DESCRIPTION">"><TMPL_VAR NAME="TAG_CLOUD_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="STATISTICS_URL">" title="<TMPL_VAR NAME="STATISTICS_DESCRIPTION">"><TMPL_VAR NAME="STATISTICS_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="SUBMIT_QUOTE_URL">" title="<TMPL_VAR NAME="SUBMIT_QUOTE_DESCRIPTION">"><TMPL_VAR NAME="SUBMIT_QUOTE_SHORT_TITLE"></a></li><TMPL_IF NAME="LOGGED_IN"><li><a
|
||||
href="<TMPL_VAR NAME="ADMINISTRATION_URL">" title="<TMPL_VAR NAME="ADMINISTRATION_DESCRIPTION">"><TMPL_VAR NAME="ADMINISTRATION_SHORT_TITLE"></a></li><li><a
|
||||
href="<TMPL_VAR NAME="LOGOUT_URL">" title="<TMPL_VAR NAME="LOGOUT_DESCRIPTION">"><TMPL_VAR NAME="LOGOUT_SHORT_TITLE"></a></li><TMPL_ELSE><li><a
|
||||
href="<TMPL_VAR NAME="LOGIN_URL">" title="<TMPL_VAR NAME="LOGIN_DESCRIPTION">"><TMPL_VAR NAME="LOGIN_SHORT_TITLE"></a></li></TMPL_IF></ul>
|
||||
<div id="content">
|
|
@ -0,0 +1,11 @@
|
|||
<div id="search-form">
|
||||
<TMPL_VAR NAME="SEARCH_FORM_START">
|
||||
<div id="query-container">
|
||||
<label for="query-field"><TMPL_VAR NAME="SEARCH_QUERY_LABEL"></label>
|
||||
<input name="query" value="<TMPL_VAR NAME="SEARCH_QUERY">" id="query-field" accesskey="s" />
|
||||
</div>
|
||||
<div id="submit-container">
|
||||
<input type="submit" value="<TMPL_VAR NAME="SUBMIT_SEARCH_LABEL">" id="submit-button" />
|
||||
</div>
|
||||
<TMPL_VAR NAME="SEARCH_FORM_END">
|
||||
</div>
|
|
@ -0,0 +1,61 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/tabbed_pane.js"></script>
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<TMPL_IF NAME="UPDATE_CHECK_FAILED">
|
||||
<div id="update-information">
|
||||
<p><TMPL_VAR NAME="UPDATE_CHECK_FAILED_TEXT"></p>
|
||||
<blockquote><TMPL_VAR NAME="UPDATE_CHECK_ERROR_MESSAGE"></blockquote>
|
||||
</div>
|
||||
<TMPL_ELSE>
|
||||
<TMPL_IF NAME="UPDATE_AVAILABLE">
|
||||
<div id="update-information">
|
||||
<a href="<TMPL_VAR NAME="UPDATE_URL">"><TMPL_VAR NAME="UPDATE_AVAILABLE_TEXT">
|
||||
<TMPL_VAR NAME="UPDATE_LINK_TEXT"></a>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
</TMPL_IF>
|
||||
<ul id="admin-navigation" class="tabbed-pane-header"><li><TMPL_IF NAME="APPROVE_QUOTES_ALLOWED"><a
|
||||
href="#approve-quotes" id="tab-approve-quotes"><TMPL_VAR NAME="APPROVE_QUOTES"></a></li></TMPL_IF><TMPL_IF NAME="FLAGGED_QUOTES_ALLOWED"><li><a
|
||||
href="#flagged-quotes" id="tab-flagged-quotes"><TMPL_VAR NAME="FLAGGED_QUOTES"></a></li></TMPL_IF><TMPL_IF NAME="MANAGE_QUOTES_ALLOWED"><li><a
|
||||
href="#manage-quotes" id="tab-manage-quotes"><TMPL_VAR NAME="MANAGE_QUOTES"></a></li></TMPL_IF><TMPL_IF NAME="MANAGE_NEWS_ALLOWED"><li><a
|
||||
href="#manage-news" id="tab-manage-news"><TMPL_VAR NAME="MANAGE_NEWS"></a></li></TMPL_IF><TMPL_IF NAME="VIEW_EVENT_LOG_ALLOWED"><li><a
|
||||
href="#view_event_log" id="tab-view-event-log"><TMPL_VAR NAME="VIEW_EVENT_LOG"></a></li></TMPL_IF><TMPL_IF NAME="MANAGE_ACCOUNTS_ALLOWED"><li><a
|
||||
href="#manage_accounts" id="tab-manage-accounts"><TMPL_VAR NAME="MANAGE_ACCOUNTS"></a></li></TMPL_IF><li><a
|
||||
href="#change-password" id="tab-change-password"><TMPL_VAR NAME="CHANGE_PASSWORD"></a></li></ul>
|
||||
<ul id="admin-contents" class="tabbed-pane-contents"><TMPL_IF NAME="APPROVE_QUOTES_ALLOWED">
|
||||
<li id="approve-quotes">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="APPROVE_QUOTES"></h3>
|
||||
<TMPL_VAR NAME="APPROVE_QUOTES_HTML">
|
||||
</li></TMPL_IF><TMPL_IF NAME="FLAGGED_QUOTES_ALLOWED"><li id="flagged-quotes">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="FLAGGED_QUOTES"></h3>
|
||||
<TMPL_VAR NAME="FLAGGED_QUOTES_HTML">
|
||||
</li></TMPL_IF><TMPL_IF NAME="MANAGE_QUOTES_ALLOWED"><li id="manage-quotes">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="MANAGE_QUOTES"></h3>
|
||||
<TMPL_VAR NAME="MANAGE_QUOTES_HTML">
|
||||
</li></TMPL_IF><TMPL_IF NAME="MANAGE_NEWS_ALLOWED"><li id="manage-news">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="MANAGE_NEWS"></h3>
|
||||
<TMPL_VAR NAME="MANAGE_NEWS_HTML">
|
||||
</li></TMPL_IF><TMPL_IF NAME="VIEW_EVENT_LOG_ALLOWED"><li id="view-event-log">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="VIEW_EVENT_LOG"></h3>
|
||||
<TMPL_VAR NAME="VIEW_EVENT_LOG_HTML">
|
||||
</li></TMPL_IF><TMPL_IF NAME="MANAGE_ACCOUNTS_ALLOWED"><li id="manage-accounts">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="MANAGE_ACCOUNTS"></h3>
|
||||
<TMPL_VAR NAME="MANAGE_ACCOUNTS_HTML">
|
||||
</li></TMPL_IF><li id="change-password">
|
||||
<h3 class="tab-title"><TMPL_VAR NAME="CHANGE_PASSWORD"></h3>
|
||||
<TMPL_VAR NAME="CHANGE_PASSWORD_HTML">
|
||||
</li></ul>
|
||||
<script type="text/javascript">initializeTabbedPane('admin'
|
||||
<TMPL_IF NAME="ACTION_IS_QUOTE_FLAGS" >, document.getElementById('tab-flagged-quotes')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_QUOTE_EDIT" >, document.getElementById('tab-manage-quotes')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_QUOTE_REMOVE" >, document.getElementById('tab-manage-quotes')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_NEWS_ADD" >, document.getElementById('tab-manage-news')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_NEWS_EDIT" >, document.getElementById('tab-manage-news')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_NEWS_REMOVE" >, document.getElementById('tab-manage-news')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_LOG" >, document.getElementById('tab-view-event-log')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_ACCOUNTS" >, document.getElementById('tab-manage-accounts')</TMPL_IF>
|
||||
<TMPL_IF NAME="ACTION_IS_PASSWORD" >, document.getElementById('tab-change-password')</TMPL_IF>
|
||||
);</script>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,18 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<form method="<TMPL_IF NAME="POST_FORM">post<TMPL_ELSE>get</TMPL_IF>" action="<TMPL_VAR NAME="URL">" id="confirmation-form">
|
||||
<p id="confirmation-request"><TMPL_VAR NAME="CONFIRMATION_REQUEST"></p>
|
||||
<TMPL_IF NAME="QUOTE_BODY">
|
||||
<blockquote class="quote-body">
|
||||
<p><TMPL_VAR NAME="QUOTE_BODY"></p>
|
||||
</blockquote>
|
||||
</TMPL_IF>
|
||||
<div id="confirmation-options">
|
||||
<input type="submit" value="<TMPL_VAR NAME="CONFIRMATION_TEXT">" id="confirm-button" />
|
||||
<input type="button" value="<TMPL_VAR NAME="CANCELATION_TEXT">" id="cancel-button"
|
||||
onclick="history.go(-1);" />
|
||||
</div>
|
||||
</form>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,6 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<p><TMPL_VAR NAME="ERROR_MESSAGE"></p>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="<TMPL_VAR NAME="CHARACTER_ENCODING">"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<title><TMPL_VAR NAME="SITE_TITLE">: <TMPL_VAR NAME="PAGE_TITLE"></title>
|
||||
<subtitle><TMPL_VAR NAME="FEED_SUBTITLE"></subtitle>
|
||||
<id><TMPL_VAR NAME="SITE_URL"></id>
|
||||
<link rel="self" href="<TMPL_VAR NAME="FEED_URL">"/>
|
||||
<link rel="alternate" href="<TMPL_VAR NAME="PAGE_URL">"
|
||||
title="<TMPL_VAR NAME="SITE_TITLE">: <TMPL_VAR NAME="PAGE_TITLE">"/>
|
||||
<link rel="related" href="<TMPL_VAR NAME="SITE_URL">"
|
||||
title="<TMPL_VAR NAME="SITE_TITLE">"/>
|
||||
<updated><TMPL_VAR NAME="FEED_DATE_ISO8601"></updated>
|
||||
<author>
|
||||
<name><TMPL_VAR NAME="WEBMASTER_NAME"></name>
|
||||
<email><TMPL_VAR NAME="WEBMASTER_EMAIL"></email>
|
||||
</author>
|
||||
<generator uri="<TMPL_VAR NAME="CHIRPY_URL">" version="<TMPL_VAR NAME="CHIRPY_VERSION">"><TMPL_VAR NAME="CHIRPY_NAME"></generator>
|
||||
<TMPL_LOOP NAME="QUOTES">
|
||||
<entry>
|
||||
<title type="text"><TMPL_VAR NAME="QUOTE_TITLE"></title>
|
||||
<TMPL_LOOP NAME="QUOTE_TAGS">
|
||||
<category term="<TMPL_VAR NAME="TAG">" />
|
||||
</TMPL_LOOP>
|
||||
<link href="<TMPL_VAR NAME="QUOTE_URL">"/>
|
||||
<id><TMPL_VAR NAME="QUOTE_URL"></id>
|
||||
<published><TMPL_VAR NAME="QUOTE_DATE_ISO8601"></published>
|
||||
<updated><TMPL_VAR NAME="QUOTE_DATE_ISO8601"></updated>
|
||||
<summary type="text"><TMPL_VAR NAME="QUOTE_RATING"></summary>
|
||||
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p><b><a href="<TMPL_VAR NAME="QUOTE_RATING_UP_URL">">[<TMPL_VAR NAME="QUOTE_RATING_UP_SHORT_TITLE">]</a>
|
||||
(<TMPL_VAR NAME="QUOTE_RATING"><small>/<TMPL_VAR NAME="QUOTE_VOTE_COUNT"></small>)
|
||||
<a href="<TMPL_VAR NAME="QUOTE_RATING_DOWN_URL">">[<TMPL_VAR NAME="QUOTE_RATING_DOWN_SHORT_TITLE">]</a>
|
||||
<TMPL_IF NAME="QUOTE_IS_APPROVED">
|
||||
<a href="<TMPL_VAR NAME="QUOTE_REPORT_URL">">[<TMPL_VAR NAME="QUOTE_REPORT_SHORT_TITLE">]</a>
|
||||
</TMPL_IF>
|
||||
</b></p>
|
||||
<p><tt><TMPL_VAR NAME="QUOTE_BODY"></tt></p>
|
||||
<TMPL_IF NAME="QUOTE_NOTES"><p><b><TMPL_VAR NAME="QUOTE_NOTES_TITLE"></b>
|
||||
<TMPL_VAR NAME="QUOTE_NOTES"></p></TMPL_IF>
|
||||
</div></content>
|
||||
</entry>
|
||||
</TMPL_LOOP>
|
||||
</feed>
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="<TMPL_VAR NAME="CHARACTER_ENCODING">"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title><TMPL_VAR NAME="SITE_TITLE">: <TMPL_VAR NAME="PAGE_TITLE"></title>
|
||||
<description><TMPL_VAR NAME="FEED_SUBTITLE"></description>
|
||||
<link><TMPL_VAR NAME="PAGE_URL"></link>
|
||||
<lastBuildDate><TMPL_VAR NAME="FEED_DATE_RFC822"></lastBuildDate>
|
||||
<managingEditor><TMPL_VAR NAME="WEBMASTER_EMAIL"></managingEditor>
|
||||
<generator><TMPL_VAR NAME="CHIRPY_URL"></generator>
|
||||
<TMPL_LOOP NAME="QUOTES">
|
||||
<item>
|
||||
<title><TMPL_VAR NAME="QUOTE_TITLE"></title>
|
||||
<TMPL_LOOP NAME="QUOTE_TAGS">
|
||||
<category><TMPL_VAR NAME="TAG"></category>
|
||||
</TMPL_LOOP>
|
||||
<link><TMPL_VAR NAME="QUOTE_URL"></link>
|
||||
<guid><TMPL_VAR NAME="QUOTE_URL"></guid>
|
||||
<pubDate><TMPL_VAR NAME="QUOTE_DATE_RFC822"></pubDate>
|
||||
<description><![CDATA[
|
||||
<p><b><a href="<TMPL_VAR NAME="QUOTE_RATING_UP_URL">">[<TMPL_VAR NAME="QUOTE_RATING_UP_SHORT_TITLE">]</a>
|
||||
(<TMPL_VAR NAME="QUOTE_RATING"><small>/<TMPL_VAR NAME="QUOTE_VOTE_COUNT"></small>)
|
||||
<a href="<TMPL_VAR NAME="QUOTE_RATING_DOWN_URL">">[<TMPL_VAR NAME="QUOTE_RATING_DOWN_SHORT_TITLE">]</a>
|
||||
<TMPL_IF NAME="QUOTE_IS_APPROVED">
|
||||
<a href="<TMPL_VAR NAME="QUOTE_REPORT_URL">">[<TMPL_VAR NAME="QUOTE_REPORT_SHORT_TITLE">]</a>
|
||||
</TMPL_IF>
|
||||
</b></p>
|
||||
<p><tt><TMPL_VAR NAME="QUOTE_BODY"></tt></p>
|
||||
<TMPL_IF NAME="QUOTE_NOTES"><p><b><TMPL_VAR NAME="QUOTE_NOTES_TITLE"></b>
|
||||
<TMPL_VAR NAME="QUOTE_NOTES"></p></TMPL_IF>
|
||||
]]></description>
|
||||
</item>
|
||||
</TMPL_LOOP>
|
||||
</channel>
|
||||
</rss>
|
|
@ -0,0 +1,25 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<TMPL_IF NAME="INVALID_LOGIN">
|
||||
<div id="invalid-login-instructions">
|
||||
<p><TMPL_VAR NAME="INVALID_LOGIN_INSTRUCTIONS"></p>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
<div id="login-form">
|
||||
<TMPL_VAR NAME="LOGIN_FORM_START">
|
||||
<div id="user-name-container">
|
||||
<label for="user-name-field"><TMPL_VAR NAME="USERNAME_TITLE"></label>
|
||||
<input name="username" id="user-name-field" accesskey="u" />
|
||||
</div>
|
||||
<div id="password-container">
|
||||
<label for="password-field"><TMPL_VAR NAME="PASSWORD_TITLE"></label>
|
||||
<input type="password" name="password" id="password-field" accesskey="p" />
|
||||
</div>
|
||||
<div id="submit-container">
|
||||
<input type="submit" value="<TMPL_VAR NAME="LOGIN_BUTTON_LABEL">" id="login-button" />
|
||||
</div>
|
||||
<TMPL_VAR NAME="LOGIN_FORM_END">
|
||||
</div>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,6 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<p><TMPL_VAR NAME="MESSAGE_TEXT"></p>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,128 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<script type="text/javascript">
|
||||
var locale = new Array();
|
||||
locale["processing"] = "<TMPL_VAR NAME="PROCESSING">";
|
||||
locale["error"] = "<TMPL_VAR NAME="ERROR">";
|
||||
locale["timeout_text"] = "<TMPL_VAR NAME="QUOTE_RATING_TIMED_OUT">";
|
||||
locale["already_rated_text"] = "<TMPL_VAR NAME="QUOTE_ALREADY_RATED_TEXT">";
|
||||
locale["limit_exceeded_text"] = "<TMPL_VAR NAME="LIMIT_EXCEEDED_TEXT">";
|
||||
locale["quote_not_found_text"] = "<TMPL_VAR NAME="QUOTE_NOT_FOUND_TEXT">";
|
||||
locale["session_required_text"] = "<TMPL_VAR NAME="SESSION_REQUIRED_TEXT">";
|
||||
locale["flagged"] = "<TMPL_VAR NAME="FLAGGED">";
|
||||
</script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/ajax.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/live_rating.js"></script>
|
||||
<TMPL_IF NAME="BROWSER">
|
||||
<div class="quote-browser" id="quote-browser-top">
|
||||
<TMPL_IF NAME="PREVIOUS_URL">
|
||||
<a href="<TMPL_VAR NAME="PREVIOUS_URL">">← <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></a>
|
||||
<TMPL_ELSE>
|
||||
<span class="inactive">← <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></span>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="NEXT_URL">
|
||||
<a href="<TMPL_VAR NAME="NEXT_URL">"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> →</a>
|
||||
<TMPL_ELSE>
|
||||
<span class="inactive"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> →</span>
|
||||
</TMPL_IF>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<TMPL_IF NAME="SEARCHED">
|
||||
<TMPL_INCLUDE NAME="_search_form.html">
|
||||
</TMPL_IF>
|
||||
<ul class="quote-list">
|
||||
<TMPL_LOOP NAME="QUOTES">
|
||||
<li id="quote-<TMPL_VAR NAME="ID">" class="quote <TMPL_UNLESS NAME="IS_APPROVED">unapproved</TMPL_UNLESS> <TMPL_IF NAME="IS_FLAGGED">flagged</TMPL_IF>">
|
||||
<div class="quote-container">
|
||||
<h3 class="quote-header" id="quote-header-<TMPL_VAR NAME="ID">">
|
||||
<TMPL_IF NAME="IS_APPROVED">
|
||||
<span id="quote-live-vote-result-<TMPL_VAR NAME="ID">" class="quote-live-vote-result"></span>
|
||||
<a href="<TMPL_VAR NAME="LINK_URL">"
|
||||
title="<TMPL_VAR NAME="LINK_DESCRIPTION">" class="quote-permalink"><span class="quote-id">#<TMPL_VAR NAME="ID"></span></a>
|
||||
<a href="<TMPL_VAR NAME="RATING_UP_URL">" rel="nofollow"
|
||||
onclick="return sendRating(this, <TMPL_VAR NAME="ID">)"
|
||||
title="<TMPL_VAR NAME="RATING_UP_DESCRIPTION">" class="quote-rating-up<TMPL_IF NAME="WAS_VOTED_UP"> casted-vote</TMPL_IF>" id="quote-rating-up-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="UP">↑</a>
|
||||
<span id="quote-rating-<TMPL_VAR NAME="ID">" class="quote-rating" title="<TMPL_VAR NAME="RATING_DESCRIPTION">"><TMPL_VAR NAME="RATING_TEXT"></span><span
|
||||
class="quote-vote-count" title="<TMPL_VAR NAME="VOTE_COUNT_DESCRIPTION">">/<span id="quote-vote-count-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="VOTE_COUNT"></span></span>
|
||||
<a href="<TMPL_VAR NAME="RATING_DOWN_URL">" rel="nofollow"
|
||||
onclick="return sendRating(this, <TMPL_VAR NAME="ID">)"
|
||||
title="<TMPL_VAR NAME="RATING_DOWN_DESCRIPTION">" class="quote-rating-down<TMPL_IF NAME="WAS_VOTED_DOWN"> casted-vote</TMPL_IF>" id="quote-rating-down-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="DOWN">↓</a>
|
||||
<TMPL_IF NAME="IS_FLAGGED">
|
||||
<span class="quote-flagged">[<TMPL_VAR NAME="FLAGGED">]</span>
|
||||
<TMPL_ELSE>
|
||||
<a href="<TMPL_VAR NAME="REPORT_URL">" rel="nofollow"
|
||||
onclick="return sendReport(this, <TMPL_VAR NAME="ID">)"
|
||||
title="<TMPL_VAR NAME="REPORT_DESCRIPTION">" class="quote-report" id="quote-report-<TMPL_VAR NAME="ID">">[<TMPL_VAR NAME="REPORT">]</a>
|
||||
</TMPL_IF>
|
||||
<span class="quote-date" title="<TMPL_VAR NAME="DATE_DESCRIPTION">"><TMPL_VAR NAME="SUBMITTED_TEXT"></span>
|
||||
<TMPL_IF NAME="ALLOW_EDIT">
|
||||
<a href="<TMPL_VAR NAME="EDIT_URL">" class="quote-edit"
|
||||
title="<TMPL_VAR NAME="EDIT_DESCRIPTION">">[<TMPL_VAR NAME="EDIT">]</a>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="ALLOW_UNFLAG">
|
||||
<a href="<TMPL_VAR NAME="UNFLAG_URL">" class="quote-unflag"
|
||||
title="<TMPL_VAR NAME="UNFLAG_DESCRIPTION">">[<TMPL_VAR NAME="UNFLAG">]</a>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="ALLOW_REMOVE">
|
||||
<a href="<TMPL_VAR NAME="REMOVE_URL">" class="quote-remove"
|
||||
title="<TMPL_VAR NAME="REMOVE_DESCRIPTION">"
|
||||
onclick="if (!confirm("<TMPL_VAR NAME="REMOVAL_CONFIRMATION">")) return false; this.href += "&confirm=1"; return true;">[<TMPL_VAR NAME="REMOVE">]</a>
|
||||
</TMPL_IF>
|
||||
<TMPL_ELSE>
|
||||
<span id="quote-live-vote-result-<TMPL_VAR NAME="ID">" class="quote-live-vote-result"></span>
|
||||
<a href="<TMPL_VAR NAME="LINK_URL">"
|
||||
title="<TMPL_VAR NAME="LINK_DESCRIPTION">" class="quote-permalink"><span class="quote-id">#<TMPL_VAR NAME="ID"></span></a>
|
||||
<a href="<TMPL_VAR NAME="RATING_UP_URL">" rel="nofollow"
|
||||
onclick="return sendRating(this, <TMPL_VAR NAME="ID">)"
|
||||
title="<TMPL_VAR NAME="RATING_UP_DESCRIPTION">" class="quote-rating-up<TMPL_IF NAME="WAS_VOTED_UP"> casted-vote</TMPL_IF>" id="quote-rating-up-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="UP">↑</a>
|
||||
<span id="quote-rating-<TMPL_VAR NAME="ID">" class="quote-rating" title="<TMPL_VAR NAME="RATING_DESCRIPTION">"><TMPL_VAR NAME="RATING_TEXT"></span><span
|
||||
class="quote-vote-count" title="<TMPL_VAR NAME="VOTE_COUNT_DESCRIPTION">">/<span id="quote-vote-count-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="VOTE_COUNT"></span></span>
|
||||
<a href="<TMPL_VAR NAME="RATING_DOWN_URL">" rel="nofollow"
|
||||
onclick="return sendRating(this, <TMPL_VAR NAME="ID">)"
|
||||
title="<TMPL_VAR NAME="RATING_DOWN_DESCRIPTION">" class="quote-rating-down<TMPL_IF NAME="WAS_VOTED_DOWN"> casted-vote</TMPL_IF>" id="quote-rating-down-<TMPL_VAR NAME="ID">"><TMPL_VAR NAME="DOWN">↓</a>
|
||||
<span class="quote-date" title="<TMPL_VAR NAME="DATE_DESCRIPTION">"><TMPL_VAR NAME="SUBMITTED_TEXT"></span>
|
||||
</TMPL_IF>
|
||||
</h3>
|
||||
<blockquote class="quote-body">
|
||||
<p><TMPL_VAR NAME="BODY"></p>
|
||||
</blockquote>
|
||||
<TMPL_IF NAME="NOTES_OR_TAGS">
|
||||
<div class="quote-footer">
|
||||
<TMPL_IF NAME="NOTES">
|
||||
<div class="quote-notes">
|
||||
<p><em class="quote-notes-title"><TMPL_VAR NAME="NOTES_TITLE"></em>
|
||||
<TMPL_VAR NAME="NOTES"></p>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="TAGS">
|
||||
<div class="quote-tags">
|
||||
<p><em class="quote-tags-title"><TMPL_VAR NAME="TAGS_TITLE"></em>
|
||||
<TMPL_LOOP NAME="TAGS">
|
||||
<a href="<TMPL_VAR NAME="URL">"
|
||||
title="<TMPL_VAR NAME="LINK_DESCRIPTION">"><TMPL_VAR NAME="TAG"></a>
|
||||
</TMPL_LOOP></p>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
</div>
|
||||
</li>
|
||||
</TMPL_LOOP>
|
||||
</ul>
|
||||
<TMPL_IF NAME="BROWSER">
|
||||
<div class="quote-browser" id="quote-browser-bottom">
|
||||
<TMPL_IF NAME="PREVIOUS_URL">
|
||||
<a href="<TMPL_VAR NAME="PREVIOUS_URL">">← <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></a>
|
||||
<TMPL_ELSE>
|
||||
<span class="inactive">← <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></span>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="NEXT_URL">
|
||||
<a href="<TMPL_VAR NAME="NEXT_URL">"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> →</a>
|
||||
<TMPL_ELSE>
|
||||
<span class="inactive"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> →</span>
|
||||
</TMPL_IF>
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,12 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<TMPL_INCLUDE NAME="_search_form.html">
|
||||
<div id="search-haiku" title="Bash.org quote #14443">
|
||||
<del><Ria></del> Type in the search word,<br/>
|
||||
<del><Ria></del> apply pressure to button,<br/>
|
||||
<del><Ria></del> then read the results.<del><br/>
|
||||
<Ria> it's a haiku!</del>
|
||||
</div>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,35 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<div id="welcome-message-container">
|
||||
<div id="welcome-message">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<TMPL_VAR NAME="MOTD">
|
||||
</div>
|
||||
</div>
|
||||
<div id="news-display-container">
|
||||
<div id="news-display">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="NEWS_TITLE">
|
||||
</h2>
|
||||
<ul id="news-list">
|
||||
<TMPL_LOOP NAME="NEWS">
|
||||
<li>
|
||||
<div class="news-text"><TMPL_VAR NAME="BODY"></div>
|
||||
<p class="news-footer"><TMPL_IF NAME="AUTHOR">—<TMPL_VAR NAME="AUTHOR">,</TMPL_IF>
|
||||
<TMPL_VAR NAME="DATE">
|
||||
<TMPL_IF NAME="ALLOW_EDIT">
|
||||
<a href="<TMPL_VAR NAME="EDIT_URL">" class="news-edit">[<TMPL_VAR NAME="EDIT">]</a>
|
||||
</TMPL_IF>
|
||||
<TMPL_IF NAME="ALLOW_REMOVE">
|
||||
<a href="<TMPL_VAR NAME="REMOVE_URL">" class="news-remove"
|
||||
onclick="return confirm("<TMPL_VAR NAME="REMOVAL_CONFIRMATION">")">[<TMPL_VAR NAME="REMOVE">]</a>
|
||||
</TMPL_IF>
|
||||
</p>
|
||||
</li>
|
||||
</TMPL_LOOP>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="start-page-end"></div>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,90 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<!--[if IE]><script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/excanvas/excanvas.js"></script><![endif]-->
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/graph.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/ajax.js"></script>
|
||||
<script type="text/javascript">
|
||||
if (ajaxSupported()) {
|
||||
var ajax = getAjaxObject();
|
||||
ajax.open("GET", "<TMPL_VAR NAME="UPDATE_URL">", true);
|
||||
ajax.send("");
|
||||
}
|
||||
</script>
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<div id="statistics-sections">
|
||||
<div id="quotes-by-date" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_DATE_TITLE"></h3>
|
||||
<dl class="ogive-data label-count-7">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_DATE">
|
||||
<dt><TMPL_VAR NAME="DATE"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-hour" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_HOUR_TITLE"></h3>
|
||||
<dl class="bar-chart-data">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_HOUR">
|
||||
<dt title="<TMPL_VAR NAME="START_HOUR">"><TMPL_VAR NAME="START_HOUR">:00–<TMPL_VAR NAME="END_HOUR">:00</dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-weekday" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_WEEKDAY_TITLE"></h3>
|
||||
<dl class="bar-chart-data">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_WEEKDAY">
|
||||
<dt><TMPL_VAR NAME="WEEKDAY"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-day" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_DAY_TITLE"></h3>
|
||||
<dl class="bar-chart-data">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_DAY">
|
||||
<dt><TMPL_VAR NAME="DAY"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-month" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_MONTH_TITLE"></h3>
|
||||
<dl class="bar-chart-data">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_MONTH">
|
||||
<dt title="<TMPL_VAR NAME="MONTH_NAME_SHORT">"><TMPL_VAR NAME="MONTH_NAME"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-rating" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_RATING_TITLE"></h3>
|
||||
<dl class="bar-chart-data label-count-13">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_RATING">
|
||||
<dt><TMPL_VAR NAME="RATING"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="quotes-by-vote-count" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="QUOTES_BY_VOTE_COUNT_TITLE"></h3>
|
||||
<dl class="bar-chart-data label-count-17">
|
||||
<TMPL_LOOP NAME="QUOTES_BY_VOTE_COUNT">
|
||||
<dt><TMPL_VAR NAME="VOTE_COUNT"></dt>
|
||||
<dd><TMPL_VAR NAME="QUOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
<div id="votes-by-rating" class="statistics-section">
|
||||
<h3><TMPL_VAR NAME="VOTES_BY_RATING_TITLE"></h3>
|
||||
<dl class="pie-chart-data">
|
||||
<TMPL_LOOP NAME="VOTES_BY_RATING">
|
||||
<dt><TMPL_VAR NAME="RATING"></dt>
|
||||
<dd><TMPL_VAR NAME="VOTE_COUNT"></dd>
|
||||
</TMPL_LOOP>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div id="after-statistics"></div>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,35 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<div id="submit-form">
|
||||
<TMPL_VAR NAME="SUBMIT_FORM_START">
|
||||
<div id="quote-container">
|
||||
<label for="quote-field"><TMPL_VAR NAME="QUOTE_LABEL"></label>
|
||||
<textarea name="quote" id="quote-field" rows="8" cols="80"></textarea>
|
||||
</div>
|
||||
<div id="notes-container">
|
||||
<label for="notes-field"><TMPL_VAR NAME="NOTES_LABEL"></label>
|
||||
<textarea name="notes" id="notes-field" rows="3" cols="80"></textarea>
|
||||
</div>
|
||||
<div id="tags-container">
|
||||
<label for="tags-field"><TMPL_VAR NAME="TAGS_LABEL"></label>
|
||||
<input type="text" name="tags" value="" id="tags-field" />
|
||||
</div>
|
||||
<TMPL_IF NAME="USE_CAPTCHA">
|
||||
<div id="captcha-container">
|
||||
<input type="hidden" name="captcha_hash" value="<TMPL_VAR NAME="CAPTCHA_HASH">" />
|
||||
<img src="<TMPL_VAR NAME="CAPTCHA_IMAGE_URL">"
|
||||
alt="<TMPL_VAR NAME="CAPTCHA_IMAGE_TEXT">" id="captcha-image"
|
||||
<TMPL_IF NAME="CAPTCHA_IMAGE_WIDTH">width="<TMPL_VAR NAME="CAPTCHA_IMAGE_WIDTH">"</TMPL_IF>
|
||||
<TMPL_IF NAME="CAPTCHA_IMAGE_HEIGHT">height="<TMPL_VAR NAME="CAPTCHA_IMAGE_HEIGHT">"</TMPL_IF> />
|
||||
<label for="captcha-code-field"><TMPL_VAR NAME="CAPTCHA_CODE_LABEL"></label>
|
||||
<input type="text" name="captcha_code" value="" id="captcha-code-field" />
|
||||
</div>
|
||||
</TMPL_IF>
|
||||
<div id="submit-container">
|
||||
<input type="submit" value="<TMPL_IF NAME="NO_APPROVAL"><TMPL_VAR NAME="SUBMIT_LABEL_NO_APPROVAL"><TMPL_ELSE><TMPL_VAR NAME="SUBMIT_LABEL"></TMPL_IF>" id="submit-button" />
|
||||
</div>
|
||||
<TMPL_VAR NAME="SUBMIT_FORM_END">
|
||||
</div>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,20 @@
|
|||
<TMPL_INCLUDE NAME="_head.html">
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/slider/range.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/slider/timer.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/slider/slider.js"></script>
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="RESOURCES_URL">/js/tag_cloud.js"></script>
|
||||
<div id="tag-cloud-slider-placeholder"></div>
|
||||
<h2>
|
||||
<TMPL_VAR NAME="PAGE_TITLE">
|
||||
</h2>
|
||||
<ul id="tag-cloud" class="tag-cloud">
|
||||
<TMPL_LOOP NAME="TAGS">
|
||||
<li class="used-<TMPL_VAR NAME="USAGE_COUNT">"><a href="<TMPL_VAR NAME="URL">"
|
||||
style="font-size: <TMPL_VAR NAME="SIZE_PERCENTAGE">%;"
|
||||
title="<TMPL_VAR NAME="LINK_DESCRIPTION"> (<TMPL_VAR NAME="USAGE_COUNT">)"><TMPL_VAR NAME="TAG"></a></li>
|
||||
</TMPL_LOOP>
|
||||
</ul>
|
||||
<script type="text/javascript">
|
||||
initializeTagCloudSlider("<TMPL_VAR NAME="USAGE_SLIDER_TITLE">");
|
||||
</script>
|
||||
<TMPL_INCLUDE NAME="_foot.html">
|
|
@ -0,0 +1,27 @@
|
|||
<p>Welcome to <em><TMPL_VAR NAME="SITE_TITLE"></em>!</p>
|
||||
<p>This web site is a collection of quotes from the Internet Relay Chat
|
||||
network. Currently, the database contains a total of <TMPL_VAR NAME="APPROVED_QUOTE_COUNT"> quote(s).
|
||||
<TMPL_IF NAME="UNAPPROVED_QUOTE_COUNT">
|
||||
We also have <TMPL_IF NAME="MODERATION_QUEUE_PUBLIC"><a
|
||||
href="<TMPL_VAR NAME="MODERATION_QUEUE_URL">"></TMPL_IF><TMPL_VAR NAME="UNAPPROVED_QUOTE_COUNT">
|
||||
quote(s)<TMPL_IF NAME="MODERATION_QUEUE_PUBLIC"></a></TMPL_IF> awaiting moderation.
|
||||
</TMPL_IF>
|
||||
</p>
|
||||
<p>Feel free to look around! Don’t forget to vote for the quotes
|
||||
you love by clicking on the <em>Up↑</em> link. Give the ones you hate the
|
||||
old thumbs down using the <em>Down↓</em> link. If you feel any of the
|
||||
quotes should not be in the collection, use the <em>Report</em> link to
|
||||
request removal. If you wish to contact the administrator of this web site,
|
||||
you can do so by sending an e-mail to <a
|
||||
href="mailto:<TMPL_VAR NAME="WEBMASTER_EMAIL">"><TMPL_VAR NAME="WEBMASTER_EMAIL"></a>.</p>
|
||||
<p><TMPL_VAR NAME="SITE_TITLE">
|
||||
is powered by
|
||||
<TMPL_VAR NAME="CHIRPY_LINK">,
|
||||
a rather fabulous and freely available quote management system.</p>
|
||||
<form method="get" action="<TMPL_VAR NAME="SITE_URL">">
|
||||
<div id="jump-to-quote-form">
|
||||
<label for="jump-to-id-field">Jump to Quote:</label>
|
||||
<input name="id" id="jump-to-id-field"/>
|
||||
<input type="submit" value="→" id="jump-to-quote-submit-button"/>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
###############################################################################
|
||||
# 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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# chirpy_rqms_import.php #
|
||||
# Imports data from an existing RQMS installation into Chirpy! #
|
||||
###############################################################################
|
||||
# $Id:: chirpy_rqms_import.php 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# CONFIGURATION #
|
||||
###############################################################################
|
||||
|
||||
// Chirpy!'s table prefix: same as in the configuration.
|
||||
$chirpy_table_prefix = 'chirpy_';
|
||||
|
||||
// Set this to false to keep this script from emptying Chirpy!'s tables first.
|
||||
// This can be useful if you want to import your Rash installation's quotes
|
||||
// AFTER using Chirpy! for a while. Note that setting this option to true will
|
||||
// copy Rash's quote IDs, which can be useful if you want to keep existing
|
||||
// links intact, while a value of false will not guarantee that.
|
||||
$clear_chirpy_tables = true;
|
||||
|
||||
// Rash only saves the date of news items, not the time. To compensate, time
|
||||
// is set to midnight in your time zone. Enter your time zone here. This needs
|
||||
// to be a time zone that Perl's strtotime() function can understand, or the
|
||||
// script will not work properly. Both GMT offsets like ‘+0200’ (no colon!)
|
||||
// and zones like ‘CET’ should be supported.
|
||||
$timezone = 'GMT';
|
||||
|
||||
###############################################################################
|
||||
# DO NOT TOUCH ANYTHING BELOW THIS LINE #
|
||||
###############################################################################
|
||||
|
||||
require('config.php');
|
||||
|
||||
set_magic_quotes_runtime(0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: text/plain; charset=UTF-8');
|
||||
|
||||
log_event('Connecting to ‘' . $hostname . '’ …', true);
|
||||
mysql_connect($hostname, $username, $dbpasswd)
|
||||
or die('Database connection failed: ' . mysql_error());
|
||||
|
||||
log_event('Accessing database ‘' . $dbname . '’ …', true);
|
||||
mysql_select_db($dbname)
|
||||
or die('Failed to select database: ' . mysql_error());
|
||||
|
||||
if ($clear_chirpy_tables) {
|
||||
log_event('Clearing Chirpy!’s tables …');
|
||||
$tables = array();
|
||||
$tables[] = 'accounts';
|
||||
$tables[] = 'events';
|
||||
$tables[] = 'event_metadata';
|
||||
$tables[] = 'news';
|
||||
$tables[] = 'quotes';
|
||||
$tables[] = 'quote_tag';
|
||||
$tables[] = 'sessions';
|
||||
$tables[] = 'tags';
|
||||
$tables[] = 'vars';
|
||||
foreach ($tables as $table)
|
||||
clear_table($chirpy_table_prefix . $table);
|
||||
log_event('Tables cleared', true);
|
||||
}
|
||||
|
||||
log_event('Importing user information …');
|
||||
$users_result = mysql_query('SELECT * FROM `' . $rashusers . '`')
|
||||
or die('Error retrieving user information: ' . mysql_error());
|
||||
$count_users = 0;
|
||||
while ($row = mysql_fetch_array($users_result)) {
|
||||
$count_users++;
|
||||
mysql_query('INSERT INTO `' . $chirpy_table_prefix . 'accounts`'
|
||||
. ' (`username`, `password`, `level`)'
|
||||
. ' VALUES ("' . $row['user'] . '", "' . $row['password'] . '", '
|
||||
. convert_level($row['level']) . ')')
|
||||
or die('Error importing user information: ' . mysql_error());
|
||||
}
|
||||
log_event('Users imported: ' . $count_users, true);
|
||||
mysqL_free_result($users_result);
|
||||
|
||||
log_event('Importing news items …');
|
||||
$news_result = mysql_query('SELECT * FROM `' . $newstable . '`')
|
||||
or die('Error retrieving news: ' . mysql_error());
|
||||
$count_news = 0;
|
||||
while ($row = mysql_fetch_array($news_result)) {
|
||||
$count_news++;
|
||||
$date = date_to_timestamp($row['date']);
|
||||
if ($date <= 0)
|
||||
die('Unable to convert ‘' + $row['date'] + '’ into a UNIX timestamp');
|
||||
mysql_query('INSERT INTO `' . $chirpy_table_prefix . 'news`'
|
||||
. ' (`body`, `date`)'
|
||||
. ' VALUES ("' . addslashes(decode_html($row['news']))
|
||||
. '", FROM_UNIXTIME(' . $date . '))')
|
||||
or die('Error importing news item: ' . mysql_error());
|
||||
}
|
||||
log_event('News items imported: ' . $count_news, true);
|
||||
mysqL_free_result($news_result);
|
||||
|
||||
log_event('Importing quotes …');
|
||||
$quotes_result = mysql_query('SELECT * FROM `' . $quotetable . '`')
|
||||
or die('Error retrieving quotes: ' . mysql_error());
|
||||
$count_quotes = 0;
|
||||
while ($row = mysql_fetch_array($quotes_result)) {
|
||||
$count_quotes++;
|
||||
$rating = $row['rating'];
|
||||
// We use abs($rating) as the number of votes here, since RQMS doesn't
|
||||
// seem to keep it anywhere.
|
||||
$votes = abs($rating);
|
||||
$score = (($votes + $rating) / 2 + 1) / (($votes - $rating) / 2 + 1);
|
||||
mysql_query('INSERT INTO `' . $chirpy_table_prefix . 'quotes` ('
|
||||
. ($clear_chirpy_tables ? '`id`, ' : '')
|
||||
. '`body`, `rating`, `votes`, `submitted`, `approved`, `flagged`,'
|
||||
. ' `score`) VALUES ('
|
||||
. ($clear_chirpy_tables ? $row['id'] . ', ' : '')
|
||||
. '"' . addslashes(decode_html($row['quote'])) . '"'
|
||||
. ', ' . $rating
|
||||
. ', ' . $votes
|
||||
. ', FROM_UNIXTIME(' . $row['date'] . ')'
|
||||
. ', ' . ($row['approve'] ? 1 : 0)
|
||||
. ', ' . ($row['check'] ? 0 : 1)
|
||||
. ', ' . $score . ')')
|
||||
or die('Error importing quote: ' . mysql_error());
|
||||
}
|
||||
log_event('Quotes imported: ' . $count_quotes, true);
|
||||
mysqL_free_result($quotes_result);
|
||||
|
||||
log_event('Importing unverified quotes …');
|
||||
$unverified_quotes_result = mysql_query('SELECT * FROM `' . $subtable . '`')
|
||||
or die('Error retrieving unverified quotes: ' . mysql_error());
|
||||
$count_unverified_quotes = 0;
|
||||
while ($row = mysql_fetch_array($unverified_quotes_result)) {
|
||||
$count_unverified_quotes++;
|
||||
mysql_query('INSERT INTO `' . $chirpy_table_prefix . 'quotes`'
|
||||
. ' (`body`, `submitted`)'
|
||||
. ' VALUES ("' . decode_html($row['quote'])
|
||||
. '", FROM_UNIXTIME(' . time() . '))')
|
||||
or die('Error importing unverified quote: ' . mysql_error());
|
||||
}
|
||||
log_event('Unverified quotes imported: ' . $count_unverified_quotes, true);
|
||||
mysqL_free_result($unverified_quotes_result);
|
||||
|
||||
log_event('Closing database connection …', true);
|
||||
mysql_close();
|
||||
|
||||
log_event('Import finished!');
|
||||
|
||||
function clear_table ($name) {
|
||||
mysql_query('TRUNCATE TABLE `' . $name . '`')
|
||||
or die('Error clearing table ‘' . $name . '’: ' . mysql_error());
|
||||
mysql_query('ALTER TABLE `' . $name . '` AUTO_INCREMENT = 1')
|
||||
or die('Error resetting auto-increment index for table ‘' . $name
|
||||
. '’: ' . mysql_error());
|
||||
}
|
||||
|
||||
function convert_level ($level) {
|
||||
return ($level && $level >= 1 && $level <= 3 ? (4 - $level) * 3 : 0);
|
||||
}
|
||||
|
||||
function decode_html ($text) {
|
||||
return html_entity_decode(
|
||||
preg_replace('|\s*<\s*br\s*/?\s*>\s*|', "\n", $text));
|
||||
}
|
||||
|
||||
function date_to_timestamp ($date) {
|
||||
global $timezone;
|
||||
$date .= ' 00:00 ' . $timezone;
|
||||
return strtotime($date);
|
||||
}
|
||||
|
||||
function log_event ($text, $end_segment) {
|
||||
echo $text . "\r\n";
|
||||
if ($end_segment) echo "\r\n";
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
?>
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# gzip.pl #
|
||||
# Provides compression for certain files, relying on .htaccess directives #
|
||||
###############################################################################
|
||||
# $Id:: gzip.pl 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use CGI;
|
||||
use CGI::Carp qw/fatalsToBrowser/;
|
||||
|
||||
use constant MODULES => [
|
||||
'HTTP::Date',
|
||||
'Digest::MD5',
|
||||
'Compress::Zlib'
|
||||
];
|
||||
use constant CACHE_DIR => 'src/cache/gzip';
|
||||
use constant MIME_TYPES => {
|
||||
'css' => 'text/css',
|
||||
'js' => 'text/javascript'
|
||||
};
|
||||
|
||||
my $cgi = new CGI();
|
||||
my $uri = $cgi->param('uri');
|
||||
my $filename = $cgi->param('filename');
|
||||
|
||||
foreach my $module (@{MODULES()}) {
|
||||
eval 'require ' . $module;
|
||||
&redirect()
|
||||
if ($@);
|
||||
}
|
||||
|
||||
unless (-d CACHE_DIR) {
|
||||
mkdir CACHE_DIR, 0777
|
||||
or &redirect();
|
||||
}
|
||||
|
||||
&redirect()
|
||||
unless (defined $uri && $ENV{'REDIRECT_URL'} eq $uri);
|
||||
|
||||
&redirect()
|
||||
unless (defined $filename && -s $filename);
|
||||
|
||||
my $file_date = (stat($filename))[9];
|
||||
|
||||
my $md5 = Digest::MD5::md5_hex($filename);
|
||||
my $etag = '"' . $md5 . '-' . sprintf('%x', $file_date) . '"';
|
||||
|
||||
my $ims = $cgi->http('If-Modified-Since');
|
||||
my $inm = $cgi->http('If-None-Match');
|
||||
|
||||
if ((defined $ims || defined $inm)
|
||||
&& ((defined $ims && $file_date <= HTTP::Date::str2time($ims))
|
||||
|| (defined $inm && $etag eq $inm))) {
|
||||
print $cgi->header(-status => '304 Not Modified');
|
||||
exit;
|
||||
}
|
||||
|
||||
my $cache_file = CACHE_DIR . '/' . $md5;
|
||||
my $contents;
|
||||
if (!-f $cache_file || (stat($cache_file))[9] < $file_date) {
|
||||
$contents = Compress::Zlib::memGzip(&get_file_contents($filename));
|
||||
&put_file_contents($cache_file, $contents);
|
||||
}
|
||||
else {
|
||||
$contents = &get_file_contents($cache_file);
|
||||
}
|
||||
|
||||
my $extension;
|
||||
$filename =~ /([^.]+)$/ and $extension = $1;
|
||||
my $ctype = (defined $extension && exists MIME_TYPES->{$extension}
|
||||
? MIME_TYPES->{$extension} : 'text/plain');
|
||||
|
||||
print $cgi->header(
|
||||
-type => $ctype,
|
||||
-Last_Modified => HTTP::Date::time2str($file_date),
|
||||
-ETag => $etag,
|
||||
-Content_Encoding => 'gzip',
|
||||
-Content_Length => length($contents)
|
||||
);
|
||||
binmode STDOUT;
|
||||
print $contents;
|
||||
|
||||
sub redirect {
|
||||
print $cgi->header(-Location => $uri . '?nogzip');
|
||||
exit;
|
||||
}
|
||||
|
||||
sub get_file_contents {
|
||||
my $filename = shift;
|
||||
local $/ = undef;
|
||||
local *FILE;
|
||||
open(FILE, '<', $filename)
|
||||
or die 'Failed to read "' . $filename . '": ' . $!;
|
||||
my $contents = <FILE>;
|
||||
close(FILE);
|
||||
return $contents;
|
||||
}
|
||||
|
||||
sub put_file_contents {
|
||||
my ($filename, $contents) = @_;
|
||||
local *FILE;
|
||||
open(FILE, '>', $filename)
|
||||
or die 'Failed to write to "' . $filename . '": ' . $!;
|
||||
print FILE $contents;
|
||||
close(FILE);
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/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 #
|
||||
###############################################################################
|
||||
|
||||
###############################################################################
|
||||
# setup.pl #
|
||||
# Generic installation/upgrade script #
|
||||
###############################################################################
|
||||
# $Id:: setup.pl 291 2007-02-05 21:24:46Z ceetee $ #
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use CGI;
|
||||
use CGI::Carp qw(fatalsToBrowser);
|
||||
|
||||
BEGIN {
|
||||
unshift @INC, 'src/modules';
|
||||
}
|
||||
|
||||
use Chirpy 0.3;
|
||||
use Chirpy::Util 0.3;
|
||||
use Chirpy::Account 0.3;
|
||||
use Chirpy::NewsItem 0.3;
|
||||
|
||||
use constant DEFAULT_USERNAME => 'superuser';
|
||||
use constant DEFAULT_PASSWORD => 'password';
|
||||
use constant DEFAULT_NEWS_ITEM => 'Welcome to this brand new '
|
||||
. Chirpy::FULL_PRODUCT_NAME . ' installation! For more about '
|
||||
. Chirpy::PRODUCT_NAME . ', be sure to visit the project homepage, '
|
||||
. 'located at <' . Chirpy::URL . '>!';
|
||||
|
||||
my $ch = new Chirpy('./chirpy.ini');
|
||||
my $cgi = new CGI();
|
||||
|
||||
print $cgi->header(-type => 'text/html; charset=US-ASCII');
|
||||
|
||||
&_header();
|
||||
|
||||
if ($cgi->request_method() eq 'POST') {
|
||||
print '<pre>';
|
||||
|
||||
my $fresh = $cgi->param('fresh');
|
||||
|
||||
if ($fresh) {
|
||||
&_log('Removing old installation (if any) ...');
|
||||
$ch->remove();
|
||||
&_log('Setting up ' . Chirpy::FULL_PRODUCT_NAME . ' ...');
|
||||
my $account = new Chirpy::Account(
|
||||
undef,
|
||||
DEFAULT_USERNAME,
|
||||
Chirpy::Util::encrypt(DEFAULT_PASSWORD),
|
||||
Chirpy::Account::USER_LEVEL_9
|
||||
);
|
||||
my $news = new Chirpy::NewsItem(
|
||||
undef,
|
||||
DEFAULT_NEWS_ITEM,
|
||||
$account,
|
||||
time
|
||||
);
|
||||
$ch->set_up([ $account ], [ $news ]);
|
||||
&_log('Account "' . DEFAULT_USERNAME . '" and news item added.');
|
||||
&_log('Setup completed!');
|
||||
}
|
||||
else {
|
||||
&_log('Upgrading to ' . Chirpy::FULL_PRODUCT_NAME . ' ...');
|
||||
$ch->set_up();
|
||||
&_log('Upgrade successful!');
|
||||
}
|
||||
|
||||
print '</pre>', $/,
|
||||
'<p><strong>Finally, you <em>must remove this file</em> (<code>',
|
||||
$0, '</code>) on the ',
|
||||
'server immediately. Failing to do so will introduce a substantial ',
|
||||
'<em>security hazard</em>.</strong></p>', $/,
|
||||
'<p>Once you have completed the final step, you may click on the ',
|
||||
'button below to surf to your new ', Chirpy::PRODUCT_NAME, ' ',
|
||||
'installation.</p>', $/,
|
||||
'<form method="get" action="./"><div>', $/,
|
||||
'<input type="submit" value="Click here to launch ',
|
||||
Chirpy::PRODUCT_NAME, '">', $/, '</div></form>';
|
||||
}
|
||||
else {
|
||||
print '<p>Welcome to the ', Chirpy::FULL_PRODUCT_NAME, ' setup ',
|
||||
'script.</p>', $/,
|
||||
'<p><strong>Please make sure that all the necessary files are ',
|
||||
'present and that you have edited <code>chirpy.ini</code> ',
|
||||
'to match your configuration. Otherwise, the setup process ',
|
||||
'<em>will</em> fail.</strong></p>', $/,
|
||||
'<p>Now, please tell us if this is a fresh installation, or you ',
|
||||
'are upgrading an existing installation.</p>', $/,
|
||||
'<form method="POST" action="', $cgi->script_name(), '"><div>', $/,
|
||||
'<input type="submit" name="fresh" ',
|
||||
'value="FRESH INSTALLATION" ',
|
||||
'onclick="return confirm("This will DELETE all existing data. ',
|
||||
'Are you sure?");">', $/,
|
||||
'<input type="submit" name="upgrade" ',
|
||||
'value="UPGRADE (a.k.a. Keep My Stuff!)">', $/,
|
||||
'</div></form>', $/,
|
||||
'<p><strong>Please click the button only once!</strong> ',
|
||||
'The operation might take a while to complete.</p>';
|
||||
}
|
||||
|
||||
&_footer();
|
||||
|
||||
sub _header {
|
||||
print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"', $/,
|
||||
'"http://www.w3.org/TR/html4/strict.dtd">', $/,
|
||||
'<html>', $/,
|
||||
'<head>', $/,
|
||||
'<meta http-equiv="Content-Type"', $/,
|
||||
'content="text/html; charset=UTF-8">', $/,
|
||||
'<title>', Chirpy::FULL_PRODUCT_NAME, ' Setup</title>', $/,
|
||||
'</head>', $/,
|
||||
'<body>', $/,
|
||||
'<h1>', Chirpy::FULL_PRODUCT_NAME, ' Setup</h1>', $/;
|
||||
}
|
||||
|
||||
sub _footer {
|
||||
print '</body>', $/, '</html>';
|
||||
}
|
||||
|
||||
sub _log {
|
||||
my $message = shift;
|
||||
print $message, $/;
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -18,7 +18,7 @@
|
|||
<a href="http://mail.csclub.uwaterloo.ca/">webmail</a>
|
||||
<a href="http://csclub.uwaterloo.ca/newsgroup/">newsgroups</a>
|
||||
<a href="http://csclub.uwaterloo.ca/mailman/">mailman</a>
|
||||
<a href="http://csclub.uwaterloo.ca/pub/qdb/">quoteboard</a>
|
||||
<a href="{{ url_root}}pub/qdb/">quoteboard</a>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="{{ url_root }}changelog">changelog</a>
|
||||
|
|
Loading…
Reference in New Issue