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"
41 sub date_of_form
($$;%) {
42 my ($form, $prefix, %default) = @_;
52 eval { $date = DateTime
->new
53 ( year
=> ($form->field($prefix.'_year') ne '' ?
$form->field($prefix.'_year') : $default{year
})
54 , month
=> ($form->field($prefix.'_month') ne '' ?
substr($form->field($prefix.'_month'), 0, 2) : $default{month
})
55 , day
=> ($form->field($prefix.'_day') ne '' ?
$form->field($prefix.'_day') : $default{day
})
56 , hour
=> ($form->field($prefix.'_hour') ne '' ?
$form->field($prefix.'_hour') : $default{hour
})
57 , minute
=> ($form->field($prefix.'_minute') ne '' ?
$form->field($prefix.'_minute') : $default{minute
})
60 , time_zone
=> 'local'
61 , locale
=> $config{locale
}
62 )->set_time_zone('floating') };
65 sub duration_of_form
($$) {
66 my ($form, $prefix) = @_;
68 eval { $dur = DateTime
::Duration
->new
69 ( years
=> $form->field($prefix.'_year')
70 , months
=> $form->field($prefix.'_month')
71 , days
=> $form->field($prefix.'_day')
72 , weeks
=> $form->field($prefix.'_week')
73 , hours
=> $form->field($prefix.'_hour')
74 , minutes
=> $form->field($prefix.'_minute')
77 , end_of_month
=> 'limit'
81 sub page_of_event
($$$$$) {
82 my ($form, $from_date, $to_date, $name, $newevent_base) = @_;
84 if ($form->field('from_hour') ne '' or $form->field('from_minute') ne '') {
85 if ($from_date->hour() == $to_date->hour()
86 and $from_date->minute() == $to_date->minute()) {
87 $time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
90 $time = sprintf('%02dh%02d-%02dh%02d'
91 , $from_date->hour(), $from_date->minute()
92 , $to_date->hour(), $to_date->minute());
97 . ($newevent_base?
'/':'').$from_date->year()
98 . '/'.sprintf('%02d', $from_date->month())
99 . '/'.sprintf('%02d', $from_date->day())
100 . '/'. ($time ne '' ?
$time . '/' : '')
104 sub check_cannewevent
($$$$) {
110 # Must be a legal filename.
111 if (IkiWiki
::file_pruned
($destfile)) {
112 error
(sprintf(gettext
("illegal name")));
114 # Must not be a known source file.
115 if (exists $pagesources{$dest}) {
116 error
(sprintf(gettext
("%s already exists"),
117 htmllink
("", "", $dest
119 , noimageinline
=> 1)));
121 # Must not exist on disk already.
122 if (-l
"$config{srcdir}/$destfile" || -e _
) {
123 error
(sprintf(gettext
("%s already exists on disk"), $destfile));
127 IkiWiki
::check_canedit
($dest, $cgi, $session);
130 IkiWiki
::run_hooks
(can_newevent
=> sub {
131 return if defined $can_newevent;
132 my $ret=shift->(cgi
=> $cgi, session
=> $session, dest
=> $dest, destfile
=> $destfile);
137 elsif (ref $ret eq 'CODE') {
141 elsif (defined $ret) {
147 return defined $can_newevent ?
$can_newevent : 1;
149 sub post_newevent
($$$) {
154 IkiWiki
::redirect
($cgi, urlto
($dest));
159 # ( base => ($config{newevent_base} ? $config{newevent_base} : gettext('Agenda'))
161 #($form, $buttons) = newevent_form()
162 # if not defined $form;
163 #my $ret = $form->render();
164 return "<div class='newevent'></div>";
166 sub sessioncgi
($$) {
167 my ($cgi, $session) = @_;
168 if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
169 # TOTRY: decode_cgi_utf8($cgi);
171 my $now_date = DateTime
->now
172 ( time_zone
=> 'local'
173 , locale
=> $config{locale
}
174 )->set_time_zone('floating');
175 my %dows = map { ($_ => $now_date->{locale
}->day_format_wide->[ $_ ]) } (0..6);
176 my %months = map { ($_ => $now_date->{locale
}->month_format_wide->[ $_ - 1 ]) } (1..12);
178 eval { $cgi_date = DateTime
->new
179 ( year
=> defined $cgi->param("year") ?
$cgi->param("year") : $now_date->year()
180 , month
=> defined $cgi->param("month") ?
$cgi->param("month") : $now_date->month()
181 , day
=> defined $cgi->param("day") ?
$cgi->param("day") : $now_date->day()
182 , hour
=> defined $cgi->param("hour") ?
$cgi->param("hour") : $now_date->hour()
183 , minute
=> defined $cgi->param("minute") ?
$cgi->param("minute") : $now_date->minute()
186 , time_zone
=> 'local'
187 , locale
=> $config{locale
}
188 )->set_time_zone('floating') };
189 error
(sprintf(gettext
("illegal date")))
192 my @years = ($cgi_date->year() .. $cgi_date->year()+5);
194 = (defined $config{week_start_day
} and $config{week_start_day
} >= 0 and $config{week_start_day
} <= 6)
195 ?
$config{week_start_day
}
197 my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
199 my $page = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('page'))));
200 my $newevent_base = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('base'))));
201 my $tags = $typedlinks{$page}{tag
};
202 my $buttons = [qw{Preview Create
}];
203 my ($from_date, $to_date, $end_date, $inc_dur);
204 my $form = CGI
::FormBuilder
->new
205 ( action
=> IkiWiki
::cgiurl
()
209 from_date from_year from_month from_day from_hour from_minute
210 to_date to_year to_month to_day to_hour to_minute
211 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
212 end_times end_date end_year end_month end_day end_hour end_minute
219 # form_required_text => 'form_required_text'
220 # , form_invalid_text => 'form_invalid_text'
221 # , form_invalid_file => 'form_invalid_file'
222 # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
223 form_invalid_select
=> gettext
('invalid selection')
229 , required
=> [qw{do base year month day name from_date to_date end_date inc_dur
}]
230 , submit
=> [qw{Preview Create
}]
231 , title
=> gettext
("newevent")
232 , template
=> { template
("newevent.tmpl") }
234 { from_date
=> { perl
=> sub {
235 my (undef, $form) = @_;
236 $from_date = date_of_form
($form, 'from')
237 unless defined $from_date;
240 , to_date
=> { perl
=> sub {
241 my (undef, $form) = @_;
242 $from_date = date_of_form
($form, 'from')
243 unless defined $from_date;
244 if (defined $from_date) {
245 $to_date = date_of_form
($form, 'to'
246 , year
=> $from_date->year()
247 , month
=> $from_date->month()
248 , day
=> $from_date->day()
249 , hour
=> $from_date->hour()
250 , minute
=> $from_date->minute());
252 and (DateTime
->compare($from_date, $to_date) <= 0)
256 , end_date
=> { perl
=> sub {
257 my (undef, $form) = @_;
258 if ( $form->field('end_year') ne ''
259 or $form->field('end_month') ne ''
260 or $form->field('end_day') ne '' ) {
261 $from_date = date_of_form
($form, 'from')
262 unless defined $from_date;
263 if (defined $from_date) {
264 $end_date = date_of_form
($form, 'end'
265 , year
=> $from_date->year()
266 , month
=> $from_date->month()
267 , day
=> $from_date->day()
268 , hour
=> $from_date->hour()
269 , minute
=> $from_date->minute());
270 (defined $from_date and defined $end_date
271 and DateTime
->compare($from_date, $end_date) <= 0)
281 , end_times
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
282 , inc_year
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
283 , inc_month
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
284 , inc_week
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
285 , inc_day
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
286 , inc_hour
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
287 , inc_minute
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
289 my (undef, $form) = @_;
290 $inc_dur = duration_of_form
($form, 'inc');
292 and ($inc_dur->is_positive() or $inc_dur->is_zero());
296 $form->title(sprintf(gettext
("creating new events"), pagetitle
(IkiWiki
::basename
($page))));
297 $form->field(name
=> "do", type
=> "hidden", value
=> 'newevent', force
=> 1);
298 $form->field(name
=> "base", type
=> "hidden", force
=> 1
299 , value
=> ($form->field('base') ?
$form->field('base') : $newevent_base));
300 $form->field(name
=> "from_date", type
=> "hidden", value
=> '1', force
=> 1);
301 $form->field(name
=> "to_date", type
=> "hidden", value
=> '1', force
=> 1);
302 $form->field(name
=> "end_date", type
=> "hidden", value
=> '1', force
=> 1);
303 $form->field(name
=> "inc_dur", type
=> "hidden", value
=> '1', force
=> 1);
304 $form->field(name
=> "from_year", type
=> 'select', value
=> $cgi_date->year(), options
=> \
@years);
305 $form->field(name
=> "from_month", type
=> 'select'
306 , value
=> sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
307 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
308 $form->field(name
=> "from_day", type
=> 'select'
309 , value
=> sprintf("%02d", $cgi_date->day())
310 , options
=> \
@days);
311 $form->field(name
=> "from_hour", type
=> 'select', value
=> '', options
=> \
@hours);
312 $form->field(name
=> "from_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
313 $form->field(name
=> "name", type
=> 'text', size
=> 60, value
=> gettext
('New event'));
314 $form->field(name
=> "to_year", type
=> 'select', value
=> '', options
=> \
@years);
315 $form->field(name
=> "to_month", type
=> 'select'
317 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
318 $form->field(name
=> "to_day", type
=> 'select'
320 , options
=> \
@days);
321 $form->field(name
=> "to_hour", type
=> 'select', value
=> '', options
=> \
@hours);
322 $form->field(name
=> "to_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
323 $form->field(name
=> "end_year", type
=> 'select', value
=> '', options
=> \
@years);
324 $form->field(name
=> "end_month", type
=> 'select', value
=> ''
325 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
326 $form->field(name
=> "end_day", type
=> 'select', value
=> '', options
=> \
@days);
327 $form->field(name
=> "end_hour", type
=> 'select', value
=> '', options
=> \
@hours);
328 $form->field(name
=> "end_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
329 $form->field(name
=> "end_times", type
=> 'text', value
=> '0', size
=> 2);
330 $form->field(name
=> "inc_year", type
=> 'text', value
=> '0', size
=> 2);
331 $form->field(name
=> "inc_month", type
=> 'text', value
=> '0', size
=> 2);
332 $form->field(name
=> "inc_week", type
=> 'text', value
=> '0', size
=> 2);
333 $form->field(name
=> "inc_day", type
=> 'text', value
=> '0', size
=> 2);
334 $form->field(name
=> "inc_hour", type
=> 'text', value
=> '0', size
=> 2);
335 $form->field(name
=> "inc_minute", type
=> 'text', value
=> '0', size
=> 2);
336 my $tmpl_neweventcontent = template
("neweventcontent.tmpl");
337 $tmpl_neweventcontent->param(title
=> gettext
('Title of the event'));
338 $tmpl_neweventcontent->param(tags
=> [map {{name
=> $_}} (sort keys %$tags)]);
339 $form->field(name
=> "content", type
=> "textarea", size
=> 30, rows
=> 20, cols
=> 80
340 , value
=> $tmpl_neweventcontent->output());
341 $form->field(name
=> "dom", type
=> 'select', multiple
=> 1, size
=> 35
342 , options
=> [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
344 IkiWiki
::decode_form_utf8
($form);
345 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
346 shift->(form
=> $form, cgi
=> $cgi, session
=> $session, buttons
=> $buttons);
348 IkiWiki
::decode_form_utf8
($form);
350 if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
351 #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
353 = $form->field('base')
354 ?
$form->field('base')
355 : (defined $config{newevent_base
} ?
$config{newevent_base
} : gettext
('Agenda'));
357 = $form->field('end_times') == 0
358 ?
undef : $form->field('end_times');
360 foreach ($form->field('dom')) {
361 $dom = {} if not defined $dom;
364 my $name = $form->field('name');
365 $name = IkiWiki
::possibly_foolish_untaint
(IkiWiki
::titlepage
($name));
366 # NOTE: these untaints are safe because of the checks
367 # performed in check_cannewevent later.
368 my $content = $form->field('content');
369 $content =~ s/\r\n/\n/gs;
370 $content =~ s/\n$//s;
372 # Queue of event creations to perfom.
376 = defined $config{newevent_max_per_commit
}
377 ?
$config{newevent_max_per_commit
} : (2 * 365) ;
378 my $pageext = $config{default_pageext
};
379 while (++$events_try <= $events_max
380 and (not defined $end_times or --$end_times >= 0)
381 and (not defined $end_date or DateTime
->compare($from_date, $end_date) <= 0)) {
382 my $dest = page_of_event
($form, $from_date, $to_date, $name, $newevent_base);
383 my $week = $from_date->weekday_of_month();
384 my $day = $now_date->{locale
}->day_format_wide->[$from_date->day_of_week()-1];
385 if (not defined $dom or exists $dom->{"$week° $day"}) {
388 , file
=> IkiWiki
::newpagefile
($dest, $pageext)
394 last unless defined $inc_dur and $inc_dur->is_positive();
395 $from_date = $from_date->clone->add_duration($inc_dur);
396 $to_date = $to_date->clone->add_duration($inc_dur);
398 error
("events try per commit overflow: $events_max")
399 unless $events_try <= $events_max;
400 my $tmpl_neweventpage = template
("neweventpage.tmpl");
403 $tmpl_neweventpage->clear_params();
404 $tmpl_neweventpage->param(content
=> $content);
405 $tmpl_neweventpage->param(page
=> $_->{page
});
406 $tmpl_neweventpage->param(event
=> $i);
407 $tmpl_neweventpage->param("event_first" => 1)
409 $tmpl_neweventpage->param("event_last" => 1)
410 if $i == @events - 1;
411 $tmpl_neweventpage->param(events
=> \
@events);
412 $tmpl_neweventpage->param(from_date
=> "$_->{from}");
413 $tmpl_neweventpage->param(name
=> $_->{name
});
414 $tmpl_neweventpage->param(to_date
=> "$_->{to}");
415 $_->{content
} = $tmpl_neweventpage->output();
418 if ($form->submitted eq 'Create') {
419 @events = newevent_hook
423 , session
=> $session
425 require IkiWiki
::Render
;
427 IkiWiki
::disable_commit_hook
()
429 foreach my $event (@events) {
430 create
($event, $cgi, $session, \
%months, $newevent_base);
433 IkiWiki
::rcs_commit_staged
434 ( message
=> sprintf(gettext
("new event"))
435 , session
=> $session );
436 IkiWiki
::enable_commit_hook
();
437 IkiWiki
::rcs_update
();
440 IkiWiki
::saveindex
();
442 post_newevent
($cgi, $session, (defined $events[0] ?
$events[0]->{page
} : ''));
444 elsif ($form->submitted eq 'Preview') {
445 preview
($cgi, $session, $form, \
@events, \
%months);
446 IkiWiki
::showform
($form, $buttons, $session, $cgi);
450 IkiWiki
::showform
($form, $buttons, $session, $cgi);
457 my ($cgi, $session, $form, $events, $months) = @_;
458 $form->tmpl_param(year
=> gettext
("year"));
459 $form->tmpl_param(month
=> gettext
("month"));
460 $form->tmpl_param(day
=> gettext
("day"));
461 $form->tmpl_param(hour
=> gettext
("hour"));
462 $form->tmpl_param(min
=> gettext
("min"));
463 $form->tmpl_param(dow
=> gettext
("day of week"));
464 $form->tmpl_param(page
=> gettext
("page"));
465 $form->tmpl_param(events
=> [
467 { from_year
=> $_->{from
}->year()
468 , from_month
=> sprintf('%02d', $_->{from
}->month())
469 , from_monthname
=> $months->{$_->{from
}->month()}
470 , from_day
=> sprintf('%02d', $_->{from
}->day())
471 , from_hour
=> sprintf('%02d', $_->{from
}->hour())
472 , from_minute
=> sprintf('%02d', $_->{from
}->minute())
473 , from_dow
=> $_->{from
}->dow()
474 , from_downame
=> $_->{from
}->day_name()
475 , to_year
=> $_->{to
}->year()
476 , to_month
=> sprintf('%02d', $_->{to
}->month())
477 , to_monthname
=> $months->{$_->{to
}->month()}
478 , to_day
=> sprintf('%02d', $_->{to
}->day())
479 , to_hour
=> sprintf('%02d', $_->{to
}->hour())
480 , to_minute
=> sprintf('%02d', $_->{to
}->minute())
481 , to_dow
=> $_->{to
}->dow()
483 htmllink
("", "", $_->{page
}
484 , linktext
=> $_->{page
}
485 , noimageinline
=> 1)
490 my $page = @
$events[0];
492 my $new = not exists $pagesources{$page};
493 # temporarily record its type
494 my $type = $config{default_pageext
};
495 $pagesources{$page} = $page.".".$type if $new;
496 my %wasrendered = map { $_ => 1 } @
{$renderedfiles{$page}};
497 my $content = @
$events[0]->{content
};
499 IkiWiki
::run_hooks
(editcontent
=> sub {
502 , content
=> $content
504 , session
=> $session
507 my $preview = IkiWiki
::htmlize
($page, $page, $type,
508 IkiWiki
::linkify
($page, $page,
509 IkiWiki
::preprocess
($page, $page,
510 IkiWiki
::filter
($page, $page, $content), 0, 1)));
511 IkiWiki
::run_hooks
(format
=> sub {
513 ( content
=> $preview
517 $form->tmpl_param("preview", $preview);
519 # Previewing may have created files on disk.
520 # Keep a list of these to be deleted later.
521 my %previews = map { $_ => 1 } @
{$wikistate{editpage
}{previews
}};
522 foreach my $f (@
{$renderedfiles{$page}}) {
523 $previews{$f} = 1 unless $wasrendered{$f};
526 # Throw out any other state changes made during previewing,
527 # and save the previews list.
528 IkiWiki
::loadindex
();
529 @
{$wikistate{editpage
}{previews
}} = keys %previews;
530 IkiWiki
::saveindex
();
533 $form->tmpl_param("preview", gettext
("No event"));
537 my ($event, $cgi, $session, $months, $newevent_base) = @_;
544 my $pageext = $config{default_pageext
};
546 $config{cgi
} = 0; # NOTE: avoid CGI error message
547 eval { writefile
($event->{file
}, $config{srcdir
}, $event->{content
}) };
549 IkiWiki
::rcs_add
($event->{file
});
554 . ($newevent_base?
'/':'').$event->{from
}->year()
555 . '/'.sprintf('%02d', $event->{from
}->month())
557 my $monthfile = IkiWiki
::newpagefile
($monthpage, $pageext);
558 if (not exists $pagesources{$monthpage}
559 and not -l
$config{srcdir
}.'/'.$monthfile
561 my $tmpl_neweventmonth = template
("neweventmonth.tmpl");
562 $tmpl_neweventmonth->param(base
=> $newevent_base);
563 $tmpl_neweventmonth->param(year
=> $event->{from
}->year());
564 $tmpl_neweventmonth->param(month
=> sprintf('%02d', $event->{from
}->month()));
565 $tmpl_neweventmonth->param(monthname
=> $months->{$event->{from
}->month()});
566 my $content = $tmpl_neweventmonth->output();
567 eval { writefile
($monthfile, $config{srcdir
}, $content) };
569 IkiWiki
::rcs_add
($monthfile);
575 . '/'.sprintf('%02d', $event->{from
}->day())
577 my $dayfile = IkiWiki
::newpagefile
($daypage, $pageext);
578 if (not exists $pagesources{$daypage}
579 and not -l
$config{srcdir
}.'/'.$dayfile
581 my $tmpl_neweventday = template
("neweventday.tmpl");
582 $tmpl_neweventday->param(base
=> $newevent_base);
583 $tmpl_neweventday->param(year
=> $event->{from
}->year());
584 $tmpl_neweventday->param(month
=> sprintf('%02d', $event->{from
}->month()));
585 $tmpl_neweventday->param(monthname
=> $months->{$event->{from
}->month()});
586 $tmpl_neweventday->param(day
=> sprintf('%02d', $event->{from
}->day()));
587 $tmpl_neweventday->param(dayname
=> $event->{from
}->day_name());
588 my $content = $tmpl_neweventday->output();
589 eval { writefile
($dayfile, $config{srcdir
}, $content) };
591 IkiWiki
::rcs_add
($dayfile);
598 my @events = @
{$params{events
}};
599 my %done = %{$params{done
}};
600 my $cgi = $params{cgi
};
601 my $session = $params{session
};
605 foreach my $event (@events) {
606 unless (exists $done{$event->{page
}} && $done{$event->{file
}}) {
607 IkiWiki
::run_hooks
(newevent
=> sub {
611 , session
=> $session
614 $done{$event->{page
}} = 1;
617 push @events, newevent_hook
621 , session
=> $session
623 my %seen; # NOTE: insure unicity
624 return grep { ! $seen{$_->{page
}}++ } @events;