X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/blobdiff_plain/da26603e0780b3079ee8e94ec3651fdc615cc3f9..98f0d672dfcbc178b80a2d5f9a8b2f2633c8f039:/IkiWiki/Plugin/po.pm diff --git a/IkiWiki/Plugin/po.pm b/IkiWiki/Plugin/po.pm index 7cec589ab..1955ac304 100644 --- a/IkiWiki/Plugin/po.pm +++ b/IkiWiki/Plugin/po.pm @@ -10,6 +10,16 @@ use warnings; use strict; use IkiWiki 3.00; use Encode; +BEGIN { + eval 'use Locale::Po4a::Common qw(nowrapi18n)'; + if ($@) { + warning(sprintf(gettext('%s is too old, can not disable %s; '. + 'a denial of service can thus be '. + 'triggered by malicious content'), + 'Locale::Po4a', 'Text::WrapI18N')); + eval 'use Locale::Po4a::Common'; + } +} use Locale::Po4a::Chooser; use Locale::Po4a::Po; use File::Basename; @@ -39,7 +49,7 @@ sub import { hook(type => "rename", id => "po", call => \&renamepages, first => 1); hook(type => "delete", id => "po", call => \&mydelete); hook(type => "change", id => "po", call => \&change); - hook(type => "cansave", id => "po", call => \&cansave); + hook(type => "checkcontent", id => "po", call => \&checkcontent); hook(type => "canremove", id => "po", call => \&canremove); hook(type => "canrename", id => "po", call => \&canrename); hook(type => "editcontent", id => "po", call => \&editcontent); @@ -56,6 +66,8 @@ sub import { inject(name => "IkiWiki::urlto", call => \&myurlto); $origsubs{'nicepagetitle'}=\&IkiWiki::nicepagetitle; inject(name => "IkiWiki::nicepagetitle", call => \&mynicepagetitle); + $origsubs{'cgiurl'}=\&IkiWiki::cgiurl; + inject(name => "IkiWiki::cgiurl", call => \&mycgiurl); } @@ -128,7 +140,8 @@ sub getsetup () { sub checkconfig () { foreach my $field (qw{po_master_language po_slave_languages}) { if (! exists $config{$field} || ! defined $config{$field}) { - error(sprintf(gettext("Must specify %s when using the %s plugin"), $field, 'po')); + error(sprintf(gettext("Must specify %s when using the %s plugin"), + $field, 'po')); } } if (! (keys %{$config{po_slave_languages}})) { @@ -150,8 +163,8 @@ sub checkconfig () { elsif (! grep { $config{po_link_to} eq $_ } ('default', 'current', 'negotiated')) { - warn(sprintf(gettext('po_link_to=%s is not a valid setting, falling back to po_link_to=default'), - $config{po_link_to})); + warn(sprintf(gettext('%s is not a valid value for po_link_to, falling back to po_link_to=default'), + $config{po_link_to})); $config{po_link_to}='default'; } elsif ($config{po_link_to} eq "negotiated" && ! $config{usedirs}) { @@ -262,7 +275,7 @@ sub pagetemplate (@) { $template->param(percenttranslated => percenttranslated($page)); } if ($template->query(name => "istranslation")) { - $template->param(istranslation => scalar istranslation($page)); + $template->param(istranslation => istranslation($page)); } if ($template->query(name => "istranslatable")) { $template->param(istranslatable => istranslatable($page)); @@ -354,6 +367,42 @@ sub mydelete(@) { sub change(@) { my @rendered=@_; + # All meta titles are first extracted at scan time, i.e. before we turn + # PO files back into translated markdown; escaping of double-quotes in + # PO files breaks the meta plugin's parsing enough to save ugly titles + # to %pagestate at this time. + # + # Then, at render time, every page's passes on row through the Great + # Rendering Chain (filter->preprocess->linkify->htmlize), and the meta + # plugin's preprocess hook is this time in a position to correctly + # extract the titles from slave pages. + # + # This is, unfortunately, too late: if the page A, linking to the page B, + # is rendered before B, it will display the wrongly-extracted meta title + # as the link text to B. + # + # On the one hand, such a corner case only happens on rebuild: on + # refresh, every rendered page is fixed to contain correct meta titles. + # On the other hand, it can take some time to get every page fixed. + # We therefore re-render every rendered page after a rebuild to fix them + # at once. As this more or less doubles the time needed to rebuild the + # wiki, we do so only when really needed. + + if (@rendered + && exists $config{rebuild} && defined $config{rebuild} && $config{rebuild} + && UNIVERSAL::can("IkiWiki::Plugin::meta", "getsetup") + && exists $config{meta_overrides_page_title} + && defined $config{meta_overrides_page_title} + && $config{meta_overrides_page_title}) { + debug(sprintf(gettext("re-rendering all pages to fix meta titles"))); + resetalreadyfiltered(); + require IkiWiki::Render; + foreach my $file (@rendered) { + debug(sprintf(gettext("rendering %s"), $file)); + IkiWiki::render($file); + } + } + my $updated_po_files=0; # Refresh/create POT and PO files as needed. @@ -387,11 +436,11 @@ sub change(@) { } } -sub cansave ($$$$) { - my ($page, $content, $cgi, $session) = (shift, shift, shift, shift); +sub checkcontent (@) { + my %params=@_; - if (istranslation($page)) { - my $res = isvalidpo($content); + if (istranslation($params{page})) { + my $res = isvalidpo($params{content}); if ($res) { return undef; } @@ -425,7 +474,7 @@ sub canrename ($$@) { # by looking for the master page in the list of to-be-renamed pages we # saved early in the renaming process. my $orig_torename = $session->param("po_orig_torename"); - unless (scalar grep { $_->{src} eq $masterpage } @{$orig_torename}) { + unless (grep { $_->{src} eq $masterpage } @{$orig_torename}) { return gettext("Can not rename a translation. Renaming the master page, ". "though, renames its translations as well."); } @@ -474,28 +523,32 @@ sub formbuilder_setup (@) { } } -# Do not allow to create pages of type po: they are automatically created. -# The main reason to do so is to bypass the "favor the type of linking page -# on page creation" logic, which is unsuitable when a broken link is clicked -# on a slave (PO) page. sub formbuilder (@) { my %params=@_; my $form=$params{form}; my $q=$params{cgi}; - return unless (defined $form->field("do") && $form->field("do") eq "create"); + return unless defined $form->field("do"); - for my $field ($form->field) { - next unless "$field" eq "type"; - if ($field->type eq 'select') { - # remove po from the types list - my @types = grep { $_ ne 'po' } $field->options; - $field->options(\@types) if scalar @types; - } - else { - # make sure the default value is not po; - # does this case actually happen? - debug sprintf("po(formbuilder) type field is not select - not implemented yet"); + # Do not allow to create pages of type po: they are automatically created. + # The main reason to do so is to bypass the "favor the type of linking page + # on page creation" logic, which is unsuitable when a broken link is clicked + # on a slave (PO) page. + # This cannot be done in the formbuilder_setup hook as the list of types is + # computed later. + if ($form->field("do") eq "create") { + foreach my $field ($form->field) { + next unless "$field" eq "type"; + if ($field->type eq 'select') { + # remove po from the list of types + my @types = grep { $_ ne 'po' } $field->options; + $field->options(\@types) if @types; + } + else { + # make sure the default value is not po; + # does this case actually happen? + debug "po(formbuilder) ".gettext("type field is not selected - not implemented yet"); + } } } } @@ -588,9 +641,22 @@ sub mynicepagetitle ($;$) { my $res = $origsubs{'nicepagetitle'}->($page, $unescaped); return $res unless istranslation($page); return $res unless $config{po_translation_status_in_links}; + my @caller = caller(1); + return $res if (exists $caller[3] && defined $caller[3] + && $caller[3] eq "IkiWiki::Plugin::parentlinks::parentlinks"); return $res.' ('.percenttranslated($page).' %)'; } +sub mycgiurl (@) { + my %params=@_; + + # slave pages have no subpages + if (istranslation($params{'from'})) { + $params{'from'} = masterpage($params{'from'}); + } + return $origsubs{'cgiurl'}->(%params); +} + # ,---- # | Blackboxes for private data # `---- @@ -643,8 +709,9 @@ sub istranslatablefile ($) { my $file=shift; return 0 unless defined $file; - return 0 if (defined pagetype($file) && pagetype($file) eq 'po'); + return 0 if defined pagetype($file) && pagetype($file) eq 'po'; return 0 if $file =~ /\.pot$/; + return 0 unless -e "$config{srcdir}/$file"; # underlay dirs may be read-only return 1 if pagespec_match(pagename($file), $config{po_translatable_pages}); return; } @@ -660,18 +727,19 @@ sub istranslatable ($) { sub _istranslation ($) { my $page=shift; + $page='' unless defined $page && length $page; my $hasleadingslash = ($page=~s#^/##); my $file=$pagesources{$page}; - return 0 unless (defined $file + return 0 unless defined $file && defined pagetype($file) - && pagetype($file) eq 'po'); + && pagetype($file) eq 'po'; return 0 if $file =~ /\.pot$/; my ($masterpage, $lang) = ($page =~ /(.*)[.]([a-z]{2})$/); - return 0 unless (defined $masterpage && defined $lang + return 0 unless defined $masterpage && defined $lang && length $masterpage && length $lang && defined $pagesources{$masterpage} - && defined $config{po_slave_languages}{$lang}); + && defined $config{po_slave_languages}{$lang}; return (maybe_add_leading_slash($masterpage, $hasleadingslash), $lang) if istranslatable($masterpage); @@ -685,7 +753,7 @@ sub istranslation ($) { $translations{$masterpage}{$lang}=$page unless exists $translations{$masterpage}{$lang}; return (maybe_add_leading_slash($masterpage, $hasleadingslash), $lang); } - return; + return ""; } sub masterpage ($) { @@ -724,7 +792,7 @@ sub otherlanguages ($) { my $page=shift; my %ret; - return \%ret unless (istranslation($page) || istranslatable($page)); + return \%ret unless istranslation($page) || istranslatable($page); my $curlang=lang($page); foreach my $lang ($config{po_master_language}{code}, keys %{$config{po_slave_languages}}) { @@ -783,20 +851,22 @@ sub refreshpofiles ($@) { my @pofiles=@_; my $potfile=potfile($masterfile); - (-e $potfile) - or error(sprintf(gettext("po(refreshpofiles) POT file (%s) does not exist"), - $potfile)); + if (! -e $potfile) { + error("po(refreshpofiles) ".sprintf(gettext("POT file (%s) does not exist"), $potfile)); + } foreach my $pofile (@pofiles) { IkiWiki::prep_writefile(basename($pofile),dirname($pofile)); if (-e $pofile) { system("msgmerge", "-U", "--backup=none", $pofile, $potfile) == 0 - or error(sprintf(gettext("po(refreshpofiles) failed to update %s"), + or error("po(refreshpofiles) ". + sprintf(gettext("failed to update %s"), $pofile)); } else { File::Copy::syscopy($potfile,$pofile) - or error(sprintf(gettext("po(refreshpofiles) failed to copy the POT file to %s"), + or error("po(refreshpofiles) ". + sprintf(gettext("failed to copy the POT file to %s"), $pofile)); } } @@ -844,8 +914,8 @@ sub percenttranslated ($) { 'file_in_name' => [ $masterfile ], 'file_in_charset' => 'utf-8', 'file_out_charset' => 'utf-8', - ) or error(sprintf(gettext("po(percenttranslated) failed to translate %s"), - $page)); + ) or error("po(percenttranslated) ". + sprintf(gettext("failed to translate %s"), $page)); my ($percent,$hit,$queries) = $doc->stats(); $percent =~ s/\.[0-9]+$//; return $percent; @@ -919,7 +989,7 @@ sub deletetranslations ($) { } } @todelete; - if (scalar @todelete) { + if (@todelete) { commit_and_refresh( gettext("removed obsolete PO files"), "IkiWiki::Plugin::po::deletetranslations"); @@ -1050,7 +1120,8 @@ sub isvalidpo ($) { if ($res) { return IkiWiki::SuccessReason->new("valid gettext data"); } - return IkiWiki::FailReason->new("invalid gettext data"); + return IkiWiki::FailReason->new("invalid gettext data, go back ". + "to previous page to go on with edit"); } # ,---- @@ -1090,7 +1161,7 @@ sub match_lang ($$;@) { my $regexp=IkiWiki::glob2re($wanted); my $lang=IkiWiki::Plugin::po::lang($page); - if ($lang!~/^$regexp$/i) { + if ($lang !~ /^$regexp$/i) { return IkiWiki::FailReason->new("file language is $lang, not $wanted"); } else {