Update HttpProxy for Moose.
This commit is contained in:
parent
4f2c5495e6
commit
03ccc7febb
1 changed files with 147 additions and 160 deletions
|
|
@ -18,12 +18,134 @@ use HTTP::Request::Common;
|
||||||
use HTML::Entities;
|
use HTML::Entities;
|
||||||
use WebGUI::International;
|
use WebGUI::International;
|
||||||
use WebGUI::Storage;
|
use WebGUI::Storage;
|
||||||
use WebGUI::Asset::Wobject;
|
|
||||||
use WebGUI::Asset::Wobject::HttpProxy::Parse;
|
use WebGUI::Asset::Wobject::HttpProxy::Parse;
|
||||||
use WebGUI::Macro;
|
use WebGUI::Macro;
|
||||||
use Apache2::Upload;
|
use Apache2::Upload;
|
||||||
|
|
||||||
our @ISA = qw(WebGUI::Asset::Wobject);
|
use WebGUI::Definition::Asset;
|
||||||
|
extends 'WebGUI::Asset::Wobject';
|
||||||
|
aspect assetName => ['assetName', 'Asset_HttpProxy'];
|
||||||
|
aspect uiLevel => 5;
|
||||||
|
aspect icon => 'httpProxy.gif';
|
||||||
|
aspect tableName => 'HttpProxy';
|
||||||
|
property templateId => (
|
||||||
|
fieldType => "template",
|
||||||
|
default => 'PBtmpl0000000000000033',
|
||||||
|
namespace => 'HttpProxy',
|
||||||
|
tab => 'display',
|
||||||
|
label => ['http proxy template title', 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['http proxy template title description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property proxiedUrl => (
|
||||||
|
fieldType => "url",
|
||||||
|
default => 'http://',
|
||||||
|
tab => 'properties',
|
||||||
|
label => [1, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['1 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property useAmpersand => (
|
||||||
|
fieldType => "yesNo",
|
||||||
|
default => 0,
|
||||||
|
tab => 'properties',
|
||||||
|
label => ["use ampersand", 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ["use ampersand help", 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property timeout => (
|
||||||
|
fieldType => "selectBox",
|
||||||
|
default => 30,
|
||||||
|
tab => 'properties',
|
||||||
|
options => \&_timeout_options,
|
||||||
|
label => [4, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['4 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
sub _timeout_options {
|
||||||
|
my %timeoutOptions;
|
||||||
|
tie %timeoutOptions, 'Tie::IxHash';
|
||||||
|
%timeoutOptions = map{$_ => $_} (5, 10, 20, 30, 60);
|
||||||
|
return \%timeoutOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
property removeStyle => (
|
||||||
|
fieldType => "yesNo",
|
||||||
|
default => 1,
|
||||||
|
tab => 'display',
|
||||||
|
label => [6, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['6 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property cacheTimeout => (
|
||||||
|
fieldType => "interval",
|
||||||
|
default => 0,
|
||||||
|
tab => 'display',
|
||||||
|
label => ['cache timeout', 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['cache timeout description', 'Asset_HttpProxy'],
|
||||||
|
uiLevel => 8,
|
||||||
|
);
|
||||||
|
|
||||||
|
property filterHtml => (
|
||||||
|
fieldType => "filterContent",
|
||||||
|
default => "javascript",
|
||||||
|
tab => 'display',
|
||||||
|
label => [418, 'WebGUI', 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['418 description', 'WebGUI', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property urlPatternFilter => (
|
||||||
|
fieldType => "textarea",
|
||||||
|
default => "",
|
||||||
|
tab => "display",
|
||||||
|
label => ["url pattern filter label", 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ["url pattern filter hover help", 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property followExternal => (
|
||||||
|
fieldType => "yesNo",
|
||||||
|
default => 1,
|
||||||
|
tab => 'security',
|
||||||
|
label => [5, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['5 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property rewriteUrls => (
|
||||||
|
fieldType => "yesNo",
|
||||||
|
default => 1,
|
||||||
|
tab => 'properties',
|
||||||
|
label => [12, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['12 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property followRedirect => (
|
||||||
|
fieldType => "yesNo",
|
||||||
|
default => 0,
|
||||||
|
tab => 'security',
|
||||||
|
label => [8, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['8 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property searchFor => (
|
||||||
|
fieldType => "text",
|
||||||
|
default => undef,
|
||||||
|
tab => 'display',
|
||||||
|
label => [13, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['13 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property stopAt => (
|
||||||
|
fieldType => "text",
|
||||||
|
default => undef,
|
||||||
|
tab => 'display',
|
||||||
|
label => [14, 'Asset_HttpProxy'],
|
||||||
|
hoverHelp => ['14 description', 'Asset_HttpProxy'],
|
||||||
|
);
|
||||||
|
|
||||||
|
property cookieJarStorageId => (
|
||||||
|
noFormPost => 1,
|
||||||
|
fieldType => "hidden",
|
||||||
|
default => undef
|
||||||
|
);
|
||||||
|
|
||||||
#-------------------------------------------------------------------
|
#-------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -47,7 +169,7 @@ sub appendToUrl {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $url = shift;
|
my $url = shift;
|
||||||
my $paramSet = shift;
|
my $paramSet = shift;
|
||||||
my $seperator = ($self->get("useAmpersand")) ? "&" : ";";
|
my $seperator = ($self->useAmpersand) ? "&" : ";";
|
||||||
if (index($url, '?') == length($url)-1) {
|
if (index($url, '?') == length($url)-1) {
|
||||||
$url .= $paramSet;
|
$url .= $paramSet;
|
||||||
} elsif (index($url, '?') >= 0) {
|
} elsif (index($url, '?') >= 0) {
|
||||||
|
|
@ -59,141 +181,6 @@ sub appendToUrl {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------
|
|
||||||
sub definition {
|
|
||||||
my $class = shift;
|
|
||||||
my $session = shift;
|
|
||||||
my $definition = shift;
|
|
||||||
my $i18n = WebGUI::International->new($session,"Asset_HttpProxy");
|
|
||||||
my %timeoutOptions;
|
|
||||||
tie %timeoutOptions, 'Tie::IxHash';
|
|
||||||
%timeoutOptions = map{$_ => $_} (5, 10, 20, 30, 60);
|
|
||||||
|
|
||||||
push(@{$definition}, {
|
|
||||||
assetName => $i18n->get('assetName'),
|
|
||||||
uiLevel => 5,
|
|
||||||
icon => 'httpProxy.gif',
|
|
||||||
tableName => 'HttpProxy',
|
|
||||||
className => 'WebGUI::Asset::Wobject::HttpProxy',
|
|
||||||
autoGenerateForms => 1,
|
|
||||||
properties => {
|
|
||||||
templateId => {
|
|
||||||
fieldType => "template",
|
|
||||||
defaultValue => 'PBtmpl0000000000000033',
|
|
||||||
namespace => 'HttpProxy',
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get('http proxy template title'),
|
|
||||||
hoverHelp => $i18n->get('http proxy template title description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
proxiedUrl => {
|
|
||||||
fieldType => "url",
|
|
||||||
defaultValue => 'http://',
|
|
||||||
tab => 'properties',
|
|
||||||
label => $i18n->get(1),
|
|
||||||
hoverHelp => $i18n->get('1 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
useAmpersand => {
|
|
||||||
fieldType => "yesNo",
|
|
||||||
defaultValue => 0,
|
|
||||||
tab => 'properties',
|
|
||||||
label => $i18n->get("use ampersand"),
|
|
||||||
hoverHelp => $i18n->get("use ampersand help")
|
|
||||||
},
|
|
||||||
|
|
||||||
timeout => {
|
|
||||||
fieldType => "selectBox",
|
|
||||||
defaultValue => 30,
|
|
||||||
tab => 'properties',
|
|
||||||
options => \%timeoutOptions,
|
|
||||||
label => $i18n->get(4),
|
|
||||||
hoverHelp => $i18n->get('4 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
removeStyle => {
|
|
||||||
fieldType => "yesNo",
|
|
||||||
defaultValue => 1,
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get(6),
|
|
||||||
hoverHelp => $i18n->get('6 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
cacheTimeout => {
|
|
||||||
fieldType => "interval",
|
|
||||||
defaultValue => 0,
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get('cache timeout'),
|
|
||||||
hoverHelp => $i18n->get('cache timeout description'),
|
|
||||||
uiLevel => 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
filterHtml => {
|
|
||||||
fieldType => "filterContent",
|
|
||||||
defaultValue => "javascript",
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get(418, 'WebGUI'),
|
|
||||||
hoverHelp => $i18n->get('418 description', 'WebGUI'),
|
|
||||||
},
|
|
||||||
|
|
||||||
urlPatternFilter=>{
|
|
||||||
fieldType => "textarea",
|
|
||||||
defaultValue => "",
|
|
||||||
tab => "display",
|
|
||||||
label => $i18n->get("url pattern filter label"),
|
|
||||||
hoverHelp => $i18n->get("url pattern filter hover help"),
|
|
||||||
},
|
|
||||||
|
|
||||||
followExternal => {
|
|
||||||
fieldType => "yesNo",
|
|
||||||
defaultValue => 1,
|
|
||||||
tab => 'security',
|
|
||||||
label => $i18n->get(5),
|
|
||||||
hoverHelp => $i18n->get('5 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
rewriteUrls => {
|
|
||||||
fieldType => "yesNo",
|
|
||||||
defaultValue => 1,
|
|
||||||
tab => 'properties',
|
|
||||||
label => $i18n->get(12),
|
|
||||||
hoverHelp => $i18n->get('12 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
followRedirect => {
|
|
||||||
fieldType => "yesNo",
|
|
||||||
defaultValue => 0,
|
|
||||||
tab => 'security',
|
|
||||||
label => $i18n->get(8),
|
|
||||||
hoverHelp => $i18n->get('8 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
searchFor => {
|
|
||||||
fieldType => "text",
|
|
||||||
defaultValue => undef,
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get(13),
|
|
||||||
hoverHelp => $i18n->get('13 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
stopAt => {
|
|
||||||
fieldType => "text",
|
|
||||||
defaultValue => undef,
|
|
||||||
tab => 'display',
|
|
||||||
label => $i18n->get(14),
|
|
||||||
hoverHelp => $i18n->get('14 description'),
|
|
||||||
},
|
|
||||||
|
|
||||||
cookieJarStorageId => {
|
|
||||||
noFormPost => 1,
|
|
||||||
fieldType => "hidden",
|
|
||||||
defaultValue => undef
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return $class->SUPER::definition($session, $definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------
|
#-------------------------------------------------------------------
|
||||||
|
|
||||||
=head2 getContentLastModified
|
=head2 getContentLastModified
|
||||||
|
|
@ -218,11 +205,11 @@ Return a WebGUI::Storage object to hold cookie data.
|
||||||
sub getCookieJar {
|
sub getCookieJar {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $storage;
|
my $storage;
|
||||||
unless ($self->get("cookieJarStorageId")) {
|
unless ($self->cookieJarStorageId) {
|
||||||
$storage = WebGUI::Storage->create($self->session);
|
$storage = WebGUI::Storage->create($self->session);
|
||||||
$self->update({cookieJarStorageId=>$storage->getId});
|
$self->update({cookieJarStorageId=>$storage->getId});
|
||||||
} else {
|
} else {
|
||||||
$storage = WebGUI::Storage->get($self->session,$self->get("cookieJarStorageId"));
|
$storage = WebGUI::Storage->get($self->session,$self->cookieJarStorageId);
|
||||||
}
|
}
|
||||||
return $storage;
|
return $storage;
|
||||||
}
|
}
|
||||||
|
|
@ -238,11 +225,11 @@ See WebGUI::Asset::prepareView() for details.
|
||||||
sub prepareView {
|
sub prepareView {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
$self->SUPER::prepareView();
|
$self->SUPER::prepareView();
|
||||||
my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId"));
|
my $template = WebGUI::Asset::Template->new($self->session, $self->templateId);
|
||||||
if (!$template) {
|
if (!$template) {
|
||||||
WebGUI::Error::ObjectNotFound::Template->throw(
|
WebGUI::Error::ObjectNotFound::Template->throw(
|
||||||
error => qq{Template not found},
|
error => qq{Template not found},
|
||||||
templateId => $self->get("templateId"),
|
templateId => $self->templateId,
|
||||||
assetId => $self->getId,
|
assetId => $self->getId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -278,8 +265,8 @@ sub purgeCache {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $cache = $self->session->cache;
|
my $cache = $self->session->cache;
|
||||||
eval {
|
eval {
|
||||||
$cache->delete([$self->get("proxiedUrl"),"URL"]);
|
$cache->delete([$self->proxiedUrl,"URL"]);
|
||||||
$cache->delete([$self->get("proxiedUrl"),"HEADER"]);
|
$cache->delete([$self->proxiedUrl,"HEADER"]);
|
||||||
};
|
};
|
||||||
$self->SUPER::purgeCache;
|
$self->SUPER::purgeCache;
|
||||||
}
|
}
|
||||||
|
|
@ -299,7 +286,7 @@ sub view {
|
||||||
my $redirect = 0;
|
my $redirect = 0;
|
||||||
my $response;
|
my $response;
|
||||||
my $header;
|
my $header;
|
||||||
my $proxiedUrl = $self->get("proxiedUrl");
|
my $proxiedUrl = $self->proxiedUrl;
|
||||||
WebGUI::Macro::process($self->session,\$proxiedUrl);
|
WebGUI::Macro::process($self->session,\$proxiedUrl);
|
||||||
|
|
||||||
my $i18n = WebGUI::International->new($self->session, 'Asset_HttpProxy');
|
my $i18n = WebGUI::International->new($self->session, 'Asset_HttpProxy');
|
||||||
|
|
@ -316,7 +303,7 @@ sub view {
|
||||||
$proxiedUrl = $self->session->form->process("FormAction") || $self->session->form->process("proxiedUrl") || $proxiedUrl ;
|
$proxiedUrl = $self->session->form->process("FormAction") || $self->session->form->process("proxiedUrl") || $proxiedUrl ;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $self->processTemplate({},$self->get("templateId"))
|
return $self->processTemplate({},$self->templateId)
|
||||||
unless ($proxiedUrl ne "");
|
unless ($proxiedUrl ne "");
|
||||||
|
|
||||||
my $requestMethod = $self->session->env->get("REQUEST_METHOD") || "GET";
|
my $requestMethod = $self->session->env->get("REQUEST_METHOD") || "GET";
|
||||||
|
|
@ -338,7 +325,7 @@ sub view {
|
||||||
|
|
||||||
my $userAgent = new LWP::UserAgent;
|
my $userAgent = new LWP::UserAgent;
|
||||||
$userAgent->agent($self->session->env->get("HTTP_USER_AGENT"));
|
$userAgent->agent($self->session->env->get("HTTP_USER_AGENT"));
|
||||||
$userAgent->timeout($self->get("timeout"));
|
$userAgent->timeout($self->timeout);
|
||||||
$userAgent->env_proxy;
|
$userAgent->env_proxy;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -351,16 +338,16 @@ sub view {
|
||||||
|
|
||||||
|
|
||||||
## Make sure the user isn't leaving where we've allowed
|
## Make sure the user isn't leaving where we've allowed
|
||||||
if ($self->get("followExternal")==0
|
if ($self->followExternal==0
|
||||||
&& (URI->new($self->get('proxiedUrl'))->host) ne (URI->new($proxiedUrl)->host) ) {
|
&& (URI->new($self->proxiedUrl)->host) ne (URI->new($proxiedUrl)->host) ) {
|
||||||
$var{header} = "text/html";
|
$var{header} = "text/html";
|
||||||
$var{content} = sprintf $i18n->get('may not leave error message'), $self->get("proxiedUrl");
|
$var{content} = sprintf $i18n->get('may not leave error message'), $self->proxiedUrl;
|
||||||
last;
|
last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$header = new HTTP::Headers;
|
$header = new HTTP::Headers;
|
||||||
$header->referer($self->get("proxiedUrl")); # To get around referrer blocking
|
$header->referer($self->proxiedUrl); # To get around referrer blocking
|
||||||
|
|
||||||
|
|
||||||
my $request; # Create the request
|
my $request; # Create the request
|
||||||
|
|
@ -428,7 +415,7 @@ sub view {
|
||||||
last REDIRECT;
|
last REDIRECT;
|
||||||
}
|
}
|
||||||
##At least 1 time through the loop
|
##At least 1 time through the loop
|
||||||
last REDIRECT if (not $self->get("followRedirect")); # No redirection. Overruled by setting
|
last REDIRECT if (not $self->followRedirect); # No redirection. Overruled by setting
|
||||||
}
|
}
|
||||||
|
|
||||||
if($response->is_success) {
|
if($response->is_success) {
|
||||||
|
|
@ -437,8 +424,8 @@ sub view {
|
||||||
if($response->content_type eq "text/html"
|
if($response->content_type eq "text/html"
|
||||||
|| ($response->content_type eq "" && $var{content}=~/<html/gis)) {
|
|| ($response->content_type eq "" && $var{content}=~/<html/gis)) {
|
||||||
|
|
||||||
$var{"search.for"} = $self->getValue("searchFor");
|
$var{"search.for"} = $self->searchFor;
|
||||||
$var{"stop.at"} = $self->getValue("stopAt");
|
$var{"stop.at"} = $self->stopAt;
|
||||||
if ($var{"search.for"}) {
|
if ($var{"search.for"}) {
|
||||||
$var{content} =~ /^(.*?)\Q$var{"search.for"}\E(.*)$/gis;
|
$var{content} =~ /^(.*?)\Q$var{"search.for"}\E(.*)$/gis;
|
||||||
$var{"content.leading"} = $1 || $var{content};
|
$var{"content.leading"} = $1 || $var{content};
|
||||||
|
|
@ -449,7 +436,7 @@ sub view {
|
||||||
$var{content} = $1 || $var{content};
|
$var{content} = $1 || $var{content};
|
||||||
$var{"content.trailing"} = $2;
|
$var{"content.trailing"} = $2;
|
||||||
}
|
}
|
||||||
my $p = WebGUI::Asset::Wobject::HttpProxy::Parse->new($self->session, $proxiedUrl, $var{content}, $self->getId,$self->get("rewriteUrls"),$self->getUrl,$self->get("urlPatternFilter"));
|
my $p = WebGUI::Asset::Wobject::HttpProxy::Parse->new($self->session, $proxiedUrl, $var{content}, $self->getId,$self->rewriteUrls,$self->getUrl,$self->urlPatternFilter);
|
||||||
$var{content} = $p->filter; # Rewrite content. (let forms/links return to us).
|
$var{content} = $p->filter; # Rewrite content. (let forms/links return to us).
|
||||||
$p->DESTROY;
|
$p->DESTROY;
|
||||||
|
|
||||||
|
|
@ -457,19 +444,19 @@ sub view {
|
||||||
$var{header} = "text/html";
|
$var{header} = "text/html";
|
||||||
$var{content} = sprintf $i18n->get('no frame error message'), $proxiedUrl;
|
$var{content} = sprintf $i18n->get('no frame error message'), $proxiedUrl;
|
||||||
} else {
|
} else {
|
||||||
$var{content} =~ s/\<style.*?\/style\>//isg if ($self->get("removeStyle"));
|
$var{content} =~ s/\<style.*?\/style\>//isg if ($self->removeStyle);
|
||||||
$var{content} = WebGUI::HTML::cleanSegment($var{content}, 1);
|
$var{content} = WebGUI::HTML::cleanSegment($var{content}, 1);
|
||||||
$var{content} = WebGUI::HTML::filter($var{content}, $self->get("filterHtml"));
|
$var{content} = WebGUI::HTML::filter($var{content}, $self->filterHtml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { # Fetching page failed...
|
} else { # Fetching page failed...
|
||||||
$var{header} = "text/html";
|
$var{header} = "text/html";
|
||||||
$var{content} = sprintf $i18n->get('fetch page error'), $proxiedUrl, $proxiedUrl, $response->status_line;
|
$var{content} = sprintf $i18n->get('fetch page error'), $proxiedUrl, $proxiedUrl, $response->status_line;
|
||||||
}
|
}
|
||||||
unless ($self->get("cacheTimeout") <= 10) {
|
unless ($self->cacheTimeout <= 10) {
|
||||||
eval{
|
eval{
|
||||||
$cache->set([$proxiedUrl,'URL'], $var{content}, $self->get("cacheTimeout"));
|
$cache->set([$proxiedUrl,'URL'], $var{content}, $self->cacheTimeout);
|
||||||
$cache->set([$proxiedUrl,'HEADER'], $var{header}, $self->get("cacheTimeout"));
|
$cache->set([$proxiedUrl,'HEADER'], $var{header}, $self->cacheTimeout);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue