Skip to content
Browse files

Added opt-out-link

  • Loading branch information...
1 parent a076ade commit 7d77dc5099e2a326afd13e51a74af3af6c1603ab Akron committed Mar 1, 2013
Showing with 438 additions and 69 deletions.
  1. +4 −1 Changes
  2. +2 −0 MANIFEST
  3. +63 −17 Readme.pod
  4. +139 −24 lib/Mojolicious/Plugin/Piwik.pm
  5. +94 −0 t/piwik-opt-out.t
  6. +130 −0 t/piwik-track.t
  7. +6 −27 t/piwik.t
View
5 Changes
@@ -1,8 +1,11 @@
-0.09 2013-01-31
+0.09 2013-03-01
- Update year.
- Update comments.
- Documentation tweaks.
- First implementation of Tracking API.
+ - Add opt-out iframe.
+ - Add opt-out-link tag.
+ - Improve documentation.
0.08 2012-12-02
- Fixed documentation.
View
2 MANIFEST
@@ -1,5 +1,7 @@
lib/Mojolicious/Plugin/Piwik.pm
t/piwik.t
+t/piwik-opt-out.t
+t/piwik-track.t
Changes
MANIFEST
LICENSE
View
80 Readme.pod
@@ -26,6 +26,8 @@ Mojolicious::Plugin::Piwik - Use Piwik in Mojolicious
L<Mojolicious::Plugin::Piwik> is a simple plugin for embedding
L<Piwik|http://piwik.org/> Analysis in your Mojolicious app.
+Please respect the privacy of your visitors and do not track
+more information than necessary!
=head1 METHODS
@@ -103,6 +105,20 @@ was registered.
This tag should be included at the bottom
of the body tag of your website.
+ %= piwik_tag 'opt-out', width => 400
+
+The special C<opt-out> tag renders an iframe helping
+your visitors to disallow tracking via javascript.
+See the L<default tag helper|Mojolicious::Plugin::TagHelpers/tag>
+for explanation of usage.
+
+ <%= piwik_tag 'opt-out-link', begin %>Opt Out<% end %>
+ # <a href="..." rel="nofollow">Opt Out</a>
+
+The special C<opt-out-link> renders an anchor link
+to the opt-out page to be used if the visitor does
+not allow third party cookies.
+
=head2 piwik_api
@@ -129,40 +145,58 @@ of the body tag of your website.
}
);
-Sends a Piwik API request and returns the response as a hash
+Sends an API request and returns the response as a hash
or array reference (the decoded JSON response).
Accepts the API method, a hash reference
with request parameters as described in the
L<Piwik API|http://piwik.org/docs/analytics-api/>, and
optionally a callback, if the request is meant to be non-blocking.
-In addition to the parameters of the API reference, the following
+The L<Tracking API|http://piwik.org/docs/tracking-api/reference/>
+uses the method name C<Track> and will forward user agent and
+referrer information based on the controller request as well as the
+url of the requested resource, unless Do-Not-Track is activated.
+The ip address is not forwarded.
+
+ $c->piwik_api(
+ Track => {
+ idsite => '4',
+ res => [1024, 768],
+ action_url => 'http://khm.li/12',
+ action_name => 'Märchen/Rapunzel'
+ });
+
+As the C<url> parameter is used to define the Piwik instance,
+the url of the requested resource to be named C<action_url>.
+
+Please remember that cookie-based opt-out can't be supported.
+
+In addition to the parameters of the API references, the following
parameters are allowed:
=over 2
-=item C<url>
+=item
-The url of your Piwik instance.
+C<url> - The url of your Piwik instance.
Defaults to the url given when the plugin was registered.
-=item C<secure>
+=item
-Boolean value that indicates a request using the https scheme.
+C<secure> - Boolean value that indicates a request using the https scheme.
Defaults to false.
-=item C<api_test>
+=item
-Boolean value that indicates a test request, that returns the
-created request url instead of the JSON response.
-Defaults to false.
+C<api_test> - Boolean value that indicates a test request, that returns the
+created request url instead of the JSON response. Defaults to false.
=back
-C<idSite> is an alias of C<site_id> and defaults to the id
+C<idSite> is an alias of C<site_id> and C<idsite> and defaults to the id
of the plugin registration.
Some parameters are allowed to be array references instead of string values,
-for example C<idSite> and C<date> (for ranges).
+for example C<idSite> (for analysis), C<date> (for ranges) and C<res> (for tracking).
my $json = $c->piwik_api(
'API.get' => {
@@ -172,13 +206,16 @@ for example C<idSite> and C<date> (for ranges).
secure => 1
});
+In case of an error, C<piwik_api> tries to response with a meaningsful
+descriptin in the hash value of C<error>.
+If an image is expected instead of a JSON object
+(as for the Tracking or the C<ImageGraph> API), the image is base64
+encoded and mime-type prefixed in the hash value of image.
+
=head1 LIMITATIONS
-Currently the API requests always expect JSON, so it's not recommended
-for the C<ImageGraph> API.
-The plugin is also limited to the Analysis API and lacks support for
-eCommerce tracking.
+The plugin lacks support for eCommerce tracking.
=head1 TESTING
@@ -190,9 +227,13 @@ and run C<make test>, for example:
{
token_auth => '123456abcdefghijklmnopqrstuvwxyz',
url => 'http://piwik.khm.li/',
- site_id => 1
+ site_id => 1,
+ action_url => 'http://khm.li/Test',
+ action_name => 'Märchen/Test'
};
+The user agent to be ignored is called C<Mojo-Test>.
+
=head1 DEPENDENCIES
@@ -211,6 +252,11 @@ Copyright (C) 2012-2013, L<Nils Diewald|http://nils-diewald.de/>.
This program is free software, you can redistribute it
and/or modify it under the same terms as Perl.
+Please make sure you are using Piwik in compliance to the law.
+For german users,
+L<this information|https://www.datenschutzzentrum.de/tracking/piwik/>
+may help you to design your service correctly.
+
This plugin was developed for
L<khm.li - Kinder- und Hausmärchen der Brüder Grimm|http://khm.li/>.
View
163 lib/Mojolicious/Plugin/Piwik.pm
@@ -6,13 +6,12 @@ use Mojo::UserAgent;
our $VERSION = '0.09';
# Todo:
-# - Add tracking API support
+# - Better test tracking API support
# See http://piwik.org/docs/javascript-tracking/
-# Use $c->piwik_api(Track => {}) for the tracking api
# http://piwik.org/docs/tracking-api/reference/
+# - Support custom values in tracking api.
# - Add eCommerce support
# http://piwik.org/docs/ecommerce-analytics/
-# - Add ImageGraph API support.
# - Improve error handling.
@@ -38,8 +37,25 @@ sub register {
# Do not embed
return '' unless $embed;
- # Controller is not needed
- shift;
+ # Controller
+ my $c = shift;
+
+ # Use opt-out tag
+ my %opt;
+ if ($_[0] && index(lc $_[0], 'opt-out') == 0) {
+ my $opt_out = shift;
+
+ # Get iframe content
+ my $cb = ref $_[-1] eq 'CODE' ? pop : 0;
+
+ # Accept parameters
+ %opt = @_;
+ $opt{out} = $opt_out;
+ $opt{cb} = $cb;
+
+ # Empty arguments
+ @_ = ();
+ };
my $site_id = shift || $plugin_param->{site_id} || 1;
my $url = shift || $plugin_param->{url};
@@ -54,6 +70,30 @@ sub register {
s{(?<!/)$}{/};
};
+ # Render opt-out tag
+ if (my $opt_out = delete $opt{out}) {
+
+ # Get protocol
+ my $req_url = $c->req->url;
+ my $prot = $req_url->scheme ? lc $req_url->scheme : 'http';
+
+ my $cb = delete $opt{cb};
+ my $oo_url = "${prot}://${url}index.php?module=CoreAdminHome&action=optOut";
+
+ if ($opt_out eq 'opt-out-link') {
+ $opt{href} = $oo_url;
+ $opt{rel} //= 'nofollow';
+ return $c->tag('a', %opt, ($cb || sub { 'Piwik Opt-Out' }));
+ };
+
+ $opt{src} = $oo_url;
+ $opt{width} ||= '600px';
+ $opt{height} ||= '200px';
+ $opt{frameborder} ||= 'no';
+
+ return $c->tag('iframe', %opt, ($cb || sub { '' }));
+ };
+
# Create piwik tag
b(<<"SCRIPTTAG")->squish;
<script type="text/javascript">var _paq=_paq||[];(function(){var
@@ -96,9 +136,15 @@ SCRIPTTAG
# delete unused parameters
delete @{$param}{qw/site_id idSite idsite format module method/};
+ # Token Auth
+ my $token_auth = delete $param->{token_auth} ||
+ $plugin_param->{token_auth} || 'anonymous';
+
# Tracking API
if (lc $method eq 'track') {
+ $url->path('piwik.php');
+
# Request Headers
my $header = $c->req->headers;
@@ -112,29 +158,28 @@ SCRIPTTAG
$_->{rand} = int(rand(10_000));
$_->{rec} = 1;
$_->{apiv} = 1;
- $_->{url} //= $c->url_for()->to_abs;
+ $_->{url} = delete $_->{action_url} || $c->url_for->to_abs;
# Todo: maybe make optional with parameter
# $_->{_id} = rand ...
};
# Resolution
if ($param->{res} && ref $param->{res}) {
- $param->{res} = join 'x', @{$param->{res}}[0,1];
+ $param->{res} = join 'x', @{$param->{res}}[0, 1];
};
$url->query(
- idsite => ref $site_id ? $site_id->[0] : $site_id
+ idsite => ref $site_id ? $site_id->[0] : $site_id,
+ format => 'JSON'
);
+
+ $url->query({token_auth => $token_auth}) if $token_auth;
}
# Analysis API
else {
- # Token Auth
- my $token_auth = delete $param->{token_auth} ||
- $plugin_param->{token_auth} || 'anonymous';
-
# Create request method
$url->query(
module => 'API',
@@ -201,7 +246,9 @@ SCRIPTTAG
# Blocking
unless ($cb) {
my $tx = $ua->get($url);
- return $tx->res->json if $tx->success;
+
+ return _treat_response($tx->res) if $tx->success;
+
return;
}
@@ -210,14 +257,46 @@ SCRIPTTAG
$ua->get(
$url => sub {
my ($ua, $tx) = @_;
- my $json = $tx->res->json if $tx->success;
+
+ my $json = {};
+ $json = _treat_response($tx->res) if $tx->success;
+
$cb->($json);
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
};
});
};
+sub _treat_response {
+ my $res = shift;
+ my $ct = $res->headers->content_type;
+
+ if (index($ct, 'json') >= 0) {
+ return $res->json;
+ }
+
+ elsif (index($ct, 'html') >= 0) {
+
+ # Find error message in html
+ my $found = $res->dom->at('#contentsimple > p');
+
+ # Return unknown error
+ return { error => 'unknown' } unless $found;
+
+ # Return error message as json
+ return { error => $found->all_text };
+ }
+
+ elsif ($ct =~ m{^image/(gif|jpe?g)}) {
+ return {
+ image => 'data:image/' . $1 . ';base64,' . b($res->body)->b64_encode
+ };
+ };
+
+ return { error => 'Unknown response type' };
+};
+
1;
@@ -252,6 +331,8 @@ Mojolicious::Plugin::Piwik - Use Piwik in Mojolicious
L<Mojolicious::Plugin::Piwik> is a simple plugin for embedding
L<Piwik|http://piwik.org/> Analysis in your Mojolicious app.
+Please respect the privacy of your visitors and do not track
+more information than necessary!
=head1 METHODS
@@ -329,6 +410,20 @@ was registered.
This tag should be included at the bottom
of the body tag of your website.
+ %= piwik_tag 'opt-out', width => 400
+
+The special C<opt-out> tag renders an iframe helping
+your visitors to disallow tracking via javascript.
+See the L<default tag helper|Mojolicious::Plugin::TagHelpers/tag>
+for explanation of usage.
+
+ <%= piwik_tag 'opt-out-link', begin %>Opt Out<% end %>
+ # <a href="..." rel="nofollow">Opt Out</a>
+
+The special C<opt-out-link> renders an anchor link
+to the opt-out page to be used if the visitor does
+not allow third party cookies.
+
=head2 piwik_api
@@ -355,26 +450,33 @@ of the body tag of your website.
}
);
-Sends a Piwik API request and returns the response as a hash
+Sends an API request and returns the response as a hash
or array reference (the decoded JSON response).
Accepts the API method, a hash reference
with request parameters as described in the
L<Piwik API|http://piwik.org/docs/analytics-api/>, and
optionally a callback, if the request is meant to be non-blocking.
-To request the L<Tracking API|http://piwik.org/docs/tracking-api/reference/>
-use the method name C<Track>.
+The L<Tracking API|http://piwik.org/docs/tracking-api/reference/>
+uses the method name C<Track> and will forward user agent and
+referrer information based on the controller request as well as the
+url of the requested resource, unless Do-Not-Track is activated.
+The ip address is not forwarded.
$c->piwik_api(
Track => {
idsite => '4',
- res => [1024, 768]
+ res => [1024, 768],
+ action_url => 'http://khm.li/12',
+ action_name => 'Märchen/Rapunzel'
});
-The Tracking API will forward user agent and referrer information
-as well as the URL of the requested resource.
+As the C<url> parameter is used to define the Piwik instance,
+the url of the requested resource to be named C<action_url>.
-In addition to the parameters of the API reference, the following
+Please remember that cookie-based opt-out can't be supported.
+
+In addition to the parameters of the API references, the following
parameters are allowed:
=over 2
@@ -409,11 +511,15 @@ for example C<idSite> (for analysis), C<date> (for ranges) and C<res> (for track
secure => 1
});
+In case of an error, C<piwik_api> tries to response with a meaningsful
+descriptin in the hash value of C<error>.
+If an image is expected instead of a JSON object
+(as for the Tracking or the C<ImageGraph> API), the image is base64
+encoded and mime-type prefixed in the hash value of image.
+
=head1 LIMITATIONS
-Currently the API requests always expect JSON, so it's not recommended
-for the C<ImageGraph> API.
The plugin lacks support for eCommerce tracking.
@@ -426,9 +532,13 @@ and run C<make test>, for example:
{
token_auth => '123456abcdefghijklmnopqrstuvwxyz',
url => 'http://piwik.khm.li/',
- site_id => 1
+ site_id => 1,
+ action_url => 'http://khm.li/Test',
+ action_name => 'Märchen/Test'
};
+The user agent to be ignored is called C<Mojo-Test>.
+
=head1 DEPENDENCIES
@@ -447,6 +557,11 @@ Copyright (C) 2012-2013, L<Nils Diewald|http://nils-diewald.de/>.
This program is free software, you can redistribute it
and/or modify it under the same terms as Perl.
+Please make sure you are using Piwik in compliance to the law.
+For german users,
+L<this information|https://www.datenschutzzentrum.de/tracking/piwik/>
+may help you to design your service correctly.
+
This plugin was developed for
L<khm.li - Kinder- und Hausmärchen der Brüder Grimm|http://khm.li/>.
View
94 t/piwik-opt-out.t
@@ -0,0 +1,94 @@
+#!/usr/bin/env perl
+use Test::Mojo;
+use Test::More;
+use Mojolicious::Lite;
+use Mojo::JSON;
+use utf8;
+
+$|++;
+use lib '../lib';
+
+our $ft = 'auth.pl';
+
+my $t = Test::Mojo->new;
+
+my $app = $t->app;
+
+$app->mode('production');
+
+$app->plugin(Piwik => {
+ url => 'sojolicio.us/piwik'
+});
+
+my $oo = $app->piwik_tag('opt-out');
+
+like($oo, qr{http://sojolicio\.us/}, 'Opt-Out');
+like($oo, qr{frameborder="no"}, 'Opt-Out');
+like($oo, qr{height="200px"}, 'Opt-Out');
+like($oo, qr{width="600px"}, 'Opt-Out');
+like($oo, qr{^<iframe}, 'Opt-Out');
+like($oo, qr{&amp;}, 'Opt-Out');
+
+$oo = $app->piwik_tag('opt-out', 'width' => '100%');
+
+like($oo, qr{http://sojolicio\.us/}, 'Opt-Out');
+like($oo, qr{frameborder="no"}, 'Opt-Out');
+like($oo, qr{height="200px"}, 'Opt-Out');
+like($oo, qr{width="100%"}, 'Opt-Out');
+like($oo, qr{^<iframe}, 'Opt-Out');
+like($oo, qr{&amp;}, 'Opt-Out');
+
+$oo = $app->piwik_tag('opt-out', 'frameborder' => 'yes');
+
+like($oo, qr{http://sojolicio\.us/}, 'Opt-Out');
+like($oo, qr{frameborder="yes"}, 'Opt-Out');
+like($oo, qr{height="200px"}, 'Opt-Out');
+like($oo, qr{width="600px"}, 'Opt-Out');
+like($oo, qr{^<iframe}, 'Opt-Out');
+like($oo, qr{&amp;}, 'Opt-Out');
+
+$oo = $app->piwik_tag('opt-out', 'frameborder' => 'yes' => sub { 'No iframes supported'});
+
+like($oo, qr{http://sojolicio\.us/}, 'Opt-Out');
+like($oo, qr{frameborder="yes"}, 'Opt-Out');
+like($oo, qr{height="200px"}, 'Opt-Out');
+like($oo, qr{width="600px"}, 'Opt-Out');
+like($oo, qr{^<iframe}, 'Opt-Out');
+like($oo, qr{>No iframes supported<}, 'Opt-Out');
+like($oo, qr{&amp;}, 'Opt-Out');
+
+
+my $c = Mojolicious::Controller->new;
+
+$c->app($app);
+
+$c->req->url(Mojo::URL->new('http:/khm.li/Rapunzel'));
+
+$oo = $c->piwik_tag('opt-out');
+
+like($oo, qr{http://sojolicio\.us/}, 'Opt-Out');
+
+$c->req->url(Mojo::URL->new('https:/khm.li/Rapunzel'));
+
+$oo = $c->piwik_tag('opt-out');
+
+like($oo, qr{https://sojolicio\.us/}, 'Opt-Out');
+like($oo, qr{iframe}, 'Opt-Out');
+
+$oo = $c->piwik_tag('opt-out-link');
+
+like($oo, qr{href="https://sojolicio\.us/piwik/index\.php\?module=CoreAdminHome&amp;action=optOut}, 'opt-out-link');
+like($oo, qr{>Piwik Opt-Out<}, 'opt-out-link');
+like($oo, qr{rel="nofollow"}, 'opt-out-link');
+
+$oo = $c->piwik_tag('opt-out-link', sub { 'MyOptOut' });
+
+like($oo, qr{href="https://sojolicio\.us/piwik/index\.php\?module=CoreAdminHome&amp;action=optOut}, 'opt-out-link');
+like($oo, qr{>MyOptOut<}, 'opt-out-link');
+like($oo, qr{rel="nofollow"}, 'opt-out-link');
+
+$oo = $c->render_partial(inline => "<%= piwik_tag 'opt-out-link', begin %>Opt Out!<% end %>");
+
+like($oo, qr{<a href=".+" rel="nofollow">Opt Out!</a>}, 'opt-out-link');
+
+done_testing;
View
130 t/piwik-track.t
@@ -0,0 +1,130 @@
+#!/usr/bin/env perl
+use Test::Mojo;
+use Test::More;
+use Mojolicious::Lite;
+use Mojo::JSON;
+use utf8;
+
+$|++;
+use lib '../lib';
+
+our $ft = 'auth.pl';
+
+my $t = Test::Mojo->new;
+
+my $app = $t->app;
+
+$app->mode('production');
+
+$app->plugin(Piwik => {
+ url => 'sojolicio.us/piwik'
+});
+
+my $c = Mojolicious::Controller->new;
+
+$c->app($app);
+
+$c->req->url(Mojo::URL->new('http:/khm.li/Rapunzel'));
+
+$c->app($app);
+for ($c->req->headers) {
+ $_->user_agent('Firefox');
+ $_->referrer('http://khm.li/');
+};
+
+my $track = $c->piwik_api(
+ Track => {
+ idsite => '4',
+ api_test => 1,
+ res => [1024, 768]
+ });
+
+like($track, qr{idsite=4}, 'Tracking 1');
+like($track, qr{ua=Firefox}, 'Tracking 1');
+like($track, qr{rec=1}, 'Tracking 1');
+like($track, qr{urlref=http://khm\.li/}, 'Tracking 1');
+like($track, qr{res=1024x768}, 'Tracking 1');
+
+$c->app($app);
+for ($c->req->headers) {
+ $_->user_agent('Mojo-Test');
+ $_->referrer('http://khm.li/');
+};
+
+$track = $c->piwik_api(
+ Track => {
+ idsite => [qw/4 5 6/],
+ api_test => 1,
+ res => '1024x768',
+ action_url => 'http://khm.li/Rapunzel',
+ action_name => 'Märchen/Rapunzel'
+ });
+
+like($track, qr{idsite=4}, 'Tracking 2');
+like($track, qr{ua=Mojo-Test}, 'Tracking 2');
+like($track, qr{rec=1}, 'Tracking 2');
+like($track, qr{urlref=http://khm\.li/}, 'Tracking 2');
+like($track, qr{url=http://khm\.li/Rapunzel}, 'Tracking 2');
+like($track, qr{action_name=M%C3%A4rchen/Rapunzel}, 'Tracking 2');
+like($track, qr{res=1024x768}, 'Tracking 2');
+
+# Do not track
+$c->req->headers->dnt(1);
+
+$track = $c->piwik_api(
+ Track => {
+ idsite => [qw/4 5 6/],
+ api_test => 1,
+ res => '1024x768',
+ action_url => 'http://khm.li/Rapunzel',
+ action_name => 'Märchen/Rapunzel'
+ });
+
+ok(!$track, 'Do not track');
+
+# Life tests:
+# Testing the piwik api is hard to do ...
+my (%param, $f);
+if (
+ -f ($f = 't/' . $ft) ||
+ -f ($f = $ft) ||
+ -f ($f = '../t/' . $ft) ||
+ -f ($f = '../../t/' . $ft)
+ ) {
+ if (open (CFG, '<' . $f)) {
+ my $cfg = join('', <CFG>);
+ close(CFG);
+ %param = %{ eval $cfg };
+ };
+};
+
+unless ($param{url}) {
+ done_testing;
+ exit;
+};
+
+$track = $c->piwik_api(
+ Track => {
+ idsite => [qw/4 5 6/],
+ res => '1024x768',
+ action_url => 'http://khm.li/Test',
+ action_name => 'Märchen/Test',
+ %param
+});
+
+ok(!$track, 'Do not track');
+
+$c->req->headers->dnt(0);
+
+$track = $c->piwik_api(
+ Track => {
+ res => '1024x768',
+ %param
+});
+
+ok(!$track->{error}, 'No error');
+ok($track->{image}, 'Image');
+like($track->{image}, qr{base64}, 'Image');
+like($track->{image}, qr{image/gif}, 'Image');
+
+done_testing;
View
33 t/piwik.t
@@ -3,6 +3,7 @@ use Test::Mojo;
use Test::More;
use Mojolicious::Lite;
use Mojo::JSON;
+use Data::Dumper;
use utf8;
$|++;
@@ -77,28 +78,6 @@ like($url, qr{urls%5B0%5D=http:\/\/grimms-abenteuer\.de/}, 'Piwik API 9');
like($url, qr{urls%5B1%5D=http:\/\/khm\.li/}, 'Piwik API 10');
like($url, qr{idSite=4,5}, 'Piwik API 11');
-# Tracking API
-my $c = Mojolicious::Controller->new;
-$c->app($app);
-for ($c->req->headers) {
- $_->user_agent('Firefox');
- $_->referrer('http://khm.li/');
-};
-
-my $track = $c->piwik_api(
- Track => {
- idsite => '4',
- api_test => 1,
- res => [1024, 768]
- });
-
-like($track, qr{idsite=4}, 'Tracking 1');
-like($track, qr{ua=Firefox}, 'Tracking 1');
-like($track, qr{rec=1}, 'Tracking 1');
-like($track, qr{urlref=http://khm\.li/}, 'Tracking 1');
-like($track, qr{res=1024x768}, 'Tracking 1');
-
-
# Life tests:
# Testing the piwik api is hard to do ...
my (%param, $f);
@@ -158,11 +137,11 @@ my $array = $app->piwik_api(
}
);
-is($array->[0]->[0], 'piwik', 'API.getDescriptionArray 1');
-is($array->[1]->[0], 'open source', 'API.getDescriptionArray 2');
-is($array->[2]->[0], 'web analytics', 'API.getDescriptionArray 3');
-is($array->[3]->[0], 'free', 'API.getDescriptionArray 4');
-is($array->[4]->[0], 'Strong message: Свободный Тибет',
+is($array->[0], 'piwik', 'API.getDescriptionArray 1');
+is($array->[1], 'open source', 'API.getDescriptionArray 2');
+is($array->[2], 'web analytics', 'API.getDescriptionArray 3');
+is($array->[3], 'free', 'API.getDescriptionArray 4');
+is($array->[4], 'Strong message: Свободный Тибет',
'API.getDescriptionArray 5');
my $table = $app->piwik_api(

0 comments on commit 7d77dc5

Please sign in to comment.
Something went wrong with that request. Please try again.