- Inclusion of UsersOnline macro into the core (#766)

This commit is contained in:
JT Smith 2008-10-21 19:54:51 +00:00
parent 79635c0c2d
commit b41c1639a1
13 changed files with 840 additions and 1 deletions

View file

@ -7,6 +7,7 @@
- Made Classname from control a subclass of ReadOnly.
- Added query keys to WebGUI::Crud.
- EMS Saved Address (#8864)
- Inclusion of UsersOnline macro into the core (#766)
- WebGUI::Crud can now automatically resolve differences between its
definition and the table schema.
- Fixed a limit bug in the asset discovery service.

View file

@ -32,13 +32,22 @@ my $session = start(); # this line required
repairManageWorkflows($session);
addPreTextToThingyFields($session);
updateAddressBook($session);
addUsersOnlineMacro($session);
finish($session); # this line required
#----------------------------------------------------------------------------
sub addUsersOnlineMacro {
my $session = shift;
print "\tMaking the UsersOnline macro available." unless $quiet;
$session->config->addToHash("macros","UsersOnline","UsersOnline");
print "DONE!\n" unless $quiet;
}
#----------------------------------------------------------------------------
sub updateAddressBook {
my $session = shift;
print "\tAdd Organization and Email Address to address book." unless $quiet;
print "\tAdding organization and email to address book." unless $quiet;
my $db = $session->db;
$db->write("alter table address add column organization char(255)");
$db->write("alter table address add column email char(255)");
@ -58,6 +67,7 @@ sub repairManageWorkflows {
print "DONE!\n" unless $quiet;
}
#----------------------------------------------------------------------------
sub addPreTextToThingyFields {
my $session = shift;
print "\tAdding a pre-text property to Thingy fields... " unless $quiet;

View file

@ -691,6 +691,7 @@
"SpectreCheck" : "SpectreCheck",
"Thumbnail" : "Thumbnail",
"User" : "User",
"UsersOnline" : "UsersOnline",
"u" : "u_companyUrl",
"ViewCart" : "ViewCart"
},

View file

@ -0,0 +1,58 @@
package WebGUI::Help::Macro_UsersOnline;
our $HELP = {
'users online' => {
title => 'users online title',
body => 'users online body',
variables => [
{ name => 'members' },
{ name => 'visitors' },
{ name => 'total' },
{ name => 'isVisitor' },
{ name => 'hasMembers' },
{ name => 'member_loop',
variables => [
{ name => 'username' },
{ name => 'firstName' },
{ name => 'middleName' },
{ name => 'lastName' },
{ name => 'alias' },
{ name => 'avatar' },
{ name => 'uid' },
{ name => 'sessionId' },
{ name => 'ip' },
{ name => 'lastActivity' },
],
},
{ name => "visitor_loop",
variables => [
{ name => 'sessionId' },
{ name => 'ip' },
{ name => 'lastActivity' },
],
},
{ name => 'usersOnline_label' },
{ name => 'members_label' },
{ name => 'visitors_label' },
{ name => 'total_label' },
{ name => 'membersOnline_label' },
{ name => 'visitorsOnline_label' },
{ name => 'avatar_label' },
{ name => 'name_label' },
{ name => 'alias_label' },
{ name => 'session_label' },
{ name => 'ip_label' },
{ name => 'lastActivity_label' },
],
fields => [],
related => [],
},
};
1;

View file

@ -0,0 +1,299 @@
package WebGUI::Macro::UsersOnline;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2004 Plain Black LLC.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
Authors of Macro:
Carlos Rivero (http://www.carlosrivero.com),
Bernd Kalbfuss-Zimmermann
-------------------------------------------------------------------
=cut
use strict;
use Apache2::ServerRec;
use Net::DNS;
use WebGUI::Asset::Template;
use WebGUI::International;
use WebGUI::Session::DateTime;
use WebGUI::SQL;
use WebGUI::User;
=head1 NAME
Package WebGUI::Macro::UsersOnline;
=head1 DESCRIPTION
Macro for displaying users that are online.
=cut
#-------------------------------------------------------------------
=head2 process ( [templateId], [minutes], [maxMembers], [maxVisitors] )
Returns a fragment of HTML code showing the number of users online. Appearance
can be altered by specifying an alternative template.
=head3 templateId
The ID of the template to be processed. The default is 'h_T2xtOxGRQ9QJOR6ebLpQ'.
=head3 minutes
The number of minutes the last activity may date back for a user to count as
online.cThe default is 5 minutes.
=head3 maxMembers
The maximum number of members to be returned in the members template loop. The
default is 10.
=head3 maxVisitors
The maximum number of visitors to be returned in the visitors template loop. The
default is 10.
=cut
sub process {
# Assign parameters
my $session = shift;
my $templateId = shift;
my $minutes = shift;
my $maxMembers = shift;
my $maxVisitors = shift;
# Assign default values
$templateId ||= "h_T2xtOxGRQ9QJOR6ebLpQ";
$minutes = 5 unless defined $minutes;
$maxMembers = 10 unless defined $maxMembers;
$maxVisitors = 10 unless defined $maxVisitors;
# Create hash of template variables
my %var;
# Obtain internationalization instance
my $i18n = WebGUI::International->new($session, "Macro_UsersOnline");
# Get preferred time format of current user
my $time_format = $session->user->profileField("timeFormat");
# Calculate epoch time for comparison to last activity
my $dt = $session->datetime;
my $epoch = $dt->time();
$epoch = $session->datetime->addToTime($epoch, 0, -$minutes, 0);
# Let private subroutines do the work
_visitors($session, \%var, $epoch, $maxVisitors);
_members($session, \%var, $epoch, $maxMembers);
# Calculate the total number of active users
$var{'total'} = $var{'members'} + $var{'visitors'};
# Set some flags
$var{'isVisitor'} = 1 if ($session->user->userId == 1);
$var{'hasMembers'} = 1 if $var{'member_loop'};
# Assign labels
$var{'usersOnline_label'} = $i18n->get("Users Online");
$var{'members_label'} = $i18n->get("Members");
$var{'visitors_label'} = $i18n->get("Visitors");
$var{'total_label'} = $i18n->get("Total");
$var{'membersOnline_label'} = $i18n->get("Members Online");
$var{'visitorsOnline_label'} = $i18n->get("Visitors Online");
$var{'avatar_label'} = $i18n->get("Avatar");
$var{'name_label'} = $i18n->get("Name");
$var{'alias_label'} = $i18n->get("Alias");
$var{'session_label'} = $i18n->get("Session");
$var{'ip_label'} = $i18n->get("IP");
$var{'lastActivity_label'} = $i18n->get("Last Activity");
# Process Template
return WebGUI::Asset::Template->new($session,$templateId)->process(\%var);
}
#-------------------------------------------------------------------
=head2 _visitors ( session, varRef, epoch, maxVisitors )
Fills template variables concerning visitors, i.e. the number of visitors and
all variables in the visitors loop. Private subroutine used by process().
=head3 session
A reference to the current session.
=head3 varRef
A reference to the hash of template variables.
=head3 epoch
Epoch time for comparison to last activity.
=head3 maxVisitors
The maximum number of visitors to be returned in the visitors template loop.
=cut
sub _visitors {
# Assign parameters
my $session = shift;
my $var = shift;
my $epoch = shift;
my $maxVisitors = shift;
# Obtain some session-associated objects
my $db = $session->db();
my $dt = $session->datetime;
# Get preferred time format of current user
my $time_format = $session->user->profileField("timeFormat");
# SQL conditional clause for filtering server IP
my $ip_clause;
# Check whether instance of Apache2::ServerRec is available
if($session->server) {
# Query hostname of server
my $hostname = $session->server->server_hostname();
# Look up server IP addresses
my $res = Net::DNS::Resolver->new();
my $query = $res->search($hostname);
if ($query) {
foreach my $rr ($query->answer) {
next unless $rr->type eq "A";
# Generate SQL clause which excludes server IPs
$ip_clause = $ip_clause . "AND STRCMP(lastIp, '" . $rr->address . "') ";
}
}
}
# Determine the number of visitors. Server IP adresses and the loopback
# network are excluded. We only count IPs - not sessions. The reason is
# that crawlers tend to open multiple sessions(e.g. googlebot) and thereby
# increase the count artificially. Note, that the number determined here
# may deviate from the number of items returned in the visitor loop.
$$var{'visitors'} = $db->quickScalar("SELECT COUNT(DISTINCT lastIp) FROM " .
"userSession WHERE (lastPageView > $epoch) AND (userId = 1) AND " .
"lastIp NOT LIKE '127.%.%.%'" . $ip_clause);
# Query session IDs and IPs of visitors
my $query = $db->prepare("SELECT sessionId, lastIp, lastPageView FROM " .
"userSession WHERE (lastPageView > $epoch) AND (userId = 1) AND " .
"lastIp NOT LIKE '127.%.%.%' " . $ip_clause . "LIMIT $maxVisitors");
$query->execute;
# Iterate through rows
while (my %row = $query->hash) {
# Add item to visitor template loop
push(@{$$var{'visitor_loop'}}, {
sessionId => $row{'sessionId'},
ip => $row{'lastIp'},
lastActivity => $dt->epochToHuman($row{'lastPageView'}, $time_format)
});
}
return;
}
#-------------------------------------------------------------------
=head2 _members ( session, varRef, epoch, maxMembers )
Fills template variables concerning members, i.e. the number of members and
all variables in the members loop. Private subroutine used by process().
=head3 session
A reference to the current session.
=head3 varRef
A reference to the hash of template variables.
=head3 epoch
Epoch time for comparison to last activity.
=head3 maxMembers
The maximum number of members to be returned in the members template loop.
=cut
sub _members {
# Assign parameters
my $session = shift;
my $var = shift;
my $epoch = shift;
my $maxMembers = shift;
# Obtain some session-associated objects
my $db = $session->db();
my $dt = $session->datetime;
# Get preferred time format of current user
my $time_format = $session->user->profileField("timeFormat");
# Determine the number of registered users that are online. The Admin
# account is excluded from the list.
$$var{'members'} = $db->quickScalar("SELECT COUNT(DISTINCT userId) FROM " .
"userSession where (lastPageView > $epoch) and (userId != 1) and " .
"(userId != 3)");
# Query the names of registered users that are online. The showOnline flag
# in the user profile is respected.
my $query = $db->prepare("SELECT userId, sessionId, lastIp, lastPageView " .
"FROM userSession WHERE (lastPageView > $epoch) AND (userId != 1) " .
"AND (userId != 3) LIMIT $maxMembers");
$query->execute;
# Iterate through rows
while (my %row = $query->hash) {
# Create instance of WebGUI::User for the user id from the current row
my $user = WebGUI::User->new($session, $row{'userId'});
# Only show users with the "showOnline" flag set to true
if ($user->profileField("showOnline")) {
# Find URL of avatar if available
my $avatar_url;
my $avatar = $user->profileField("avatar");
if ($avatar) {
my $storage = WebGUI::Storage->get($session, $avatar);
my @files = @{ $storage->getFiles() };
# We assume it is the first file in the storage. But maybe
# that is incorrect?
$avatar_url = $storage->getUrl($files[0]);
}
# Add item to member template loop
push(@{$$var{'member_loop'}}, {
username => $user->username(),
firstName => $user->profileField("firstName"),
middleName => $user->profileField("middleName"),
lastName => $user->profileField("lastName"),
alias => $user->profileField("alias"),
avatar => $avatar_url,
uid => $row{'userId'},
sessionId => $row{'sessionId'},
ip => $row{'lastIp'},
lastActivity => $dt->epochToHuman($row{'lastPageView'}, $time_format)
});
}
}
return;
}
1;

View file

@ -0,0 +1,223 @@
package WebGUI::i18n::English::Macro_UsersOnline;
use utf8;
our $I18N = {
'Users Online' => {
message => q|Users Online|,
lastUpdated => 1178783587
},
'Members' => {
message => q|Members|,
lastUpdated => 1178783587
},
'Visitors' => {
message => q|Visitors|,
lastUpdated => 1178783587
},
'Total' => {
message => q|Total|,
lastUpdated => 1178783587
},
'Members Online' => {
message => q|Members Online|,
lastUpdated => 1178783587
},
'Visitors Online' => {
message => q|Visitors Online|,
lastUpdated => 1178783587
},
'Avatar' => {
message => q|Avatar|,
lastUpdated => 1178783587
},
'Name' => {
message => q|Name|,
lastUpdated => 1178783587
},
'Alias' => {
message => q|Alias|,
lastUpdated => 1178783587
},
'Session' => {
message => q|Session|,
lastUpdated => 1178783587
},
'IP' => {
message => q|IP|,
lastUpdated => 1178783587
},
'Last Activity' => {
message => q|Last Activity|,
lastUpdated => 1178783587
},
'users online title' => {
message => q|Users Online Macro|,
lastUpdated => 1178783587
},
'users online body' => {
message => q|Macro for displaying users that are online. The number of online users, divided into members and visitors, is determined. Users are considered online if the last page request has occurred within a definable period of time. User-specific data is provided in two loops.|,
lastUpdated => 1178783587
},
'usersOnline_label' => {
message => q|The words "Users Online".|,
lastUpdated => 1178783587
},
'members_label' => {
message => q|The word "Members".|,
lastUpdated => 1178783587
},
'visitors_label' => {
message => q|The word "Visitors".|,
lastUpdated => 1178783587
},
'total_label' => {
message => q|The word "Total".|,
lastUpdated => 1178783587
},
'membersOnline_label' => {
message => q|The words "Members Online".|,
lastUpdated => 1178783587
},
'visitorsOnline_label' => {
message => q|The words "Visitors Online".|,
lastUpdated => 1178783587
},
'avatar_label' => {
message => q|The word "Avatar".|,
lastUpdated => 1178783587
},
'name_label' => {
message => q|The word "Name".|,
lastUpdated => 1178783587
},
'alias_label' => {
message => q|The word "Alias".|,
lastUpdated => 1178783587
},
'session_label' => {
message => q|The word "Session".|,
lastUpdated => 1178783587
},
'ip_label' => {
message => q|The word "IP".|,
lastUpdated => 1178783587
},
'lastActivity_label' => {
message => q|The words "Last Activity".|,
lastUpdated => 1178783587
},
'members' => {
message => q|The number of members online.|,
lastUpdated => 1178783587
},
'visitors' => {
message => q|The number of visitors online.|,
lastUpdated => 1178783587
},
'total' => {
message => q|The total number of users online.|,
lastUpdated => 1178783587
},
'isVisitor' => {
message => q|True if current user is visitor.|,
lastUpdated => 1178783587
},
'hasMembers' => {
message => q|True if member loop contains items.|,
lastUpdated => 1178783587
},
'members_loops' => {
message => q|Loop containing all members that are online and have agreed publication.|,
lastUpdated => 1178783587
},
'username' => {
message => q|The username.|,
lastUpdated => 1178783587
},
'firstName' => {
message => q|The first name.|,
lastUpdated => 1178783587
},
'middleName' => {
message => q|The middle name.|,
lastUpdated => 1178783587
},
'lastName' => {
message => q|The last name.|,
lastUpdated => 1178783587
},
'alias' => {
message => q|The alias of the user.|,
lastUpdated => 1178783587
},
'avatar' => {
message => q|The URL of the user's avatar.|,
lastUpdated => 1178783587
},
'uid' => {
message => q|The user Id.|,
lastUpdated => 1178783587
},
'sessionId' => {
message => q|The Id of the user session.|,
lastUpdated => 1178783587
},
'ip' => {
message => q|The IP adress of the user.|,
lastUpdated => 1178783587
},
'lastActivity' => {
message => q|The time of the last activity.|,
lastUpdated => 1178783587
},
'visitors_loops' => {
message => q|Loop containing all visitors that are online.|,
lastUpdated => 1178783587
},
};
1;

247
t/Macro/UsersOnline.t Normal file
View file

@ -0,0 +1,247 @@
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2007 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use FindBin;
use strict;
use lib "$FindBin::Bin/../lib";
use JSON;
use WebGUI::Test;
use WebGUI::International;
use WebGUI::Session;
use WebGUI::Session::Var;
use WebGUI::User;
use WebGUI::Macro::UsersOnline;
use Test::More; # increment this value for each test you create
my $session = WebGUI::Test->session;
my $template = setupJSONtemplate($session);
my @users = setupUsers($session);
my $i18n = WebGUI::International->new($session,'Macro_UsersOnline');
my $numTests = 1; # Module loading test
$numTests += 31; # Static tests
plan tests => $numTests;
my $macro = 'WebGUI::Macro::UsersOnline';
my $loaded = use_ok($macro);
SKIP: {
skip "Unable to load $macro", $numTests-1 unless $loaded;
# Basic testing -----------------------------------------------------------
# Check for default template
my $defTemplate = WebGUI::Asset->new($session, 'pUwYHCjfQUMcRXRmKOlaRQ');
is(defined $defTemplate, 1, 'default template is present');
# Call with default values
my $html = WebGUI::Macro::UsersOnline::process($session);
is((length $html) > 0, 1, 'call with default template and values returns some output');
# Test labels -------------------------------------------------------------
$session->user({userId => 1});
my $json = WebGUI::Macro::UsersOnline::process($session, $template->getId);
my $vars = JSON::from_json($json);
is($vars->{'usersOnline_label'}, $i18n->get('Users Online'), 'usersOnline_label');
is($vars->{'members_label'}, $i18n->get('Members'), 'members_label');
is($vars->{'visitors_label'}, $i18n->get('Visitors'), 'visitors_label');
is($vars->{'total_label'}, $i18n->get('Total'), 'total_label');
is($vars->{'membersOnline_label'}, $i18n->get('Members Online'), 'membersOnline_label');
is($vars->{'visitorsOnline_label'}, $i18n->get('Visitors Online'), 'visitorsOnline_label');
is($vars->{'avatar_label'}, $i18n->get('Avatar'), 'avatar_label');
is($vars->{'name_label'}, $i18n->get('Name'), 'name_label');
is($vars->{'alias_label'}, $i18n->get('Alias'), 'alias_label');
is($vars->{'session_label'}, $i18n->get('Session'), 'session_label');
is($vars->{'ip_label'}, $i18n->get('IP'), 'ip_label');
is($vars->{'lastActivity_label'}, $i18n->get('Last Activity'), 'lastActivity_label');
# Test logic --------------------------------------------------------------
is($vars->{'visitors'} =~ /[0-9]+/, 1, 'visitors is numeric');
is($vars->{'visitors'} > 0, 1, 'visitors > 0 when calling as visitor');
is($vars->{'members'} =~ /[0-9]+/, 1, 'members is numeric');
is($vars->{'visitors'} > 0, 1, 'members > 0 since we have created one visible active member');
is($vars->{'total'}, $vars->{'visitors'} + $vars->{'members'}, 'total == visitors + members');
is($vars->{'isVisitor'}, 1, 'isVisitor is defined when calling as visitor');
is(defined $vars->{'hasMembers'}, 1, 'hasMembers is defined since we have created one visible active member');
# Check member loop -------------------------------------------------------
my $allFieldsSet = 1;
my $firstUserPresent = 0;
my $secondUserAbsent = 1;
foreach (@{$vars->{'member_loop'}}) {
# Check whether first user is present
if ($_->{'username'} eq 'tester1') {
# Indicate success
$firstUserPresent = 1;
# Check whether alias and name have been set correctly
$allFieldsSet = $_->{'alias'} eq 'alias1';
$allFieldsSet &&= $_->{'firstName'} eq 'first1';
$allFieldsSet &&= $_->{'middleName'} eq 'middle1';
$allFieldsSet &&= $_->{'lastName'} eq 'last1';
}
# Check whether second user is present
if ($_->{'username'} eq 'tester2') {
# Indicate failure
$secondUserAbsent = 0;
}
# Check whether remaining fields have been set. Note that we cannot
# check for the alias, name or avatar, since these values may be
# optional.
$allFieldsSet &&= $_->{'uid'} =~ /.*/;
$allFieldsSet &&= $_->{'sessionId'} =~ /.*/;
# $allFieldsSet &&= $_->{'ip'} =~ /.*/; # IP is not set for our test user
$allFieldsSet &&= $_->{'lastActivity'} =~ /.*/;
}
# Check booleans indicating errors
is($allFieldsSet, 1, 'fields in the member loop have been set correctly');
is($firstUserPresent, 1, 'the first user is present in the member loop');
is($secondUserAbsent, 1, 'the second user is absent from the member loop');
# Check visitor loop ------------------------------------------------------
$allFieldsSet = 1;
foreach (@{$vars->{'visitor_loop'}}) {
# $allFieldsSet &&= $_->{'ip'} =~ /.*/; # IP is not set for our test user
$allFieldsSet &&= $_->{'sessionId'} =~ /.*/;
$allFieldsSet &&= $_->{'lastActivity'} =~ /.*/;
}
# Check booleans indicating errors
is($allFieldsSet, 1, 'fields in the visitor loop have been set correctly');
# Test macro parameters ---------------------------------------------------
# Call with zero max limits
$json = WebGUI::Macro::UsersOnline::process($session, $template->getId, undef, 0, 0);
$vars = JSON::from_json($json);
is(defined $vars->{'hasMembers'}, '', 'hasMembers undefined when display limit is set to zero');
is(@{$vars->{'member_loop'}} == 0, 1, 'empty member loop when display limit is set to zero');
is(@{$vars->{'visitor_loop'}} == 0, 1, 'empty visitor loop when display limit is set to zero');
# Call with zero max inactivity time
$json = WebGUI::Macro::UsersOnline::process($session, $template->getId, 0, undef, undef);
$vars = JSON::from_json($json);
is(defined $vars->{'hasMembers'}, '', 'hasMembers undefined when max inactivity time is set to zero');
is(@{$vars->{'member_loop'}} == 0, 1, 'empty member loop when max inactivity time is set to zero');
is(@{$vars->{'visitor_loop'}} == 0, 1, 'empty visitor loop when max inactivity time is set to zero');
}
sub setupUsers {
my ($session) = @_;
my @users;
my $user;
# Create first user
$user = WebGUI::User->new($session, 'new');
$user->username("tester1");
$user->profileField('showOnline', 1);
$user->profileField('alias', 'alias1');
$user->profileField('firstName', 'first1');
$user->profileField('middleName', 'middle1');
$user->profileField('lastName', 'last1');
unshift @users, $user;
# Create second user
$user = WebGUI::User->new($session, 'new');
$user->username("tester2");
$user->profileField('showOnline', 0);
$user->profileField('alias', 'alias2');
$user->profileField('firstName', 'first2');
$user->profileField('middleName', 'middle2');
$user->profileField('lastName', 'last2');
unshift @users, $user;
# Create sessions such that users are added to the userSession table
foreach (@users) {
$session->user({user => $_});
WebGUI::Session::Var->new($session);
}
return @users;
}
sub setupJSONtemplate {
my ($session) = @_;
my $templateBody = <<EOTMPL;
{
"members":<tmpl_var members>,
"visitors":<tmpl_var visitors>,
"total":<tmpl_var total>,
<tmpl_if isVisitor>"isVisitor":<tmpl_var isVisitor>,</tmpl_if>
<tmpl_if hasMembers>"hasMembers":<tmpl_var hasMembers>,</tmpl_if>
"usersOnline_label":"<tmpl_var usersOnline_label>",
"members_label":"<tmpl_var members_label>",
"visitors_label":"<tmpl_var visitors_label>",
"total_label":"<tmpl_var total_label>",
"membersOnline_label":"<tmpl_var membersOnline_label>",
"visitorsOnline_label":"<tmpl_var visitorsOnline_label>",
"avatar_label":"<tmpl_var avatar_label>",
"name_label":"<tmpl_var name_label>",
"alias_label":"<tmpl_var alias_label>",
"session_label":"<tmpl_var session_label>",
"ip_label":"<tmpl_var ip_label>",
"lastActivity_label":"<tmpl_var lastActivity_label>",
"member_loop":[
<tmpl_loop member_loop>
{
"username":"<tmpl_var username>",
"firstName":"<tmpl_var firstName>",
"middleName":"<tmpl_var middleName>",
"lastName":"<tmpl_var lastName>",
"alias":"<tmpl_var alias>",
"avatar":"<tmpl_var avatar>",
"uid":"<tmpl_var uid>",
"sessionId":"<tmpl_var sessionId>",
"ip":"<tmpl_var ip>",
"lastActivity":"<tmpl_var lastActivity>"
}<tmpl_unless __last__>,</tmpl_unless>
</tmpl_loop>
],
"visitor_loop":[
<tmpl_loop visitor_loop>
{
"sessionId":"<tmpl_var sessionId>",
"ip":"<tmpl_var ip>",
"lastActivity":"<tmpl_var lastActivity>"
}<tmpl_unless __last__>,</tmpl_unless>
</tmpl_loop>
]
}
EOTMPL
my $template = WebGUI::Asset->getImportNode($session)->addChild({className=>'WebGUI::Asset::Template', namespace => 'Shop/MiniCart', template=>$templateBody});
return $template;
}
END { ##Clean-up after yourself, always
$template->purge;
$_->delete foreach (@users);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B