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
($cgi->param("page"));
200 my $tags = $typedlinks{$page}{tag
};
201 my $buttons = [qw{Preview Create
}];
202 my ($from_date, $to_date, $end_date, $inc_dur);
203 my $form = CGI
::FormBuilder
->new
204 ( action
=> IkiWiki
::cgiurl
()
208 from_date from_year from_month from_day from_hour from_minute
209 to_date to_year to_month to_day to_hour to_minute
210 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
211 end_times end_date end_year end_month end_day end_hour end_minute
218 # form_required_text => 'form_required_text'
219 # , form_invalid_text => 'form_invalid_text'
220 # , form_invalid_file => 'form_invalid_file'
221 # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
222 form_invalid_select
=> gettext
('invalid selection')
228 , required
=> [qw{do year month day name from_date to_date end_date inc_dur
}]
229 , submit
=> [qw{Preview Create
}]
230 , title
=> gettext
("newevent")
231 , template
=> { template
("newevent.tmpl") }
233 { from_date
=> { perl
=> sub {
234 my (undef, $form) = @_;
235 $from_date = date_of_form
($form, 'from')
236 unless defined $from_date;
239 , to_date
=> { perl
=> sub {
240 my (undef, $form) = @_;
241 $from_date = date_of_form
($form, 'from')
242 unless defined $from_date;
243 if (defined $from_date) {
244 $to_date = date_of_form
($form, 'to'
245 , year
=> $from_date->year()
246 , month
=> $from_date->month()
247 , day
=> $from_date->day()
248 , hour
=> $from_date->hour()
249 , minute
=> $from_date->minute());
251 and (DateTime
->compare($from_date, $to_date) <= 0)
255 , end_date
=> { perl
=> sub {
256 my (undef, $form) = @_;
257 if ( $form->field('end_year') ne ''
258 or $form->field('end_month') ne ''
259 or $form->field('end_day') ne '' ) {
260 $from_date = date_of_form
($form, 'from')
261 unless defined $from_date;
262 if (defined $from_date) {
263 $end_date = date_of_form
($form, 'end'
264 , year
=> $from_date->year()
265 , month
=> $from_date->month()
266 , day
=> $from_date->day()
267 , hour
=> $from_date->hour()
268 , minute
=> $from_date->minute());
269 (defined $from_date and defined $end_date
270 and DateTime
->compare($from_date, $end_date) <= 0)
279 , end_times
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
280 , inc_year
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
281 , inc_month
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
282 , inc_week
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
283 , inc_day
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
284 , inc_hour
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
285 , inc_minute
=> sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
287 my (undef, $form) = @_;
288 $inc_dur = duration_of_form
($form, 'inc');
290 and ($inc_dur->is_positive() or $inc_dur->is_zero());
294 $form->title(sprintf(gettext
("creating new events"), pagetitle
(IkiWiki
::basename
($page))));
295 $form->field(name
=> "do", type
=> "hidden", value
=> 'newevent', force
=> 1);
296 $form->field(name
=> "from_date", type
=> "hidden", value
=> '1', force
=> 1);
297 $form->field(name
=> "to_date", type
=> "hidden", value
=> '1', force
=> 1);
298 $form->field(name
=> "end_date", type
=> "hidden", value
=> '1', force
=> 1);
299 $form->field(name
=> "inc_dur", type
=> "hidden", value
=> '1', force
=> 1);
300 $form->field(name
=> "from_year", type
=> 'select', value
=> $cgi_date->year(), options
=> \
@years);
301 $form->field(name
=> "from_month", type
=> 'select'
302 , value
=> sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
303 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
304 $form->field(name
=> "from_day", type
=> 'select'
305 , value
=> sprintf("%02d", $cgi_date->day())
306 , options
=> \
@days);
307 $form->field(name
=> "from_hour", type
=> 'select', value
=> '', options
=> \
@hours);
308 $form->field(name
=> "from_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
309 $form->field(name
=> "name", type
=> 'text', size
=> 60, value
=> gettext
('New event'));
310 $form->field(name
=> "to_year", type
=> 'select', value
=> '', options
=> \
@years);
311 $form->field(name
=> "to_month", type
=> 'select'
313 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
314 $form->field(name
=> "to_day", type
=> 'select'
316 , options
=> \
@days);
317 $form->field(name
=> "to_hour", type
=> 'select', value
=> '', options
=> \
@hours);
318 $form->field(name
=> "to_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
319 $form->field(name
=> "end_year", type
=> 'select', value
=> '', options
=> \
@years);
320 $form->field(name
=> "end_month", type
=> 'select', value
=> ''
321 , options
=> [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
322 $form->field(name
=> "end_day", type
=> 'select', value
=> '', options
=> \
@days);
323 $form->field(name
=> "end_hour", type
=> 'select', value
=> '', options
=> \
@hours);
324 $form->field(name
=> "end_minute", type
=> 'select', value
=> '', options
=> \
@minutes);
325 $form->field(name
=> "end_times", type
=> 'text', value
=> '0', size
=> 2);
326 $form->field(name
=> "inc_year", type
=> 'text', value
=> '0', size
=> 2);
327 $form->field(name
=> "inc_month", type
=> 'text', value
=> '0', size
=> 2);
328 $form->field(name
=> "inc_week", type
=> 'text', value
=> '0', size
=> 2);
329 $form->field(name
=> "inc_day", type
=> 'text', value
=> '0', size
=> 2);
330 $form->field(name
=> "inc_hour", type
=> 'text', value
=> '0', size
=> 2);
331 $form->field(name
=> "inc_minute", type
=> 'text', value
=> '0', size
=> 2);
332 my $tmpl_neweventcontent = template
("neweventcontent.tmpl");
333 $tmpl_neweventcontent->param(title
=> gettext
('Title of the event'));
334 $tmpl_neweventcontent->param(tags
=> [map {{name
=> $_}} (sort keys %$tags)]);
335 $form->field(name
=> "content", type
=> "textarea", size
=> 30, rows
=> 20, cols
=> 80
336 , value
=> $tmpl_neweventcontent->output());
337 $form->field(name
=> "dom", type
=> 'select', multiple
=> 1, size
=> 35
338 , options
=> [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
340 IkiWiki
::decode_form_utf8
($form);
341 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
342 shift->(form
=> $form, cgi
=> $cgi, session
=> $session, buttons
=> $buttons);
344 IkiWiki
::decode_form_utf8
($form);
346 if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
347 #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
348 my $newevent_base = $cgi->param('base');
349 $newevent_base = $config{newevent_base
}
350 unless defined $newevent_base;
351 $newevent_base = gettext
('Agenda')
352 unless defined $newevent_base;
354 = $form->field('end_times') == 0
355 ?
undef : $form->field('end_times');
357 foreach ($form->field('dom')) {
358 $dom = {} if not defined $dom;
361 my $name = $form->field('name');
362 $name = IkiWiki
::possibly_foolish_untaint
(IkiWiki
::titlepage
($name));
363 # NOTE: these untaints are safe because of the checks
364 # performed in check_cannewevent later.
365 my $content = $form->field('content');
366 $content =~ s/\r\n/\n/gs;
367 $content =~ s/\n$//s;
369 # Queue of event creations to perfom.
373 = defined $config{newevent_max_per_commit
}
374 ?
$config{newevent_max_per_commit
} : (2 * 365) ;
375 my $pageext = $config{default_pageext
};
376 while (++$events_try <= $events_max
377 and (not defined $end_times or --$end_times >= 0)
378 and (not defined $end_date or DateTime
->compare($from_date, $end_date) <= 0)) {
379 my $dest = page_of_event
($form, $from_date, $to_date, $name, $newevent_base);
380 my $week = $from_date->weekday_of_month();
381 my $day = $now_date->{locale
}->day_format_wide->[$from_date->day_of_week()-1];
382 if (not defined $dom or exists $dom->{"$week° $day"}) {
385 , file
=> IkiWiki
::newpagefile
($dest, $pageext)
391 last unless defined $inc_dur and $inc_dur->is_positive();
392 $from_date = $from_date->clone->add_duration($inc_dur);
393 $to_date = $to_date->clone->add_duration($inc_dur);
395 error
("events try per commit overflow: $events_max")
396 unless $events_try <= $events_max;
397 my $tmpl_neweventpage = template
("neweventpage.tmpl");
400 $tmpl_neweventpage->clear_params();
401 $tmpl_neweventpage->param(content
=> $content);
402 $tmpl_neweventpage->param(page
=> $_->{page
});
403 $tmpl_neweventpage->param(event
=> $i);
404 $tmpl_neweventpage->param("event_first" => 1)
406 $tmpl_neweventpage->param("event_last" => 1)
407 if $i == @events - 1;
408 $tmpl_neweventpage->param(events
=> \
@events);
409 $tmpl_neweventpage->param(from_date
=> "$_->{from}");
410 $tmpl_neweventpage->param(name
=> $_->{name
});
411 $tmpl_neweventpage->param(to_date
=> "$_->{to}");
412 $_->{content
} = $tmpl_neweventpage->output();
415 if ($form->submitted eq 'Create') {
416 @events = newevent_hook
420 , session
=> $session
422 require IkiWiki
::Render
;
424 IkiWiki
::disable_commit_hook
()
426 foreach my $event (@events) {
427 create
($event, $cgi, $session, \
%months, $newevent_base);
430 IkiWiki
::rcs_commit_staged
431 ( message
=> sprintf(gettext
("new event"))
432 , session
=> $session );
433 IkiWiki
::enable_commit_hook
();
434 IkiWiki
::rcs_update
();
437 IkiWiki
::saveindex
();
439 post_newevent
($cgi, $session, (defined $events[0] ?
$events[0]->{page
} : ''));
441 elsif ($form->submitted eq 'Preview') {
442 preview
($cgi, $session, $form, \
@events, \
%months);
443 IkiWiki
::showform
($form, $buttons, $session, $cgi);
447 IkiWiki
::showform
($form, $buttons, $session, $cgi);
454 my ($cgi, $session, $form, $events, $months) = @_;
455 $form->tmpl_param(year
=> gettext
("year"));
456 $form->tmpl_param(month
=> gettext
("month"));
457 $form->tmpl_param(day
=> gettext
("day"));
458 $form->tmpl_param(hour
=> gettext
("hour"));
459 $form->tmpl_param(min
=> gettext
("min"));
460 $form->tmpl_param(dow
=> gettext
("day of week"));
461 $form->tmpl_param(page
=> gettext
("page"));
462 $form->tmpl_param(events
=> [
464 { from_year
=> $_->{from
}->year()
465 , from_month
=> sprintf('%02d', $_->{from
}->month())
466 , from_monthname
=> $months->{$_->{from
}->month()}
467 , from_day
=> sprintf('%02d', $_->{from
}->day())
468 , from_hour
=> sprintf('%02d', $_->{from
}->hour())
469 , from_minute
=> sprintf('%02d', $_->{from
}->minute())
470 , from_dow
=> $_->{from
}->dow()
471 , from_downame
=> $_->{from
}->day_name()
472 , to_year
=> $_->{to
}->year()
473 , to_month
=> sprintf('%02d', $_->{to
}->month())
474 , to_monthname
=> $months->{$_->{to
}->month()}
475 , to_day
=> sprintf('%02d', $_->{to
}->day())
476 , to_hour
=> sprintf('%02d', $_->{to
}->hour())
477 , to_minute
=> sprintf('%02d', $_->{to
}->minute())
478 , to_dow
=> $_->{to
}->dow()
480 htmllink
("", "", $_->{page
}
481 , linktext
=> $_->{page
}
482 , noimageinline
=> 1)
487 my $page = @
$events[0];
489 my $new = not exists $pagesources{$page};
490 # temporarily record its type
491 my $type = $config{default_pageext
};
492 $pagesources{$page} = $page.".".$type if $new;
493 my %wasrendered = map { $_ => 1 } @
{$renderedfiles{$page}};
494 my $content = @
$events[0]->{content
};
496 IkiWiki
::run_hooks
(editcontent
=> sub {
499 , content
=> $content
501 , session
=> $session
504 my $preview = IkiWiki
::htmlize
($page, $page, $type,
505 IkiWiki
::linkify
($page, $page,
506 IkiWiki
::preprocess
($page, $page,
507 IkiWiki
::filter
($page, $page, $content), 0, 1)));
508 IkiWiki
::run_hooks
(format
=> sub {
510 ( content
=> $preview
514 $form->tmpl_param("preview", $preview);
516 # Previewing may have created files on disk.
517 # Keep a list of these to be deleted later.
518 my %previews = map { $_ => 1 } @
{$wikistate{editpage
}{previews
}};
519 foreach my $f (@
{$renderedfiles{$page}}) {
520 $previews{$f} = 1 unless $wasrendered{$f};
523 # Throw out any other state changes made during previewing,
524 # and save the previews list.
525 IkiWiki
::loadindex
();
526 @
{$wikistate{editpage
}{previews
}} = keys %previews;
527 IkiWiki
::saveindex
();
530 $form->tmpl_param("preview", gettext
("No event"));
534 my ($event, $cgi, $session, $months, $newevent_base) = @_;
541 my $pageext = $config{default_pageext
};
543 $config{cgi
} = 0; # NOTE: avoid CGI error message
544 eval { writefile
($event->{file
}, $config{srcdir
}, $event->{content
}) };
546 IkiWiki
::rcs_add
($event->{file
});
551 . ($newevent_base?
'/':'').$event->{from
}->year()
552 . '/'.sprintf('%02d', $event->{from
}->month())
554 my $monthfile = IkiWiki
::newpagefile
($monthpage, $pageext);
555 if (not exists $pagesources{$monthpage}
556 and not -l
$config{srcdir
}.'/'.$monthfile
558 my $tmpl_neweventmonth = template
("neweventmonth.tmpl");
559 $tmpl_neweventmonth->param(year
=> $event->{from
}->year());
560 $tmpl_neweventmonth->param(month
=> sprintf('%02d', $event->{from
}->month()));
561 $tmpl_neweventmonth->param(monthname
=> $months->{$event->{from
}->month()});
562 my $content = $tmpl_neweventmonth->output();
563 eval { writefile
($monthfile, $config{srcdir
}, $content) };
565 IkiWiki
::rcs_add
($monthfile);
571 . '/'.sprintf('%02d', $event->{from
}->day())
573 my $dayfile = IkiWiki
::newpagefile
($daypage, $pageext);
574 if (not exists $pagesources{$daypage}
575 and not -l
$config{srcdir
}.'/'.$dayfile
577 my $tmpl_neweventday = template
("neweventday.tmpl");
578 $tmpl_neweventday->param(year
=> $event->{from
}->year());
579 $tmpl_neweventday->param(month
=> sprintf('%02d', $event->{from
}->month()));
580 $tmpl_neweventday->param(monthname
=> $months->{$event->{from
}->month()});
581 $tmpl_neweventday->param(day
=> sprintf('%02d', $event->{from
}->day()));
582 $tmpl_neweventday->param(dayname
=> $event->{from
}->day_name());
583 my $content = $tmpl_neweventday->output();
584 eval { writefile
($dayfile, $config{srcdir
}, $content) };
586 IkiWiki
::rcs_add
($dayfile);
593 my @events = @
{$params{events
}};
594 my %done = %{$params{done
}};
595 my $cgi = $params{cgi
};
596 my $session = $params{session
};
600 foreach my $event (@events) {
601 unless (exists $done{$event->{page
}} && $done{$event->{file
}}) {
602 IkiWiki
::run_hooks
(newevent
=> sub {
606 , session
=> $session
609 $done{$event->{page
}} = 1;
612 push @events, newevent_hook
616 , session
=> $session
618 my %seen; # NOTE: insure unicity
619 return grep { ! $seen{$_->{page
}}++ } @events;