Merge branch 'master' of git.triptico.com:git/gruta
[gruta.git] / Gruta.pm
blob024f024febd4f314c557d923e6a25a83edbdb712
1 package Gruta;
3 use strict;
4 use warnings;
6 use locale;
8 use Gruta::Data;
10 $Gruta::VERSION = '2.0.1-dev';
12 sub sources { return @{$_[0]->{sources}}; }
13 sub template { return $_[0]->{template}; }
14 sub cgi { return $_[0]->{cgi}; }
16 sub version { return $Gruta::VERSION; }
18 sub log {
19 my $self = shift;
20 my $msg = shift;
22 print STDERR $self->{id}, ' ', scalar(localtime), ': ', $msg, "\n";
26 sub _call {
27 my $self = shift;
28 my $method = shift;
29 my $short = shift;
31 my @r = ();
33 if (!exists($self->{calls}->{$method})) {
35 # cache all calls in the sources
36 my @c = ();
38 foreach my $s ($self->sources()) {
39 if (my $m = $s->can($method)) {
40 push(@c, sub { return $m->($s, @_) });
44 $self->{calls}->{$method} = [ @c ];
47 foreach my $m (@{ $self->{calls}->{$method}}) {
48 my @pr = $m->(@_);
50 if (@pr && $pr[0]) {
51 @r = (@r, @pr);
53 last if $short;
57 return wantarray ? @r : $r[0];
60 sub topic { my $self = shift; return $self->_call('topic', 1, @_); }
61 sub topics { my $self = shift; return $self->_call('topics', 0); }
63 sub user { my $self = shift; return $self->_call('user', 1, @_); }
64 sub users { my $self = shift; return $self->_call('users', 0); }
66 sub story {
67 my $self = shift;
68 my $topic_id = shift;
69 my $id = shift;
71 if (! $topic_id || ! $id) {
72 return undef;
75 my $story = undef;
76 my $ck = $topic_id . '/' . $id;
78 if ($story = $self->{story_cache}->{$ck}) {
79 return $story;
82 if (not $story = $self->_call('story', 1, $topic_id, $id)) {
83 return undef;
86 my $format = $story->get('format') || 'grutatxt';
88 if (my $rndr = $self->{renderers_h}->{$format}) {
89 $rndr->story($story);
92 return $self->{story_cache}->{$ck} = $story;
96 sub stories { my $self = shift; return $self->_call('stories', 0, @_); }
98 sub stories_by_date {
99 my $self = shift;
100 my $topics = shift;
101 my %opts = @_;
103 my @r = sort { $b->[2] cmp $a->[2] } $self->_call('stories_by_date', 0, $topics, %opts);
105 if ($opts{num} && scalar(@r) > $opts{num}) {
106 @r = @r[0 .. $opts{num} - 1];
109 return @r;
112 sub search_stories {
113 my $self = shift;
114 my $topic_id = shift;
116 my @l = $self->_call('search_stories', 1, $topic_id, @_);
118 return sort { $self->story($topic_id, $a)->get('title') cmp
119 $self->story($topic_id, $b)->get('title') } @l;
122 sub stories_top_ten {
123 my $self = shift;
125 my @l = $self->_call('stories_top_ten', 0, @_);
127 return sort { $b->[0] <=> $a->[0] } @l;
130 sub stories_by_tag {
131 my $self = shift;
133 my @l = $self->_call('stories_by_tag', 0, @_);
135 return sort { $self->story($a->[0], $a->[1])->get('title') cmp
136 $self->story($b->[0], $b->[1])->get('title') } @l;
139 sub tags {
140 my $self = shift;
142 my @l = $self->_call('tags', 0, @_);
144 return sort { $a->[0] cmp $b->[0] } @l;
147 sub insert_topic { my $self = shift; return $self->_call('insert_topic', 1, @_); }
148 sub insert_user { my $self = shift; return $self->_call('insert_user', 1, @_); }
149 sub insert_story { my $self = shift; return $self->_call('insert_story', 1, @_); }
152 sub auth {
153 my $self = shift;
155 if (@_) { $self->{auth} = shift; } # Gruta::Data::User
157 return $self->{auth};
161 sub auth_from_sid {
162 my $self = shift;
163 my $sid = shift;
165 my $u = undef;
167 if ($sid) {
168 $self->_call('purge_old_sessions', 0);
170 if (my $session = $self->_call('session', 1, $sid)) {
171 $u = $session->source->user( $session->get('user_id') );
173 if ($u) {
174 $u->set('sid', $sid);
175 $self->auth($u);
180 return $u;
184 sub login {
185 my $self = shift;
186 my $user_id = shift;
187 my $passwd = shift;
189 my $sid = undef;
191 if (my $u = $self->user( $user_id )) {
193 # account expired? go!
194 if (my $xdate = $u->get('xdate')) {
195 if (Gruta::Data::today() > $xdate) {
196 return undef;
200 my $p = $u->get('password');
202 if (Gruta::Data::crypt($passwd, $p) eq $p) {
203 # create new sid
204 my $session = Gruta::Data::Session->new(user_id => $user_id);
206 $u->source->insert_session( $session );
208 $sid = $session->get('id');
209 $u->set('sid', $sid);
210 $self->auth($u);
214 return $sid;
218 sub logout {
219 my $self = shift;
221 if (my $auth = $self->auth()) {
222 if( my $sid = $auth->get('sid')) {
223 if (my $session = $auth->source->session( $sid )) {
224 $session->delete() if $session->can('delete');
229 $self->auth( undef );
230 return $self;
234 sub base_url { $_[0]->{args}->{base_url} || '' };
236 sub url {
237 my $self = shift;
238 my $st = shift || '';
239 my %args = @_;
241 my $ret = $self->base_url();
243 if ($self->{args}->{static_urls}) {
244 my $kn = scalar(keys(%args));
246 if ($st eq 'INDEX' && $kn == 0) {
247 return $ret;
249 elsif ($st eq 'TOPIC' && $kn == 1) {
250 return $ret . $args{topic} . '/';
252 elsif ($st eq 'STORY' && $kn == 2) {
253 return $ret . $args{topic} . '/' . $args{id} . '.html';
257 if ($st) {
258 $args{t} = $st;
260 $ret .= '?' . join(';', map { "$_=$args{$_}" } sort keys(%args));
263 return $ret;
267 sub _topic_special_uri {
268 my $self = shift;
269 my $topic_id = shift;
271 my $ret = undef;
273 if (my $t = $self->topic($topic_id)) {
274 $ret = sprintf('<a href="%s">%s</a>',
275 $self->url('TOPIC', 'topic' => $topic_id),
276 $t->get('name')
279 else {
280 $ret = "Bad topic $topic_id";
283 return $ret;
287 sub _story_special_uri {
288 my $self = shift;
289 my $topic_id = shift;
290 my $story_id = shift;
292 my $ret = undef;
294 if (my $s = $self->story($topic_id, $story_id)) {
295 $ret = sprintf('<a href="%s">%s</a>',
296 $self->url('STORY',
297 'topic' => $topic_id,
298 'id' => $story_id
300 $s->get('title')
303 else {
304 $ret = "Bad story '$topic_id/$story_id'";
307 return $ret;
311 sub _img_special_uri {
312 my $self = shift;
313 my $src = shift;
314 my $class = shift;
316 my $r = sprintf('<img src = "%simg/%s" />',
317 $self->base_url(), $src
320 if ($class) {
321 $r = "<span class = '$class'>" . $r . '</span>';
324 return $r;
328 sub _content_special_uri {
329 my $self = shift;
330 my $topic_id = shift;
331 my $story_id = shift;
332 my $field = shift;
334 my $ret = undef;
336 if (my $s = $self->story($topic_id, $story_id)) {
337 $ret = $self->special_uris($s->get($field));
339 else {
340 $ret = "Bad story '$topic_id/$story_id'";
343 return $ret;
348 sub special_uris {
349 my $self = shift;
350 my $string = shift;
352 $string =~ s!topic://([\w\d_-]+)!$self->_topic_special_uri($1)!ge;
353 $string =~ s!story://([\w\d_-]+)/([\w\d_-]+)!$self->_story_special_uri($1,$2)!ge;
354 $string =~ s!img://([\w\d_\.-]+)/?([\w\d_-]*)!$self->_img_special_uri($1,$2)!ge;
355 $string =~ s!body://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'body')!ge;
356 $string =~ s!abstract://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'abstract')!ge;
358 return $string;
362 sub transfer_to_source {
363 my $self = shift;
364 my $dst = shift;
366 foreach my $id ($self->users()) {
367 my $u = $self->user($id);
368 $dst->insert_user($u);
371 foreach my $topic_id (sort $self->topics()) {
372 my $t = $self->topic($topic_id);
374 my $nti = $topic_id;
376 # is it an archive?
377 if ($nti =~ /-arch$/) {
378 # don't insert topic, just rename
379 $nti =~ s/-arch$//;
381 else {
382 $dst->insert_topic($t);
385 foreach my $id ($self->stories($topic_id)) {
387 # get story and its tags
388 my $s = $self->story($topic_id, $id);
389 my @tags = $s->tags();
391 # set new topic
392 $s->set('topic_id', $nti);
394 my $ns = $dst->insert_story($s);
396 if (@tags) {
397 $ns->tags(@tags);
402 return $self;
406 sub flush_story_cache {
407 my $self = shift;
409 $self->{story_cache} = {};
413 sub new {
414 my $class = shift;
416 my $g = bless( { @_ } , $class);
418 $g->{id} ||= 'Gruta';
419 $g->{args} ||= {};
421 $g->{story_cache} = {};
422 $g->{renderers_h} = {};
423 $g->{calls} = {};
425 if ($g->{sources}) {
426 if (ref($g->{sources}) ne 'ARRAY') {
427 $g->{sources} = [ $g->{sources} ];
430 foreach my $s (@{$g->{sources}}) {
431 $s->data($g);
435 if ($g->{renderers}) {
436 if (ref($g->{renderers}) ne 'ARRAY') {
437 $g->{renderers} = [ $g->{renderers} ];
440 foreach my $r (@{$g->{renderers}}) {
441 $g->{renderers_h}->{$r->{renderer_id}} = $r;
445 if ($g->{template}) {
446 $g->template->data($g);
449 if ($g->{cgi}) {
450 $g->cgi->data($g);
453 return $g;
456 sub run {
457 my $self = shift;
459 if ($self->{cgi}) {
460 $self->cgi->run();