Strip carriage returns when saving a template's content.
[gruta.git] / Gruta / Template / Artemus.pm
blob5c5dc821309d0487ab87cb51495bbb996bd38457
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};
20 $a->{lang} = $args{lang} || 'en';
22 if (!$a->{path}) {
23 # no path? set the default one
24 $a->{path} = [
25 '/usr/share/gruta/templates/artemus/ALL',
26 '/usr/share/gruta/templates/artemus/' . $a->{lang}
30 if (!ref($a->{path})) {
31 $a->{path} = [ split(':', $a->{path}) ];
34 return $a;
38 sub _artemus {
39 my $self = shift;
41 if (not $self->{_artemus}) {
42 my $data = $self->data();
44 my %f = ();
45 my %v = ();
47 $f{url} = sub {
48 my $t = shift;
50 return $data->url($t, @_);
53 $f{aurl} = sub {
54 my $t = shift;
56 my $ret = $data->url($t, @_);
58 if ($ret !~ /^http:/) {
59 $ret = "http://{-cfg_host_name}/$ret";
62 return $ret;
65 $f{date} = sub {
66 my $fmt = shift;
67 my $d = shift || Gruta::Data::today();
69 return Gruta::Data::format_date($d, $fmt);
72 foreach my $p (Gruta::Data::Topic->new->afields()) {
73 $f{'topic_' . $p} = sub {
74 my $topic = shift;
75 my $ret = '';
77 if ($topic ne '[]') {
78 if (my $topic = $data->source->topic($topic)) {
79 $ret = $topic->get($p) || '';
83 return $ret;
87 foreach my $p (Gruta::Data::Story->new->afields()) {
88 $f{'story_' . $p} = sub {
89 my $topic_id = shift;
90 my $id = shift;
91 my $ret = '';
93 if ($id ne '[]') {
94 my $story;
96 if ($story = $data->source->story($topic_id, $id)) {
97 $ret = $story->get($p);
101 return $self->{_artemus}->armor($ret);
105 $f{story_abstract} = sub {
106 my $story = $data->source->story($_[0], $_[1]);
107 my $ret = $data->special_uris($story->get('abstract'));
109 return $self->{_artemus}->armor($ret);
112 $f{story_body} = sub {
113 my $topic_id = shift;
114 my $id = shift;
115 my $ret = undef;
117 if (my $topic = $data->source->topic($topic_id)) {
118 if (my $story = $data->source->story($topic_id, $id)) {
119 my $date2 = $story->get('date2');
121 # if no user and story is not freed, bounce
122 if (!$data->auth() && $date2 && $date2 gt Gruta::Data::today()) {
123 # return directly to avoid armoring
124 return '{-restricted_access}';
126 else {
127 # touch the story if user is not
128 # (potentially) involved on it
129 if (! $topic->is_editor($data->auth())) {
130 $story->touch();
133 $ret = $data->special_uris($story->get('body'));
138 if (!defined($ret)) {
139 $data->cgi->redirect('404');
140 $ret = '';
143 return $self->{_artemus}->armor($ret);
146 $f{story_date} = 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->source->story($topic_id, $id)) {
156 $ret = $story->date($format);
160 return $self->{_artemus}->armor($ret);
163 $f{story_date2} = sub {
164 my $format = shift;
165 my $topic_id = shift;
166 my $id = shift;
167 my $ret = '';
169 if ($id ne '[]') {
170 my $story;
172 if ($story = $data->source->story($topic_id, $id)) {
173 $ret = $story->date2($format);
177 return $self->{_artemus}->armor($ret);
180 foreach my $p (Gruta::Data::User->new->afields()) {
181 $f{'user_' . $p} = sub {
182 my $id = shift;
183 my $ret = '';
185 if ($id ne '[]') {
186 $ret = $data->source->user($id)->get($p);
189 return $self->{_artemus}->armor($ret);
193 $f{user_xdate} = sub {
194 my $format = shift;
195 my $id = shift;
196 my $ret = '';
198 if ($id ne '[]') {
199 $ret = $data->source->user($id)->xdate($format);
202 return $ret;
205 $f{template} = sub {
206 my $id = shift;
207 my $ret = '';
209 if ($id ne '[]') {
210 # try to find it in the source first
211 if (my $t = $data->source->template($id)) {
212 $ret = $t->get('content');
214 else {
215 # not in source; search the stock ones
216 $ret = $data->template->template($id) || '';
219 $ret = $self->{_artemus}->armor($ret);
222 return $ret;
225 $f{save_template} = sub {
226 my $id = shift;
227 my $content = shift;
228 my $msg = shift;
230 $content = $self->{_artemus}->unarmor($content);
231 $content =~ s/\r//g;
233 my $template = $data->source->template($id);
235 if (!$template) {
236 $template = Gruta::Data::Template->new(
237 id => $id
241 $template->set('content', $content);
243 if ($template->source()) {
244 $template = $template->save();
246 else {
247 $template = $data->source->insert_template($template);
250 return $msg || "OK";
253 $f{is_logged_in} = sub {
254 return $data->auth() ? 1 : 0;
257 $f{is_admin} = sub {
258 return $data->auth() && $data->auth->get('is_admin') ? 1 : 0;
261 $f{is_topic_editor} = sub {
262 if (my $topic = $data->source->topic($_[0])) {
263 return $topic->is_editor($data->auth()) ? 1 : 0;
266 return 0;
269 $f{login} = sub {
270 my $user_id = shift;
271 my $password = shift;
272 my $error_msg = shift;
274 if ($user_id eq '') {
275 $error_msg = '{-block_login}';
277 elsif (my $sid = $data->login($user_id, $password)) {
278 $data->cgi->cookie("sid=$sid");
279 $data->cgi->redirect('INDEX');
280 $self->{abort} = 1;
283 return $error_msg || 'Login incorrect.';
286 $f{logout} = sub {
287 $data->logout();
288 $data->cgi->redirect('INDEX');
289 $self->{abort} = 1;
292 $f{assert} = sub {
293 my $cond = shift;
294 my $redir = shift || 'ADMIN';
296 if (! $cond) {
297 $data->cgi->redirect($redir);
298 $self->{abort} = 1;
301 return '';
304 $f{username} = sub {
305 return $data->auth() && $data->auth->get('username') || '';
308 $f{userid} = sub {
309 return $data->auth() && $data->auth->get('id') || '';
312 $f{search_stories} = sub {
313 my $topic_id = shift;
314 my $query = shift;
315 my $future = shift;
316 my $template = shift || 'link_to_story_with_edit';
317 my $sep = shift || '';
319 my $ret = '';
320 my @l = $data->source->search_stories($topic_id, $query, $future);
322 if (@l) {
323 $ret = "<p><b>{-topic_name|$topic_id}</b><br>\n";
325 $ret .= '<ul>';
326 $ret .= join($sep, map { "<li>{-$template|$topic_id|$_}</li>" } @l);
327 $ret .= '</ul>';
329 $self->{search_count} += scalar(@l);
332 return $ret;
335 $f{is_visible_story} = sub {
336 if (my $story = $data->source->story($_[0], $_[1])) {
337 return $story->is_visible($data->auth()) ? 1 : 0;
340 return 0;
343 $f{redir_if_archived} = sub {
344 my $template = shift;
345 my $topic_id = shift;
346 my $id = shift;
348 if ($topic_id =~ /-arch$/) {
349 return '';
352 my $story = $data->source->story($topic_id, $id);
354 if ($story && $story->get('topic_id') =~ /-arch$/) {
355 $data->cgi->redirect(
356 $template,
357 'topic' => $story->get('topic_id'),
358 'id' => $id
360 $self->{abort} = 1;
363 return '';
366 $f{topic_has_archive} = sub {
367 return $data->source->topic($_[0] . '-arch') ? 1 : 0;
370 $f{save_topic} = sub {
371 my $topic_id = shift || return 'Error 1';
373 my $topic = undef;
375 if (not $topic = $data->source->topic($topic_id)) {
376 $topic = Gruta::Data::Topic->new (
377 id => $topic_id );
380 $topic->set('name', shift);
381 $topic->set('editors', shift);
382 $topic->set('internal', shift eq 'on' ? 1 : 0);
383 $topic->set('max_stories', shift);
384 $topic->set('description', shift);
386 # update or insert
387 if ($topic->source()) {
388 $topic = $topic->save();
390 else {
391 $topic = $data->source->insert_topic($topic);
394 return $topic ? 'OK' : 'Error 2';
397 $f{save_story} = sub {
398 my $topic_id = shift || return 'Error 1';
399 my $id = shift;
401 my $story = undef;
403 # if there is an id, try to load the story
404 if ($id) {
405 # this may crash if id is not valid
406 $story = $data->source->story($topic_id, $id);
409 if (!$story) {
410 $story = Gruta::Data::Story->new (
411 topic_id => $topic_id,
412 id => $id
416 my $content = shift;
417 $content = $self->{_artemus}->unarmor($content);
418 $content =~ s/\r//g;
420 $story->set('content', $content);
422 # pick date and drop time
423 my $y = shift;
424 my $m = shift;
425 my $d = shift;
426 shift; shift; shift;
427 my $date = Gruta::Data::today();
429 if ($y && $m && $d) {
430 $date = sprintf("%04d%02d%02d000000", $y, $m, $d);
433 $story->set('date', $date);
434 $story->set('format', shift || 'grutatxt');
436 # get the tags
437 my $tags = shift;
439 # get date2
440 $y = shift;
441 $m = shift;
442 $d = shift;
444 if ($y && $m && $d) {
445 $date = sprintf("%04d%02d%02d000000", $y, $m, $d);
447 else {
448 $date = '';
451 $story->set('date2', $date);
453 $story->set('description', shift);
455 # if there is no userid, add one
456 if (!$story->get('userid')) {
457 $story->set('userid', $data->auth->get('id'));
460 # render the story
461 $data->render($story);
463 if ($story->source()) {
464 $story = $story->save();
466 else {
467 $story = $data->source->insert_story($story);
470 $story->tags(split(/\s*,\s*/, $tags));
472 return $story ? $story->get('id') : 'Error 2';
475 $f{save_user} = sub {
476 my $id = shift || return 'Error 1';
477 my $username = shift;
478 my $email = shift;
479 my $is_admin = shift;
480 my $can_upload = shift;
481 my $pass1 = shift;
482 my $pass2 = shift;
483 my $xy = shift;
484 my $xm = shift;
485 my $xd = shift;
487 if ($data->auth->get('username') ne $username &&
488 ! $data->auth->get('is_admin')) {
489 $data->cgi->redirect('LOGIN');
490 $self->{abort} = 1;
491 return '';
494 my $user = undef;
496 if (not $user = $data->source->user($id)) {
497 $user = Gruta::Data::User->new (
498 id => $id,
499 is_admin => 0,
500 can_upload => 0,
501 xdate => ''
505 $user->set('username', $username);
506 $user->set('email', $email);
508 # these params can only be set by an admin
509 if ($data->auth->get('is_admin')) {
511 $user->set('is_admin', $is_admin eq 'on' ? 1 : 0);
512 $user->set('can_upload', $can_upload eq 'on' ? 1 : 0);
514 if ($xy and $xm and $xd) {
515 $user->set('xdate',
516 sprintf('%04d%02d%02d000000',
517 $xy, $xm, $xd));
519 else {
520 $user->set('xdate', '');
524 if ($pass1 and $pass2) {
525 if ($pass1 ne $pass2) {
526 croak "Passwords are different";
529 $user->password($pass1);
532 if ($user->source()) {
533 $user = $user->save();
535 else {
536 $user = $data->source->insert_user($user);
539 return $user ? 'OK' : 'Error 2';
542 $f{upload} = sub {
544 $data->cgi->upload($_[0], $_[1]);
545 return 'OK';
548 $f{delete_story} = sub {
549 my $topic_id = shift || return 'Error 1';
550 my $id = shift;
552 $data->source->story($topic_id, $id)->delete();
554 return 'OK';
557 $f{search_count} = sub { $self->{search_count}; };
559 $f{content_type} = sub {
560 $data->cgi->http_headers('Content-Type' => $_[0]);
561 return '';
564 $f{topics} = sub { join(':', $data->source->topics()); };
565 $f{users} = sub { join(':', $data->source->users()); };
567 $f{templates} = sub { join(':',
568 $data->template->templates($data->source->templates()));
571 $f{renderers} = sub { join(':', sort(keys(%{$data->{renderers_h}}))); };
572 $f{upload_dirs} = sub { join(':', $data->cgi->upload_dirs()); };
573 $f{tags} = sub { join(':',
574 map { $_->[0] . ',' . $_->[1] } $data->source->tags()); };
576 $f{var} = sub {
577 my $ret = $self->{cgi_vars}->{$_[0]} || $_[1] || '';
579 return $self->{_artemus}->armor($ret);
582 $f{story_tags} = sub {
583 my $topic_id = shift;
584 my $id = shift;
585 my $ret = '';
587 if ($id ne '[]') {
588 if (my $story = $data->source->story($topic_id, $id)) {
589 $ret = join(':', $story->tags());
593 return $ret;
596 $f{stories_by_date} = sub {
597 my $topic = shift;
598 my $num = shift;
599 my $offset = shift;
600 my $from_date = shift;
601 my $to_date = shift;
602 my $future = shift;
604 my @ret = map { join(',', @{$_}) }
605 $data->source->stories_by_date(
606 $topic ?
607 [ map { (split(',', $_))[0] }
608 split(':', $topic)
609 ] : undef,
610 num => $num,
611 offset => $offset,
612 from => $from_date,
613 to => $to_date,
614 future => $future
617 # $self->{search_count} += scalar(@ret);
619 return join(':', @ret);
622 $f{stories_by_tag} = sub {
623 my $topic = shift;
624 my $tag = shift;
625 my $future = shift;
627 my @ret = $data->source->stories_by_tag(
628 $topic ?
629 [ map { (split(',', $_))[0] }
630 split(':', $topic)
631 ] : undef,
632 $tag, $future);
634 $self->{search_count} += scalar(@ret);
636 return join(':', map { join(',', @{$_}) } @ret);
639 $f{stories_top_ten} = sub {
640 my $num = shift;
642 return join(':', map { join(',', @{$_}) }
643 $data->source->stories_top_ten($num)
647 $f{about} = sub {
648 return 'Gruta ' . $data->version();
651 $self->{abort} = 0;
652 $self->{unresolved} = [];
653 $self->{search_count} = 0;
655 $self->{_artemus} = Artemus->new(
656 'include-path' => $self->{path},
657 'funcs' => \%f,
658 'vars' => \%v,
659 'unresolved' => $self->{unresolved},
660 'abort' => \$self->{abort},
661 'loader_func' => sub {
662 my $ret = undef;
664 if (my $t = $data->source->template($_[0])) {
665 $ret = $t->get('content');
668 return $ret;
673 return $self->{_artemus};
677 sub data {
678 my $self = shift;
679 my $data = shift;
681 if (defined($data)) {
682 $self->{data} = $data;
683 $self->{_artemus} = undef;
686 return $self->{data};
690 sub cgi_vars {
691 my $self = shift;
693 if (@_) {
694 $self->{cgi_vars} = shift;
695 $self->{_artemus} = undef;
698 return $self->{cgi_vars};
702 sub process {
703 my $self = shift;
704 my $st = shift;
706 my $ret = $self->_artemus->process('{-' . $st . '}');
708 # process special HTML variables
709 my $t;
711 if ($t = $self->{_artemus}->{vars}->{html_title}) {
712 $ret =~ s!</head>!<title>$t</title></head>!i;
715 if ($t = $self->{_artemus}->{vars}->{html_description}) {
716 $ret =~ s!</head>!<meta name="description" content="$t"></head>!i;
719 if ($t = $self->{_artemus}->{vars}->{html_keywords}) {
720 $ret =~ s!</head>!<meta name="keywords" content="$t"></head>!i;
723 return $ret;