File Coverage

blib/lib/WWW/ClickSource.pm
Criterion Covered Total %
statement 67 84 79.7
branch 36 50 72.0
condition 28 43 65.1
subroutine 8 14 57.1
pod 8 8 100.0
total 147 199 73.8


line stmt bran cond sub pod time code
1             package WWW::ClickSource;
2              
3 9     9   6352 use strict;
  9         21  
  9         259  
4 9     9   43 use warnings;
  9         15  
  9         208  
5              
6 9     9   204 use 5.010;
  9         30  
7              
8 9     9   4904 use URI;
  9         39692  
  9         263  
9 9     9   3862 use WWW::ClickSource::Request;
  9         22  
  9         266  
10              
11 9     9   54 use base 'Exporter';
  9         17  
  9         11742  
12              
13             our $VERSION = 1.0002;
14              
15             our @EXPORT_OK = ('detect_source');
16              
17             =head1 NAME
18              
19             WWW::ClickSource - Determine the source of a visit on your website : organic, adwords, facebook, referer site
20              
21             =head1 VERSION
22              
23             Version 1.0002
24              
25             =head1 DESCRIPTION
26              
27             Help determine the source of the traffic on your website.
28              
29             This module tries to do what GoogleAnalytics, Piwik and other monitoring tools do, but it's something you can
30             use on the backend of your application in real time.
31              
32             This module can be used together with L to get an even deeper understanding of where your
33             traffic is generated from.
34              
35             =head1 SYNOPSIS
36              
37             Can be used in one of two ways
38              
39             OOP interface :
40              
41             use WWW::ClickSource;
42            
43             my $click_source = WWW::ClickSource->new($request);
44              
45             my $source = $click_source->source();
46             my $medium = $click_source->medium();
47             my $campaign = $click_source->campaign();
48             my $category = $click_source->category();
49              
50             or using Export
51              
52             use WWW::ClickSource qw/detect_click_source/;
53              
54             my %click_info = detect_click_source($request);
55              
56             The C<$request> argument is one of L object or a hash ref with the fallowing structure:
57              
58             {
59             host => 'mydomain.com',
60             params => {
61             param_1 => 'value_1',
62             ...
63             param_n => 'value_n',
64             },
65             referer => 'http://referer-website.com/some_link.html?param1=value1'
66             }
67              
68             params contains the query params from the current HTTP request.
69              
70             =head1 EXAMPLE
71              
72             Here is an example on how you can use this module, to keep track of where the user came from using your session object
73              
74             In case we have a new session but the request had another page on your website as a referer (category is 'pageview') we
75             actually want to tag the current page view as being direct traffic. You have to do this yourself because C
76             doesn't know the status of your session.
77              
78             my $click_source = WWW::ClickSource->new($request);
79            
80             if (! $session->click_source ) {
81             if ($click_source->category ne "pageview") {
82             $session->click_source($click_source->to_hash);
83             }
84             else {
85             $session->click_source({category => 'direct'});
86             }
87             }
88             elsif ($click_source->category ne "pageview") {
89             $session->click_source($click_source->to_hash);
90             }
91              
92             =head1 METHODS
93              
94             =head2 new
95              
96             Creates a new C object
97              
98             =cut
99             sub new {
100 1     1 1 455 my ($class,$request,%options) = @_;
101            
102 1         2 my $self = {};
103            
104 1 50       4 if (! $request) {
105 0         0 die 'WWW::ClickSource::new() must be called with a $request argument'
106             }
107            
108 1         3 $self = detect_click_source($request);
109            
110 1 50       3 if ($options{keep_request}) {
111 0         0 $self->{request} = $request;
112             }
113            
114 1         2 bless $self, $class;
115            
116 1         4 return $self;
117             }
118              
119             =head2 detect_click_source
120              
121             Determine where the user came from based on a request object
122              
123             =cut
124             sub detect_click_source {
125 24     24 1 12782 my ($user_request) = @_;
126            
127 24         98 my $request = WWW::ClickSource::Request->new($user_request);
128            
129 24         36 my %click_info;
130            
131 24 100       102 if ( my $params = $request->{params} ) {
132 19 50 66     222 if ( $params->{utm_source} || $params->{utm_campaign} || $params->{utm_medium} ) {
      33        
133             %click_info = (
134             source => $params->{utm_source} // '',
135             campaign => $params->{utm_campaign} // '',
136 5   50     33 medium => $params->{utm_medium} // '',
      50        
      50        
137             );
138            
139 5 50       14 if (! $click_info{source} ) {
140 0 0       0 if ( $request->{referer} ) {
141 0 0       0 if ($request->{referer}->scheme =~ /https?/) {
    0          
142 0         0 $click_info{source} = $request->{referer}->host;
143             }
144             elsif ($request->{referer}->scheme eq 'android-app') {
145             $click_info{source} = 'android-app',
146             $click_info{app} = $request->{referer}->path,
147 0         0 }
148             }
149             }
150            
151 5 100       36 if ( $click_info{medium} =~ m/cpc|cpm|facebook_ads/ ) {
    50          
152 4         9 $click_info{category} = 'paid';
153             }
154             elsif ( $request->{referer} ) {
155 1         10 $click_info{category} = 'referer';
156             }
157             else {
158 0         0 $click_info{category} = 'other';
159             }
160             }
161            
162 19 100       51 if ( $params->{gclid} ) { #gclid is a google adwords specific parameter
163 4 100       96 if ( $request->{referer} ) {
164 3 100       33 if ( $request->{referer}->scheme =~ /https?/ ) {
    50          
165 2 50       57 if ( $request->{referer}->host =~ m/(?:google\.(?:com?\.)?\w{2,3}|googleadservices\.com|googleads\.\w+\.doubleclick\.net)$/ ) {
166 2         85 %click_info = (
167             %click_info,
168             source => 'google',
169             medium => 'cpc',
170             category => 'paid',
171             );
172             }
173             } elsif ( $request->{referer}->scheme eq 'android-app' ) {
174             %click_info = (
175             %click_info,
176             source => 'android-app',
177             app => $request->{referer}->authority,
178 1         68 medium => 'cpc',
179             category => 'paid',
180            
181             );
182             }
183             else {
184             %click_info = (
185             %click_info, # utm_* params take precedence over our guess
186 0         0 source => $request->{referer} ."", #stringify
187             medium => 'cpc',
188             category => 'paid',
189             );
190             }
191             }
192             else { #gclid param without referer - just use defaults for google, since we don't know anything else
193 1         3 %click_info = (
194             %click_info,
195             source => 'google',
196             medium => 'cpc',
197             category => 'paid',
198            
199             );
200             }
201             }
202             }
203            
204 24 100       118 if (! $click_info{medium} ) {
205 15 100       343 if ( $request->{referer} ) {
206            
207 14 100       135 if ( $request->{referer}->scheme =~ /https?/ ) {
    50          
208            
209 13         368 my $referer_base_url = $request->{referer}->host . $request->{referer}->path;
210            
211 13 100       638 if ( $referer_base_url =~ m/(?:google\.(?:com?\.)?\w{2,3}|googleadservices\.com).*?\/aclk/ ) {
212            
213 2         7 %click_info = (
214             %click_info,
215             source => 'google',
216             medium => 'cpc',
217             category => 'paid',
218             );
219             }
220             else {
221 11 100       27 if ( $request->{referer}->host eq $request->{host} ) {
222             %click_info = (
223             %click_info,
224             source => $request->{host},
225 1         31 category => 'pageview',
226             );
227             }
228             else {
229             %click_info = (
230             %click_info,
231             source => $request->{referer}->host,
232 10         222 category => 'referer',
233             );
234             }
235             }
236             }
237             elsif ( $request->{referer}->scheme eq 'android-app' ) {
238             %click_info = (
239             %click_info,
240             source => 'android-app',
241             app => $request->{referer}->authority,
242 1         41 category => 'referer',
243             );
244             }
245             else {
246             %click_info = (
247             %click_info,
248 0         0 source => $request->{referer} ."", #stringify
249             category => 'referer',
250             );
251             }
252            
253            
254             }
255             else {
256 1         2 $click_info{category} = 'direct';
257             }
258            
259 15 100 100     351 if ( $click_info{source} && $click_info{source} =~ m/l\.facebook\.com/ ) {
    100 100        
260 1         3 $click_info{source} = 'facebook';
261 1         2 $click_info{medium} = 'paid';
262             }
263             elsif ( $click_info{source} && $click_info{source} =~ m/(?:(?:m|www)\.)?(facebook|twitter|linkedin|plus\.google)\.(?:com?\.)?\w{2,3}/ ) {
264 2         6 $click_info{source} = $1;
265 2         5 $click_info{medium} = 'social';
266             }
267             }
268            
269 24 100 100     157 if ( $click_info{source} && $click_info{category} eq "referer" && (
      100        
      100        
270             $click_info{source} =~ m/(?:www|search\.)?(google|yahoo|bing|yandex|baidu|aol|ask|duckduckgo)\.(?:com?\.)?\w{2,3}$/ ||
271             $click_info{source} =~ m/webcache\.(google)usercontent\.com/ )
272             ) {
273 7         14 $click_info{source} = $1;
274 7         11 $click_info{category} = 'organic';
275 7         10 $click_info{medium} = 'organic';
276             }
277            
278            
279             #default to empty strings to avoid undefined value warnings in string comparisons
280 24   100     64 $click_info{source} //= '';
281 24   100     85 $click_info{campaign} //= '';
282 24   100     61 $click_info{medium} //= '';
283 24   50     44 $click_info{category} //= '';
284            
285 24 100       233 return %click_info if wantarray;
286            
287 1         5 return \%click_info;
288             }
289              
290             =head2 source
291              
292             Source of the click picked up from C request param or referer domain name
293              
294             Only available in OOP mode
295              
296             =cut
297             sub source {
298 0     0 1   return $_[0]{source};
299             }
300              
301             =head2 medium
302              
303             Medium from which the click originated, usually picked up from C request param
304              
305             Only available in OOP mode
306              
307             =cut
308             sub medium {
309 0     0 1   return $_[0]{medium};
310             }
311              
312             =head2 category
313              
314             Click category, can be one of : direct, paid, referer, pageview
315              
316             'pageview' means the user came accessed the current page by clicking on a link on another page
317             of the same website. (referer host is the same as your domain name)
318              
319             Only available in OOP mode
320              
321             =cut
322             sub category {
323 0     0 1   return $_[0]{category};
324             }
325              
326             =head2 campaign
327              
328             Campaign from which the click originated, usually picked up from C request param
329              
330             Only available in OOP mode
331              
332             =cut
333             sub campaign {
334 0     0 1   return $_[0]{campaign};
335             }
336              
337              
338             =head2 to_hash
339              
340             Return a hash containing all the relevant attributes of the current object
341              
342             Only available in OOP mode
343              
344             =cut
345             sub to_hash {
346 0     0 1   my $self = shift;
347            
348             my %info = (
349             source => $self->{source} // '',
350             campaign => $self->{campaign} // '',
351             category => $self->{category} // '',
352 0   0       medium => $self->{medium} // ''
      0        
      0        
      0        
353             );
354            
355 0           return \%info;
356             }
357              
358             =head2 request
359              
360             Instance of L or a subclass of it, representing the internal request object
361             used to extract the info we need
362              
363             Only available in OOP mode and if you specify that you want access to the request object using keep_request => 1
364              
365             my $click_source = WWW::ClickSource->new($request, keep_request => 1);
366              
367             =cut
368             sub request {
369 0     0 1   return $_[0]{request};
370             }
371              
372             1;
373              
374             =head1 AUTHOR
375              
376             Gligan Calin Horea, C<< >>
377              
378             =head1 REPOSITORY
379              
380             L
381              
382             =head1 BUGS
383              
384             Please report any bugs or feature requests to C, or through
385             the web interface at L. I will be notified, and then you'll
386             automatically be notified of progress on your bug as I make changes.
387              
388             =head1 SUPPORT
389              
390             You can find documentation for this module with the perldoc command.
391              
392             perldoc WWW::ClickSource
393              
394             You can also look for information at:
395              
396             =over 4
397              
398             =item * RT: CPAN's request tracker (report bugs here)
399              
400             L
401              
402             =item * AnnoCPAN: Annotated CPAN documentation
403              
404             L
405              
406             =item * CPAN Ratings
407              
408             L
409              
410             =item * Search CPAN
411              
412             L
413              
414             =back
415              
416             =head1 LICENSE AND COPYRIGHT
417              
418             Copyright 2016 Gligan Calin Horea.
419              
420             This program is free software; you can redistribute it and/or modify it
421             under the terms of either: the GNU General Public License as published
422             by the Free Software Foundation; or the Artistic License.
423              
424             See http://dev.perl.org/licenses/ for more information.
425              
426              
427             =cut