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, $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 . ($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{base} ? $config{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);
170 my $base = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('base'))));
171 &IkiWiki
::check_canedit
($base, $cgi, $session);
172 my $page = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('page'))));
174 my $now_date = DateTime
->now
175 ( time_zone
=> 'local'
176 , locale
=> $config{locale
}
177 )->set_time_zone('floating');
178 my %dows = map { ($_ => $now_date->{locale
}->day_format_wide->[ $_ ]) } (0..6);
179 my %months = map { ($_ => $now_date->{locale
}->month_format_wide->[ $_ - 1 ]) } (1..12);
181 eval { $cgi_date = DateTime
->new
182 ( year
=> defined $cgi->param("year") ?
$cgi->param("year") : $now_date->year()
183 , month
=> defined $cgi->param("month") ?
$cgi->param("month") : $now_date->month()
184 , day
=> defined $cgi->param("day") ?
$cgi->param("day") : $now_date->day()
185 , hour
=> defined $cgi->param("hour") ?
$cgi->param("hour") : $now_date->hour()
186 , minute
=> defined $cgi->param("minute") ?
$cgi->param("minute") : $now_date->minute()
189 , time_zone
=> 'local'
190 , locale
=> $config{locale
}
191 )->set_time_zone('floating') };
192 error
(sprintf(gettext
("illegal date")))
195 my @years = ($cgi_date->year() .. $cgi_date->year()+5);
197 = (defined $config{week_start_day
} and $config{week_start_day
} >= 0 and $config{week_start_day
} <= 6)
198 ?
$config{week_start_day
}
200 my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
202 my $tags = $typedlinks{$page}{tag
};
203 my $buttons = [qw{Preview Create
}];
204 my ($from_date, $to_date, $end_date, $inc_dur);
205 my $form = CGI
::FormBuilder
->new
206 ( action
=> IkiWiki
::cgiurl
()
210 from_date from_year from_month from_day from_hour from_minute
211 to_date to_year to_month to_day to_hour to_minute
212 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
213 end_times end_date end_year end_month end_day end_hour end_minute
220 # form_required_text => 'form_required_text'
221 # , form_invalid_text => 'form_invalid_text'
222 # , form_invalid_file => 'form_invalid_file'
223 # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
224 form_invalid_select
=> gettext
('invalid selection')
230 , required
=> [qw{do base year month day name from_date to_date end_date inc_dur
}]
231 , submit
=> [qw{Preview Create
}]
232 , title
=> gettext
("newevent")
233 , template
=> { template
("newevent.tmpl") }
235 { from_date
=> { perl
=> sub {
236 my (undef, $form) = @_;
237 $from_date = date_of_form
($form, 'from')
238 unless defined $from_date;
241 , to_date
=> { perl
=> sub {
242 my (undef, $form) = @_;
243 $from_date = date_of_form
($form, 'from')
244 unless defined $from_date;
245 if (defined $from_date) {
246 $to_date = date_of_form
($form, 'to'
247 , year
=> $from_date->year()
248 , month
=> $from_date->month()
249 , day
=> $from_date->day()
250 , hour
=> $from_date->hour()
251 , minute
=> $from_date->minute());
253 and (DateTime
->compare($from_date, $to_date) <= 0)
257 , end_date
=> { perl
=> sub {
258 my (undef, $form) = @_;
259 if ( $form->field('end_year') ne ''
260 or $form->field('end_month') ne ''
261 or $form->field('end_day') ne '' ) {
262 $from_date = date_of_form
($form, 'from')
263 unless defined $from_date;
264 if (defined $from_date) {
265 $end_date = date_of_form
($form, 'end'
266 , year
=> $from_date->year()
267 , month
=> $from_date->month()
268 , day
=> $from_date->day()
269 , hour
=> $from_date->hour()
270 , minute
=> $from_date->minute());
271 (defined $from_date and defined $end_date
272 and DateTime
->compare($from_date, $end_date) <= 0)
282 , end_times
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
283 , inc_year
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
284 , inc_month
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
285 , inc_week
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
286 , inc_day
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
287 , inc_hour
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
288 , inc_minute
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
290 my (undef, $form) = @_;
291 $inc_dur = duration_of_form
($form, 'inc');
293 and ($inc_dur->is_positive() or $inc_dur->is_zero());
297 $form->title(sprintf(gettext
("creating new events"), pagetitle
(IkiWiki
::basename
($page))));
298 $form->field(name
=> "do", type
=> "hidden", value
=> 'newevent', force
=> 1);
299 $form->field(name
=> "base", type
=> "hidden", force
=> 1
300 , value
=> ($form->field('base') ?
$form->field('base') : $base));
301 $form->field(name
=> "from_date", type
=> "hidden", value
=> '1', force
=> 1);
302 $form->field(name
=> "to_date", type
=> "hidden", value
=> '1', force
=> 1);
303 $form->field(name
=> "end_date", type
=> "hidden", value
=> '1', force
=> 1);
304 $form->field(name
=> "inc_dur", type
=> "hidden", value
=> '1', force
=> 1);
305 $form->field(name
=> "from_year", type
=> 'select', value
=> $cgi_date->year(), options
=> \
@years);
306 $form->field(name
=> "from_month", type
=> 'select'
307 , value
=> sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
308 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
309 $form->field(name
=> "from_day", type
=> 'select'
310 , value
=> sprintf("%02d", $cgi_date->day())
311 , options
=> \
@days);
312 $form->field(name
=> "from_hour", type
=> 'select', value
=> '', options
=> \
@hours);
313 $form->field(name
=> "from_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
314 $form->field(name
=> "name", type
=> 'text', size
=> 60, value
=> gettext
('New event'));
315 $form->field(name
=> "to_year", type
=> 'select', value
=> '', options
=> \
@years);
316 $form->field(name
=> "to_month", type
=> 'select'
318 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
319 $form->field(name
=> "to_day", type
=> 'select'
321 , options
=> \
@days);
322 $form->field(name
=> "to_hour", type
=> 'select', value
=> '', options
=> \
@hours);
323 $form->field(name
=> "to_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
324 $form->field(name
=> "end_year", type
=> 'select', value
=> '', options
=> \
@years);
325 $form->field(name
=> "end_month", type
=> 'select', value
=> ''
326 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
327 $form->field(name
=> "end_day", type
=> 'select', value
=> '', options
=> \
@days);
328 $form->field(name
=> "end_hour", type
=> 'select', value
=> '', options
=> \
@hours);
329 $form->field(name
=> "end_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
330 $form->field(name
=> "end_times", type
=> 'text', value
=> '0', size
=> 2);
331 $form->field(name
=> "inc_year", type
=> 'text', value
=> '0', size
=> 2);
332 $form->field(name
=> "inc_month", type
=> 'text', value
=> '0', size
=> 2);
333 $form->field(name
=> "inc_week", type
=> 'text', value
=> '0', size
=> 2);
334 $form->field(name
=> "inc_day", type
=> 'text', value
=> '0', size
=> 2);
335 $form->field(name
=> "inc_hour", type
=> 'text', value
=> '0', size
=> 2);
336 $form->field(name
=> "inc_minute", type
=> 'text', value
=> '0', size
=> 2);
337 my $tmpl_neweventcontent = template
("neweventcontent.tmpl");
338 $tmpl_neweventcontent->param(title
=> gettext
('Title of the event'));
339 $tmpl_neweventcontent->param(tags
=> [map {{name
=> $_}} (sort keys %$tags)]);
340 $form->field(name
=> "content", type
=> "textarea", size
=> 30, rows
=> 20, cols
=> 80
341 , value
=> $tmpl_neweventcontent->output());
342 $form->field(name
=> "dom", type
=> 'select', multiple
=> 1, size
=> 35
343 , options
=> [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
345 IkiWiki
::decode_form_utf8
($form);
346 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
347 shift->(form
=> $form, cgi
=> $cgi, session
=> $session, buttons
=> $buttons);
349 IkiWiki
::decode_form_utf8
($form);
351 if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
352 #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
354 = $form->field('base')
355 ?
$form->field('base')
356 : (defined $config{base
} ?
$config{base
} : gettext
('Agenda'));
358 = $form->field('end_times') == 0
359 ?
undef : $form->field('end_times');
361 foreach ($form->field('dom')) {
362 $dom = {} if not defined $dom;
365 my $name = $form->field('name');
366 $name = IkiWiki
::possibly_foolish_untaint
(IkiWiki
::titlepage
($name));
367 # NOTE: these untaints are safe because of the checks
368 # performed in check_cannewevent later.
369 my $content = $form->field('content');
370 $content =~ s/\r\n/\n/gs;
371 $content =~ s/\n$//s;
373 # Queue of event creations to perfom.
377 = defined $config{newevent_max_per_commit
}
378 ?
$config{newevent_max_per_commit
} : (2 * 365) ;
379 my $pageext = $config{default_pageext
};
380 while (++$events_try <= $events_max
381 and (not defined $end_times or --$end_times >= 0)
382 and (not defined $end_date or DateTime
->compare($from_date, $end_date) <= 0)) {
383 my $dest = page_of_event
($form, $from_date, $to_date, $name, $base);
384 my $week = $from_date->weekday_of_month();
385 my $day = $now_date->{locale
}->day_format_wide->[$from_date->day_of_week()-1];
386 if (not defined $dom or exists $dom->{"$week° $day"}) {
389 , file
=> IkiWiki
::newpagefile
($dest, $pageext)
395 last unless defined $inc_dur and $inc_dur->is_positive();
396 $from_date = $from_date->clone->add_duration($inc_dur);
397 $to_date = $to_date->clone->add_duration($inc_dur);
399 error
("events try per commit overflow: $events_max")
400 unless $events_try <= $events_max;
401 my $tmpl_neweventpage = template
("neweventpage.tmpl");
404 $tmpl_neweventpage->clear_params();
405 $tmpl_neweventpage->param(content
=> $content);
406 $tmpl_neweventpage->param(page
=> $_->{page
});
407 $tmpl_neweventpage->param(event
=> $i);
408 $tmpl_neweventpage->param("event_first" => 1)
410 $tmpl_neweventpage->param("event_last" => 1)
411 if $i == @events - 1;
412 $tmpl_neweventpage->param(events
=> \
@events);
413 $tmpl_neweventpage->param(from_date
=> "$_->{from}");
414 $tmpl_neweventpage->param(name
=> $_->{name
});
415 $tmpl_neweventpage->param(to_date
=> "$_->{to}");
416 $_->{content
} = $tmpl_neweventpage->output();
419 if ($form->submitted eq 'Create') {
420 @events = newevent_hook
424 , session
=> $session
426 require IkiWiki
::Render
;
428 IkiWiki
::disable_commit_hook
()
430 foreach my $event (@events) {
431 create
($event, $cgi, $session, \
%months, $base);
434 IkiWiki
::rcs_commit_staged
435 ( message
=> sprintf(gettext
("new event"))
436 , session
=> $session );
437 IkiWiki
::enable_commit_hook
();
438 IkiWiki
::rcs_update
();
441 IkiWiki
::saveindex
();
443 post_newevent
($cgi, $session, (defined $events[0] ?
$events[0]->{page
} : ''));
445 elsif ($form->submitted eq 'Preview') {
446 preview
($cgi, $session, $form, \
@events, \
%months);
447 IkiWiki
::showform
($form, $buttons, $session, $cgi);
451 IkiWiki
::showform
($form, $buttons, $session, $cgi);
458 my ($cgi, $session, $form, $events, $months) = @_;
459 $form->tmpl_param(year
=> gettext
("year"));
460 $form->tmpl_param(month
=> gettext
("month"));
461 $form->tmpl_param(day
=> gettext
("day"));
462 $form->tmpl_param(hour
=> gettext
("hour"));
463 $form->tmpl_param(min
=> gettext
("min"));
464 $form->tmpl_param(dow
=> gettext
("day of week"));
465 $form->tmpl_param(page
=> gettext
("page"));
466 $form->tmpl_param(events
=> [
468 { from_year
=> $_->{from
}->year()
469 , from_month
=> sprintf('%02d', $_->{from
}->month())
470 , from_monthname
=> $months->{$_->{from
}->month()}
471 , from_day
=> sprintf('%02d', $_->{from
}->day())
472 , from_hour
=> sprintf('%02d', $_->{from
}->hour())
473 , from_minute
=> sprintf('%02d', $_->{from
}->minute())
474 , from_dow
=> $_->{from
}->dow()
475 , from_downame
=> $_->{from
}->day_name()
476 , to_year
=> $_->{to
}->year()
477 , to_month
=> sprintf('%02d', $_->{to
}->month())
478 , to_monthname
=> $months->{$_->{to
}->month()}
479 , to_day
=> sprintf('%02d', $_->{to
}->day())
480 , to_hour
=> sprintf('%02d', $_->{to
}->hour())
481 , to_minute
=> sprintf('%02d', $_->{to
}->minute())
482 , to_dow
=> $_->{to
}->dow()
484 htmllink
("", "", $_->{page
}
485 , linktext
=> $_->{page
}
486 , noimageinline
=> 1)
491 my $page = @
$events[0];
493 my $new = not exists $pagesources{$page};
494 # temporarily record its type
495 my $type = $config{default_pageext
};
496 $pagesources{$page} = $page.".".$type if $new;
497 my %wasrendered = map { $_ => 1 } @
{$renderedfiles{$page}};
498 my $content = @
$events[0]->{content
};
500 IkiWiki
::run_hooks
(editcontent
=> sub {
503 , content
=> $content
505 , session
=> $session
508 my $preview = IkiWiki
::htmlize
($page, $page, $type,
509 IkiWiki
::linkify
($page, $page,
510 IkiWiki
::preprocess
($page, $page,
511 IkiWiki
::filter
($page, $page, $content), 0, 1)));
512 IkiWiki
::run_hooks
(format
=> sub {
514 ( content
=> $preview
518 $form->tmpl_param("preview", $preview);
520 # Previewing may have created files on disk.
521 # Keep a list of these to be deleted later.
522 my %previews = map { $_ => 1 } @
{$wikistate{editpage
}{previews
}};
523 foreach my $f (@
{$renderedfiles{$page}}) {
524 $previews{$f} = 1 unless $wasrendered{$f};
527 # Throw out any other state changes made during previewing,
528 # and save the previews list.
529 IkiWiki
::loadindex
();
530 @
{$wikistate{editpage
}{previews
}} = keys %previews;
531 IkiWiki
::saveindex
();
534 $form->tmpl_param("preview", gettext
("No event"));
538 my ($event, $cgi, $session, $months, $base) = @_;
545 my $pageext = $config{default_pageext
};
547 $config{cgi
} = 0; # NOTE: avoid CGI error message
548 eval { writefile
($event->{file
}, $config{srcdir
}, $event->{content
}) };
550 IkiWiki
::rcs_add
($event->{file
});
555 . ($base?
'/':'').$event->{from
}->year()
556 . '/'.sprintf('%02d', $event->{from
}->month())
558 my $monthfile = IkiWiki
::newpagefile
($monthpage, $pageext);
559 if (not exists $pagesources{$monthpage}
560 and not -l
$config{srcdir
}.'/'.$monthfile
562 my $tmpl_neweventmonth = template
("neweventmonth.tmpl");
563 $tmpl_neweventmonth->param(base
=> $base);
564 $tmpl_neweventmonth->param(year
=> $event->{from
}->year());
565 $tmpl_neweventmonth->param(month
=> sprintf('%02d', $event->{from
}->month()));
566 $tmpl_neweventmonth->param(monthname
=> $months->{$event->{from
}->month()});
567 my $content = $tmpl_neweventmonth->output();
568 eval { writefile
($monthfile, $config{srcdir
}, $content) };
570 IkiWiki
::rcs_add
($monthfile);
576 . '/'.sprintf('%02d', $event->{from
}->day())
578 my $dayfile = IkiWiki
::newpagefile
($daypage, $pageext);
579 if (not exists $pagesources{$daypage}
580 and not -l
$config{srcdir
}.'/'.$dayfile
582 my $tmpl_neweventday = template
("neweventday.tmpl");
583 $tmpl_neweventday->param(base
=> $base);
584 $tmpl_neweventday->param(year
=> $event->{from
}->year());
585 $tmpl_neweventday->param(month
=> sprintf('%02d', $event->{from
}->month()));
586 $tmpl_neweventday->param(monthname
=> $months->{$event->{from
}->month()});
587 $tmpl_neweventday->param(day
=> sprintf('%02d', $event->{from
}->day()));
588 $tmpl_neweventday->param(dayname
=> $event->{from
}->day_name());
589 my $content = $tmpl_neweventday->output();
590 eval { writefile
($dayfile, $config{srcdir
}, $content) };
592 IkiWiki
::rcs_add
($dayfile);
599 my @events = @
{$params{events
}};
600 my %done = %{$params{done
}};
601 my $cgi = $params{cgi
};
602 my $session = $params{session
};
606 foreach my $event (@events) {
607 unless (exists $done{$event->{page
}} && $done{$event->{file
}}) {
608 IkiWiki
::run_hooks
(newevent
=> sub {
612 , session
=> $session
615 $done{$event->{page
}} = 1;
618 push @events, newevent_hook
622 , session
=> $session
624 my %seen; # NOTE: insure unicity
625 return grep { ! $seen{$_->{page
}}++ } @events;