1 package BSE::Edit::Article;
3 use base qw(BSE::Edit::Base);
4 use BSE::Util::Tags qw(tag_error_img);
5 use BSE::Util::SQL qw(now_sqldate now_sqldatetime);
6 use BSE::Util::Valid qw/valid_date/;
8 use DevHelp::HTML qw(:default popup_menu);
10 use BSE::CfgInfo qw(custom_class admin_base_url);
12 sub article_dispatch {
13 my ($self, $req, $article, $articles) = @_;
15 BSE::Permissions->check_logon($req)
16 or return BSE::Template->get_refresh($req->url('logon'), $req->cfg);
20 my %actions = $self->article_actions;
21 for my $check (keys %actions) {
22 if ($cgi->param($check) || $cgi->param("$check.x")) {
29 ($action, @extraargs) = $self->other_article_actions($cgi);
32 my $method = $actions{$action};
33 return $self->$method($req, $article, $articles, @extraargs);
36 sub noarticle_dispatch {
37 my ($self, $req, $articles) = @_;
39 BSE::Permissions->check_logon($req)
40 or return BSE::Template->get_refresh($req->url('logon'), $req->cfg);
44 my %actions = $self->noarticle_actions;
45 for my $check (keys %actions) {
46 if ($cgi->param($check) || $cgi->param("$check.x")) {
51 my $method = $actions{$action};
52 return $self->$method($req, $articles);
62 add_stepkid => 'add_stepkid',
63 del_stepkid => 'del_stepkid',
64 save_stepkids => 'save_stepkids',
65 add_stepparent => 'add_stepparent',
66 del_stepparent => 'del_stepparent',
67 save_stepparents => 'save_stepparents',
68 artimg => 'save_image_changes',
69 addimg => 'add_image',
71 showimages => 'show_images',
72 process => 'save_image_changes',
73 removeimg => 'remove_img',
74 moveimgup => 'move_img_up',
75 moveimgdown => 'move_img_down',
76 filelist => 'filelist',
78 fileswap => 'fileswap',
80 filesave => 'filesave',
86 sub other_article_actions {
87 my ($self, $cgi) = @_;
89 for my $param ($cgi->param) {
90 if ($param =~ /^removeimg_(\d+)(\.x)?$/) {
91 return ('removeimg', $1 );
98 sub noarticle_actions {
107 my ($self, $parentid, $articles) = @_;
109 if ($parentid == -1) {
113 title=>'All Sections',
120 return $articles->getByPkey($parentid);
125 my ($object, $args) = @_;
127 my $value = $object->{$args};
128 defined $value or $value = '';
129 if ($value =~ /\cJ/ && $value =~ /\cM/) {
136 my ($object, $args) = @_;
138 my $value = $object->{$args};
139 defined $value or $value = '';
140 if ($value =~ /\cJ/ && $value =~ /\cM/) {
143 escape_html($value, '<>&"');
147 my ($level, $cfg) = @_;
149 escape_html($cfg->entry('level names', $level, 'Article'));
158 sub reparent_updown {
162 sub should_be_catalog {
163 my ($self, $article, $parent, $articles) = @_;
165 if ($article->{parentid} && (!$parent || $parent->{id} != $article->{parentid})) {
166 $parent = $articles->getByPkey($article->{id});
169 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
171 return $article->{parentid} && $parent &&
172 ($article->{parentid} == $shopid ||
173 $parent->{generator} eq 'Generate::Catalog');
176 sub possible_parents {
177 my ($self, $article, $articles, $req) = @_;
182 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
183 my @parents = $articles->getBy('level', $article->{level}-1);
184 @parents = grep { $_->{generator} eq 'Generate::Article'
185 && $_->{id} != $shopid } @parents;
187 # user can only select parent they can add to
188 @parents = grep $req->user_can('edit_add_child', $_), @parents;
190 @values = ( map {$_->{id}} @parents );
191 %labels = ( map { $_->{id} => "$_->{title} ($_->{id})" } @parents );
193 if ($article->{level} == 1 && $req->user_can('edit_add_child')) {
195 $labels{-1} = "No parent - this is a section";
198 if ($article->{id} && $self->reparent_updown($article)) {
199 # we also list the siblings and grandparent (if any)
200 my @siblings = grep $_->{id} != $article->{id} && $_->{id} != $shopid,
201 $articles->getBy(parentid => $article->{parentid});
202 @siblings = grep $req->user_can('edit_add_child', $_), @siblings;
203 push @values, map $_->{id}, @siblings;
204 @labels{map $_->{id}, @siblings} =
205 map { "-- move down a level -- $_->{title} ($_->{id})" } @siblings;
207 if ($article->{parentid} != -1) {
208 my $parent = $articles->getByPkey($article->{parentid});
209 if ($parent->{parentid} != -1) {
210 my $gparent = $articles->getByPkey($parent->{parentid});
211 if ($req->user_can('edit_add_child', $gparent)) {
212 push @values, $gparent->{id};
213 $labels{$gparent->{id}} =
214 "-- move up a level -- $gparent->{title} ($gparent->{id})";
218 if ($req->user_can('edit_add_child')) {
220 $labels{-1} = "-- move up a level -- become a section";
226 return (\@values, \%labels);
230 my ($self, $article, $articles, $cgi, $req, $what) = @_;
232 if ($what eq 'listed') {
233 my @values = qw(0 1);
234 my %labels = ( 0=>"No", 1=>"Yes");
235 if ($article->{level} <= 2) {
236 $labels{2} = "In Sections, but not menu";
240 $labels{2} = "In content, but not menus";
243 return popup_menu(-name=>'listed',
246 -default=>$article->{listed});
249 my ($values, $labels) = $self->possible_parents($article, $articles, $req);
251 if (defined $article->{parentid}) {
252 $html = popup_menu(-name=>'parentid',
255 -default => $article->{parentid},
259 $html = popup_menu(-name=>'parentid',
265 # munge the html - we display a default value, so we need to wrap the
266 # default <select /> around this one
267 $html =~ s!^<select[^>]+>|</select>!!gi;
273 my ($arg, $acts, $funcname, $templater) = @_;
274 my ($func, $args) = split ' ', $arg, 2;
275 return $templater->perform($acts, $func, $args) ? 'checked' : '';
278 sub iter_get_images {
279 my ($self, $article) = @_;
281 $article->{id} or return;
282 $self->get_images($article);
286 my ($article, $articles) = @_;
289 $article->{id} or return;
290 if (UNIVERSAL::isa($article, 'Article')) {
291 @children = $article->children;
293 elsif ($article->{id}) {
294 @children = $articles->children($article->{id});
297 return sort { $b->{displayOrder} <=> $a->{displayOrder} } @children;
300 sub tag_if_have_child_type {
301 my ($level, $cfg) = @_;
303 defined $cfg->entry("level names", $level+1);
307 my ($args, $acts, $isname, $templater) = @_;
309 my ($func, $funcargs) = split ' ', $args, 2;
310 return $templater->perform($acts, $func, $funcargs) ? 'Yes' : 'No';
313 sub default_template {
314 my ($self, $article, $cfg, $templates) = @_;
316 if ($article->{parentid}) {
317 my $template = $cfg->entry("children of $article->{parentid}", "template");
319 if $template && grep $_ eq $template, @$templates;
321 if ($article->{level}) {
322 my $template = $cfg->entry("level $article->{level}", "template");
324 if $template && grep $_ eq $template, @$templates;
326 return $templates->[0];
330 my ($self, $article, $cfg, $cgi) = @_;
332 my @templates = sort $self->templates($article);
334 if ($article->{template} && grep $_ eq $article->{template}, @templates) {
335 $default = $article->{template};
339 $default = $self->default_template($article, $cfg, \@templates);
341 return popup_menu(-name=>'template',
342 -values=>\@templates,
348 my ($self, $article) = @_;
351 my $imagedir = $self->{cfg}->entry('paths', 'images', $Constants::IMAGEDIR);
352 if (opendir TITLE_IMAGES, "$imagedir/titles") {
354 grep -f "$imagedir/titles/$_" && /\.(gif|jpeg|jpg|png)$/i,
355 readdir TITLE_IMAGES;
356 closedir TITLE_IMAGES;
362 sub tag_title_images {
363 my ($self, $article, $cfg, $cgi) = @_;
365 my @images = $self->title_images($article);
366 my @values = ( '', @images );
367 my %labels = ( '' => 'None', map { $_ => $_ } @images );
369 popup_menu(-name=>'titleImage',
372 -default=>$article->{id} ? $article->{titleImage} : '',
376 sub base_template_dirs {
381 my ($self, $article) = @_;
383 my @dirs = $self->base_template_dirs;
384 if (my $parentid = $article->{parentid}) {
385 my $section = "children of $parentid";
386 if (my $dirs = $self->{cfg}->entry($section, 'template_dirs')) {
387 push @dirs, split /,/, $dirs;
390 if (my $id = $article->{id}) {
391 my $section = "article $id";
392 if (my $dirs = $self->{cfg}->entry($section, 'template_dirs')) {
393 push @dirs, split /,/, $dirs;
396 if ($article->{level}) {
397 push @dirs, $article->{level};
398 my $dirs = $self->{cfg}->entry("level $article->{level}", 'template_dirs');
399 push @dirs, split /,/, $dirs if $dirs;
406 my ($self, $article) = @_;
408 my @dirs = $self->template_dirs($article);
410 my @basedirs = BSE::Template->template_dirs($self->{cfg});
411 for my $basedir (@basedirs) {
412 for my $dir (@dirs) {
413 my $path = File::Spec->catdir($basedir, $dir);
415 if (opendir TEMPLATE_DIR, $path) {
416 push(@templates, sort map "$dir/$_",
417 grep -f "$path/$_" && /\.(tmpl|html)$/i, readdir TEMPLATE_DIR);
418 closedir TEMPLATE_DIR;
424 # eliminate any dups, and order it nicely
426 @templates = sort { lc($a) cmp lc($b) }
427 grep !$seen{$_}++, @templates;
429 return (@templates, $self->extra_templates($article));
432 sub extra_templates {
433 my ($self, $article) = @_;
435 my $basedir = $self->{cfg}->entryVar('paths', 'templates');
437 if (my $id = $article->{id}) {
438 push @templates, 'index.tmpl'
439 if $id == 1 && -f "$basedir/index.html";
440 push @templates, 'index2.tmpl'
441 if $id == 2 && -f "$basedir/index2.html";
442 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
443 push @templates, "shop_sect.tmpl"
444 if $id == $shopid && -f "$basedir/shop_sect.tmpl";
445 my $section = "article $id";
446 my $extras = $self->{cfg}->entry($section, 'extra_templates');
447 push @templates, grep /\.(tmpl|html)$/i, split /,/, $extras
457 return '' unless $article->{id} && $article->{id} != -1;
459 <a href="$ENV{SCRIPT_NAME}?id=$article->{parentid}">Edit parent</a> |
466 return unless $article->{id} && $article->{id} > 0;
470 sub _load_step_kids {
471 my ($article, $step_kids) = @_;
473 my @stepkids = OtherParents->getBy(parentId=>$article->{id}) if $article->{id};
474 %$step_kids = map { $_->{childId} => $_ } @stepkids;
475 $step_kids->{loaded} = 1;
478 sub tag_if_step_kid {
479 my ($article, $allkids, $rallkid_index, $step_kids) = @_;
481 _load_step_kids($article, $step_kids) unless $step_kids->{loaded};
483 my $kid = $allkids->[$$rallkid_index]
485 exists $step_kids->{$kid->{id}};
489 my ($article, $allkids, $rallkid_index, $step_kids, $arg) = @_;
491 _load_step_kids($article, $step_kids) unless $step_kids->{loaded};
493 my $kid = $allkids->[$$rallkid_index]
495 my $step_kid = $step_kids->{$kid->{id}}
498 #print STDERR "found kid (want $arg): ", Dumper($kid), Dumper($step_kid);
499 escape_html($step_kid->{$arg});
502 sub tag_move_stepkid {
503 my ($self, $cgi, $req, $article, $allkids, $rallkids_index, $arg,
504 $acts, $funcname, $templater) = @_;
506 $req->user_can(edit_reorder_children => $article)
509 @$allkids > 1 or return '';
511 my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater);
512 $img_prefix = '' unless defined $img_prefix;
513 $urladd = '' unless defined $urladd;
515 my $cgi_uri = $self->{cfg}->entry('uri', 'cgi', '/cgi-bin');
516 my $url = $ENV{SCRIPT_NAME} . "?id=$article->{id}";
517 if ($cgi->param('_t')) {
518 $url .= "&_t=".$cgi->param('_t');
523 if ($$rallkids_index < $#$allkids) {
524 $down_url = "$cgi_uri/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids->[$$rallkids_index]{id}&other=$allkids->[$$rallkids_index+1]{id}";
527 if ($$rallkids_index > 0) {
528 $up_url = "$cgi_uri/admin/move.pl?stepparent=$article->{id}&d=swap&id=$allkids->[$$rallkids_index]{id}&other=$allkids->[$$rallkids_index-1]{id}";
531 return make_arrows($req->cfg, $down_url, $up_url, $url, $img_prefix);
534 sub possible_stepkids {
535 my ($req, $article, $articles, $stepkids) = @_;
537 $req->user_can(edit_stepkid_add => $article)
540 my @possible = sort { lc $a->{title} cmp lc $b->{title} }
541 grep !$stepkids->{$_->{id}} && $_->{id} != $article->{id}, $articles->all;
542 if ($req->access_control) {
543 @possible = grep $req->user_can(edit_stepparent_add => $_), @possible;
548 sub tag_possible_stepkids {
549 my ($step_kids, $req, $article, $possstepkids, $articles, $cgi) = @_;
551 _load_step_kids($article, $step_kids) unless $step_kids->{loaded};
552 @$possstepkids = possible_stepkids($req, $article, $articles, $step_kids)
553 unless @$possstepkids;
554 my %labels = map { $_->{id} => "$_->{title} ($_->{id})" } @$possstepkids;
556 popup_menu(-name=>'stepkid',
557 -values=> [ map $_->{id}, @$possstepkids ],
558 -labels => \%labels);
561 sub tag_if_possible_stepkids {
562 my ($step_kids, $req, $article, $possstepkids, $articles, $cgi) = @_;
564 _load_step_kids($article, $step_kids) unless $step_kids->{loaded};
565 @$possstepkids = possible_stepkids($req, $article, $articles, $step_kids)
566 unless @$possstepkids;
571 sub iter_get_stepparents {
574 return unless $article->{id} && $article->{id} > 0;
576 OtherParents->getBy(childId=>$article->{id});
579 sub tag_ifStepParents {
580 my ($args, $acts, $funcname, $templater) = @_;
582 return $templater->perform($acts, 'ifStepparents', '');
585 sub tag_stepparent_targ {
586 my ($article, $targs, $rindex, $arg) = @_;
588 if ($article->{id} && $article->{id} > 0 && !@$targs) {
589 @$targs = $article->step_parents;
591 escape_html($targs->[$$rindex]{$arg});
594 sub tag_move_stepparent {
595 my ($self, $cgi, $req, $article, $stepparents, $rindex, $arg,
596 $acts, $funcname, $templater) = @_;
598 $req->user_can(edit_reorder_stepparents => $article)
601 @$stepparents > 1 or return '';
603 my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater);
604 $img_prefix = '' unless defined $img_prefix;
605 $urladd = '' unless defined $urladd;
607 my $cgi_uri = $self->{cfg}->entry('uri', 'cgi', '/cgi-bin');
608 my $images_uri = $self->{cfg}->entry('uri', 'images', '/images');
610 my $url = $ENV{SCRIPT_NAME} . "?id=$article->{id}";
611 if ($cgi->param('_t')) {
612 $url .= "&_t=".$cgi->param('_t');
615 $url .= "#stepparents";
616 my $blank = qq!<img src="$images_uri/trans_pixel.gif" width="17" height="13" border="0" align="absbottom" alt="" />!;
618 if ($$rindex < $#$stepparents) {
619 $down_url = "$cgi_uri/admin/move.pl?stepchild=$article->{id}&id=$stepparents->[$$rindex]{parentId}&d=swap&other=$stepparents->[$$rindex+1]{parentId}";
623 $up_url = "$cgi_uri/admin/move.pl?stepchild=$article->{id}&id=$stepparents->[$$rindex]{parentId}&d=swap&other=$stepparents->[$$rindex-1]{parentId}";
626 return make_arrows($req->cfg, $down_url, $up_url, $url, $img_prefix);
629 sub _stepparent_possibles {
630 my ($req, $article, $articles, $targs) = @_;
632 $req->user_can(edit_stepparent_add => $article)
635 @$targs = $article->step_parents unless @$targs;
636 my %targs = map { $_->{id}, 1 } @$targs;
637 my @possibles = grep !$targs{$_->{id}} && $_->{id} != $article->{id},
639 if ($req->access_control) {
640 @possibles = grep $req->user_can(edit_stepkid_add => $_), @possibles;
642 @possibles = sort { lc $a->{title} cmp lc $b->{title} } @possibles;
647 sub tag_if_stepparent_possibles {
648 my ($req, $article, $articles, $targs, $possibles) = @_;
650 if ($article->{id} && $article->{id} > 0 && !@$possibles) {
651 @$possibles = _stepparent_possibles($req, $article, $articles, $targs);
656 sub tag_stepparent_possibles {
657 my ($cgi, $req, $article, $articles, $targs, $possibles) = @_;
659 if ($article->{id} && $article->{id} > 0 && !@$possibles) {
660 @$possibles = _stepparent_possibles($req, $article, $articles, $targs);
662 popup_menu(-name=>'stepparent',
663 -values => [ map $_->{id}, @$possibles ],
664 -labels => { map { $_->{id}, "$_->{title} ($_->{id})" }
671 return unless $article->{id} && $article->{id} > 0;
673 return $article->files;
676 sub tag_edit_parent {
679 return '' unless $article->{id} && $article->{id} != -1;
682 <a href="$ENV{SCRIPT_NAME}?id=$article->{parentid}">Edit parent</a> |
686 sub tag_if_children {
687 my ($args, $acts, $funcname, $templater) = @_;
689 return $templater->perform($acts, 'ifChildren', '');
693 my ($self, $req, $article, $kids, $rindex, $arg,
694 $acts, $funcname, $templater) = @_;
696 $req->user_can('edit_reorder_children', $article)
699 @$kids > 1 or return '';
701 $$rindex >=0 && $$rindex < @$kids
702 or return '** movechild can only be used in the children iterator **';
704 my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater);
705 $img_prefix = '' unless defined $img_prefix;
706 $urladd = '' unless defined $urladd;
708 my $cgi_uri = $self->{cfg}->entry('uri', 'cgi', '/cgi-bin');
709 my $images_uri = $self->{cfg}->entry('uri', 'images', '/images');
710 my $urlbase = admin_base_url($req->cfg);
711 my $refresh_url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}";
712 my $t = $req->cgi->param('_t');
713 if ($t && $t =~ /^\w+$/) {
714 $refresh_url .= "&_t=$t";
717 $refresh_url .= $urladd;
719 my $id = $kids->[$$rindex]{id};
721 if ($$rindex < $#$kids) {
722 $down_url = "$cgi_uri/admin/move.pl?id=$id&d=down&edit=1&all=1";
726 $up_url = "$cgi_uri/admin/move.pl?id=$id&d=up&edit=1&all=1"
729 return make_arrows($req->cfg, $down_url, $up_url, $refresh_url, $img_prefix);
733 my ($article, $args, $acts, $funcname, $templater) = @_;
734 my ($which, $name) = split / /, $args, 2;
738 && ($gen_class = $templater->perform($acts, $which, 'generator'))) {
739 eval "use $gen_class";
741 my $gen = $gen_class->new(top => $article);
742 my $link = $gen->edit_link($templater->perform($acts, $which, 'id'));
743 return qq!<a href="$link">$name</a>!;
750 my ($req, $article, $rindex, $images, $arg,
751 $acts, $funcname, $templater) = @_;
753 $req->user_can(edit_images_reorder => $article)
756 @$images > 1 or return '';
758 $$rindex >= 0 && $$rindex < @$images
759 or return '** imgmove can only be used in image iterator **';
761 my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater);
762 $img_prefix = '' unless defined $img_prefix;
763 $urladd = '' unless defined $urladd;
765 my $urlbase = admin_base_url($req->cfg);
766 my $url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}";
767 my $t = $req->cgi->param('_t');
768 if ($t && $t =~ /^\w+$/) {
773 my $image = $images->[$$rindex];
775 if ($$rindex < $#$images) {
776 $down_url = "$ENV{SCRIPT_NAME}?id=$article->{id}&moveimgdown=1&imageid=$image->{id}";
780 $up_url = "$ENV{SCRIPT_NAME}?id=$article->{id}&moveimgup=1&imageid=$image->{id}";
782 return make_arrows($req->cfg, $down_url, $up_url, $url, $img_prefix);
786 my ($self, $req, $article, $files, $rindex, $arg,
787 $acts, $funcname, $templater) = @_;
789 $req->user_can('edit_files_reorder', $article)
792 @$files > 1 or return '';
794 my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater);
795 $img_prefix = '' unless defined $img_prefix;
796 $urladd = '' unless defined $urladd;
798 $$rindex >= 0 && $$rindex < @$files
799 or return '** movefiles can only be used in the files iterator **';
801 my $urlbase = admin_base_url($req->cfg);
802 my $url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}$urladd";
803 my $t = $req->cgi->param('_t');
804 if ($t && $t =~ /^\w+$/) {
809 if ($$rindex < $#$files) {
810 $down_url = "$ENV{SCRIPT_NAME}?fileswap=1&id=$article->{id}&file1=$files->[$$rindex]{id}&file2=$files->[$$rindex+1]{id}";
814 $up_url = "$ENV{SCRIPT_NAME}?fileswap=1&id=$article->{id}&file1=$files->[$$rindex]{id}&file2=$files->[$$rindex-1]{id}";
817 return make_arrows($req->cfg, $down_url, $up_url, $url, $img_prefix);
821 my ($article, $cgi, $args, $acts, $funcname, $templater) = @_;
823 my ($col, $func, $funcargs) = split ' ', $args, 3;
824 my $value = $cgi->param($col);
825 if (defined $value) {
826 return escape_html($value);
830 return $templater->perform($acts, $func, $funcargs);
833 $value = $article->{$args};
834 defined $value or $value = '';
835 return escape_html($value);
840 sub iter_admin_users {
841 require BSE::TB::AdminUsers;
843 BSE::TB::AdminUsers->all;
846 sub iter_admin_groups {
847 require BSE::TB::AdminGroups;
849 BSE::TB::AdminGroups->all;
852 sub tag_if_field_perm {
853 my ($req, $article, $field) = @_;
855 unless ($field =~ /^\w+$/) {
856 print STDERR "Bad fieldname '$field'\n";
859 if ($article->{id}) {
860 return $req->user_can("edit_field_edit_$field", $article);
863 #print STDERR "adding, always successful\n";
869 my ($self, $req, $article, $args, $acts, $funcname, $templater) = @_;
871 my ($col, $func, $funcargs) = split ' ', $args, 3;
872 if ($article->{id}) {
874 return $templater->perform($acts, $func, $funcargs);
877 my $value = $article->{$args};
878 defined $value or $value = '';
879 return escape_html($value);
883 my $value = $self->default_value($req, $article, $col);
884 return escape_html($value);
894 sub tag_if_flag_set {
895 my ($article, $arg, $acts, $funcname, $templater) = @_;
897 my @args = DevHelp::Tags->get_parms($arg, $acts, $templater);
900 return index($article->{flags}, $args[0]) >= 0;
904 my ($article, $articles) = @_;
908 defined($temp->{parentid}) or return;
909 while ($temp->{parentid} > 0
910 and my $crumb = $articles->getByPkey($temp->{parentid})) {
911 unshift @crumbs, $crumb;
919 my ($args, $acts, $funcname, $templater) = @_;
921 exists $acts->{$args} or return "** need an article name **";
922 my $generator = $templater->perform($acts, $args, 'generator');
924 $generator =~ /^(?:BSE::)?Generate::(\w+)$/
925 or return "** invalid generator $generator **";
931 my ($self, $acts, $request, $article, $articles, $msg, $errors) = @_;
933 my $cgi = $request->cgi;
934 $msg ||= $cgi->param('message');
937 if (keys %$errors && !$msg) {
938 # try to get the errors in the same order as the table
939 my @cols = $self->table_object($articles)->rowClass->columns;
941 my @out = grep defined, delete @work{@cols};
943 $msg = join "<br>", @out, values %work;
946 if ($article->{id}) {
947 if ($article->{parentid} > 0) {
948 $parent = $article->parent;
951 $parent = { title=>"No parent - this is a section", id=>-1 };
955 $parent = { title=>"How did we get here?", id=>0 };
957 my $cfg = $self->{cfg};
958 my $mbcs = $cfg->entry('html', 'mbcs', 0);
959 my $tag_hash = $mbcs ? \&tag_hash_mbcs : \&tag_hash;
969 my $stepparent_index;
970 my @stepparent_targs;
971 my @stepparentpossibles;
976 BSE::Util::Tags->basic($acts, $cgi, $cfg),
977 BSE::Util::Tags->admin($acts, $cfg),
978 BSE::Util::Tags->secure($request),
979 article => [ $tag_hash, $article ],
980 old => [ \&tag_old, $article, $cgi ],
981 default => [ \&tag_default, $self, $request, $article ],
982 articleType => [ \&tag_art_type, $article->{level}, $cfg ],
983 parentType => [ \&tag_art_type, $article->{level}-1, $cfg ],
984 ifNew => [ \&tag_if_new, $article ],
985 list => [ \&tag_list, $self, $article, $articles, $cgi, $request ],
986 script => $ENV{SCRIPT_NAME},
987 level => $article->{level},
988 checked => \&tag_checked,
989 DevHelp::Tags->make_iterator2
990 ([ \&iter_get_images, $self, $article ], 'image', 'images', \@images,
992 imgmove => [ \&tag_imgmove, $request, $article, \$image_index, \@images ],
994 DevHelp::Tags->make_iterator2
995 ([ \&iter_get_kids, $article, $articles ],
996 'child', 'children', \@children, \$child_index),
997 ifchildren => \&tag_if_children,
998 childtype => [ \&tag_art_type, $article->{level}+1, $cfg ],
999 ifHaveChildType => [ \&tag_if_have_child_type, $article->{level}, $cfg ],
1000 movechild => [ \&tag_movechild, $self, $request, $article, \@children,
1003 templates => [ \&tag_templates, $self, $article, $cfg, $cgi ],
1004 titleImages => [ \&tag_title_images, $self, $article, $cfg, $cgi ],
1005 editParent => [ \&tag_edit_parent, $article ],
1006 DevHelp::Tags->make_iterator2
1007 ([ \&iter_allkids, $article ], 'kid', 'kids', \@allkids, \$allkid_index),
1009 [ \&tag_if_step_kid, $article, \@allkids, \$allkid_index, \%stepkids ],
1010 stepkid => [ \&tag_step_kid, $article, \@allkids, \$allkid_index,
1013 [ \&tag_move_stepkid, $self, $cgi, $request, $article, \@allkids,
1015 possible_stepkids =>
1016 [ \&tag_possible_stepkids, \%stepkids, $request, $article,
1017 \@possstepkids, $articles, $cgi ],
1019 [ \&tag_if_possible_stepkids, \%stepkids, $request, $article,
1020 \@possstepkids, $articles, $cgi ],
1021 DevHelp::Tags->make_iterator2
1022 ( [ \&iter_get_stepparents, $article ], 'stepparent', 'stepparents',
1023 \@stepparents, \$stepparent_index),
1024 ifStepParents => \&tag_ifStepParents,
1026 [ \&tag_stepparent_targ, $article, \@stepparent_targs,
1027 \$stepparent_index ],
1029 [ \&tag_move_stepparent, $self, $cgi, $request, $article, \@stepparents,
1030 \$stepparent_index ],
1031 ifStepparentPossibles =>
1032 [ \&tag_if_stepparent_possibles, $request, $article, $articles,
1033 \@stepparent_targs, \@stepparentpossibles, ],
1034 stepparent_possibles =>
1035 [ \&tag_stepparent_possibles, $cgi, $request, $article, $articles,
1036 \@stepparent_targs, \@stepparentpossibles, ],
1037 DevHelp::Tags->make_iterator2
1038 ([ \&iter_files, $article ], 'file', 'files', \@files, \$file_index ),
1040 [ \&tag_movefiles, $self, $request, $article, \@files, \$file_index ],
1041 DevHelp::Tags->make_iterator2
1042 (\&iter_admin_users, 'iadminuser', 'adminusers'),
1043 DevHelp::Tags->make_iterator2
1044 (\&iter_admin_groups, 'iadmingroup', 'admingroups'),
1045 edit => [ \&tag_edit_link, $article ],
1046 error => [ $tag_hash, $errors ],
1047 error_img => [ \&tag_error_img, $cfg, $errors ],
1048 ifFieldPerm => [ \&tag_if_field_perm, $request, $article ],
1049 parent => [ $tag_hash, $parent ],
1050 DevHelp::Tags->make_iterator2
1051 ([ \&iter_flags, $self ], 'flag', 'flags' ),
1052 ifFlagSet => [ \&tag_if_flag_set, $article ],
1053 DevHelp::Tags->make_iterator2
1054 ([ \&iter_crumbs, $article, $articles ], 'crumb', 'crumbs' ),
1055 typename => \&tag_typename,
1060 my ($self, $article, $cgi) = @_;
1062 my $base = $article->{level};
1063 my $t = $cgi->param('_t');
1064 if ($t && $t =~ /^\w+$/) {
1067 return $self->{cfg}->entry('admin templates', $base,
1068 "admin/edit_$base");
1072 my ($self, $article, $cgi) = @_;
1074 $self->edit_template($article, $cgi);
1078 my ($self, $request, $article, $articles, $msg, $errors) = @_;
1080 my $cgi = $request->cgi;
1082 %acts = $self->low_edit_tags(\%acts, $request, $article, $articles, $msg,
1084 my $template = $article->{id} ?
1085 $self->edit_template($article, $cgi) : $self->add_template($article, $cgi);
1087 return BSE::Template->get_response($template, $request->cfg, \%acts);
1091 my ($self, $request, $article, $articles, $msg, $errors) = @_;
1093 return $self->low_edit_form($request, $article, $articles, $msg, $errors);
1097 my ($self, $req, $articles, $msg, $errors) = @_;
1100 my $cgi = $req->cgi;
1101 my $parentid = $cgi->param('parentid');
1103 if ($parentid =~ /^\d+$/) {
1104 if (my $parent = $self->get_parent($parentid, $articles)) {
1105 $level = $parent->{level}+1;
1111 elsif ($parentid eq "-1") {
1115 unless (defined $level) {
1116 $level = $cgi->param('level');
1117 undef $level unless defined $level && $level =~ /^\d+$/
1118 && $level > 0 && $level < 100;
1119 defined $level or $level = 3;
1123 my @cols = Article->columns;
1124 @article{@cols} = ('') x @cols;
1126 $article{parentid} = $parentid;
1127 $article{level} = $level;
1128 $article{body} = '<maximum of 64Kb>';
1129 $article{listed} = 1;
1130 $article{generator} = $self->generator;
1132 my ($values, $labels) = $self->possible_parents(\%article, $articles, $req);
1134 or return $self->edit_sections($req, $articles,
1135 "You can't add children to any article at that level");
1137 return $self->low_edit_form($req, \%article, $articles, $msg, $errors);
1140 sub generator { 'Generate::Article' }
1145 my $gen = $self->generator;
1147 ($gen =~ /(\w+)$/)[0] || 'Article';
1150 sub _validate_common {
1151 my ($self, $data, $articles, $errors, $article) = @_;
1153 # if (defined $data->{parentid} && $data->{parentid} =~ /^(?:-1|\d+)$/) {
1154 # unless ($data->{parentid} == -1 or
1155 # $articles->getByPkey($data->{parentid})) {
1156 # $errors->{parentid} = "Selected parent article doesn't exist";
1160 # $errors->{parentid} = "You need to select a valid parent";
1162 if (exists $data->{title} && $data->{title} !~ /\S/) {
1163 $errors->{title} = "Please enter a title";
1166 if (exists $data->{template} && $data->{template} =~ /\.\./) {
1167 $errors->{template} = "Please only select templates from the list provided";
1173 my ($self, $data, $articles, $errors) = @_;
1175 $self->_validate_common($data, $articles, $errors);
1176 custom_class($self->{cfg})
1177 ->article_validate($data, undef, $self->typename, $errors);
1179 return !keys %$errors;
1183 my ($self, $article, $data, $articles, $errors) = @_;
1185 $self->_validate_common($data, $articles, $errors, $article);
1186 custom_class($self->{cfg})
1187 ->article_validate($data, $article, $self->typename, $errors);
1189 if (exists $data->{release} && !valid_date($data->{release})) {
1190 $errors->{release} = "Invalid release date";
1193 return !keys %$errors;
1196 sub validate_parent {
1201 my ($self, $req, $data, $articles) = @_;
1203 custom_class($self->{cfg})
1204 ->article_fill_new($data, $self->typename);
1210 my ($self, $article) = @_;
1212 # check the config for the article and any of its ancestors
1213 my $work_article = $article;
1214 my $path = $self->{cfg}->entry('article uris', $work_article->{id});
1216 last if $work_article->{parentid} == -1;
1217 $work_article = $work_article->parent;
1218 $path = $self->{cfg}->entry('article uris', $work_article->{id});
1220 return $path if $path;
1222 $self->default_link_path($article);
1225 sub default_link_path {
1226 my ($self, $article) = @_;
1228 $self->{cfg}->entry('uri', 'articles', '/a');
1232 my ($self, $article) = @_;
1234 my $article_uri = $self->link_path($article);
1235 my $link = "$article_uri/$article->{id}.html";
1236 my $link_titles = $self->{cfg}->entryBool('basic', 'link_titles', 0);
1238 (my $extra = lc $article->{title}) =~ tr/a-z0-9/_/sc;
1239 $link .= "/".$extra;
1246 my ($self, $req, $articles) = @_;
1248 my $cgi = $req->cgi;
1250 my $table_object = $self->table_object($articles);
1251 my @columns = $table_object->rowClass->columns;
1252 $self->save_thumbnail($cgi, undef, \%data);
1253 for my $name (@columns) {
1254 $data{$name} = $cgi->param($name)
1255 if defined $cgi->param($name);
1257 $data{flags} = join '', sort $cgi->param('flags');
1261 $self->validate(\%data, $articles, \%errors)
1262 or return $self->add_form($req, $articles, $msg, \%errors);
1265 if ($data{parentid} > 0) {
1266 $parent = $articles->getByPkey($data{parentid}) or die;
1267 $req->user_can('edit_add_child', $parent)
1268 or return $self->add_form($req, $articles,
1269 "You cannot add a child to that article");
1270 for my $name (@columns) {
1271 if (exists $data{$name} &&
1272 !$req->user_can("edit_add_field_$name", $parent)) {
1273 delete $data{$name};
1278 $req->user_can('edit_add_child')
1279 or return $self->add_form($req, $articles,
1280 "You cannot create a top-level article");
1281 for my $name (@columns) {
1282 if (exists $data{$name} &&
1283 !$req->user_can("edit_add_field_$name")) {
1284 delete $data{$name};
1289 $self->validate_parent(\%data, $articles, $parent, \$msg)
1290 or return $self->add_form($req, $articles, $msg);
1292 $self->fill_new_data($req, \%data, $articles);
1293 my $level = $parent ? $parent->{level}+1 : 1;
1294 $data{displayOrder} = time;
1295 $data{titleImage} ||= '';
1296 $data{imagePos} = 'tr';
1297 $data{release} = sql_date($data{release}) || now_sqldate();
1298 $data{expire} = sql_date($data{expire}) || $Constants::D_99;
1299 unless ($data{template}) {
1301 $self->{cfg}->entry("children of $data{parentid}", 'template');
1303 $self->{cfg}->entry("level $level", 'template');
1306 $data{admin} ||= '';
1308 $data{threshold} = $parent->{threshold}
1309 if !defined $data{threshold} || $data{threshold} =~ /^\s*$/;
1310 $data{summaryLength} = $parent->{summaryLength}
1311 if !defined $data{summaryLength} || $data{summaryLength} =~ /^\s*$/;
1314 $data{threshold} = $self->{cfg}->entry("level $level", 'threshold', 5)
1315 if !defined $data{threshold} || $data{threshold} =~ /^\s*$/;
1316 $data{summaryLength} = 200
1317 if !defined $data{summaryLength} || $data{summaryLength} =~ /^\s*$/;
1319 $data{generator} = $self->generator;
1320 $data{lastModified} = now_sqldatetime();
1321 $data{level} = $level;
1322 $data{listed} = 1 unless defined $data{listed};
1325 my $article = $table_object->add(@data{@columns});
1327 # we now have an id - generate the links
1329 my $cgi_uri = $self->{cfg}->entry('uri', 'cgi', '/cgi-bin');
1330 $article->setAdmin("$cgi_uri/admin/admin.pl?id=$article->{id}");
1331 $article->setLink($self->make_link($article));
1334 use Util 'generate_article';
1335 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1337 my $r = $cgi->param('r');
1339 $r .= ($r =~ /\?/) ? '&' : '?';
1340 $r .= "id=$article->{id}";
1344 $r = admin_base_url($req->cfg) . $article->{admin};
1346 return BSE::Template->get_refresh($r, $self->{cfg});
1351 my ($self, $req, $article, $data) = @_;
1353 if (exists $data->{body}) {
1354 $data->{body} =~ s/\x0D\x0A/\n/g;
1355 $data->{body} =~ tr/\r/\n/;
1357 for my $col (Article->columns) {
1358 next if $col =~ /^custom/;
1359 $article->{$col} = $data->{$col}
1360 if exists $data->{$col} && $col ne 'id' && $col ne 'parentid';
1362 custom_class($self->{cfg})
1363 ->article_fill_old($article, $data, $self->typename);
1369 my ($self, $req, $article, $articles) = @_;
1371 $req->user_can(edit_save => $article)
1372 or return $self->edit_form($req, $article, $articles,
1373 "You don't have access to save this article");
1375 my $cgi = $req->cgi;
1377 for my $name ($article->columns) {
1378 $data{$name} = $cgi->param($name)
1379 if defined($cgi->param($name)) and $name ne 'id' && $name ne 'parentid'
1380 && $req->user_can("edit_field_edit_$name", $article);
1382 # possibly this needs tighter error checking
1383 $data{flags} = join '', sort $cgi->param('flags')
1384 if $req->user_can("edit_field_edit_flags", $article);
1386 $self->validate_old($article, \%data, $articles, \%errors)
1387 or return $self->edit_form($req, $article, $articles, undef, \%errors);
1388 $self->save_thumbnail($cgi, $article, \%data)
1389 if $req->user_can('edit_field_edit_thumbImage', $article);
1390 $self->fill_old_data($req, $article, \%data);
1391 if (exists $article->{template} &&
1392 $article->{template} =~ m|\.\.|) {
1393 my $msg = "Please only select templates from the list provided";
1394 return $self->edit_form($req, $article, $articles, $msg);
1398 my $newparentid = $cgi->param('parentid');
1399 if ($newparentid && $req->user_can('edit_field_edit_parentid', $article)) {
1400 if ($newparentid == $article->{parentid}) {
1403 elsif ($newparentid != -1) {
1404 print STDERR "Reparenting...\n";
1405 my $newparent = $articles->getByPkey($newparentid);
1407 if ($newparent->{level} != $article->{level}-1) {
1408 # the article cannot become a child of itself or one of it's
1410 if ($article->{id} == $newparentid
1411 || $self->is_descendant($article->{id}, $newparentid, $articles)) {
1412 my $msg = "Cannot become a child of itself or of a descendant";
1413 return $self->edit_form($req, $article, $articles, $msg);
1415 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
1416 if ($self->is_descendant($article->{id}, $shopid, $articles)) {
1417 my $msg = "Cannot become a descendant of the shop";
1418 return $self->edit_form($req, $article, $articles, $msg);
1421 $self->reparent($article, $newparentid, $articles, \$msg)
1422 or return $self->edit_form($req, $article, $articles, $msg);
1425 # stays at the same level, nothing special
1426 $article->{parentid} = $newparentid;
1432 # becoming a section
1434 $self->reparent($article, -1, $articles, \$msg)
1435 or return $self->edit_form($req, $article, $articles, $msg);
1439 $article->{listed} = $cgi->param('listed')
1440 if defined $cgi->param('listed') &&
1441 $req->user_can('edit_field_edit_listed', $article);
1442 $article->{release} = sql_date($cgi->param('release'))
1443 if defined $cgi->param('release') &&
1444 $req->user_can('edit_field_edit_release', $article);
1446 $article->{expire} = sql_date($cgi->param('expire')) || $Constants::D_99
1447 if defined $cgi->param('expire') &&
1448 $req->user_can('edit_field_edit_expire', $article);
1449 $article->{lastModified} = now_sqldatetime();
1450 if ($article->{link} &&
1451 !$self->{cfg}->entry('protect link', $article->{id})) {
1452 my $article_uri = $self->make_link($article);
1453 $article->setLink($article_uri);
1458 use Util 'generate_article';
1459 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1461 return $self->refresh($article, $cgi, undef, 'Article saved');
1466 my ($year, $month, $day);
1469 if (($day, $month, $year) = ($str =~ m!(\d+)/(\d+)/(\d+)!)) {
1470 $year += 2000 if $year < 100;
1472 return sprintf("%04d-%02d-%02d", $year, $month, $day);
1478 my ($self, $article, $newparentid, $articles, $rmsg) = @_;
1481 if ($newparentid == -1) {
1485 my $parent = $articles->getByPkey($newparentid);
1487 $$rmsg = "Cannot get new parent article";
1490 $newlevel = $parent->{level} + 1;
1492 # the caller will save this one
1493 $article->{parentid} = $newparentid;
1494 $article->{level} = $newlevel;
1495 $article->{displayOrder} = time;
1497 my @change = ( [ $article->{id}, $newlevel ] );
1499 my $this = shift @change;
1500 my ($art, $level) = @$this;
1502 my @kids = $articles->getBy(parentid=>$art);
1503 push @change, map { [ $_->{id}, $level+1 ] } @kids;
1505 for my $kid (@kids) {
1506 $kid->{level} = $level+1;
1514 # tests if $desc is a descendant of $art
1515 # where both are article ids
1517 my ($self, $art, $desc, $articles) = @_;
1521 my $parent = shift @check;
1522 $parent == $desc and return 1;
1523 my @kids = $articles->getBy(parentid=>$parent);
1524 push @check, map $_->{id}, @kids;
1530 sub save_thumbnail {
1531 my ($self, $cgi, $original, $newdata) = @_;
1533 unless ($original) {
1534 @$newdata{qw/thumbImage thumbWidth thumbHeight/} = ('', 0, 0);
1536 my $imagedir = $self->{cfg}->entry('paths', 'images', $Constants::IMAGEDIR);
1537 if ($cgi->param('remove_thumb') && $original && $original->{thumbImage}) {
1538 unlink("$imagedir/$original->{thumbImage}");
1539 @$newdata{qw/thumbImage thumbWidth thumbHeight/} = ('', 0, 0);
1541 my $image = $cgi->param('thumbnail');
1542 if ($image && -s $image) {
1543 # where to put it...
1545 $image =~ /([\w.-]+)$/ and $name = $1;
1546 my $filename = time . "_" . $name;
1550 $filename = time . '_' . $counter . '_' . $name
1551 until sysopen( OUTPUT, "$imagedir/$filename",
1552 O_WRONLY| O_CREAT| O_EXCL)
1553 || ++$counter > 100;
1555 fileno(OUTPUT) or die "Could not open image file: $!";
1561 # read the image in from the browser and output it to our
1563 print STDERR "\$image ",ref $image,"\n";
1565 print OUTPUT $buffer while sysread $image, $buffer, 1024;
1568 or die "Could not close image output file: $!";
1572 if ($original && $original->{thumbImage}) {
1573 #unlink("$imagedir/$original->{thumbImage}");
1575 @$newdata{qw/thumbWidth thumbHeight/} = imgsize("$imagedir/$filename");
1576 $newdata->{thumbImage} = $filename;
1581 my ($self, $article) = @_;
1583 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
1584 if ($article && $article->{id} && $article->{id} == $shopid) {
1585 return ( 'BSE::Edit::Catalog' );
1587 return ( 'BSE::Edit::Article' );
1591 my ($self, $req, $article, $articles) = @_;
1593 $req->user_can(edit_stepkid_add => $article)
1594 or return $self->edit_form($req, $article, $articles,
1595 "You don't have access to add step children to this article");
1597 my $cgi = $req->cgi;
1598 require 'BSE/Admin/StepParents.pm';
1600 my $childId = $cgi->param('stepkid');
1602 or die "No stepkid supplied to add_stepkid";
1604 or die "Invalid stepkid supplied to add_stepkid";
1605 my $child = $articles->getByPkey($childId)
1606 or die "Article $childId not found";
1608 $req->user_can(edit_stepparent_add => $child)
1609 or die "You don't have access to add a stepparent to that article\n";
1611 use BSE::Util::Valid qw/valid_date/;
1612 my $release = $cgi->param('release');
1613 valid_date($release) or $release = undef;
1614 my $expire = $cgi->param('expire');
1615 valid_date($expire) or $expire = undef;
1618 BSE::Admin::StepParents->add($article, $child, $release, $expire);
1621 return $self->edit_form($req, $article, $articles, $@);
1624 use Util 'generate_article';
1625 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1627 return $self->refresh($article, $cgi, 'step', 'Stepchild added');
1631 my ($self, $req, $article, $articles) = @_;
1633 $req->user_can(edit_stepkid_delete => $article)
1634 or return $self->edit_form($req, $article, $articles,
1635 "You don't have access to delete stepchildren from this article");
1637 my $cgi = $req->cgi;
1638 require 'BSE/Admin/StepParents.pm';
1640 my $childId = $cgi->param('stepkid');
1642 or die "No stepkid supplied to add_stepkid";
1644 or die "Invalid stepkid supplied to add_stepkid";
1645 my $child = $articles->getByPkey($childId)
1646 or die "Article $childId not found";
1648 $req->user_can(edit_stepparent_delete => $child)
1649 or die "You cannot remove stepparents from that article\n";
1651 BSE::Admin::StepParents->del($article, $child);
1655 return $self->edit_form($req, $article, $articles, $@);
1657 use Util 'generate_article';
1658 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1660 return $self->refresh($article, $cgi, 'step', 'Stepchild deleted');
1664 my ($self, $req, $article, $articles) = @_;
1666 $req->user_can(edit_stepkid_save => $article)
1667 or return $self->edit_form($req, $article, $articles,
1668 "No access to save stepkid data for this article");
1670 my $cgi = $req->cgi;
1671 require 'BSE/Admin/StepParents.pm';
1672 my @stepcats = OtherParents->getBy(parentId=>$article->{id});
1673 my %stepcats = map { $_->{parentId}, $_ } @stepcats;
1674 my %datedefs = ( release => '2000-01-01', expire=>'2999-12-31' );
1675 for my $stepcat (@stepcats) {
1676 $req->user_can(edit_stepparent_save => $stepcat->{childId})
1678 for my $name (qw/release expire/) {
1679 my $date = $cgi->param($name.'_'.$stepcat->{childId});
1680 if (defined $date) {
1682 $date = $datedefs{$name};
1684 elsif (valid_date($date)) {
1685 use BSE::Util::SQL qw/date_to_sql/;
1686 $date = date_to_sql($date);
1689 return $self->refresh($article, $cgi, '', "Invalid date '$date'");
1691 $stepcat->{$name} = $date;
1697 $@ and return $self->refresh($article, $cgi, '', $@);
1699 use Util 'generate_article';
1700 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1702 return $self->refresh($article, $cgi, 'step', 'Stepchild information saved');
1705 sub add_stepparent {
1706 my ($self, $req, $article, $articles) = @_;
1708 $req->user_can(edit_stepparent_add => $article)
1709 or return $self->edit_form($req, $article, $articles,
1710 "You don't have access to add stepparents to this article");
1712 my $cgi = $req->cgi;
1713 require 'BSE/Admin/StepParents.pm';
1715 my $step_parent_id = $cgi->param('stepparent');
1716 defined($step_parent_id)
1717 or die "No stepparent supplied to add_stepparent";
1718 int($step_parent_id) eq $step_parent_id
1719 or die "Invalid stepcat supplied to add_stepcat";
1720 my $step_parent = $articles->getByPkey($step_parent_id)
1721 or die "Parent $step_parent_id not found\n";
1723 $req->user_can(edit_stepkid_add => $step_parent)
1724 or die "You don't have access to add a stepkid to that article\n";
1726 my $release = $cgi->param('release');
1728 or $release = "01/01/2000";
1729 use BSE::Util::Valid qw/valid_date/;
1730 $release eq '' or valid_date($release)
1731 or die "Invalid release date";
1732 my $expire = $cgi->param('expire');
1734 or $expire = '31/12/2999';
1735 $expire eq '' or valid_date($expire)
1736 or die "Invalid expire data";
1739 BSE::Admin::StepParents->add($step_parent, $article, $release, $expire);
1741 $@ and return $self->refresh($article, $cgi, 'step', $@);
1743 use Util 'generate_article';
1744 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1746 return $self->refresh($article, $cgi, 'stepparents', 'Stepparent added');
1749 sub del_stepparent {
1750 my ($self, $req, $article, $articles) = @_;
1752 $req->user_can(edit_stepparent_delete => $article)
1753 or return $self->edit_form($req, $article, $articles,
1754 "You cannot remove stepparents from that article");
1756 my $cgi = $req->cgi;
1757 require 'BSE/Admin/StepParents.pm';
1758 my $step_parent_id = $cgi->param('stepparent');
1759 defined($step_parent_id)
1760 or return $self->refresh($article, $cgi, 'stepparents',
1761 "No stepparent supplied to add_stepcat");
1762 int($step_parent_id) eq $step_parent_id
1763 or return $self->refresh($article, $cgi, 'stepparents',
1764 "Invalid stepparent supplied to add_stepparent");
1765 my $step_parent = $articles->getByPkey($step_parent_id)
1766 or return $self->refresh($article, $cgi, 'stepparent',
1767 "Stepparent $step_parent_id not found");
1769 $req->user_can(edit_stepkid_delete => $step_parent)
1770 or die "You don't have access to remove the stepkid from that article\n";
1773 BSE::Admin::StepParents->del($step_parent, $article);
1775 $@ and return $self->refresh($article, $cgi, 'stepparents', $@);
1777 use Util 'generate_article';
1778 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1780 return $self->refresh($article, $cgi, 'stepparents', 'Stepparent deleted');
1783 sub save_stepparents {
1784 my ($self, $req, $article, $articles) = @_;
1786 $req->user_can(edit_stepparent_save => $article)
1787 or return $self->edit_form($req, $article, $articles,
1788 "No access to save stepparent data for this artice");
1790 my $cgi = $req->cgi;
1792 require 'BSE/Admin/StepParents.pm';
1793 my @stepparents = OtherParents->getBy(childId=>$article->{id});
1794 my %stepparents = map { $_->{parentId}, $_ } @stepparents;
1795 my %datedefs = ( release => '2000-01-01', expire=>'2999-12-31' );
1796 for my $stepparent (@stepparents) {
1797 $req->user_can(edit_stepkid_save => $stepparent->{parentId})
1799 for my $name (qw/release expire/) {
1800 my $date = $cgi->param($name.'_'.$stepparent->{parentId});
1801 if (defined $date) {
1803 $date = $datedefs{$name};
1805 elsif (valid_date($date)) {
1806 use BSE::Util::SQL qw/date_to_sql/;
1807 $date = date_to_sql($date);
1810 return $self->refresh($article, $cgi, "Invalid date '$date'");
1812 $stepparent->{$name} = $date;
1816 $stepparent->save();
1818 $@ and return $self->refresh($article, $cgi, '', $@);
1821 use Util 'generate_article';
1822 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1824 return $self->refresh($article, $cgi, 'stepparents',
1825 'Stepparent information saved');
1829 my ($self, $article, $cgi, $name, $message, $extras) = @_;
1831 my $url = $cgi->param('r');
1833 my $urlbase = admin_base_url($self->{cfg});
1834 $url = "$urlbase$ENV{SCRIPT_NAME}?id=$article->{id}";
1835 $url .= "&message=" . CGI::escape($message) if $message;
1836 if ($cgi->param('_t')) {
1837 $url .= "&_t=".CGI::escape($cgi->param('_t'));
1839 $url .= $extras if defined $extras;
1840 my $cgiextras = $cgi->param('e');
1841 $url .= "#$name" if $name;
1844 return BSE::Template->get_refresh($url, $self->{cfg});
1848 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1851 %acts = $self->low_edit_tags(\%acts, $req, $article, $articles, $msg, $errors);
1852 my $template = 'admin/article_img';
1854 return BSE::Template->get_response($template, $req->cfg, \%acts);
1857 sub save_image_changes {
1858 my ($self, $req, $article, $articles) = @_;
1860 $req->user_can(edit_images_save => $article)
1861 or return $self->edit_form($req, $article, $articles,
1862 "You don't have access to save image information for this article");
1864 my $cgi = $req->cgi;
1865 my $image_pos = $cgi->param('imagePos');
1867 && $image_pos =~ /^(?:tl|tr|bl|br)$/
1868 && $image_pos ne $article->{imagePos}) {
1869 $article->{imagePos} = $image_pos;
1872 my @images = $self->get_images($article);
1875 return $self->refresh($article, $cgi, undef, 'No images to save information for');
1878 my @alt = $cgi->param('alt');
1881 for my $index (0..$#images) {
1882 $index < @alt or last;
1883 $images[$index]{alt} = $alt[$index];
1886 my @urls = $cgi->param('url');
1889 for my $index (0..$#images) {
1890 $index < @urls or next;
1891 $images[$index]{url} = $urls[$index];
1895 my @names = map scalar($cgi->param('name'.$_)), 0..$#images;
1897 # make sure there aren't any dups
1900 for my $name (@names) {
1901 defined $name or $name = '';
1903 if ($name =~ /^[a-z_]\w*$/i) {
1904 if ($used{lc $name}++) {
1905 $errors{"name$index"} = 'Names must be empty, or alphanumeric and unique to the article';
1909 $errors{"name$index"} = 'Image identifiers must be unique to the article';
1912 unless ($errors{"name$index"}) {
1914 $self->validate_image_name($name, \$msg)
1915 or $errors{"name$index"} = $msg;
1922 and return $self->edit_form($req, $article, $articles, undef,
1924 for my $index (0..$#images) {
1925 $images[$index]{name} = $names[$index];
1928 for my $image (@images) {
1933 use Util 'generate_article';
1934 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
1936 return $self->refresh($article, $cgi, undef, 'Image information saved');
1940 my ($self, $req, $article, $articles) = @_;
1942 $req->user_can(edit_images_add => $article)
1943 or return $self->edit_form($req, $article, $articles,
1944 "You don't have access to add new images to this article");
1946 my $cgi = $req->cgi;
1950 my $imageref = $cgi->param('name');
1951 if (defined $imageref && $imageref ne '') {
1952 if ($imageref =~ /^[a-z_]\w+$/i) {
1953 # make sure it's unique
1954 my @images = $self->get_images($article);
1955 for my $img (@images) {
1956 if (defined $img->{name} && lc $img->{name} eq lc $imageref) {
1957 $errors{name} = 'Duplicate image name';
1963 $errors{name} = 'Name must be empty or alphanumeric';
1969 unless ($errors{name}) {
1971 $self->validate_image_name($imageref, \$workmsg)
1972 or $errors{name} = $workmsg;
1975 my $image = $cgi->param('image');
1978 $errors{image} = 'Image file is empty';
1982 #$msg = 'Enter or select the name of an image file on your machine';
1983 $errors{image} = 'Please enter an image filename';
1985 if ($msg || keys %errors) {
1986 return $self->edit_form($req, $article, $articles, $msg, \%errors);
1989 my $imagename = $image;
1990 $imagename .= ''; # force it into a string
1992 $imagename =~ /([\w.-]+)$/ and $basename = $1;
1994 # create a filename that we hope is unique
1995 my $filename = time. '_'. $basename;
1997 # for the sysopen() constants
2000 my $imagedir = $req->cfg->entry('paths', 'images', $Constants::IMAGEDIR);
2001 # loop until we have a unique filename
2003 $filename = time. '_' . $counter . '_' . $basename
2004 until sysopen( OUTPUT, "$imagedir/$filename", O_WRONLY| O_CREAT| O_EXCL)
2005 || ++$counter > 100;
2007 fileno(OUTPUT) or die "Could not open image file: $!";
2009 # for OSs with special text line endings
2016 # read the image in from the browser and output it to our output filehandle
2017 print OUTPUT $buffer while read $image, $buffer, 1024;
2021 or die "Could not close image file $filename: $!";
2026 my($width,$height) = imgsize("$imagedir/$filename");
2028 my $alt = $cgi->param('altIn');
2029 defined $alt or $alt = '';
2030 my $url = $cgi->param('url');
2031 defined $url or $url = '';
2034 articleId => $article->{id},
2044 my @cols = Image->columns;
2046 my $imageobj = Images->add(@image{@cols});
2048 use Util 'generate_article';
2049 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2051 return $self->refresh($article, $cgi, undef, 'New image added');
2056 my ($self, $req, $article, $articles, $imageid) = @_;
2058 $req->user_can(edit_images_delete => $article)
2059 or return $self->edit_form($req, $article, $articles,
2060 "You don't have access to delete images from this article");
2064 my @images = $self->get_images($article);
2065 my ($image) = grep $_->{id} == $imageid, @images
2066 or return $self->show_images($req, $article, $articles, "No such image");
2067 my $imagedir = $req->cfg->entry('paths', 'images', $Constants::IMAGEDIR);
2068 unlink "$imagedir$image->{image}";
2071 use Util 'generate_article';
2072 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2074 return $self->refresh($article, $req->cgi, undef, 'Image removed');
2078 my ($self, $req, $article, $articles) = @_;
2080 $req->user_can(edit_images_reorder => $article)
2081 or return $self->edit_form($req, $article, $articles,
2082 "You don't have access to reorder images in this article");
2084 my $imageid = $req->cgi->param('imageid');
2085 my @images = $self->get_images($article);
2086 my ($imgindex) = grep $images[$_]{id} == $imageid, 0..$#images
2087 or return $self->edit_form($req, $article, $articles, "No such image");
2089 or return $self->edit_form($req, $article, $articles, "Image is already at the top");
2090 my ($to, $from) = @images[$imgindex-1, $imgindex];
2091 ($to->{displayOrder}, $from->{displayOrder}) =
2092 ($from->{displayOrder}, $to->{displayOrder});
2096 use Util 'generate_article';
2097 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2099 return $self->refresh($article, $req->cgi, undef, 'Image moved');
2103 my ($self, $req, $article, $articles) = @_;
2105 $req->user_can(edit_images_reorder => $article)
2106 or return $self->edit_form($req, $article, $articles,
2107 "You don't have access to reorder images in this article");
2109 my $imageid = $req->cgi->param('imageid');
2110 my @images = $self->get_images($article);
2111 my ($imgindex) = grep $images[$_]{id} == $imageid, 0..$#images
2112 or return $self->edit_form($req, $article, $articles, "No such image");
2113 $imgindex < $#images
2114 or return $self->edit_form($req, $article, $articles, "Image is already at the end");
2115 my ($to, $from) = @images[$imgindex+1, $imgindex];
2116 ($to->{displayOrder}, $from->{displayOrder}) =
2117 ($from->{displayOrder}, $to->{displayOrder});
2121 use Util 'generate_article';
2122 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2124 return $self->refresh($article, $req->cgi, undef, 'Image moved');
2128 my ($self, $articles, $article) = @_;
2134 my ($self, $articles) = @_;
2149 tsv text/tab-separated-values
2161 eps application/postscript
2162 ps application/postscript
2163 doc application/msword
2167 tar application/x-tar
2168 tgz application/gzip
2169 hqx application/mac-binhex40
2170 bin application/macbinary
2171 sit application/x-stuffit
2172 Z application/x-compress
2173 xls application/ms-excel
2174 ppt application/ms-powerpoint
2175 swf application/x-shockwave-flash
2176 fla application/x-shockwave-flash
2177 dxr application/x-director
2178 dcr application/x-director
2179 dir application/x-director
2181 moov video/quicktime
2196 ra audio/x-realaudio
2197 ram audio/x-pn-realaudio
2198 rm audio/vnd.rm-realmedia
2209 sub _refresh_filelist {
2210 my ($self, $req, $article, $msg) = @_;
2212 return $self->refresh($article, $req->cgi, undef, $msg);
2216 my ($self, $req, $article, $articles, $msg, $errors) = @_;
2219 %acts = $self->low_edit_tags(\%acts, $req, $article, $articles, $msg, $errors);
2220 my $template = 'admin/filelist';
2222 return BSE::Template->get_response($template, $req->cfg, \%acts);
2226 my ($self, $req, $article, $articles) = @_;
2228 $req->user_can(edit_files_add => $article)
2229 or return $self->edit_form($req, $article, $articles,
2230 "You don't have access to add files to this article");
2233 my $cgi = $req->cgi;
2234 require ArticleFile;
2235 my @cols = ArticleFile->columns;
2237 for my $col (@cols) {
2238 if (defined $cgi->param($col)) {
2239 $file{$col} = $cgi->param($col);
2243 $file{forSale} = 0 + exists $file{forSale};
2244 $file{articleId} = $article->{id};
2245 $file{download} = 0 + exists $file{download};
2246 $file{requireUser} = 0 + exists $file{requireUser};
2248 my $downloadPath = $self->{cfg}->entryVar('paths', 'downloads');
2251 my $file = $cgi->param('file');
2253 return $self->edit_form($req, $article, $articles,
2254 "Enter or select the name of a file on your machine",
2255 { file => 'Please enter a filename' });
2258 return $self->edit_form($req, $article, $articles,
2260 { file => 'File is empty' });
2263 unless ($file{contentType}) {
2264 unless ($file =~ /\.([^.]+)$/) {
2265 $file{contentType} = "application/octet-stream";
2267 unless ($file{contentType}) {
2269 my $type = $types{$ext};
2271 $type = $self->{cfg}->entry('extensions', $ext)
2272 || $self->{cfg}->entry('extensions', ".$ext")
2273 || "application/octet-stream";
2275 $file{contentType} = $type;
2280 my $workfile = $file;
2281 $workfile =~ s![^\w.:/\\-]+!_!g;
2282 $workfile =~ tr/_/_/s;
2283 $workfile =~ /([ \w.-]+)$/ and $basename = $1;
2284 $basename =~ tr/ /_/;
2286 my $filename = time. '_'. $basename;
2288 # for the sysopen() constants
2291 # loop until we have a unique filename
2293 $filename = time. '_' . $counter . '_' . $basename
2294 until sysopen( OUTPUT, "$downloadPath/$filename",
2295 O_WRONLY| O_CREAT| O_EXCL)
2296 || ++$counter > 100;
2298 fileno(OUTPUT) or die "Could not open file: $!";
2300 # for OSs with special text line endings
2307 # read the image in from the browser and output it to our output filehandle
2308 print OUTPUT $buffer while read $file, $buffer, 8192;
2312 or die "Could not close file $filename: $!";
2314 use BSE::Util::SQL qw/now_datetime/;
2315 $file{filename} = $filename;
2316 $file{displayName} = $basename;
2317 $file{sizeInBytes} = -s $file;
2318 $file{displayOrder} = time;
2319 $file{whenUploaded} = now_datetime();
2321 require ArticleFiles;
2322 my $fileobj = ArticleFiles->add(@file{@cols});
2324 use Util 'generate_article';
2325 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2327 $self->_refresh_filelist($req, $article, 'New file added');
2331 my ($self, $req, $article, $articles) = @_;
2333 $req->user_can('edit_files_reorder', $article)
2334 or return $self->edit_form($req, $article, $articles,
2335 "You don't have access to reorder files in this article");
2337 my $cgi = $req->cgi;
2338 my $id1 = $cgi->param('file1');
2339 my $id2 = $cgi->param('file2');
2342 my @files = $article->files;
2344 my ($file1) = grep $_->{id} == $id1, @files;
2345 my ($file2) = grep $_->{id} == $id2, @files;
2347 if ($file1 && $file2) {
2348 ($file1->{displayOrder}, $file2->{displayOrder})
2349 = ($file2->{displayOrder}, $file1->{displayOrder});
2355 use Util 'generate_article';
2356 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2358 $self->refresh($article, $req->cgi, undef, 'File moved');
2362 my ($self, $req, $article, $articles) = @_;
2364 $req->user_can('edit_files_delete', $article)
2365 or return $self->edit_form($req, $article, $articles,
2366 "You don't have access to delete files from this article");
2368 my $cgi = $req->cgi;
2369 my $fileid = $cgi->param('file');
2371 my @files = $article->files;
2373 my ($file) = grep $_->{id} == $fileid, @files;
2376 my $downloadPath = $req->cfg->entryErr('paths', 'downloads');
2377 my $filename = $downloadPath . "/" . $file->{filename};
2378 my $debug_del = $req->cfg->entryBool('debug', 'file_unlink', 0);
2381 or print STDERR "Error deleting $filename: $!\n";
2390 use Util 'generate_article';
2391 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2393 $self->_refresh_filelist($req, $article, 'File deleted');
2397 my ($self, $req, $article, $articles) = @_;
2399 $req->user_can('edit_files_save', $article)
2400 or return $self->edit_form($req, $article, $articles,
2401 "You don't have access to save file information for this article");
2402 my @files = $article->files;
2404 my $cgi = $req->cgi;
2405 for my $file (@files) {
2406 if (defined $cgi->param("description_$file->{id}")) {
2407 $file->{description} = $cgi->param("description_$file->{id}");
2408 if (my $type = $cgi->param("contentType_$file->{id}")) {
2409 $file->{contentType} = $type;
2411 $file->{download} = 0 + defined $cgi->param("download_$file->{id}");
2412 $file->{forSale} = 0 + defined $cgi->param("forSale_$file->{id}");
2413 $file->{requireUser} = 0 + defined $cgi->param("requireUser_$file->{id}");
2418 use Util 'generate_article';
2419 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2421 $self->_refresh_filelist($req, $article, 'File information saved');
2425 my ($self, $req, $article, $articles, $rmsg) = @_;
2427 unless ($req->user_can('edit_delete_article', $article, $rmsg)) {
2428 $$rmsg ||= "Access denied";
2432 if ($articles->children($article->{id})) {
2433 $$rmsg = "This article has children. You must delete the children first (or change their parents)";
2436 if (grep $_ == $article->{id}, @Constants::NO_DELETE) {
2437 $$rmsg = "Sorry, these pages are essential to the site structure - they cannot be deleted";
2440 if ($article->{id} == $Constants::SHOPID) {
2441 $$rmsg = "Sorry, these pages are essential to the store - they cannot be deleted - you may want to hide the the store instead.";
2449 my ($self, $req, $article, $articles) = @_;
2452 unless ($self->can_remove($req, $article, $articles, \$why_not)) {
2453 return $self->edit_form($req, $article, $articles, $why_not);
2457 my @images = Images->getBy(articleId=>$article->{id});
2458 my $imagedir = $self->{cfg}->entry('paths', 'images', $Constants::IMAGEDIR);
2459 for my $image (@images) {
2460 unlink("$imagedir/$image->{image}");
2464 # remove any step(child|parent) links
2465 require OtherParents;
2466 my @steprels = OtherParents->anylinks($article->{id});
2467 for my $link (@steprels) {
2471 my $parentid = $article->{parentid};
2473 my $url = $req->cgi->param('r');
2475 my $urlbase = admin_base_url($req->cfg);
2476 $url = "$urlbase$ENV{SCRIPT_NAME}?id=$parentid";
2477 $url .= "&message=Article+deleted";
2479 return BSE::Template->get_refresh($url, $self->{cfg});
2483 my ($self, $req, $article, $articles) = @_;
2485 if ($req->user_can(edit_field_edit_listed => $article)
2486 && $req->user_can(edit_save => $article)) {
2487 $article->{listed} = 1;
2490 use Util 'generate_article';
2491 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2493 return $self->refresh($article, $req->cgi, undef, 'Article unhidden');
2497 my ($self, $req, $article, $articles) = @_;
2499 if ($req->user_can(edit_field_edit_listed => $article)
2500 && $req->user_can(edit_save => $article)) {
2501 $article->{listed} = 0;
2504 use Util 'generate_article';
2505 generate_article($articles, $article) if $Constants::AUTO_GENERATE;
2507 my $r = $req->cgi->param('r');
2509 $r = admin_base_url($req->cfg)
2510 . "/cgi-bin/admin/add.pl?id=" . $article->{parentid};
2512 return $self->refresh($article, $req->cgi, undef, 'Article hidden');
2516 my ($self, $req, $article, $col) = @_;
2518 if ($article->{parentid}) {
2519 my $section = "children of $article->{parentid}";
2520 my $value = $req->cfg->entry($section, $col);
2521 if (defined $value) {
2525 my $section = "level $article->{level}";
2526 my $value = $req->cfg->entry($section, $col);
2527 defined($value) and return $value;
2533 return ( 'article flags' );
2539 my $cfg = $self->{cfg};
2541 my @sections = $self->flag_sections;
2543 my %flags = map $cfg->entriesCS($_), reverse @sections;
2544 my @valid = grep /^\w$/, keys %flags;
2546 return map +{ id => $_, desc => $flags{$_} },
2547 sort { lc($flags{$a}) cmp lc($flags{$b}) }@valid;
2551 my ($self, $article) = @_;
2556 sub validate_image_name {
2557 my ($self, $name, $rmsg) = @_;
2559 1; # no extra validation
2566 BSE::Edit::Article - editing functionality for BSE articles
2570 Tony Cook <tony@develop-help.com>