Code

gitweb: Refactor git_header_html
[git.git] / gitweb / gitweb.perl
index 02d8839b9c64d65db6c3713555a317c119bac9f4..c1f45159889cfa9f0079a812d521bd6ea39d6349 100755 (executable)
@@ -328,6 +328,7 @@ our %feature = (
        # Enable grep search, which will list the files in currently selected
        # tree containing the given string. Enabled by default. This can be
        # potentially CPU-intensive, of course.
+       # Note that you need to have 'search' feature enabled too.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'grep'}{'default'} = [1];
@@ -342,6 +343,7 @@ our %feature = (
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
+       # Note that you need to have 'search' feature enabled too.
 
        # To enable system wide have in $GITWEB_CONFIG
        # $feature{'pickaxe'}{'default'} = [1];
@@ -491,6 +493,18 @@ our %feature = (
                'override' => 0,
                'default' => [0]},
 
+       # Enable and configure ability to change common timezone for dates
+       # in gitweb output via JavaScript.  Enabled by default.
+       # Project specific override is not supported.
+       'javascript-timezone' => {
+               'override' => 0,
+               'default' => [
+                       'local',     # default timezone: 'utc', 'local', or '(-|+)HHMM' format,
+                                    # or undef to turn off this feature
+                       'gitweb_tz', # name of cookie where to store selected timezone
+                       'datetime',  # CSS class used to mark up dates for manipulation
+               ]},
+
        # Syntax highlighting support. This is based on Daniel Svensson's
        # and Sham Chukoury's work in gitweb-xmms2.git.
        # It requires the 'highlight' program present in $PATH,
@@ -2632,7 +2646,7 @@ sub git_get_project_ctags {
                        close $ct;
 
                        (my $ctag = $tagfile) =~ s#.*/##;
-                       if ($val =~ /\d+/) {
+                       if ($val =~ /^\d+$/) {
                                $ctags->{$ctag} = $val;
                        } else {
                                $ctags->{$ctag} = 1;
@@ -3669,6 +3683,20 @@ sub get_page_title {
        return $title;
 }
 
+sub get_content_type_html {
+       # require explicit support from the UA if we are to send the page as
+       # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
+       # we have to do this because MSIE sometimes globs '*/*', pretending to
+       # support xhtml+xml but choking when it gets what it asked for.
+       if (defined $cgi->http('HTTP_ACCEPT') &&
+           $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
+           $cgi->Accept('application/xhtml+xml') != 0) {
+               return 'application/xhtml+xml';
+       } else {
+               return 'text/html';
+       }
+}
+
 sub print_feed_meta {
        if (defined $project) {
                my %href_params = get_feed_info();
@@ -3714,24 +3742,90 @@ sub print_feed_meta {
        }
 }
 
+sub print_header_links {
+       my $status = shift;
+
+       # print out each stylesheet that exist, providing backwards capability
+       # for those people who defined $stylesheet in a config file
+       if (defined $stylesheet) {
+               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+       } else {
+               foreach my $stylesheet (@stylesheets) {
+                       next unless $stylesheet;
+                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
+               }
+       }
+       print_feed_meta()
+               if ($status eq '200 OK');
+       if (defined $favicon) {
+               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
+       }
+}
+
+sub print_nav_breadcrumbs {
+       my %opts = @_;
+
+       print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
+       if (defined $project) {
+               print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
+               if (defined $action) {
+                       my $action_print = $action ;
+                       if (defined $opts{-action_extra}) {
+                               $action_print = $cgi->a({-href => href(action=>$action)},
+                                       $action);
+                       }
+                       print " / $action_print";
+               }
+               if (defined $opts{-action_extra}) {
+                       print " / $opts{-action_extra}";
+               }
+               print "\n";
+       }
+}
+
+sub print_search_form {
+       if (!defined $searchtext) {
+               $searchtext = "";
+       }
+       my $search_hash;
+       if (defined $hash_base) {
+               $search_hash = $hash_base;
+       } elsif (defined $hash) {
+               $search_hash = $hash;
+       } else {
+               $search_hash = "HEAD";
+       }
+       my $action = $my_uri;
+       my $use_pathinfo = gitweb_check_feature('pathinfo');
+       if ($use_pathinfo) {
+               $action .= "/".esc_url($project);
+       }
+       print $cgi->startform(-method => "get", -action => $action) .
+             "<div class=\"search\">\n" .
+             (!$use_pathinfo &&
+             $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
+             $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
+             $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
+             $cgi->popup_menu(-name => 'st', -default => 'commit',
+                              -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
+             $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
+             " search:\n",
+             $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
+             "<span title=\"Extended regular expression\">" .
+             $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
+                            -checked => $search_use_regexp) .
+             "</span>" .
+             "</div>" .
+             $cgi->end_form() . "\n";
+}
+
 sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
        my %opts = @_;
 
        my $title = get_page_title();
-       my $content_type;
-       # require explicit support from the UA if we are to send the page as
-       # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
-       # we have to do this because MSIE sometimes globs '*/*', pretending to
-       # support xhtml+xml but choking when it gets what it asked for.
-       if (defined $cgi->http('HTTP_ACCEPT') &&
-           $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ &&
-           $cgi->Accept('application/xhtml+xml') != 0) {
-               $content_type = 'application/xhtml+xml';
-       } else {
-               $content_type = 'text/html';
-       }
+       my $content_type = get_content_type_html();
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
                           -status=> $status, -expires => $expires)
                unless ($opts{'-no_http_header'});
@@ -3753,22 +3847,7 @@ EOF
        if ($ENV{'PATH_INFO'}) {
                print "<base href=\"".esc_url($base_url)."\" />\n";
        }
-       # print out each stylesheet that exist, providing backwards capability
-       # for those people who defined $stylesheet in a config file
-       if (defined $stylesheet) {
-               print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
-       } else {
-               foreach my $stylesheet (@stylesheets) {
-                       next unless $stylesheet;
-                       print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n";
-               }
-       }
-       print_feed_meta()
-               if ($status eq '200 OK');
-       if (defined $favicon) {
-               print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n);
-       }
-
+       print_header_links($status);
        print "</head>\n" .
              "<body>\n";
 
@@ -3785,59 +3864,12 @@ EOF
                                         -alt => "git",
                                         -class => "logo"}));
        }
-       print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
-       if (defined $project) {
-               print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
-               if (defined $action) {
-                       my $action_print = $action ;
-                       if (defined $opts{-action_extra}) {
-                               $action_print = $cgi->a({-href => href(action=>$action)},
-                                       $action);
-                       }
-                       print " / $action_print";
-               }
-               if (defined $opts{-action_extra}) {
-                       print " / $opts{-action_extra}";
-               }
-               print "\n";
-       }
+       print_nav_breadcrumbs(%opts);
        print "</div>\n";
 
        my $have_search = gitweb_check_feature('search');
        if (defined $project && $have_search) {
-               if (!defined $searchtext) {
-                       $searchtext = "";
-               }
-               my $search_hash;
-               if (defined $hash_base) {
-                       $search_hash = $hash_base;
-               } elsif (defined $hash) {
-                       $search_hash = $hash;
-               } else {
-                       $search_hash = "HEAD";
-               }
-               my $action = $my_uri;
-               my $use_pathinfo = gitweb_check_feature('pathinfo');
-               if ($use_pathinfo) {
-                       $action .= "/".esc_url($project);
-               }
-               print $cgi->startform(-method => "get", -action => $action) .
-                     "<div class=\"search\">\n" .
-                     (!$use_pathinfo &&
-                     $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") .
-                     $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" .
-                     $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" .
-                     $cgi->popup_menu(-name => 'st', -default => 'commit',
-                                      -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) .
-                     $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) .
-                     " search:\n",
-                     $cgi->textfield(-name => "s", -value => $searchtext) . "\n" .
-                     "<span title=\"Extended regular expression\">" .
-                     $cgi->checkbox(-name => 'sr', -value => 1, -label => 're',
-                                    -checked => $search_use_regexp) .
-                     "</span>" .
-                     "</div>" .
-                     $cgi->end_form() . "\n";
+               print_search_form();
        }
 }
 
@@ -3897,9 +3929,20 @@ sub git_footer_html {
                      qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
                      qq!           "!. href() .qq!");\n!.
                      qq!</script>\n!;
-       } elsif (gitweb_check_feature('javascript-actions')) {
+       } else {
+               my ($jstimezone, $tz_cookie, $datetime_class) =
+                       gitweb_get_feature('javascript-timezone');
+
                print qq!<script type="text/javascript">\n!.
-                     qq!window.onload = fixLinks;\n!.
+                     qq!window.onload = function () {\n!;
+               if (gitweb_check_feature('javascript-actions')) {
+                       print qq!       fixLinks();\n!;
+               }
+               if ($jstimezone && $tz_cookie && $datetime_class) {
+                       print qq!       var tz_cookie = { name: '$tz_cookie', expires: 14, path: '/' };\n!. # in days
+                             qq!       onloadTZSetup('$jstimezone', tz_cookie, '$datetime_class');\n!;
+               }
+               print qq!};\n!.
                      qq!</script>\n!;
        }
 
@@ -4103,22 +4146,25 @@ sub git_print_section {
        print $cgi->end_div;
 }
 
-sub print_local_time {
-       print format_local_time(@_);
-}
+sub format_timestamp_html {
+       my $date = shift;
+       my $strtime = $date->{'rfc2822'};
 
-sub format_local_time {
-       my $localtime = '';
-       my %date = @_;
-       if ($date{'hour_local'} < 6) {
-               $localtime .= sprintf(" (<span class=\"atnight\">%02d:%02d</span> %s)",
-                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
-       } else {
-               $localtime .= sprintf(" (%02d:%02d %s)",
-                       $date{'hour_local'}, $date{'minute_local'}, $date{'tz_local'});
+       my (undef, undef, $datetime_class) =
+               gitweb_get_feature('javascript-timezone');
+       if ($datetime_class) {
+               $strtime = qq!<span class="$datetime_class">$strtime</span>!;
        }
 
-       return $localtime;
+       my $localtime_format = '(%02d:%02d %s)';
+       if ($date->{'hour_local'} < 6) {
+               $localtime_format = '(<span class="atnight">%02d:%02d</span> %s)';
+       }
+       $strtime .= ' ' .
+                   sprintf($localtime_format,
+                           $date->{'hour_local'}, $date->{'minute_local'}, $date->{'tz_local'});
+
+       return $strtime;
 }
 
 # Outputs the author name and date in long form
@@ -4131,10 +4177,9 @@ sub git_print_authorship {
        my %ad = parse_date($co->{'author_epoch'}, $co->{'author_tz'});
        print "<$tag class=\"author_date\">" .
              format_search_author($author, "author", esc_html($author)) .
-             " [$ad{'rfc2822'}";
-       print_local_time(%ad) if ($opts{-localtime});
-       print "]" . git_get_avatar($co->{'author_email'}, -pad_before => 1)
-                 . "</$tag>\n";
+             " [".format_timestamp_html(\%ad)."]".
+             git_get_avatar($co->{'author_email'}, -pad_before => 1) .
+             "</$tag>\n";
 }
 
 # Outputs table rows containing the full author or committer information,
@@ -4151,16 +4196,16 @@ sub git_print_authorship_rows {
                my %wd = parse_date($co->{"${who}_epoch"}, $co->{"${who}_tz"});
                print "<tr><td>$who</td><td>" .
                      format_search_author($co->{"${who}_name"}, $who,
-                              esc_html($co->{"${who}_name"})) . " " .
+                                          esc_html($co->{"${who}_name"})) . " " .
                      format_search_author($co->{"${who}_email"}, $who,
-                              esc_html("<" . $co->{"${who}_email"} . ">")) .
+                                          esc_html("<" . $co->{"${who}_email"} . ">")) .
                      "</td><td rowspan=\"2\">" .
                      git_get_avatar($co->{"${who}_email"}, -size => 'double') .
                      "</td></tr>\n" .
                      "<tr>" .
-                     "<td></td><td> $wd{'rfc2822'}";
-               print_local_time(%wd);
-               print "</td>" .
+                     "<td></td><td>" .
+                     format_timestamp_html(\%wd) .
+                     "</td>" .
                      "</tr>\n";
        }
 }
@@ -5648,7 +5693,8 @@ sub git_summary {
              "<tr id=\"metadata_desc\"><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" .
              "<tr id=\"metadata_owner\"><td>owner</td><td>" . esc_html($owner) . "</td></tr>\n";
        if (defined $cd{'rfc2822'}) {
-               print "<tr id=\"metadata_lchange\"><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n";
+               print "<tr id=\"metadata_lchange\"><td>last change</td>" .
+                     "<td>".format_timestamp_html(\%cd)."</td></tr>\n";
        }
 
        # use per project git URL list in $projectroot/$project/cloneurl
@@ -6101,7 +6147,7 @@ sub git_blob_plain {
        # want to be sure not to break that by serving the image as an
        # attachment (though Firefox 3 doesn't seem to care).
        my $sandbox = $prevent_xss &&
-               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))$!;
+               $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!;
 
        print $cgi->header(
                -type => $type,