2 package IkiWiki
::Plugin
::newevent
;
13 #hook(type => "formbuilder", id => "newevent", call => \&formbuilder);
14 #hook(type => "formbuilder_setup", id => "newevent", call => \&formbuilder_setup);
15 hook
(type
=> "getsetup", id
=> "newevent", call
=> \
&getsetup
);
16 #hook(type => "preprocess", id => "newevent", call => \&preprocess);
17 #hook(type => "refresh", id => "newevent", call => \&refresh);
18 hook
(type
=> "sessioncgi", id
=> "newevent", call
=> \
&sessioncgi
);
21 my @days = ('01'..'31');
22 my @hours = ('00'..'23');
23 my @minutes = ('00'..'59');
35 , description
=> "prefix of the agenda hierarchy"
42 my ($base, $model) = @_;
43 my $page = IkiWiki
::dirname
($base).'/'.'templates/'.$model;
44 my $file = defined srcfile
($page, 1) ?
'/'.$page : $model;
45 return template
($file);
47 sub date_of_form
($$;%) {
48 my ($form, $prefix, %default) = @_;
58 eval { $date = DateTime
->new
59 ( year
=> ($form->field($prefix.'_year') ne '' ?
$form->field($prefix.'_year') : $default{year
})
60 , month
=> ($form->field($prefix.'_month') ne '' ?
substr($form->field($prefix.'_month'), 0, 2) : $default{month
})
61 , day
=> ($form->field($prefix.'_day') ne '' ?
$form->field($prefix.'_day') : $default{day
})
62 , hour
=> ($form->field($prefix.'_hour') ne '' ?
$form->field($prefix.'_hour') : $default{hour
})
63 , minute
=> ($form->field($prefix.'_minute') ne '' ?
$form->field($prefix.'_minute') : $default{minute
})
66 , time_zone
=> 'local'
67 , locale
=> $config{locale
}
68 )->set_time_zone('floating') };
71 sub duration_of_form
($$) {
72 my ($form, $prefix) = @_;
74 eval { $dur = DateTime
::Duration
->new
75 ( years
=> $form->field($prefix.'_year')
76 , months
=> $form->field($prefix.'_month')
77 , days
=> $form->field($prefix.'_day')
78 , weeks
=> $form->field($prefix.'_week')
79 , hours
=> $form->field($prefix.'_hour')
80 , minutes
=> $form->field($prefix.'_minute')
83 , end_of_month
=> 'limit'
87 sub page_of_event
($$$$$) {
88 my ($form, $from_date, $to_date, $name, $base) = @_;
90 if ($form->field('from_hour') ne '' or $form->field('from_minute') ne '') {
91 if ($from_date->hour() == $to_date->hour()
92 and $from_date->minute() == $to_date->minute()) {
93 $time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
96 $time = sprintf('%02dh%02d-%02dh%02d'
97 , $from_date->hour(), $from_date->minute()
98 , $to_date->hour(), $to_date->minute());
103 . ($base?
'/':'').$from_date->year()
104 . '/'.sprintf('%02d', $from_date->month())
105 . '/'.sprintf('%02d', $from_date->day())
106 . '/'. ($time ne '' ?
$time . '/' : '')
110 sub check_cannewevent
($$$$) {
116 # Must be a legal filename.
117 if (IkiWiki
::file_pruned
($destfile)) {
118 error
(sprintf(gettext
("illegal name")));
120 # Must not be a known source file.
121 if (exists $pagesources{$dest}) {
122 error
(sprintf(gettext
("%s already exists"),
123 htmllink
("", "", $dest
125 , noimageinline
=> 1)));
127 # Must not exist on disk already.
128 if (-l
"$config{srcdir}/$destfile" || -e _
) {
129 error
(sprintf(gettext
("%s already exists on disk"), $destfile));
133 IkiWiki
::check_canedit
($dest, $cgi, $session);
136 IkiWiki
::run_hooks
(can_newevent
=> sub {
137 return if defined $can_newevent;
138 my $ret=shift->(cgi
=> $cgi, session
=> $session, dest
=> $dest, destfile
=> $destfile);
143 elsif (ref $ret eq 'CODE') {
147 elsif (defined $ret) {
153 return defined $can_newevent ?
$can_newevent : 1;
155 sub post_newevent
($$$) {
160 IkiWiki
::redirect
($cgi, urlto
($dest));
165 # ( base => ($config{base} ? $config{base} : gettext('Agenda'))
167 #($form, $buttons) = newevent_form()
168 # if not defined $form;
169 #my $ret = $form->render();
170 return "<div class='newevent'></div>";
172 sub sessioncgi
($$) {
173 my ($cgi, $session) = @_;
174 if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
175 # TOTRY: decode_cgi_utf8($cgi);
176 my $base = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('base'))));
177 &IkiWiki
::check_canedit
($base, $cgi, $session);
178 my $page = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('page'))));
180 my $now_date = DateTime
->now
181 ( time_zone
=> 'local'
182 , locale
=> $config{locale
}
183 )->set_time_zone('floating');
184 my %dows = map { ($_ => $now_date->{locale
}->day_format_wide->[ $_ ]) } (0..6);
185 my %months = map { ($_ => $now_date->{locale
}->month_format_wide->[ $_ - 1 ]) } (1..12);
187 eval { $cgi_date = DateTime
->new
188 ( year
=> defined $cgi->param("year") ?
$cgi->param("year") : $now_date->year()
189 , month
=> defined $cgi->param("month") ?
$cgi->param("month") : $now_date->month()
190 , day
=> defined $cgi->param("day") ?
$cgi->param("day") : $now_date->day()
191 , hour
=> defined $cgi->param("hour") ?
$cgi->param("hour") : $now_date->hour()
192 , minute
=> defined $cgi->param("minute") ?
$cgi->param("minute") : $now_date->minute()
195 , time_zone
=> 'local'
196 , locale
=> $config{locale
}
197 )->set_time_zone('floating') };
198 error
(sprintf(gettext
("illegal date")))
201 my @years = ($cgi_date->year() .. $cgi_date->year()+5);
203 = (defined $config{week_start_day
} and $config{week_start_day
} >= 0 and $config{week_start_day
} <= 6)
204 ?
$config{week_start_day
}
206 my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
208 my $tags = $typedlinks{$page}{tag
};
209 my $buttons = [qw{Preview Create
}];
210 my ($from_date, $to_date, $end_date, $inc_dur);
211 my $form = CGI
::FormBuilder
->new
212 ( action
=> IkiWiki
::cgiurl
()
216 from_date from_year from_month from_day from_hour from_minute
217 to_date to_year to_month to_day to_hour to_minute
218 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
219 end_times end_date end_year end_month end_day end_hour end_minute
226 # form_required_text => 'form_required_text'
227 # , form_invalid_text => 'form_invalid_text'
228 # , form_invalid_file => 'form_invalid_file'
229 # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
230 form_invalid_select
=> gettext
('invalid selection')
236 , required
=> [qw{do base year month day name from_date to_date end_date inc_dur
}]
237 , submit
=> [qw{Preview Create
}]
238 , title
=> gettext
("newevent")
239 , template
=> { template
("newevent.tmpl") }
241 { from_date
=> { perl
=> sub {
242 my (undef, $form) = @_;
243 $from_date = date_of_form
($form, 'from')
244 unless defined $from_date;
247 , to_date
=> { perl
=> sub {
248 my (undef, $form) = @_;
249 $from_date = date_of_form
($form, 'from')
250 unless defined $from_date;
251 if (defined $from_date) {
252 $to_date = date_of_form
($form, 'to'
253 , year
=> $from_date->year()
254 , month
=> $from_date->month()
255 , day
=> $from_date->day()
256 , hour
=> $from_date->hour()
257 , minute
=> $from_date->minute());
259 and (DateTime
->compare($from_date, $to_date) <= 0)
263 , end_date
=> { perl
=> sub {
264 my (undef, $form) = @_;
265 if ( $form->field('end_year') ne ''
266 or $form->field('end_month') ne ''
267 or $form->field('end_day') ne '' ) {
268 $from_date = date_of_form
($form, 'from')
269 unless defined $from_date;
270 if (defined $from_date) {
271 $end_date = date_of_form
($form, 'end'
272 , year
=> $from_date->year()
273 , month
=> $from_date->month()
274 , day
=> $from_date->day()
275 , hour
=> $from_date->hour()
276 , minute
=> $from_date->minute());
277 (defined $from_date and defined $end_date
278 and DateTime
->compare($from_date, $end_date) <= 0)
288 , end_times
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
289 , inc_year
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
290 , inc_month
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
291 , inc_week
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
292 , inc_day
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
293 , inc_hour
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
294 , inc_minute
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
296 my (undef, $form) = @_;
297 $inc_dur = duration_of_form
($form, 'inc');
299 and ($inc_dur->is_positive() or $inc_dur->is_zero());
303 $base = $form->field('base') ?
$form->field('base') : $base;
304 $form->title(sprintf(gettext
("creating new events"), pagetitle
(IkiWiki
::basename
($page))));
305 $form->field(name
=> "do", type
=> "hidden", value
=> 'newevent', force
=> 1);
306 $form->field(name
=> "base", type
=> "hidden", force
=> 1 , value
=> $base);
307 $form->field(name
=> "from_date", type
=> "hidden", value
=> '1', force
=> 1);
308 $form->field(name
=> "to_date", type
=> "hidden", value
=> '1', force
=> 1);
309 $form->field(name
=> "end_date", type
=> "hidden", value
=> '1', force
=> 1);
310 $form->field(name
=> "inc_dur", type
=> "hidden", value
=> '1', force
=> 1);
311 $form->field(name
=> "from_year", type
=> 'select', value
=> $cgi_date->year(), options
=> \
@years);
312 $form->field(name
=> "from_month", type
=> 'select'
313 , value
=> sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
314 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
315 $form->field(name
=> "from_day", type
=> 'select'
316 , value
=> sprintf("%02d", $cgi_date->day())
317 , options
=> \
@days);
318 $form->field(name
=> "from_hour", type
=> 'select', value
=> '', options
=> \
@hours);
319 $form->field(name
=> "from_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
320 $form->field(name
=> "name", type
=> 'text', size
=> 60, value
=> gettext
('New event'));
321 $form->field(name
=> "to_year", type
=> 'select', value
=> '', options
=> \
@years);
322 $form->field(name
=> "to_month", type
=> 'select'
324 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
325 $form->field(name
=> "to_day", type
=> 'select'
327 , options
=> \
@days);
328 $form->field(name
=> "to_hour", type
=> 'select', value
=> '', options
=> \
@hours);
329 $form->field(name
=> "to_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
330 $form->field(name
=> "end_year", type
=> 'select', value
=> '', options
=> \
@years);
331 $form->field(name
=> "end_month", type
=> 'select', value
=> ''
332 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
333 $form->field(name
=> "end_day", type
=> 'select', value
=> '', options
=> \
@days);
334 $form->field(name
=> "end_hour", type
=> 'select', value
=> '', options
=> \
@hours);
335 $form->field(name
=> "end_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
336 $form->field(name
=> "end_times", type
=> 'text', value
=> '0', size
=> 2);
337 $form->field(name
=> "inc_year", type
=> 'text', value
=> '0', size
=> 2);
338 $form->field(name
=> "inc_month", type
=> 'text', value
=> '0', size
=> 2);
339 $form->field(name
=> "inc_week", type
=> 'text', value
=> '0', size
=> 2);
340 $form->field(name
=> "inc_day", type
=> 'text', value
=> '0', size
=> 2);
341 $form->field(name
=> "inc_hour", type
=> 'text', value
=> '0', size
=> 2);
342 $form->field(name
=> "inc_minute", type
=> 'text', value
=> '0', size
=> 2);
343 my $tmpl_neweventcontent = tmpl
($base, 'neweventcontent.tmpl');
344 $tmpl_neweventcontent->param(title
=> gettext
('Title of the event'));
345 $tmpl_neweventcontent->param(tags
=> [map {{name
=> $_}} (sort keys %$tags)]);
346 $form->field(name
=> "content", type
=> "textarea", size
=> 30, rows
=> 20, cols
=> 80
347 , value
=> $tmpl_neweventcontent->output());
348 $form->field(name
=> "dom", type
=> 'select', multiple
=> 1, size
=> 35
349 , options
=> [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
351 IkiWiki
::decode_form_utf8
($form);
352 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
353 shift->(form
=> $form, cgi
=> $cgi, session
=> $session, buttons
=> $buttons);
355 IkiWiki
::decode_form_utf8
($form);
357 if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
358 #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
360 = $form->field('base')
361 ?
$form->field('base')
362 : (defined $config{base
} ?
$config{base
} : gettext
('Agenda'));
364 = $form->field('end_times') == 0
365 ?
undef : $form->field('end_times');
367 foreach ($form->field('dom')) {
368 $dom = {} if not defined $dom;
371 my $name = $form->field('name');
372 $name = IkiWiki
::possibly_foolish_untaint
(IkiWiki
::titlepage
($name));
373 # NOTE: these untaints are safe because of the checks
374 # performed in check_cannewevent later.
375 my $content = $form->field('content');
376 $content =~ s/\r\n/\n/gs;
377 $content =~ s/\n$//s;
379 # Queue of event creations to perfom.
383 = defined $config{newevent_max_per_commit
}
384 ?
$config{newevent_max_per_commit
} : (2 * 365) ;
385 my $pageext = $config{default_pageext
};
386 while (++$events_try <= $events_max
387 and (not defined $end_times or --$end_times >= 0)
388 and (not defined $end_date or DateTime
->compare($from_date, $end_date) <= 0)) {
389 my $dest = page_of_event
($form, $from_date, $to_date, $name, $base);
390 my $week = $from_date->weekday_of_month();
391 my $day = $now_date->{locale
}->day_format_wide->[$from_date->day_of_week()-1];
392 if (not defined $dom or exists $dom->{"$week° $day"}) {
395 , file
=> IkiWiki
::newpagefile
($dest, $pageext)
401 last unless defined $inc_dur and $inc_dur->is_positive();
402 $from_date = $from_date->clone->add_duration($inc_dur);
403 $to_date = $to_date->clone->add_duration($inc_dur);
405 error
("events try per commit overflow: $events_max")
406 unless $events_try <= $events_max;
407 my $tmpl_neweventpage = tmpl
($base, 'neweventpage.tmpl');
410 $tmpl_neweventpage->clear_params();
411 $tmpl_neweventpage->param(content
=> $content);
412 $tmpl_neweventpage->param(page
=> $_->{page
});
413 $tmpl_neweventpage->param(event
=> $i);
414 $tmpl_neweventpage->param("event_first" => 1)
416 $tmpl_neweventpage->param("event_last" => 1)
417 if $i == @events - 1;
418 $tmpl_neweventpage->param(events
=> \
@events);
419 $tmpl_neweventpage->param(from_date
=> "$_->{from}");
420 $tmpl_neweventpage->param(name
=> $_->{name
});
421 $tmpl_neweventpage->param(to_date
=> "$_->{to}");
422 $_->{content
} = $tmpl_neweventpage->output();
425 if ($form->submitted eq 'Create') {
426 @events = newevent_hook
430 , session
=> $session
432 require IkiWiki
::Render
;
434 IkiWiki
::disable_commit_hook
()
436 foreach my $event (@events) {
437 create
($event, $cgi, $session, \
%months, $base);
440 IkiWiki
::rcs_commit_staged
441 ( message
=> sprintf(gettext
("new event"))
442 , session
=> $session );
443 IkiWiki
::enable_commit_hook
();
444 IkiWiki
::rcs_update
();
447 IkiWiki
::saveindex
();
449 post_newevent
($cgi, $session, (defined $events[0] ?
$events[0]->{page
} : ''));
451 elsif ($form->submitted eq 'Preview') {
452 preview
($cgi, $session, $form, \
@events, \
%months);
453 IkiWiki
::showform
($form, $buttons, $session, $cgi);
457 IkiWiki
::showform
($form, $buttons, $session, $cgi);
464 my ($cgi, $session, $form, $events, $months) = @_;
465 $form->tmpl_param(year
=> gettext
("year"));
466 $form->tmpl_param(month
=> gettext
("month"));
467 $form->tmpl_param(day
=> gettext
("day"));
468 $form->tmpl_param(hour
=> gettext
("hour"));
469 $form->tmpl_param(min
=> gettext
("min"));
470 $form->tmpl_param(dow
=> gettext
("day of week"));
471 $form->tmpl_param(page
=> gettext
("page"));
472 $form->tmpl_param(events
=> [
474 { from_year
=> $_->{from
}->year()
475 , from_month
=> sprintf('%02d', $_->{from
}->month())
476 , from_monthname
=> $months->{$_->{from
}->month()}
477 , from_day
=> sprintf('%02d', $_->{from
}->day())
478 , from_hour
=> sprintf('%02d', $_->{from
}->hour())
479 , from_minute
=> sprintf('%02d', $_->{from
}->minute())
480 , from_dow
=> $_->{from
}->dow()
481 , from_downame
=> $_->{from
}->day_name()
482 , to_year
=> $_->{to
}->year()
483 , to_month
=> sprintf('%02d', $_->{to
}->month())
484 , to_monthname
=> $months->{$_->{to
}->month()}
485 , to_day
=> sprintf('%02d', $_->{to
}->day())
486 , to_hour
=> sprintf('%02d', $_->{to
}->hour())
487 , to_minute
=> sprintf('%02d', $_->{to
}->minute())
488 , to_dow
=> $_->{to
}->dow()
490 htmllink
("", "", $_->{page
}
491 , linktext
=> $_->{page
}
492 , noimageinline
=> 1)
497 my $page = @
$events[0];
499 my $new = not exists $pagesources{$page};
500 # temporarily record its type
501 my $type = $config{default_pageext
};
502 $pagesources{$page} = $page.".".$type if $new;
503 my %wasrendered = map { $_ => 1 } @
{$renderedfiles{$page}};
504 my $content = @
$events[0]->{content
};
506 IkiWiki
::run_hooks
(editcontent
=> sub {
509 , content
=> $content
511 , session
=> $session
514 my $preview = IkiWiki
::htmlize
($page, $page, $type,
515 IkiWiki
::linkify
($page, $page,
516 IkiWiki
::preprocess
($page, $page,
517 IkiWiki
::filter
($page, $page, $content), 0, 1)));
518 IkiWiki
::run_hooks
(format
=> sub {
520 ( content
=> $preview
524 $form->tmpl_param("preview", $preview);
526 # Previewing may have created files on disk.
527 # Keep a list of these to be deleted later.
528 my %previews = map { $_ => 1 } @
{$wikistate{editpage
}{previews
}};
529 foreach my $f (@
{$renderedfiles{$page}}) {
530 $previews{$f} = 1 unless $wasrendered{$f};
533 # Throw out any other state changes made during previewing,
534 # and save the previews list.
535 IkiWiki
::loadindex
();
536 @
{$wikistate{editpage
}{previews
}} = keys %previews;
537 IkiWiki
::saveindex
();
540 $form->tmpl_param("preview", gettext
("No event"));
544 my ($event, $cgi, $session, $months, $base) = @_;
551 my $pageext = $config{default_pageext
};
553 $config{cgi
} = 0; # NOTE: avoid CGI error message
554 eval { writefile
($event->{file
}, $config{srcdir
}, $event->{content
}) };
556 IkiWiki
::rcs_add
($event->{file
});
561 . ($base?
'/':'').$event->{from
}->year()
562 . '/'.sprintf('%02d', $event->{from
}->month())
564 my $monthfile = IkiWiki
::newpagefile
($monthpage, $pageext);
565 if (not exists $pagesources{$monthpage}
566 and not -l
$config{srcdir
}.'/'.$monthfile
568 my $tmpl_neweventmonth = tmpl
($base, 'neweventmonth.tmpl');
569 $tmpl_neweventmonth->param(base
=> $base);
570 $tmpl_neweventmonth->param(year
=> $event->{from
}->year());
571 $tmpl_neweventmonth->param(month
=> sprintf('%02d', $event->{from
}->month()));
572 $tmpl_neweventmonth->param(monthname
=> $months->{$event->{from
}->month()});
573 my $content = $tmpl_neweventmonth->output();
574 eval { writefile
($monthfile, $config{srcdir
}, $content) };
576 IkiWiki
::rcs_add
($monthfile);
582 . '/'.sprintf('%02d', $event->{from
}->day())
584 my $dayfile = IkiWiki
::newpagefile
($daypage, $pageext);
585 if (not exists $pagesources{$daypage}
586 and not -l
$config{srcdir
}.'/'.$dayfile
588 my $tmpl_neweventday = tmpl
($base, 'neweventday.tmpl');
589 $tmpl_neweventday->param(base
=> $base);
590 $tmpl_neweventday->param(year
=> $event->{from
}->year());
591 $tmpl_neweventday->param(month
=> sprintf('%02d', $event->{from
}->month()));
592 $tmpl_neweventday->param(monthname
=> $months->{$event->{from
}->month()});
593 $tmpl_neweventday->param(day
=> sprintf('%02d', $event->{from
}->day()));
594 $tmpl_neweventday->param(dayname
=> $event->{from
}->day_name());
595 my $content = $tmpl_neweventday->output();
596 eval { writefile
($dayfile, $config{srcdir
}, $content) };
598 IkiWiki
::rcs_add
($dayfile);
605 my @events = @
{$params{events
}};
606 my %done = %{$params{done
}};
607 my $cgi = $params{cgi
};
608 my $session = $params{session
};
612 foreach my $event (@events) {
613 unless (exists $done{$event->{page
}} && $done{$event->{file
}}) {
614 IkiWiki
::run_hooks
(newevent
=> sub {
618 , session
=> $session
621 $done{$event->{page
}} = 1;
624 push @events, newevent_hook
628 , session
=> $session
630 my %seen; # NOTE: insure unicity
631 return grep { ! $seen{$_->{page
}}++ } @events;