New Artemus function about().
[gruta.git] / Gruta / Template / Artemus.pm
blob9f4669c4b346a8617ce59ac6e4132d61cce851de
1 package Gruta::Template::Artemus;
3 use strict;
4 use warnings;
5 use Carp;
7 use base 'Gruta::Template::BASE';
9 use Artemus;
10 use Gruta::Data;
12 sub new {
13 my $class = shift;
14 my %args = @_;
16 my $a = bless( {}, $class );
18 $a->{_artemus} = undef;
19 $a->{path} = $args{path};
21 $a->create();
23 return $a;
27 sub _artemus {
28 my $self = shift;
30 if (not $self->{_artemus}) {
31 my $data = $self->data();
33 my %f = ();
34 my %v = ();
36 $f{l} = sub {
37 my $t = shift;
39 return '?t=' . $t . ';' . join(';', @_);
42 $f{'add'} = sub { $_[0] + $_[1]; };
43 $f{'sub'} = sub { $_[0] - $_[1]; };
44 $f{'gt'} = sub { $_[0] > $_[1]; };
45 $f{'lt'} = sub { $_[0] < $_[1]; };
46 $f{'eq'} = sub { $_[0] eq $_[1] ? 1 : 0; };
48 $f{date} = sub {
49 my $fmt = shift;
50 my $d = shift || Gruta::Data::today();
52 return Gruta::Data::format_date($d, $fmt);
55 $f{random} = sub { $_[rand(scalar(@_))]; };
57 foreach my $p (Gruta::Data::Topic->new->afields()) {
58 $f{'topic_' . $p} = sub {
59 my $topic = shift;
60 my $ret = '';
62 if ($topic ne '[]') {
63 if (my $topic = $data->topic($topic)) {
64 $ret = $topic->get($p) || '';
68 return $ret;
72 foreach my $p (Gruta::Data::Story->new->afields()) {
73 $f{'story_' . $p} = sub {
74 my $topic_id = shift;
75 my $id = shift;
76 my $ret = '';
78 if ($id ne '[]') {
79 my $story;
81 if ($story = $data->story($topic_id, $id)) {
82 $ret = $story->get($p);
86 return $ret;
90 $f{story_abstract} = sub {
91 my $story = $data->story($_[0], $_[1]);
93 return $data->special_uris($story->get('abstract'));
96 $f{story_body} = sub {
97 my $topic_id = shift;
98 my $id = shift;
99 my $ret = undef;
101 if (my $topic = $data->topic($topic_id)) {
102 if (my $story = $data->story($topic_id, $id)) {
103 my $date2 = $story->get('date2');
105 # if no user and story is not freed, bounce
106 if (!$data->auth() && $date2 && $date2 > Gruta::Data::today()) {
107 $ret = '{-restricted_access}';
109 else {
110 # touch the story if user is not
111 # (potentially) involved on it
112 if (! $topic->is_editor($data->auth())) {
113 $story->touch();
116 $ret = $data->special_uris($story->get('body'));
121 if (!defined($ret)) {
122 $data->cgi->redirect('?t=404');
123 $ret = '';
126 return $ret;
129 $f{story_date} = sub {
130 my $format = shift;
131 my $topic_id = shift;
132 my $id = shift;
133 my $ret = '';
135 if ($id ne '[]') {
136 my $story;
138 if ($story = $data->story($topic_id, $id)) {
139 $ret = $story->date($format);
143 return $ret;
146 $f{story_date2} = sub {
147 my $format = shift;
148 my $topic_id = shift;
149 my $id = shift;
150 my $ret = '';
152 if ($id ne '[]') {
153 my $story;
155 if ($story = $data->story($topic_id, $id)) {
156 $ret = $story->date2($format);
160 return $ret;
163 foreach my $p (Gruta::Data::User->new->afields()) {
164 $f{'user_' . $p} = sub {
165 my $id = shift;
166 my $ret = '';
168 if ($id ne '[]') {
169 $ret = $data->user($id)->get($p);
172 return $ret;
176 $f{user_xdate} = sub {
177 my $format = shift;
178 my $id = shift;
179 my $ret = '';
181 if ($id ne '[]') {
182 $ret = $data->user($id)->xdate($format);
185 return $ret;
188 $f{template} = sub {
189 my $t = shift;
190 my $ret = '';
192 if ($t ne '[]') {
193 $t = $data->template->template($t);
194 $ret = $self->{_artemus}->armor($t);
197 return $ret;
200 $f{save_template} = sub {
201 my $template = shift;
202 my $content = shift;
203 my $msg = shift;
205 $content = $self->{_artemus}->unarmor($content);
206 $data->template->save_template($template, $content);
208 return $msg || "Template saved.";
211 $f{loop_topics} = sub {
212 my $template = shift;
213 my $sep = shift;
215 return join($sep, map {
216 my $t = $data->topic($_);
217 sprintf('{-%s|%s|%s}',
218 $template, $t->get('name'),
219 $t->get('id')
221 } $data->topics());
224 $f{loop_users} = sub {
225 return join($_[1], map { "{-$_[0]|$_}" } $data->users());
228 $f{loop_renderers} = sub {
229 return join($_[1], map { "{-$_[0]|$_}" }
230 sort(keys(%{$data->{renderers_h}})));
233 $f{loop_templates} = sub {
234 return join($_[1], map { "{-$_[0]|$_}" }
235 $data->template->templates());
238 $f{loop_upload_dirs} = sub {
239 return join($_[1], map { "{-$_[0]|$_}" }
240 $data->cgi->upload_dirs());
243 $f{loop_story_tags} = sub {
244 my $topic_id = shift;
245 my $id = shift;
246 my $ret = '';
248 my $story;
250 if ($story = $data->story($topic_id, $id)) {
251 $ret = join($_[1], map { "{-$_[0]|$_}" }
252 $story->tags());
255 return $ret;
258 $f{story_loop_by_date} = sub {
259 my $topic = shift;
260 my $num = shift;
261 my $offset = shift;
262 my $template = shift;
263 my $sep = shift;
264 my $from = shift;
265 my $to = shift;
266 my $future = shift;
268 return join($sep, map { "{-$template|$topic|$_->[0]}" }
269 $data->stories_by_date(
270 [ $topic ],
271 num => $num,
272 offset => $offset,
273 from => $from,
274 to => $to,
275 future => $future
280 $f{is_logged_in} = sub {
281 return $data->auth() ? 1 : 0;
284 $f{is_admin} = sub {
285 return $data->auth() && $data->auth->get('is_admin') ? 1 : 0;
288 $f{is_topic_editor} = sub {
289 if (my $topic = $data->topic($_[0])) {
290 return $topic->is_editor($data->auth()) ? 1 : 0;
293 return 0;
296 $f{login} = sub {
297 my $user_id = shift;
298 my $password = shift;
299 my $error_msg = shift;
301 if ($user_id eq '' || $user_id eq 'cgi-userid') {
302 $error_msg = '{-login_box}';
304 elsif (my $sid = $data->login($user_id, $password)) {
305 $data->cgi->cookie("sid=$sid");
306 $data->cgi->redirect('?t=INDEX');
307 $self->{abort} = 1;
310 return $error_msg || 'Login incorrect.';
313 $f{logout} = sub {
314 $data->logout();
315 $data->cgi->redirect('?t=INDEX');
316 $self->{abort} = 1;
319 $f{assert} = sub {
320 my $cond = shift;
321 my $redir = shift || 'ADMIN';
323 if (! $cond) {
324 $data->cgi->redirect('?t=' . $redir);
325 $self->{abort} = 1;
328 return '';
331 $f{username} = sub {
332 return $data->auth() && $data->auth->get('username') || '';
335 $f{userid} = sub {
336 return $data->auth() && $data->auth->get('id') || '';
339 $f{search_stories} = sub {
340 my $topic_id = shift;
341 my $query = shift;
342 my $future = shift;
343 my $template = shift || '_story_link_as_item_with_edit';
344 my $sep = shift || '';
346 my $ret = '';
347 my @l = $data->search_stories($topic_id, $query, $future);
349 if (@l) {
350 $ret = "<p><b>{-topic_name|$topic_id}</b><br>\n";
352 $ret .= '<ul>';
353 $ret .= join($sep, map { "{-$template|$topic_id|$_}" } @l);
354 $ret .= '</ul>';
356 $self->{search_count} += scalar(@l);
359 return $ret;
362 $f{is_visible_story} = sub {
363 if (my $story = $data->story($_[0], $_[1])) {
364 return $story->is_visible($data->auth()) ? 1 : 0;
367 return 0;
370 $f{redir_if_archived} = sub {
371 my $template = shift;
372 my $topic_id = shift;
373 my $id = shift;
375 if ($topic_id =~ /-arch$/) {
376 return '';
379 my $story = $data->story($topic_id, $id);
381 if ($story && $story->get('topic_id') =~ /-arch$/) {
382 $data->cgi->redirect(
383 sprintf('?t=%s;topic=%s;id=%s',
384 $template,
385 $story->get('topic_id'),
386 $id)
388 $self->{abort} = 1;
391 return '';
394 $f{topic_has_archive} = sub {
395 return $data->topic($_[0] . '-arch') ? 1 : 0;
398 $f{save_topic} = sub {
399 my $topic_id = shift || return 'Error 1';
401 my $topic = undef;
403 if (not $topic = $data->topic($topic_id)) {
404 $topic = Gruta::Data::Topic->new (
405 id => $topic_id );
408 $topic->set('name', shift);
409 $topic->set('editors', shift);
410 $topic->set('internal', shift eq 'on' ? 1 : 0);
411 $topic->set('max_stories', shift);
413 # update or insert
414 if ($topic->source()) {
415 $topic = $topic->save();
417 else {
418 $topic = $data->insert_topic($topic);
421 return $topic ? 'OK' : 'Error 2';
424 $f{save_story} = sub {
425 my $topic_id = shift || return 'Error 1';
426 my $id = shift;
428 my $story = undef;
430 if (not $story = $data->story($topic_id, $id)) {
431 $story = Gruta::Data::Story->new (
432 topic_id => $topic_id,
433 id => $id
437 my $content = shift;
438 $content = $self->{_artemus}->unarmor($content);
439 $content =~ s/\r//g;
441 $story->set('content', $content);
443 # pick date and drop time
444 my $y = shift;
445 my $m = shift;
446 my $d = shift;
447 shift; shift; shift;
448 my $date = Gruta::Data::today();
450 if ($y && $m && $d) {
451 $date = sprintf("%04d%02d%02d000000", $y, $m, $d);
454 $story->set('date', $date);
455 $story->set('format', shift || 'grutatxt');
457 # get the tags
458 my $tags = shift;
460 # get date2
461 $y = shift;
462 $m = shift;
463 $d = shift;
465 if ($y && $m && $d) {
466 $date = sprintf("%04d%02d%02d000000", $y, $m, $d);
468 else {
469 $date = '';
472 $story->set('date2', $date);
474 # if there is no userid, add one
475 if (!$story->get('userid')) {
476 $story->set('userid', $data->auth->get('id'));
479 # drop all cached stories
480 $data->flush_story_cache();
482 if ($story->source()) {
483 $story = $story->save();
485 else {
486 $story = $data->insert_story($story);
489 if ($tags ne 'cgi-tags') {
490 $story->tags(split(/\s*,\s*/, $tags));
493 return $story ? $story->get('id') : 'Error 2';
496 $f{save_user} = sub {
497 shift; # new (ignored)
498 my $id = shift || return 'Error 1';
499 my $username = shift;
500 my $email = shift;
501 my $is_admin = shift;
502 my $can_upload = shift;
503 my $pass1 = shift;
504 my $pass2 = shift;
505 my $xy = shift;
506 my $xm = shift;
507 my $xd = shift;
509 if ($data->auth->get('username') ne $username &&
510 ! $data->auth->get('is_admin')) {
511 $data->cgi->redirect('?t=LOGIN');
512 $self->{abort} = 1;
513 return '';
516 my $user = undef;
518 if (not $user = $data->user($id)) {
519 $user = Gruta::Data::User->new (
520 id => $id,
521 is_admin => 0,
522 can_upload => 0,
523 xdate => ''
527 $user->set('username', $username);
528 $user->set('email', $email);
530 # these params can only be set by an admin
531 if ($data->auth->get('is_admin')) {
533 $user->set('is_admin', $is_admin eq 'on' ? 1 : 0);
534 $user->set('can_upload', $can_upload eq 'on' ? 1 : 0);
536 if ($xy and $xm and $xd) {
537 $user->set('xdate',
538 sprintf('%04d%02d%02d000000',
539 $xy, $xm, $xd));
541 else {
542 $user->set('xdate', '');
546 if ($pass1 and $pass2) {
547 if ($pass1 ne $pass2) {
548 croak "Passwords are different";
551 $user->password($pass1);
554 if ($user->source()) {
555 $user = $user->save();
557 else {
558 $user = $data->insert_user($user);
561 return $user ? 'OK' : 'Error 2';
564 $f{upload} = sub {
566 $data->cgi->upload($_[0], $_[1]);
567 return 'OK';
570 $f{delete_story} = sub {
571 my $topic_id = shift || return 'Error 1';
572 my $id = shift;
574 $data->story($topic_id, $id)->delete();
576 # drop all cached stories
577 $data->flush_story_cache();
579 return 'OK';
582 $f{search_count} = sub { $self->{search_count}; };
584 $f{content_type} = sub {
585 $data->cgi->http_headers('Content-Type' => $_[0]);
586 return '';
589 $f{topics} = sub { join(':', $data->topics()); };
590 $f{templates} = sub { join(':', $data->template->templates()); };
591 $f{users} = sub { join(':', $data->users()); };
593 $f{renderers} = sub { join(':', sort(keys(%{$data->{renderers_h}}))); };
594 $f{upload_dirs} = sub { join(':', $data->cgi->upload_dirs()); };
595 $f{tags} = sub { join(':', map { $_->[0] . ',' . $_->[1] } $data->tags()); };
597 $f{var} = sub {
598 my $ret = $self->{cgi_vars}->{$_[0]} || $_[1] || '';
600 return $self->{_artemus}->armor($ret);
603 $f{story_tags} = sub {
604 my $topic_id = shift;
605 my $id = shift;
606 my $ret = '';
608 if ($id ne '[]') {
609 my $story = $data->story($topic_id, $id);
611 $ret = join(':', $story->tags());
614 return $ret;
617 $f{stories_by_date} = sub {
618 my $topic = shift;
619 my $num = shift;
620 my $offset = shift;
621 my $from_date = shift;
622 my $to_date = shift;
623 my $future = shift;
625 my @ret = map { join(',', @{$_}) }
626 $data->stories_by_date(
627 $topic ?
628 [ map { (split(',', $_))[0] }
629 split(':', $topic)
630 ] : undef,
631 num => $num,
632 offset => $offset,
633 from => $from_date,
634 to => $to_date,
635 future => $future
638 $self->{search_count} += scalar(@ret);
640 return join(':', @ret);
643 $f{stories_by_tag} = sub {
644 my $topic = shift;
645 my $tag = shift;
646 my $future = shift;
648 my @ret = $data->stories_by_tag(
649 $topic ?
650 [ map { (split(',', $_))[0] }
651 split(':', $topic)
652 ] : undef,
653 $tag, $future);
655 $self->{search_count} += scalar(@ret);
657 return join(':', map { $_->[0] . ',' . $_->[1] } @ret);
660 $f{stories_top_ten} = sub {
661 my $num = shift;
663 return join(':', map { join(',', @{$_}) }
664 $data->stories_top_ten($num)
668 $f{about} = sub {
669 return 'Gruta ' . $data->version();
672 $self->{abort} = 0;
673 $self->{unresolved} = [];
674 $self->{search_count} = 0;
676 $self->{_artemus} = Artemus->new(
677 'include-path' => $self->{path},
678 'funcs' => \%f,
679 'vars' => \%v,
680 'unresolved' => $self->{unresolved},
681 'abort' => \$self->{abort},
684 if ($self->{cgi_vars}) {
685 foreach my $k (keys(%{ $self->{cgi_vars} })) {
686 my $c = $self->{_artemus}->
687 armor($self->{cgi_vars}->{$k});
688 $c =~ s/\r//g;
690 $v{"cgi-${k}"} = $c;
695 return $self->{_artemus};
699 sub data {
700 my $self = shift;
701 my $data = shift;
703 if (defined($data)) {
704 $self->{data} = $data;
705 $self->{_artemus} = undef;
708 return $self->{data};
712 sub cgi_vars {
713 my $self = shift;
715 if (@_) {
716 $self->{cgi_vars} = shift;
717 $self->{_artemus} = undef;
720 return $self->{cgi_vars};
724 sub process { $_[0]->_artemus->process('{-' . $_[1] . '}'); }