Many fixes to Art5 and templates.
[gruta.git] / Gruta.pm
blob82983733513457a9e3800a89ed6e93c4514e2918
1 package Gruta;
3 use strict;
4 use warnings;
6 use locale;
8 use Gruta::Data;
10 $Gruta::VERSION = '2.2.2-dev';
11 $Gruta::VERSION_CODENAME = '"Lucca"';
13 sub source {
14 $_[0]->{source};
17 sub template {
18 $_[0]->{template};
21 sub cgi {
22 $_[0]->{cgi};
25 sub version {
26 $Gruta::VERSION . ' ' . $Gruta::VERSION_CODENAME;
29 sub log {
30 my $self = shift;
31 my $msg = shift;
33 print STDERR $self->{id}, ' ', scalar(localtime), ': ', $msg, "\n";
37 sub render {
38 my $self = shift;
39 my $story = shift; # Gruta::Data::Story
41 my $format = $story->get('format') || 'grutatxt';
43 if (my $rndr = $self->{renderers_h}->{$format}) {
44 $rndr->story($story);
49 sub auth {
50 my $self = shift;
52 if (@_) {
53 $self->{auth} = shift; # Gruta::Data::User
56 return $self->{auth};
60 sub session {
61 my $self = shift;
63 if (@_) {
64 $self->{session} = shift; # Gruta::Data::Session
67 return $self->{session};
71 sub auth_from_sid {
72 my $self = shift;
73 my $sid = shift;
75 my $u = undef;
77 if ($sid) {
78 $self->source->purge_old_sessions();
80 if (my $session = $self->source->session($sid)) {
81 $u = $session->source->user( $session->get('user_id') );
83 if ($u) {
84 $self->auth($u);
85 $self->session($session);
90 return $u;
94 sub login {
95 my $self = shift;
96 my $user_id = shift;
97 my $passwd = shift;
99 my $sid = undef;
101 if (my $u = $self->source->user( $user_id )) {
103 # account expired? go!
104 if (my $xdate = $u->get('xdate')) {
105 if (Gruta::Data::today() > $xdate) {
106 return undef;
110 my $p = $u->get('password');
112 if (Gruta::Data::crypt($passwd, $p) eq $p) {
113 # create new sid
114 my $session = Gruta::Data::Session->new(user_id => $user_id);
115 $self->source->insert_session($session);
117 # store user and session
118 $self->auth($u);
119 $self->session($session);
121 # and return sid to signal a valid login
122 $sid = $session->get('id');
127 return $sid;
131 sub logout {
132 my $self = shift;
134 if (my $session = $self->session()) {
135 $session->delete();
138 $self->auth(undef);
139 $self->session(undef);
141 return $self;
145 sub base_url {
146 $_[0]->{args}->{base_url} || ''
150 sub url {
151 my $self = shift;
152 my $st = shift || '';
153 my %args = @_;
155 if (scalar(@_) % 2) {
156 $self->log('Bad url: ' . join(';', $st, @_));
159 my $ret = $self->base_url();
161 # strip all undefined or empty arguments
162 %args = map { $_, $args{$_} } grep { $args{$_} } keys(%args);
164 if ($self->{args}->{static_urls}) {
165 my $kn = scalar(keys(%args));
167 if ($kn == 0) {
168 my %p = (
169 'INDEX' => '',
170 'RSS' => 'rss.xml',
171 'SITEMAP' => 'sitemap.xml',
172 'CSS' => 'style.css',
173 'TAGS' => 'tag/',
174 'TOP_TEN' => 'top/'
177 if (exists($p{$st})) {
178 return $ret . $p{$st};
182 if ($kn == 1) {
183 if ($st eq 'INDEX' && $args{offset}) {
184 return $ret . $args{offset} . '.html';
186 if ($st eq 'TOPIC' && $args{topic}) {
187 return $ret . $args{topic} . '/';
189 if ($st eq 'SEARCH_BY_TAG' && $args{tag}) {
190 return $ret . 'tag/' . $args{tag} . '.html';
194 if ($kn == 2) {
195 if ($st eq 'STORY' && $args{topic} && $args{id}) {
196 return $ret . $args{topic} . '/' . $args{id} . '.html';
198 if ($st eq 'TOPIC' && $args{topic} && $args{offset}) {
199 return $ret . $args{topic} . '/' . $args{offset} . '.html';
201 if ($st eq 'SEARCH_BY_DATE' && $args{from} && $args{to}) {
202 return $ret . $args{from} . '-' . $args{to} . '.html';
207 if ($st) {
208 $args{t} = $st;
210 $ret .= '?' . join(';', map { "$_=$args{$_}" } sort keys(%args));
213 return $ret;
217 sub _topic_special_uri {
218 my $self = shift;
219 my $topic_id = shift;
221 my $ret = undef;
223 if (my $t = $self->source->topic($topic_id)) {
224 $ret = sprintf('<a href="%s">%s</a>',
225 $self->url('TOPIC', 'topic' => $topic_id),
226 $t->get('name')
229 else {
230 $ret = "Bad topic $topic_id";
233 return $ret;
237 sub _story_special_uri {
238 my $self = shift;
239 my $topic_id = shift;
240 my $story_id = shift;
242 my $ret = undef;
244 if (my $s = $self->source->story($topic_id, $story_id)) {
246 if ($s->is_visible($self->auth())) {
247 $ret = sprintf('<a href = "%s">%s</a>',
248 $self->url('STORY',
249 'topic' => $topic_id,
250 'id' => $story_id
252 $s->get('title')
255 else {
256 $ret = $s->get('title');
259 else {
260 $ret = "Bad story '$topic_id/$story_id'";
263 return $ret;
267 sub _img_special_uri {
268 my $self = shift;
269 my $src = shift;
270 my $class = shift;
272 my $r = sprintf('<img src = "%simg/%s" />',
273 $self->base_url(), $src
276 if ($class) {
277 $r = "<span class = '$class'>" . $r . '</span>';
280 return $r;
284 sub _content_special_uri {
285 my $self = shift;
286 my $topic_id = shift;
287 my $story_id = shift;
288 my $field = shift;
290 my $ret = undef;
292 if (my $s = $self->source->story($topic_id, $story_id)) {
293 $ret = $self->special_uris($s->get($field));
295 else {
296 $ret = "Bad story '$topic_id/$story_id'";
299 return $ret;
304 sub special_uris {
305 my $self = shift;
306 my $string = shift;
308 $string =~ s!topic://([\w\d_-]+)!$self->_topic_special_uri($1)!ge;
309 $string =~ s!story://([\w\d_-]+)/([\w\d_-]+)!$self->_story_special_uri($1,$2)!ge;
310 $string =~ s!img://([\w\d_\.-]+)/?([\w\d_-]*)!$self->_img_special_uri($1,$2)!ge;
311 $string =~ s!body://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'body')!ge;
312 $string =~ s!abstract://([\w\d_-]+)/([\w\d_-]+)!$self->_content_special_uri($1,$2,'abstract')!ge;
314 return $string;
318 sub transfer_to_source {
319 my $self = shift;
320 my $dst = shift;
322 foreach my $id ($self->source->users()) {
323 my $u = $self->source->user($id);
324 $dst->insert_user($u);
327 foreach my $topic_id (sort $self->source->topics()) {
328 my $t = $self->source->topic($topic_id);
330 my $nti = $topic_id;
332 # is it an archive?
333 if ($nti =~ /-arch$/) {
334 # don't insert topic, just rename
335 $nti =~ s/-arch$//;
337 else {
338 $dst->insert_topic($t);
341 foreach my $id ($self->source->stories($topic_id)) {
343 # get story and its tags
344 my $s = $self->source->story($topic_id, $id);
345 my @tags = $s->tags();
347 # set new topic
348 $s->set('topic_id', $nti);
350 my $ns = $dst->insert_story($s);
352 if (@tags) {
353 $ns->tags(@tags);
358 foreach my $id ($self->source->templates()) {
359 my $t = $self->source->template($id);
360 $dst->insert_template($t);
363 return $self;
367 sub new {
368 my $class = shift;
370 my $g = bless( { @_ } , $class);
372 $g->{id} ||= 'Gruta';
373 $g->{args} ||= {};
375 $g->{renderers_h} = {};
377 if ($g->{sources}) {
378 if (ref($g->{sources}) ne 'ARRAY') {
379 $g->{sources} = [ $g->{sources} ];
382 if (!$g->{source}) {
383 $g->{source} = (@{$g->{sources}})[0];
387 if ($g->{source}) {
388 $g->source->data($g);
391 if ($g->{renderers}) {
392 if (ref($g->{renderers}) ne 'ARRAY') {
393 $g->{renderers} = [ $g->{renderers} ];
396 foreach my $r (@{$g->{renderers}}) {
397 $g->{renderers_h}->{$r->{renderer_id}} = $r;
401 if ($g->{template}) {
402 $g->template->data($g);
405 if ($g->{cgi}) {
406 $g->cgi->data($g);
409 return $g;
412 sub run {
413 my $self = shift;
415 if ($self->{cgi}) {
416 $self->cgi->run();