2 # Copyright (C) 2014 Julien Moutinho <julm+ikiwiki+events&autogeree.net>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License,
7 # or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18 package IkiWiki
::Plugin
::events
;
29 hook
(type
=> "getsetup", id
=> "events", call
=> \
&getsetup
);
30 hook
(type
=> "needsbuild", id
=> "events", call
=> \
&needsbuild
);
31 hook
(type
=> "preprocess", id
=> "events", call
=> \
&preprocess
);
32 hook
(type
=> "sessioncgi", id
=> "events", call
=> \
&sessioncgi
);
46 ( time_zone
=> 'local'
47 , locale
=> $config{locale
}
48 )->set_time_zone('floating');
49 my @days = ('01'..'31');
50 my @hours = ('00'..'23');
51 my @minutes = ('00'..'59');
54 sub set_rendering_expiration
($$) {
55 my ($page, $timestamp) = @_;
56 if (not exists $pagestate{$page}{events
}{expiration
}
57 or $timestamp < $pagestate{$page}{events
}{expiration
}) {
58 my $time = DateTime
->from_epoch
61 , locale
=> $config{locale
}
63 #debug("events: set_rendering_expiration(): will refresh: page=".$page
64 # . " after: date=".$time->strftime('%Y-%m-%d_%H-%M-%S'));
65 $pagestate{$page}{events
}{expiration
} = $timestamp;
68 sub set_next_rendering
(%) {
70 if ($params{type
} eq 'month'
71 and $params{focus
}->year() == $now->year()
72 and $params{focus
}->month() == $now->month()) {
73 # NOTE: calendar for current month, updates next day
74 my $update = $params{focus
}->clone;
76 $update->set_minute(0);
77 $update->set_second(0);
78 $update->set_nanosecond(0);
79 my $duration = DateTime
::Duration
->new(days
=> 1, end_of_month
=> 'limit');
80 $update->add_duration($duration);
81 set_rendering_expiration
($params{destpage
}, $update->epoch());
82 #debug("events: will refresh current month: page=".$params{destpage}
83 # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
85 elsif ($params{type
} eq 'month'
86 and (( $params{focus
}->year() == $now->year()
87 and $params{focus
}->month() > $now->month())
88 or $params{focus
}->year() > $now->year())) {
89 # NOTE: calendar for upcoming month, updates 1st of next month
90 my $update = $params{focus
}->clone;
93 $update->set_minute(0);
94 $update->set_second(0);
95 $update->set_nanosecond(0);
96 set_rendering_expiration
($params{destpage
}, $update->epoch());
97 #debug("events: will refresh upcoming month: page=".$params{destpage}
98 # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
100 elsif ($params{type
} eq 'day'
101 and ($params{focus
}->year() == $now->year()
102 and $params{focus
}->month() == $now->month()
103 and $params{focus
}->day() == $now->day())) {
104 # NOTE: calendar for current day, updates next day
105 my $update = $params{focus
}->clone;
106 $update->set_hour(0);
107 $update->set_minute(0);
108 $update->set_second(0);
109 $update->set_nanosecond(0);
110 my $duration = DateTime
::Duration
->new(days
=> 1, end_of_month
=> 'limit');
111 $update->add_duration($duration);
112 set_rendering_expiration
($params{destpage
}, $update->epoch());
113 #debug("events: will refresh current day: page=".$params{destpage}
114 # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
116 elsif ($params{type
} eq 'day'
117 and (( $params{focus
}->year() == $now->year()
118 and ( $params{focus
}->month() > $now->month()
119 or ($params{focus
}->month() == $now->month()
120 and $params{focus
}->day() > $now->day() ))
121 or $params{focus
}->year() > $now->year()))) {
122 # NOTE: calendar for upcoming day, updates that day
123 my $update = $params{focus
}->clone;
124 $update->set_hour(0);
125 $update->set_minute(0);
126 $update->set_second(0);
127 $update->set_nanosecond(0);
128 set_rendering_expiration
($params{destpage
}, $update->epoch());
129 #debug("events: will refresh upcoming day: page=".$params{destpage}
130 # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
134 my $needsbuild = shift;
135 foreach my $page (keys %pagestate) {
136 if (exists $pagestate{$page}{events
}{expiration
}) {
137 if ($pagestate{$page}{events
}{expiration
} <= $now->epoch()) {
138 # NOTE: force a rebuild so the calendar shows the current day
139 push @
$needsbuild, $pagesources{$page};
141 if (exists $pagesources{$page}
142 and grep { $_ eq $pagesources{$page} } @
$needsbuild) {
143 # NOTE: remove state, will be re-added
144 # if the calendar is still there during the rebuild
145 delete $pagestate{$page}{events
};
153 sub date_of_page
($%) {
154 my ($page, %params) = @_;
155 my $dir = IkiWiki
::dirname
($page);
156 my ($year, $month, $day, $hour, $hour_begin, $hour_end)
160 (?
:/(01|02|03|04|05|06|07|08|09|10|11|12)
161 (?
:/(01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31)
162 (?
:/(([0-2][0-9]h
[0-5][0-9])
163 (?
:-([0-2][0-9]h
[0-5][0-9]))?
)
174 , hour_begin
=> $hour_begin
175 , hour_end
=> $hour_end
177 #debug("date_of_page: dir=".$dir." ".Dumper($r));
180 sub event_of_page
($%) {
181 my ($event, $date, %params) = @_;
183 = exists $pagestate{$event}{meta
}{title
}
184 ?
$pagestate{$event}{meta
}{title
}
185 : pagetitle
(IkiWiki
::basename
($event));
197 = sort {lc $a cmp lc $b}
199 not defined $params{tags
}
200 or pagespec_match
($_, $params{tags
})
202 (keys %{$IkiWiki::typedlinks
{$event}{tag
}}));
205 my $tag_best = bestlink
($params{page
}, $_);
206 $tag_best = (length $tag_best > 0 ?
$tag_best : bestlink
($event, $_));
207 my $tag = (length $tag_best > 0 ?
$tag_best : $_);
209 = exists $pagestate{$tag}{meta
}{title
}
210 ?
$pagestate{$tag}{meta
}{title
}
211 : pagetitle
(IkiWiki
::basename
($tag));
220 #add_depends($params{page}, $event, deptype('content'));
221 # NOTE: useless now that deptype('content') is default.
222 #add_depends($params{page}, $tag, deptype('content'));
223 # XXX: much too heavy :\ and midnight refresh may fix it anyway.
224 my $class = qq{$tag};
225 $class =~ s{[^a-zA-Z0-9-]}{_}g;
226 { class => "tag tag-$class"
229 my $base = IkiWiki
::dirname
($event);
230 $base =~ s/[^a-zA-Z0-9-]/_/g;
239 sub events_of_pages
($%) {
240 my ($pages, %params) = @_;
242 my @hour_events = ();
243 my $pagedir = sub { IkiWiki
::basename
(IkiWiki
::dirname
(shift)) };
244 foreach my $page (@
$pages) {
245 my $date = date_of_page
($page);
246 if (defined $date->{hour
}) {
247 push @hour_events, {page
=>$page, date
=>$date};
250 push @day_events, {page
=>$page, date
=>$date};
254 map {event_of_page
($_->{page
}, $_->{date
}, %params)}
255 ( (sort {lc IkiWiki
::basename
($a->{page
}) cmp lc IkiWiki
::basename
($b->{page
})} @day_events)
257 my $r = $a->{date
}->{hour
} cmp $b->{date
}->{hour
};
259 else { IkiWiki
::basename
($a->{page
}) cmp IkiWiki
::basename
($b->{page
}) }
262 sub event_html
($$%) {
263 my ($date, $format, %params) = @_;
264 my $day = sprintf("%02d", $date->day());
265 my $month = sprintf("%02d", $date->month());
266 my $year_html = $date->year();
268 = $format->{month_name
}
269 ?
$date->month_name()
272 = $format->{day_name
}
273 ?
$date->day_name()." ".$date->day()
274 : $format->{day_abbr
}
275 ?
$date->day_abbr()." ".$date->day()
278 = "wday-".($date->day_of_week() - 1)
279 . ( ($date->year() == $now->year()
280 and $date->month() == $now->month()
281 and $date->day() == $now->day())
285 if (not defined $params{new
} or $params{new
} ne 'no') {
286 if ($format->{year
}) {
293 add_depends
($params{page
}, $year_page, deptype
("presence"));
294 if (exists $pagesources{$year_page}) {
300 , linktext
=> $year_html
301 , noimageinline
=> 1 );
304 if ($format->{month
}) {
312 add_depends
($params{page
}, $month_page, deptype
("presence"));
313 if (exists $pagesources{$month_page}) {
319 , linktext
=> $month_html
320 , noimageinline
=> 1 );
323 if ($format->{day
}) {
332 add_depends
($params{page
}, $day_page, deptype
("presence"));
333 if (exists $pagesources{$day_page}) {
339 , linktext
=> $day_html
340 , noimageinline
=> 1 );
343 unless ($params{nonew
} or not $format->{new
}) {
345 .= qq{<a
class='new' href
='}
347 ( base => $params{base}
350 , (($date->month() or $date->day())
351 ?(month => $month):())
352 , (($date->year() or $date->month() or $date->day())
353 ?(year => $date->year()):())
355 , page => $params{destpage}
357 . qq{' rel
='nofollow'>+</a
>};
363 , month
=> $month_html
365 , wday
=> $wday_class
368 sub preprocess_day
(@
) {
371 = pagespec_match_list
374 , deptype
=> deptype
("content")
375 # NOTE: add content dependency to update calendar when pages are tagged
380 , {day
=>1, day_name
=>1, new
=> 1}
385 = map {"<li\n class='".$_->{class}."'>".$_->{link}."</li>"}
387 "<ul\n class='events'><li class='event event-$_->{base}'>"
388 . "<span class='head'>"
389 . (defined $_->{hour
} ?
"<span class='hour'>$_->{hour}</span>" : "")
390 . "<span class='link'>$_->{link}</span>"
392 . "<ul\n class='tags'>".join("", @tags)."</ul>"
395 events_of_pages
(\
@pages, %params);
397 "<table\n class='wday'>"
398 . "<thead><tr><th><span\n class='head'><span\n class='day'>"
401 . "</span></span></th></tr></thead>"
402 . "<tbody><tr><td\n class='wday $event_html->{wday}'>"
404 . "</td></tr></tbody>"
407 sub preprocess_month
(@
) {
409 my $one_day = DateTime
::Duration
->new(days
=> 1, end_of_month
=> 'limit');
410 my $day = $params{focus
}->clone->set_day(1);
412 = DateTime
->last_day_of_month
413 ( year
=> $params{focus
}->year()
414 , month
=> $params{focus
}->month() )->day();
417 = pagespec_match_list
420 , deptype
=> deptype
("content")
421 # NOTE: add presence dependency to update calendar when pages are tagged
424 my %events_by_day = map {($_=>[])} (1 .. $last_day);
425 foreach my $event (events_of_pages
(\
@pages, %params)) {
426 my $day = $event->{date
}->{day
};
427 push @
{$events_by_day{$day}}, $event
432 my $first_wday = $day->clone();
433 my $last_wday = ($params{week_start_day
} + 6) % 7;
434 while ($first_wday->day_of_week() - 1 != $params{week_start_day
}) {
435 # NOTE: pad the begining
436 $first_wday->subtract_duration($one_day);
437 $t.="<td class='no-wday'></td>";
439 my $month = $day->month();
440 for (; $day->month() == $month; $day->add_duration($one_day)) {
444 , {day
=>1, day_abbr
=>1, new
=> 1}
446 $t.= "<td class='wday $event_html->{wday}'>";
450 = map {"<li\n class='".$_->{class}."'>".$_->{link}."</li>"}
452 "<li\n class='event event-$_->{base}'>"
453 . "<span class='head'>"
454 . (defined $_->{hour
} ?
"<span class='hour'>$_->{hour}</span>" : "")
455 . "<span class='link'>$_->{link}</span>"
457 . "<ul class='tags'>".join("", @tags)."</ul>"
460 @
{$events_by_day{sprintf('%02d',$day->day())}};
462 "<span class='head'>"
463 . "<span class='day'>"
468 . "<ul class='events'>".join("", @events)."</ul>";
470 if ($day->day_of_week() - 1 == $last_wday) {
473 if ($day->day_of_month() < $last_day);
476 while ($day->day_of_week() - 1 != $params{week_start_day
}) {
478 $day->add_duration($one_day);
479 $t.="<td class='no-wday'></td>";
485 , {year
=>1, month
=>1, month_name
=>1}
488 "<table class='month'>"
492 . "<span class='month'>$event_html->{month}</span>"
493 . " <span class='year'>$event_html->{year}</span>"
498 $_ = "<th><span>".$first_wday->day_name()."</span></th>";
499 $first_wday->add_duration($one_day);
503 . "<tbody>$t</tbody></table>";
507 $params{focus
} = $now->clone;
508 $params{focus
}->set_hour(0);
509 $params{focus
}->set_minute(0);
510 $params{focus
}->set_second(0);
511 $params{focus
}->set_nanosecond(0);
513 $params{pages
} = "*" unless defined $params{pages
};
514 $params{type
} = "month" unless defined $params{type
};
515 $params{week_start_day
} = 0 unless defined $params{week_start_day
};
516 $params{week_start_day
} = $params{week_start_day
} % 7;
518 unless (defined $params{base
}) {
520 = defined $config{events_base
}
521 ?
$config{events_base
}
526 ( day
=> $params{focus
}->day()
527 , month
=> $params{focus
}->month()
528 , year
=> $params{focus
}->year()
530 if (defined $params{year
} and $params{year
} =~ m/^(\d+)$/) {
532 $focus_set{year
} = $year;
534 if (defined $params{month
} and $params{month
} =~ m/^(\d+)$/ and ($params{type
} eq 'month' or $params{type
} eq 'day')) {
536 $focus_set{month
} = $month;
538 if (defined $params{day
} and $params{day
} =~ m/^(\d+)$/ and $params{type
} eq 'day') {
540 $focus_set{day
} = $day;
542 if (not defined $focus_set{day
}) {
546 my $month = DateTime
->new(year
=> $focus_set{year
}, month
=> $focus_set{month
}, day
=> 1);
547 my $last_day_of_month = $month->add(months
=> 1)->subtract(days
=> 1)->day();
548 $focus_set{day
} = $last_day_of_month
549 if $focus_set{day
} > $last_day_of_month;
551 $params{focus
}->set(%focus_set);
553 if (defined $params{day
} and $params{day
} =~ m/^([+-])(\d+)$/ and $params{type
} eq 'day') {
554 my ($sign, $days) = ($1, $2);
555 my $duration = DateTime
::Duration
->new(days
=> $days, end_of_month
=> 'limit');
558 ?
$params{focus
}->add_duration($duration)
559 : $params{focus
}->subtract_duration($duration);
561 if (defined $params{month
} and $params{month
} =~ m/^([+-])(\d+)$/ and ($params{type
} eq 'month' or $params{type
} eq 'day')) {
562 my ($sign, $months) = ($1, $2);
563 my $duration = DateTime
::Duration
->new(months
=> $months, end_of_month
=> 'limit');
566 ?
$params{focus
}->add_duration($duration)
567 : $params{focus
}->subtract_duration($duration);
569 if (defined $params{year
} and $params{year
} =~ m/^([+-])(\d+)$/) {
570 my ($sign, $years) = ($1, $2);
571 my $duration = DateTime
::Duration
->new(years
=> $years, end_of_month
=> 'limit');
574 ?
$params{focus
}->add_duration($duration)
575 : $params{focus
}->subtract_duration($duration);
578 #debug("events: focus=".$params{focus}->strftime('%Y-%m-%d_%H-%M-%S'));
579 $params{pages
} =~ s
[%Y][$params{focus
}->year()]eg
;
580 $params{pages
} =~ s
[%m][sprintf('%02d', $params{focus
}->month())]eg
;
581 $params{pages
} =~ s
[%d][sprintf('%02d', $params{focus
}->day())]eg
;
583 set_next_rendering
(%params);
586 if ($params{type
} eq 'month') {
587 $calendar = preprocess_month
(%params);
589 elsif ($params{type
} eq 'day') {
590 $calendar = preprocess_day
(%params);
593 return "<div class='calendar'>$calendar</div>";
598 my ($base, $model) = @_;
599 my $page = $base.'/'.'templates/'.$model;
600 my $file = defined srcfile
($page, 1) ?
'/'.$page : $model;
601 return template
($file);
603 sub date_of_form
($$;%) {
604 my ($form, $prefix, %default) = @_;
614 eval { $date = DateTime
->new
615 ( year
=> ($form->field($prefix.'_year') ne '' ?
$form->field($prefix.'_year') : $default{year
})
616 , month
=> ($form->field($prefix.'_month') ne '' ?
substr($form->field($prefix.'_month'), 0, 2) : $default{month
})
617 , day
=> ($form->field($prefix.'_day') ne '' ?
$form->field($prefix.'_day') : $default{day
})
618 , hour
=> ($form->field($prefix.'_hour') ne '' ?
$form->field($prefix.'_hour') : $default{hour
})
619 , minute
=> ($form->field($prefix.'_minute') ne '' ?
$form->field($prefix.'_minute') : $default{minute
})
622 , time_zone
=> 'local'
623 , locale
=> $config{locale
}
624 )->set_time_zone('floating') };
627 sub duration_of_form
($$) {
628 my ($form, $prefix) = @_;
630 eval { $dur = DateTime
::Duration
->new
631 ( years
=> $form->field($prefix.'_year')
632 , months
=> $form->field($prefix.'_month')
633 , days
=> $form->field($prefix.'_day')
634 , weeks
=> $form->field($prefix.'_week')
635 , hours
=> $form->field($prefix.'_hour')
636 , minutes
=> $form->field($prefix.'_minute')
639 , end_of_month
=> 'limit'
643 sub page_of_event
($$$$$) {
644 my ($form, $from_date, $to_date, $name, $base) = @_;
646 if ($form->field('from_hour') ne ''
647 or $form->field('from_minute') ne '') {
648 if ($from_date->hour() == $to_date->hour()
649 and $from_date->minute() == $to_date->minute()) {
650 $time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
653 $time = sprintf('%02dh%02d-%02dh%02d'
654 , $from_date->hour(), $from_date->minute()
655 , $to_date->hour(), $to_date->minute());
660 . ($base?
'/':'').$from_date->year()
661 . '/'.sprintf('%02d', $from_date->month())
662 . '/'.sprintf('%02d', $from_date->day())
663 . '/'. ($time ne '' ?
$time . '/' : '')
667 sub check_cannewevent
($$$$) {
673 # Must be a legal filename.
674 if (IkiWiki
::file_pruned
($destfile)) {
675 error
(sprintf(gettext
("illegal name")));
677 # Must not be a known source file.
678 if (exists $pagesources{$dest}) {
679 error
(sprintf(gettext
("%s already exists"),
680 htmllink
("", "", $dest
682 , noimageinline
=> 1)));
684 # Must not exist on disk already.
685 if (-l
"$config{srcdir}/$destfile" || -e _
) {
686 error
(sprintf(gettext
("%s already exists on disk"), $destfile));
690 IkiWiki
::check_canedit
($dest, $cgi, $session);
693 IkiWiki
::run_hooks
(can_newevent
=> sub {
694 return if defined $can_newevent;
695 my $ret=shift->(cgi
=> $cgi, session
=> $session, dest
=> $dest, destfile
=> $destfile);
700 elsif (ref $ret eq 'CODE') {
704 elsif (defined $ret) {
710 return defined $can_newevent ?
$can_newevent : 1;
712 sub post_newevent
($$$) {
717 IkiWiki
::redirect
($cgi, urlto
($dest));
722 my @events = @
{$params{events
}};
723 my %done = %{$params{done
}};
724 my $cgi = $params{cgi
};
725 my $session = $params{session
};
729 foreach my $event (@events) {
730 unless (exists $done{$event->{page
}} && $done{$event->{file
}}) {
731 IkiWiki
::run_hooks
(newevent
=> sub {
735 , session
=> $session
738 $done{$event->{page
}} = 1;
741 push @events, newevent_hook
745 , session
=> $session
747 my %seen; # NOTE: insure unicity
748 return grep { ! $seen{$_->{page
}}++ } @events;
751 my ($cgi, $session, $form, $events, $months) = @_;
752 $form->tmpl_param(year
=> gettext
("year"));
753 $form->tmpl_param(month
=> gettext
("month"));
754 $form->tmpl_param(day
=> gettext
("day"));
755 $form->tmpl_param(hour
=> gettext
("hour"));
756 $form->tmpl_param(min
=> gettext
("min"));
757 $form->tmpl_param(dow
=> gettext
("day of week"));
758 $form->tmpl_param(page
=> gettext
("page"));
759 $form->tmpl_param(events
=> [
761 { from_year
=> $_->{from
}->year()
762 , from_month
=> sprintf('%02d', $_->{from
}->month())
763 , from_monthname
=> $months->{$_->{from
}->month()}
764 , from_day
=> sprintf('%02d', $_->{from
}->day())
765 , from_hour
=> sprintf('%02d', $_->{from
}->hour())
766 , from_minute
=> sprintf('%02d', $_->{from
}->minute())
767 , from_dow
=> $_->{from
}->dow()
768 , from_downame
=> $_->{from
}->day_name()
769 , to_year
=> $_->{to
}->year()
770 , to_month
=> sprintf('%02d', $_->{to
}->month())
771 , to_monthname
=> $months->{$_->{to
}->month()}
772 , to_day
=> sprintf('%02d', $_->{to
}->day())
773 , to_hour
=> sprintf('%02d', $_->{to
}->hour())
774 , to_minute
=> sprintf('%02d', $_->{to
}->minute())
775 , to_dow
=> $_->{to
}->dow()
777 htmllink
("", "", $_->{page
}
778 , linktext
=> $_->{page
}
779 , noimageinline
=> 1)
784 my $page = @
$events[0];
786 my $new = not exists $pagesources{$page};
787 # temporarily record its type
788 my $type = $config{default_pageext
};
789 $pagesources{$page} = $page.".".$type if $new;
790 my %wasrendered = map { $_ => 1 } @
{$renderedfiles{$page}};
791 my $content = @
$events[0]->{content
};
793 IkiWiki
::run_hooks
(editcontent
=> sub {
796 , content
=> $content
798 , session
=> $session
801 my $preview = IkiWiki
::htmlize
($page, $page, $type,
802 IkiWiki
::linkify
($page, $page,
803 IkiWiki
::preprocess
($page, $page,
804 IkiWiki
::filter
($page, $page, $content), 0, 1)));
805 IkiWiki
::run_hooks
(format
=> sub {
807 ( content
=> $preview
811 $form->tmpl_param("preview", $preview);
813 # Previewing may have created files on disk.
814 # Keep a list of these to be deleted later.
815 my %previews = map { $_ => 1 } @
{$wikistate{editpage
}{previews
}};
816 foreach my $f (@
{$renderedfiles{$page}}) {
817 $previews{$f} = 1 unless $wasrendered{$f};
820 # Throw out any other state changes made during previewing,
821 # and save the previews list.
822 IkiWiki
::loadindex
();
823 @
{$wikistate{editpage
}{previews
}} = keys %previews;
824 IkiWiki
::saveindex
();
827 $form->tmpl_param("preview", gettext
("No event"));
831 my ($event, $cgi, $session, $months, $base) = @_;
838 my $pageext = $config{default_pageext
};
840 $config{cgi
} = 0; # NOTE: avoid CGI error message
841 eval { writefile
($event->{file
}, $config{srcdir
}, $event->{content
}) };
843 IkiWiki
::rcs_add
($event->{file
});
848 . ($base?
'/':'').$event->{from
}->year()
849 . '/'.sprintf('%02d', $event->{from
}->month())
851 my $monthfile = IkiWiki
::newpagefile
($monthpage, $pageext);
852 if (not exists $pagesources{$monthpage}
853 and not -l
$config{srcdir
}.'/'.$monthfile
855 my $tmpl_neweventmonth = tmpl
($base, 'neweventmonth.tmpl');
856 $tmpl_neweventmonth->param(base
=> $base);
857 $tmpl_neweventmonth->param(year
=> $event->{from
}->year());
858 $tmpl_neweventmonth->param(month
=> sprintf('%02d', $event->{from
}->month()));
859 $tmpl_neweventmonth->param(monthname
=> $months->{$event->{from
}->month()});
860 my $content = $tmpl_neweventmonth->output();
861 eval { writefile
($monthfile, $config{srcdir
}, $content) };
863 IkiWiki
::rcs_add
($monthfile);
869 . '/'.sprintf('%02d', $event->{from
}->day())
871 my $dayfile = IkiWiki
::newpagefile
($daypage, $pageext);
872 if (not exists $pagesources{$daypage}
873 and not -l
$config{srcdir
}.'/'.$dayfile
875 my $tmpl_neweventday = tmpl
($base, 'neweventday.tmpl');
876 $tmpl_neweventday->param(base
=> $base);
877 $tmpl_neweventday->param(year
=> $event->{from
}->year());
878 $tmpl_neweventday->param(month
=> sprintf('%02d', $event->{from
}->month()));
879 $tmpl_neweventday->param(monthname
=> $months->{$event->{from
}->month()});
880 $tmpl_neweventday->param(day
=> sprintf('%02d', $event->{from
}->day()));
881 $tmpl_neweventday->param(dayname
=> $event->{from
}->day_name());
882 my $content = $tmpl_neweventday->output();
883 eval { writefile
($dayfile, $config{srcdir
}, $content) };
885 IkiWiki
::rcs_add
($dayfile);
890 sub sessioncgi
($$) {
891 my ($cgi, $session) = @_;
892 if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
893 # TOTRY: decode_cgi_utf8($cgi);
894 my $base = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('base'))));
895 &IkiWiki
::check_canedit
($base, $cgi, $session);
896 my $page = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('page'))));
898 my $now_date = DateTime
->now
899 ( time_zone
=> 'local'
900 , locale
=> $config{locale
}
901 )->set_time_zone('floating');
902 my %dows = map { ($_ => $now_date->{locale
}->day_format_wide->[ $_ ]) } (0..6);
903 my %months = map { ($_ => $now_date->{locale
}->month_format_wide->[ $_ - 1 ]) } (1..12);
905 eval { $cgi_date = DateTime
->new
906 ( year
=> defined $cgi->param("year") ?
$cgi->param("year") : $now_date->year()
907 , month
=> defined $cgi->param("month") ?
$cgi->param("month") : $now_date->month()
908 , day
=> defined $cgi->param("day") ?
$cgi->param("day") : $now_date->day()
909 , hour
=> defined $cgi->param("hour") ?
$cgi->param("hour") : $now_date->hour()
910 , minute
=> defined $cgi->param("minute") ?
$cgi->param("minute") : $now_date->minute()
913 , time_zone
=> 'local'
914 , locale
=> $config{locale
}
915 )->set_time_zone('floating') };
916 error
(sprintf(gettext
("illegal date")))
919 my @years = ($cgi_date->year() .. $cgi_date->year()+5);
921 = (defined $config{week_start_day
} and $config{week_start_day
} >= 0 and $config{week_start_day
} <= 6)
922 ?
$config{week_start_day
}
924 my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
926 my $tags = $typedlinks{$page}{tag
};
927 my $buttons = [qw{Preview Create
}];
928 my ($from_date, $to_date, $end_date, $inc_dur);
929 my $form = CGI
::FormBuilder
->new
930 ( action
=> IkiWiki
::cgiurl
()
934 from_date from_year from_month from_day from_hour from_minute
935 to_date to_year to_month to_day to_hour to_minute
936 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
937 end_times end_date end_year end_month end_day end_hour end_minute
944 # form_required_text => 'form_required_text'
945 # , form_invalid_text => 'form_invalid_text'
946 # , form_invalid_file => 'form_invalid_file'
947 # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
948 form_invalid_select
=> gettext
('invalid selection')
954 , required
=> [qw{do base year month day name from_date to_date end_date inc_dur
}]
955 , submit
=> [qw{Preview Create
}]
956 , title
=> gettext
("newevent")
957 , template
=> { template
("newevent.tmpl") }
959 { from_date
=> { perl
=> sub {
960 my (undef, $form) = @_;
961 $from_date = date_of_form
($form, 'from')
962 unless defined $from_date;
965 , to_date
=> { perl
=> sub {
966 my (undef, $form) = @_;
967 $from_date = date_of_form
($form, 'from')
968 unless defined $from_date;
969 if (defined $from_date) {
970 $to_date = date_of_form
($form, 'to'
971 , year
=> $from_date->year()
972 , month
=> $from_date->month()
973 , day
=> $from_date->day()
974 , hour
=> $from_date->hour()
975 , minute
=> $from_date->minute());
977 and (DateTime
->compare($from_date, $to_date) <= 0)
981 , end_date
=> { perl
=> sub {
982 my (undef, $form) = @_;
983 if ( $form->field('end_year') ne ''
984 or $form->field('end_month') ne ''
985 or $form->field('end_day') ne '' ) {
986 $from_date = date_of_form
($form, 'from')
987 unless defined $from_date;
988 if (defined $from_date) {
989 $end_date = date_of_form
($form, 'end'
990 , year
=> $from_date->year()
991 , month
=> $from_date->month()
992 , day
=> $from_date->day()
993 , hour
=> $from_date->hour()
994 , minute
=> $from_date->minute());
995 (defined $from_date and defined $end_date
996 and DateTime
->compare($from_date, $end_date) <= 0)
1006 , end_times
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1007 , inc_year
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1008 , inc_month
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1009 , inc_week
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1010 , inc_day
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1011 , inc_hour
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1012 , inc_minute
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
1014 my (undef, $form) = @_;
1015 $inc_dur = duration_of_form
($form, 'inc');
1017 and ($inc_dur->is_positive() or $inc_dur->is_zero());
1021 $base = $form->field('base') ?
$form->field('base') : $base;
1022 $form->title(sprintf(gettext
("creating new events"), pagetitle
(IkiWiki
::basename
($page))));
1023 $form->field(name
=> "do", type
=> "hidden", value
=> 'newevent', force
=> 1);
1024 $form->field(name
=> "base", type
=> "hidden", force
=> 1 , value
=> $base);
1025 $form->field(name
=> "from_date", type
=> "hidden", value
=> '1', force
=> 1);
1026 $form->field(name
=> "to_date", type
=> "hidden", value
=> '1', force
=> 1);
1027 $form->field(name
=> "end_date", type
=> "hidden", value
=> '1', force
=> 1);
1028 $form->field(name
=> "inc_dur", type
=> "hidden", value
=> '1', force
=> 1);
1029 $form->field(name
=> "from_year", type
=> 'select', value
=> $cgi_date->year(), options
=> \
@years);
1030 $form->field(name
=> "from_month", type
=> 'select'
1031 , value
=> sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
1032 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
1033 $form->field(name
=> "from_day", type
=> 'select'
1034 , value
=> sprintf("%02d", $cgi_date->day())
1035 , options
=> \
@days);
1036 $form->field(name
=> "from_hour", type
=> 'select', value
=> '', options
=> \
@hours);
1037 $form->field(name
=> "from_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
1038 $form->field(name
=> "name", type
=> 'text', size
=> 60, value
=> gettext
('New event'));
1039 $form->field(name
=> "to_year", type
=> 'select', value
=> '', options
=> \
@years);
1040 $form->field(name
=> "to_month", type
=> 'select'
1042 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
1043 $form->field(name
=> "to_day", type
=> 'select'
1045 , options
=> \
@days);
1046 $form->field(name
=> "to_hour", type
=> 'select', value
=> '', options
=> \
@hours);
1047 $form->field(name
=> "to_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
1048 $form->field(name
=> "end_year", type
=> 'select', value
=> '', options
=> \
@years);
1049 $form->field(name
=> "end_month", type
=> 'select', value
=> ''
1050 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
1051 $form->field(name
=> "end_day", type
=> 'select', value
=> '', options
=> \
@days);
1052 $form->field(name
=> "end_hour", type
=> 'select', value
=> '', options
=> \
@hours);
1053 $form->field(name
=> "end_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
1054 $form->field(name
=> "end_times", type
=> 'text', value
=> '0', size
=> 2);
1055 $form->field(name
=> "inc_year", type
=> 'text', value
=> '0', size
=> 2);
1056 $form->field(name
=> "inc_month", type
=> 'text', value
=> '0', size
=> 2);
1057 $form->field(name
=> "inc_week", type
=> 'text', value
=> '0', size
=> 2);
1058 $form->field(name
=> "inc_day", type
=> 'text', value
=> '0', size
=> 2);
1059 $form->field(name
=> "inc_hour", type
=> 'text', value
=> '0', size
=> 2);
1060 $form->field(name
=> "inc_minute", type
=> 'text', value
=> '0', size
=> 2);
1061 my $tmpl_neweventcontent = tmpl
($base, 'neweventcontent.tmpl');
1062 $tmpl_neweventcontent->param(title
=> gettext
('Title of the event'));
1063 $tmpl_neweventcontent->param(tags
=> [map {{name
=> $_}} (sort keys %$tags)]);
1064 $form->field(name
=> "content", type
=> "textarea", size
=> 30, rows
=> 20, cols
=> 80
1065 , value
=> $tmpl_neweventcontent->output());
1066 $form->field(name
=> "dom", type
=> 'select', multiple
=> 1, size
=> 35
1067 , options
=> [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
1069 IkiWiki
::decode_form_utf8
($form);
1070 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
1071 shift->(form
=> $form, cgi
=> $cgi, session
=> $session, buttons
=> $buttons);
1073 IkiWiki
::decode_form_utf8
($form);
1075 if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
1076 #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
1078 = $form->field('base')
1079 ?
$form->field('base')
1080 : (defined $config{base
} ?
$config{base
} : gettext
('Agenda'));
1082 = $form->field('end_times') == 0
1083 ?
undef : $form->field('end_times');
1085 foreach ($form->field('dom')) {
1086 $dom = {} if not defined $dom;
1089 my $name = $form->field('name');
1090 $name = IkiWiki
::possibly_foolish_untaint
(IkiWiki
::titlepage
($name));
1091 # NOTE: these untaints are safe because of the checks
1092 # performed in check_cannewevent later.
1093 my $content = $form->field('content');
1094 $content =~ s/\r\n/\n/gs;
1095 $content =~ s/\n$//s;
1097 # Queue of event creations to perfom.
1101 = defined $config{newevent_max_per_commit
}
1102 ?
$config{newevent_max_per_commit
} : (2 * 365) ;
1103 my $pageext = $config{default_pageext
};
1104 while (++$events_try <= $events_max
1105 and (not defined $end_times or --$end_times >= 0)
1106 and (not defined $end_date or DateTime
->compare($from_date, $end_date) <= 0)) {
1107 my $dest = page_of_event
($form, $from_date, $to_date, $name, $base);
1108 my $week = $from_date->weekday_of_month();
1109 my $day = $now_date->{locale
}->day_format_wide->[$from_date->day_of_week()-1];
1110 if (not defined $dom or exists $dom->{"$week° $day"}) {
1113 , file
=> IkiWiki
::newpagefile
($dest, $pageext)
1114 , from
=> $from_date
1119 last unless defined $inc_dur and $inc_dur->is_positive();
1120 $from_date = $from_date->clone->add_duration($inc_dur);
1121 $to_date = $to_date->clone->add_duration($inc_dur);
1123 error
("events try per commit overflow: $events_max")
1124 unless $events_try <= $events_max;
1125 my $tmpl_neweventpage = tmpl
($base, 'neweventpage.tmpl');
1128 $tmpl_neweventpage->clear_params();
1129 $tmpl_neweventpage->param(content
=> $content);
1130 $tmpl_neweventpage->param(page
=> $_->{page
});
1131 $tmpl_neweventpage->param(event
=> $i);
1132 $tmpl_neweventpage->param("event_first" => 1)
1134 $tmpl_neweventpage->param("event_last" => 1)
1135 if $i == @events - 1;
1136 $tmpl_neweventpage->param(events
=> \
@events);
1137 $tmpl_neweventpage->param(from_date
=> "$_->{from}");
1138 $tmpl_neweventpage->param(name
=> $_->{name
});
1139 $tmpl_neweventpage->param(to_date
=> "$_->{to}");
1140 $_->{content
} = $tmpl_neweventpage->output();
1143 if ($form->submitted eq 'Create') {
1144 @events = newevent_hook
1147 , events
=> \
@events
1148 , session
=> $session
1150 require IkiWiki
::Render
;
1152 IkiWiki
::disable_commit_hook
()
1154 foreach my $event (@events) {
1155 create
($event, $cgi, $session, \
%months, $base);
1158 IkiWiki
::rcs_commit_staged
1159 ( message
=> sprintf(gettext
("new event"))
1160 , session
=> $session );
1161 IkiWiki
::enable_commit_hook
();
1162 IkiWiki
::rcs_update
();
1165 IkiWiki
::saveindex
();
1167 post_newevent
($cgi, $session, (defined $events[0] ?
$events[0]->{page
} : ''));
1169 elsif ($form->submitted eq 'Preview') {
1170 preview
($cgi, $session, $form, \
@events, \
%months);
1171 IkiWiki
::showform
($form, $buttons, $session, $cgi);
1175 IkiWiki
::showform
($form, $buttons, $session, $cgi);