# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

# This exists to implement the template-before_process hook.
package Bugzilla::Template::Context;

use 5.14.0;
use strict;
use warnings;

use base qw(Template::Context);

use Bugzilla::Hook;
use Scalar::Util qw(blessed);

sub process {
  my $self = shift;

  # We don't want to run the template_before_process hook for
  # template hooks (but we do want it to run if a hook calls
  # PROCESS inside itself). The problem is that the {component}->{name} of
  # hooks is unreliable--sometimes it starts with ./ and it's the
  # full path to the hook template, and sometimes it's just the relative
  # name (like hook/global/field-descs-end.none.tmpl). Also, calling
  # template_before_process for hook templates doesn't seem too useful,
  # because that's already part of the extension and they should be able
  # to modify their hook if they want (or just modify the variables in the
  # calling template).
  if (not delete $self->{bz_in_hook}) {
    $self->{bz_in_process} = 1;
  }
  my $result = $self->SUPER::process(@_);
  delete $self->{bz_in_process};
  return $result;
}

# This method is called by Template-Toolkit exactly once per template or
# block (look at a compiled template) so this is an ideal place for us to
# modify the variables before a template or block runs.
#
# We don't do it during Context::process because at that time
# our stash hasn't been set correctly--the parameters we were passed
# in the PROCESS or INCLUDE directive haven't been set, and if we're
# in an INCLUDE, the stash is not yet localized during process().
sub stash {
  my $self  = shift;
  my $stash = $self->SUPER::stash(@_);

  my $name        = $stash->{component}->{name};
  my $pre_process = $self->config->{PRE_PROCESS};

  # Checking bz_in_process tells us that we were indeed called as part of a
  # Context::process, and not at some other point.
  #
  # Checking $name makes sure that we're processing a file, and not just a
  # block, by checking that the name has a period in it. We don't allow
  # blocks because their names are too unreliable--an extension could have
  # a block with the same name, or multiple files could have a same-named
  # block, and then your extension would malfunction.
  #
  # We also make sure that we don't run, ever, during the PRE_PROCESS
  # templates, because if somebody calls Throw*Error globally inside of
  # template_before_process, that causes an infinite recursion into
  # the PRE_PROCESS templates (because Bugzilla, while inside
  # global/intialize.none.tmpl, loads the template again to create the
  # template object for Throw*Error).
  #
  # Checking Bugzilla::Hook::in prevents infinite recursion on this hook.
  if (  $self->{bz_in_process}
    and $name =~ /\./
    and !grep($_ eq $name, @$pre_process)
    and !Bugzilla::Hook::in('template_before_process'))
  {
    Bugzilla::Hook::process("template_before_process",
      {vars => $stash, context => $self, file => $name});
  }

  # This prevents other calls to stash() that might somehow happen
  # later in the file from also triggering the hook.
  delete $self->{bz_in_process};

  return $stash;
}

sub filter {
  my ($self, $name, $args) = @_;

  # If we pass an alias for the filter name, the filter code is cached
  # instead of looking for it at each call.
  # If the filter has arguments, then we can't cache it.
  $self->SUPER::filter($name, $args, $args ? undef : $name);
}

# We need a DESTROY sub for the same reason that Bugzilla::CGI does.
sub DESTROY {
  my $self = shift;
  $self->SUPER::DESTROY(@_);
}

1;

=head1 B<Methods in need of POD>

=over

=item stash

=item filter

=item process

=back
