moved qdb here because matt is lazy

This commit is contained in:
Jacob Parker 2012-03-15 21:41:53 -04:00
parent fb02a3f4c5
commit 2ee07a1396
83 changed files with 19572 additions and 1 deletions

22
pub/qdb/.htaccess Normal file
View File

@ -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

BIN
pub/qdb/.install.txt.swp Normal file

Binary file not shown.

111
pub/qdb/changelog.txt Normal file
View File

@ -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 &#xA0; instead
of &nbsp;, 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

42
pub/qdb/chirpy.ini Normal file
View File

@ -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

60
pub/qdb/index.cgi Executable file
View File

@ -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');
###############################################################################

533
pub/qdb/install.txt Normal file
View File

@ -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.
###############################################################################

340
pub/qdb/license.txt Normal file
View File

@ -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.

34
pub/qdb/readme.txt Normal file
View File

@ -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>
###############################################################################

39
pub/qdb/res/.htaccess Normal file
View File

@ -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>

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -0,0 +1 @@
opensource@google.com

View File

@ -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.

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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(/ +/);
}

View File

@ -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>

View File

@ -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;
};

View File

@ -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();
};

View File

@ -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;
};

View File

@ -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());

View File

@ -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');
}
}

View File

@ -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;
}

4
pub/qdb/src/.htaccess Normal file
View File

@ -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.

View File

@ -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:

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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

View File

@ -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

View File

@ -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;
###############################################################################

View File

@ -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

View File

@ -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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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(&quot;'
. &_text_to_xhtml($locale->get_string(
'webapp.remove_quote_without_viewing_confirmation'))
. '&quot;))">' . $/
. '<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'))
. '&rarr;" /></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()
. ' &lt;' . $self->parent()->parent()->user_level_name($level)
. '&gt;</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">'
. '&lt;&lt; '
. &_text_to_xhtml($locale->get_string('new_account'))
. ' &gt;&gt;</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() . ' &lt;'
. &_text_to_xhtml(
$self->parent()->parent()->user_level_name($user->get_level()))
. '&gt;</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">&lt;'
. &_text_to_xhtml(
$locale->get_string('no_change'))
. '&gt;</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))
. ' &lt;' . $level . '&gt;</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(&quot;'
. &_text_to_xhtml(
$locale->get_string('account_removal_confirmation'))
. '&quot;)" />' . $/
. '</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 =~ /\?/ ? '&amp;' : '?');
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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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/&amp;/&/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;
###############################################################################

View File

@ -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
========= ======
& &amp;
" &quot;
< &lt;
> &gt;
=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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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;
###############################################################################

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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">">&larr; <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></a>
<TMPL_ELSE>
<span class="inactive">&larr; <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"> &rarr;</a>
<TMPL_ELSE>
<span class="inactive"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> &rarr;</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">&uarr;</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">&darr;</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(&quot;<TMPL_VAR NAME="REMOVAL_CONFIRMATION">&quot;)) return false; this.href += &quot;&amp;confirm=1&quot;; 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">&uarr;</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">&darr;</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">">&larr; <TMPL_VAR NAME="PREVIOUS_PAGE_TITLE"></a>
<TMPL_ELSE>
<span class="inactive">&larr; <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"> &rarr;</a>
<TMPL_ELSE>
<span class="inactive"><TMPL_VAR NAME="NEXT_PAGE_TITLE"> &rarr;</span>
</TMPL_IF>
</div>
</TMPL_IF>
<TMPL_INCLUDE NAME="_foot.html">

View File

@ -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>&lt;Ria&gt;</del> Type in the search word,<br/>
<del>&lt;Ria&gt;</del> apply pressure to button,<br/>
<del>&lt;Ria&gt;</del> then read the results.<del><br/>
&lt;Ria&gt; it's a haiku!</del>
</div>
<TMPL_INCLUDE NAME="_foot.html">

View File

@ -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">&mdash;<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(&quot;<TMPL_VAR NAME="REMOVAL_CONFIRMATION">&quot;)">[<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">

View File

@ -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&ndash;<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">

View File

@ -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">

View File

@ -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">

27
pub/qdb/src/welcome.html Normal file
View File

@ -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&rsquo;t forget to vote for the quotes
you love by clicking on the <em>Up&uarr;</em> link. Give the ones you hate the
old thumbs down using the <em>Down&darr;</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="&rarr;" id="jump-to-quote-submit-button"/>
</div>
</form>

View File

@ -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";
}
###############################################################################
?>

132
pub/qdb/util/gzip.pl Normal file
View File

@ -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);
}
###############################################################################

145
pub/qdb/util/setup.pl Normal file
View 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(&quot;This will DELETE all existing data. ',
'Are you sure?&quot;);">', $/,
'<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, $/;
}
###############################################################################

View File

@ -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>