Version 2.1.1 RELEASED.
[gruta.git] / Gruta.pm
blob703cf1295631a48bcb7ae39e47d0aff511eb13a7
1 package Gruta;
3 use strict;
4 use warnings;
6 use locale;
8 use Gruta::Data;
10 $Gruta::VERSION = '2.1.1';
11 $Gruta::VERSION_CODENAME = '"Calenzano"';
13 sub sources { return @{$_[0]->{sources}}; }
14 sub template { return $_[0]->{template}; }
15 sub cgi { return $_[0]->{cgi}; }
17 sub version { return $Gruta::VERSION . ' ' . $Gruta::VERSION_CODENAME; }
19 sub log {
20 my $self = shift;
21 my $msg = shift;
23 print STDERR $self->{id}, ' ', scalar(localtime), ': ', $msg, "\n";
27 sub _call {
28 my $self = shift;
29 my $method = shift;
30 my $short = shift;
32 my @r = ();
34 if (!exists($self->{calls}->{$method})) {
36 # cache all calls in the sources
37 my @c = ();
39 foreach my $s ($self->sources()) {
40 if (my $m = $s->can($method)) {
41 push(@c, sub { return $m->($s, @_) });
45 $self->{calls}->{$method} = [ @c ];
48 foreach my $m (@{ $self->{calls}->{$method}}) {
49 my @pr = $m->(@_);
51 if (@pr && $pr[0]) {
52 @r = (@r, @pr);
54 last if $short;
58 return wantarray ? @r : $r[0];
61 sub topic { my $self = shift; return $self->_call('topic', 1, @_); }
62 sub topics { my $self = shift; return $self->_call('topics', 0); }
64 sub user { my $self = shift; return $self->_call('user', 1, @_); }
65 sub users { my $self = shift; return $self->_call('users', 0); }
67 sub story {
68 my $self = shift;
69 my $topic_id = shift;
70 my $id = shift;
72 if (! $topic_id || ! $id) {
73 return undef;
76 my $story = undef;
77 my $ck = $topic_id . '/' . $id;
79 if ($story = $self->{story_cache}->{$ck}) {
80 return $story;
83 if (not $story = $self->_call('story', 1, $topic_id, $id)) {
84 return undef;
87 my $format = $story->get('format') || 'grutatxt';
89 if (my $rndr = $self->{renderers_h}->{$format}) {
90 $rndr->story($story);
93 return $self->{story_cache}->{$ck} = $story;
97 sub stories { my $self = shift; return $self->_call('stories', 0, @_); }
99 sub stories_by_date {
100 my $self = shift;
101 my $topics = shift;
102 my %opts = @_;
104 my @r = sort { $b->[2] cmp $a->[2] } $self->_call('stories_by_date', 0, $topics, %opts);
106 if ($opts{num} && scalar(@r) > $opts{num}) {
107 @r = @r[0 .. $opts{num} - 1];
110 return @r;
113 sub search_stories {
114 my $self = shift;
115 my $topic_id = shift;
117 my @l = $self->_call('search_stories', 1, $topic_id, @_);
119 return sort { $self->story($topic_id, $a)->get('title') cmp
120 $self->story($topic_id, $b)->get('title') } @l;
123 sub stories_top_ten {
124 my $self = shift;
126 my @l = $self->_call('stories_top_ten', 0, @_);
128 return sort { $b->[0] <=> $a->[0] } @l;
131 sub stories_by_tag {
132 my $self = shift;
134 my @l = $self->_call('stories_by_tag', 0, @_);
136 return sort { $self->story($a->[0], $a->[1])->get('title') cmp
137 $self->story($b->[0], $b->[1])->get('title') } @l;
140 sub tags {
141 my $self = shift;
143 my @l = $self->_call('tags', 0, @_);
145 return sort { $a->[0] cmp $b->[0] } @l;
148 sub insert_topic { my $self = shift; return $self->_call('insert_topic', 1, @_); }
149 sub insert_user { my $self = shift; return $self->_call('insert_user', 1, @_); }
150 sub insert_story { my $self = shift; return $self->_call('insert_story', 1, @_); }
153 sub auth {
154 my $self = shift;
156 if (@_) { $self->{auth} = shift; } # Gruta::Data::User
158 return $self->{auth};
162 sub auth_from_sid {
163 my $self = shift;
164 my $sid = shift;
166 my $u = undef;
168 if ($sid) {
169 $self->_call('purge_old_sessions', 0);
171 if (my $session = $self->_call('session', 1, $sid)) {
172 $u = $session->source->user( $session->get('user_id') );
174 if ($u) {
175 $u->set('sid', $sid);
176 $self->auth($u);
181 return $u;
185 sub login {
186 my $self = shift;
187 my $user_id = shift;
188 my $passwd = shift;
190 my $sid = undef;
192 if (my $u = $self->user( $user_id )) {
194 # account expired? go!
195 if (my $xdate = $u->get('xdate')) {
196 if (Gruta::Data::today() > $xdate) {
197 return undef;
201 my $p = $u->get('password');
203 if (Gruta::Data::crypt($passwd, $p) eq $p) {
204 # create new sid
205 my $session = Gruta::Data::Session->new(user_id => $user_id);
207 $u->source->insert_session( $session );
209 $sid = $session->get('id');
210 $u->set('sid', $sid);
211 $self->auth($u);
215 return $sid;
219 sub logout {
220 my $self = shift;
222 if (my $auth = $self->auth()) {
223 if( my $sid = $auth->get('sid')) {
224 if (my $session = $auth->source->session( $sid )) {
225 $session->delete() if $session->can('delete');
230 $self->auth( undef );
231 return $self;
235 sub base_url { $_[0]->{args}->{base_url} || '' };
237 sub url {
238 my $self = shift;
239 my $st = shift || '';
240 my %args = @_;
242 my $ret = $self->base_url();
244 # strip all undefined or empty arguments
245 %args = map { $_, $args{$_} } grep { $args{$_} } keys(%args);
247 if ($self->{args}->{static_urls}) {
248 my $kn = scalar(keys(%args));
250 if ($st eq 'INDEX' && $kn == 0) {
251 return $ret;
253 elsif ($st eq 'TOPIC' && $kn == 1) {
254 return $ret . $args{topic} . '/';
256 elsif ($st eq 'STORY' && $kn == 2) {
257 return $ret . $args{topic} . '/' . $args{id} . '.html';
261 if ($st) {
262 $args{t} = $st;
264 $ret .= '?' . join(';', map { "$_=$args{$_}" } sort keys(%args));
267 return $ret;
271 sub _topic_special_uri {
272 my $self = shift;
273 my $topic_id = shift;
275 my $ret = undef;
277 if (my $t = $self->topic($topic_id)) {
278 $ret = sprintf('<a href="%s">%s</a>',
279 $self->url('TOPIC', 'topic' => $topic_id),
280 $t->get('name')
283 else {
284 $ret = "Bad topic $topic_id";
287 return $ret;
291 sub _story_special_uri {
292 my $self = shift;
293 my $topic_id = shift;
294 my $story_id = shift;
296 my $ret = undef;
298 if (my $s = $self->story($topic_id, $story_id)) {
299 $ret = sprintf('<a href="%s">%s</a>',
300 $self->url('STORY',
301 'topic' => $topic_id,
302 'id' => $story_id
304 $s->get('title')
307 else {
308 $ret = "Bad story '$topic_id/$story_id'";
311 return $ret;
315 sub _img_special_uri {
316 my $self = shift;
317 my $src = shift;
318 my $class = shift;
320 my $r = sprintf('<img src = "%simg/%s" />',
321 $self->base_url(), $src
324 if ($class) {
325 $r = "<span class = '$class'>" . $r . '</span>';
328 return $r;
332 sub _content_special_uri {
333 my $self = shift;
334 my $topic_id = shift;
335 my $story_id = shift;
336 my $field = shift;
338 my $ret = undef;
340 if (my $s = $self->story($topic_id, $story_id)) {
341 $ret = $self->special_uris($s->get($field));
343 else {
344 $ret = "Bad story '$topic_id/$story_id'";
347 return $ret;
352 sub special_uris {
353 my $self = shift;
354 my $string = shift;
356 $string =~ s!topic://([\w\d_-]+)!$self->_topic_special_uri($1)!ge;
357 $string =~ s!story://([\w\d_-]+)/([\w\d_-]+)!$self->_story_special_uri($1,$2)!ge;
358 $string =~ s!img://([\w\d_\.-]+)/?([\w\d_-]*)!$self->_img_special_uri($1,$2)!ge;
359 $string =~ s!body://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'body')!ge;
360 $string =~ s!abstract://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'abstract')!ge;
362 return $string;
366 sub transfer_to_source {
367 my $self = shift;
368 my $dst = shift;
370 foreach my $id ($self->users()) {
371 my $u = $self->user($id);
372 $dst->insert_user($u);
375 foreach my $topic_id (sort $self->topics()) {
376 my $t = $self->topic($topic_id);
378 my $nti = $topic_id;
380 # is it an archive?
381 if ($nti =~ /-arch$/) {
382 # don't insert topic, just rename
383 $nti =~ s/-arch$//;
385 else {
386 $dst->insert_topic($t);
389 foreach my $id ($self->stories($topic_id)) {
391 # get story and its tags
392 my $s = $self->story($topic_id, $id);
393 my @tags = $s->tags();
395 # set new topic
396 $s->set('topic_id', $nti);
398 my $ns = $dst->insert_story($s);
400 if (@tags) {
401 $ns->tags(@tags);
406 return $self;
410 sub flush_story_cache {
411 my $self = shift;
413 $self->{story_cache} = {};
417 sub new {
418 my $class = shift;
420 my $g = bless( { @_ } , $class);
422 $g->{id} ||= 'Gruta';
423 $g->{args} ||= {};
425 $g->{story_cache} = {};
426 $g->{renderers_h} = {};
427 $g->{calls} = {};
429 if ($g->{sources}) {
430 if (ref($g->{sources}) ne 'ARRAY') {
431 $g->{sources} = [ $g->{sources} ];
434 foreach my $s (@{$g->{sources}}) {
435 $s->data($g);
439 if ($g->{renderers}) {
440 if (ref($g->{renderers}) ne 'ARRAY') {
441 $g->{renderers} = [ $g->{renderers} ];
444 foreach my $r (@{$g->{renderers}}) {
445 $g->{renderers_h}->{$r->{renderer_id}} = $r;
449 if ($g->{template}) {
450 $g->template->data($g);
453 if ($g->{cgi}) {
454 $g->cgi->data($g);
457 return $g;
460 sub run {
461 my $self = shift;
463 if ($self->{cgi}) {
464 $self->cgi->run();