This article assumes a working knowledge of perl, with a little
familiarity with the Perl OO system. It also assumes a little
knowledge of sql
First things first, where is everything!? Scoop is set up in a type
of heirarchy, with everthing in the lib/ directory of your scoop
distribution. You'll notice 2 directories there, Bundle, and Scoop.
Don't worry about Bundle, unless you like futzing with CPAN.
Scoop.pm has all the main initialization routines, which you shouldn't
need to worry about for now. All of the different perl modules that
make up scoop fall under the Scoop directory.
Within the Scoop directory its pretty easy to understand, Polls stuff
is in Polls.pm and under the Polls/ dir, Comments in the Comments.pm
and Comments/ dir, etc. If you're adding a new module, and it starts
to get bigger than about 1000 lines, think hard about splitting it up
like some of the other modules.
All of the modules have some degree of POD ( run 'perldoc perldoc' if you're not
familiar with it), so a lot of information about how they work, and how
to use them can be gleaned from typing 'perldoc Scoop.pm' or any other
.pm file.
Now that you can poke around in the files a bit and see how things work,
whats that $S think you see around everywhere? Well if you've done other
OOPerl projects, you might have seen it as $self. Its the Scoop object,
which is implicitly passed as the first parameter to any method call
that it makes. Make sense? Here is an example.
$S->function('argument');
sub function {
my $S = shift; # gets the $S object
my $arg = shift; # gets 'argument'
}
So if you write any new functions, be sure to make the first parameter they
take be $S (i.e. just put in that 'my $S = shift;' line) or you'll notice
very strange errors... trust me on this :)
There is quite a bit more about the $S object than just being the first
argument to a method call. Since it is a blessed hash ( run
'perldoc perltoot' for more info on perl OO ) you have access to all of its
keys as well as all of its methods. For example, there are 2 ways to call
the scoop CGI.pm methods:
# first way, cgi is set up as a method
$S->cgi->param();
# second way, cgi is a hashref to a method
$S->{CGI}->param();
What you use is up to you. Most of the time you will see the second
notation in use in scoop though. For a list of most of the possible
methods and keys in $S,
check out the
Scoop Administrators Guide section on $S.
Now what about getting access to all of the vars and
blocks? Well, they're loaded at initialization, and reloaded on any change
to them. What that means for the developers is that we don't have to go
mucking around in any database calls just to get at a simple var or block.
Its loaded up at initialization, and put in the $S->{UI} hashref. All of the
vars are loaded and stored in $S->{UI}->{VARS} and all of the blocks are
in $S->{UI}->{BLOCKS}. An example usage:
$S->{UI}->{BLOCKS}->{CONTENT} = $S->{UI}->{BLOCKS}->{story_summary};
if( $S->{UI}->{VARS}->{'show_time'} ) {
my $time = localtime();
$S->{UI}->{BLOCKS}->{CONTENT} =~ s/%%TIME%%/$time/g;
}
"But wait!" you say. "I see no 'CONTENT' block anywhere in my database!
Am I missing something?" No. The CONTENT block comes from the template
that is being used to display the page. Any |nameslikethis| in vertical bars
in the template block get added to the BLOCKS hashref for you to fill
while processing the request, UNLESS the name inside the vertical bars is
an existing block, in which case that block gets substitued in for the
|nameslikethis|. You might also notice in that example above that I don't
do a substitution for |TIME|, I do it for %%TIME%%. Thats because the
database stores those as %%nameslikethis%% as opposed to |nameslikethis|.
This is to make sure that when scoop is displaying the block editor, it
doesn't substitute out the |nameslikethis|, it gives you a chance to edit
them.
So now you can call functions, and access blocks. But what about query
strings in the url? How can you get to all those key/value pairs?
With $S->{CGI}->param(). Used as $S->{CGI}->param('foo') it will return the
value of foo from the url, unless foo had multiple values, in which it
returns an arrayref of all the values (which is used only 1 place in Scoop,
at the moment, so you'll probably not have to use it ). If you call
param() with no parameters, it will return an array of all of the keys
in the url.
# url is http://scoopsite.com/?op=foo;baz=bar;baz=blah;baz=arg
my @keys = $S->{CGI}->param(); # @keys = ('op', 'baz')
my $op = $S->{CGI}->param('op'); # $op eq 'foo'
my @bazs = $S->{CGI}->param('baz'); # @bazs = ('bar', 'blah', 'arg')
One of the things you'll do most often in scoop is access the database.
This is done via a few database wrapper utilities. Each takes an anonymous
hash as its single argument, with a few predefined keys that help to
build the query. Its best shown by example, so here is a few:
my $input = $S->{CGI}->param('input');
# ALWAYS quote user input, talked about more below
# in the database section
my $q_input = $S->{DBH}->quote($input);
my ($rv, $sth) = $S->db_select({
DEBUG => 1,
WHAT => 'uid',
FROM => 'users',
WHERE => qq| nickname = $q_input |,
});
if( $rv ) {
while( my $row = $sth->fetchrow_hashref ) {
warn "nickname $input has uid $row->{uid}";
}
}
For information on what functions $sth supports, read the pod for DBI,
'perldoc DBI'. The most used in scoop is fetchrow_hashref, since its the
most readable. For the record, $rv is the return value from the query,
it will be false if it failed, and true if it succeeded.
You might have noticed the warn() call above as well. If you've done cgi
before you might recognize it. It logs a message to the error_log. Very
handy for debugging.
Here is a short list of the keys that each db_* call supports, each is a mysql
keyword, so dig in your sql manual for keys you don't understand
- db_select - DISTINCT, WHERE, FROM, GROUP_BY, ORDER_BY, LIMIT, DEBUG
- db_insert - INTO, COLS, VALUES, DEBUG
- db_update - WHERE, LIMIT, DEBUG
- db_delete - WHERE, LIMIT, DEBUG
Soon the database access functions will change a bit, to do quoting for you, but
for now, you have to be sure to quote() ALL input from the users that is going
into the database. In my example above you would have seen it used. You access the
quote function through $S->{DBH}, which is a reference to the database handle
my $foo = $S->{CGI}->param('foo');
my $q_foo = $S->{DBH}->quote($foo);
# now $q_foo is safe to use in queries
Now why should you always quote your input you ask? Well, it keeps the code more
secure. Say that I have a hidden form value like the following:
<input type="hidden" name="foo" value="bar">
When I get the value in scoop, I need to quote it. Even though its a hidden field!
This is because someone might save that page, and modify the input value to
be like the following
<input type="hidden" name="foo" value=" ';DELETE FROM USERS; ">
Now whats above won't work as-is, but with a little work, and playing around with
the options (especially since everyone has access to the scoop source) people can
see what will work to run arbitrary sql commands on your database. So you can
see, that by not quoting input from the user, you're essentially giving anybody
with enough time a myqsl prompt to your database.
Now you should know enough to create your own module. There are 3 main things you
need to do to add a module into Scoop:
- Create the file in the Scoop/ directory. At the top of the file,( no need for the
'#!/usr/bin/perl' line, since this is mod_perl), be sure to put in a 'package Scoop;'
line. This will allow
all of your function names to be accessed via the $S object. Be sure to use unique
names, so it might be handy to prefix them with a meaningful string. Like
rdf_add_channel() for RDF.pm methods.
- Make sure the file is associated with an op. Open up Scoop/ApacheHandler.pm in
your favorite text editor (vim!!) and add a call to the method you made in your
Scoop/MyModule.pm file to an appropriate place in the large if/else tree in
_main_op_select()
- Make sure your module gets loaded on startup. Just add a line for it in
startup.pl, which is in the etc/ directory of a default scoop tarball.
There are a few more small things, like setting which template to use with your op (
hint: check Scoop/Admin/OpTemplates.pm, add an op to the list, and set it in the
Template Admin tool). Stopping and Starting apache after every change is another
thing to remember to do.
So with that you should be able to hack on Scoop with relative ease. With a little
bit of messing around with the current code you'll get the hang of it quickly, its
not a very difficult system to learn. If you have any questions about a certain
implementation, feel free to mail the scoop-dev list, you can join it on
Sourceforge.net.