Yii 1.1: pcviewscounter

Module enabling counting unique and non-unique views on arbitrary content types.
18 followers

General

This is a self contained module that enables you to track and display 'view counter' on content types in your Yii based website

Features

  • Attach-able to any content type on your site.
  • Self contained widget provides all you need to integrate a 'views count widget' in existing pages.
  • Widget can be called in 'view-only' mode, which means it won't advance the counter but just display it. This is good for showing 'views count' in a tabular presentation of data. For example, when you want to show a table view (=CGridView) of posts, along with data of views count in each row (=on each post).
  • Works in AJAX after the page has been loaded. This has two great benefits:
    • It minimizes the performance impact on the site.
    • It skip false counts for search engine which, to the best of my knowledge, don't issue AJAX requests and thus no count will be accounted on visits from search engines/other-crawlers.
  • Count views for both guest and authenticated users.
  • Tracks users based on user_id (if logged in), cookie and IP address (supports tracking on both IPv4 and IPv6).
  • Counts both unique and non-unique impressions. Each instantiation of the included widget can be asked to run in unique or non-unique mode and they can be combined on the same page.

Requirements

  • Requires a 'users' table with primary key column named "id" (it uses this to record impressions made by logged in users).
  • Uses and therefore requires PcBaseArModel extension (it uses it for "safe" updates of records, using optimistic locking).
  • Requires 'cache' application component. Be sure to either have it working or rewrite ViewsCountWidget._setPersistentCounterConfig() and ViewsCountWidget._getPersistentCounterConfig(). See those methods for more information.
  • The provided widget records the IP address of the user. You'll need to provide it with the IP address. See recommended package below, in the 'Usage/Installation' section.

Usage/Installation

  • Unpack this extension under /protected/modules
  • In Yii's main.php config file:

main.php

'import' => array(
        //...
        // Views counter module
        'application.modules.PcViewsCounter.*',
        'application.modules.PcViewsCounter.models.*',
        'application.modules.PcViewsCounter.controllers.*',
        'application.modules.PcViewsCounter.components.*',
        'application.modules.PcViewsCounter.extensions.ViewsCountWidget.*',
        //...
),
//...
'modules' => array(
        //...
        'contentViewsCounter' => array(
            'class' => 'application.modules.PcViewsCounter.PcViewsCounterModule',
        ),
        //...
),
  • Setup a new encryption key in ViewsCountWidget::COOKIE_ENCRYPT_KEY (or your user's viewing history could be hijacked by a third party). This key is used to encrypt the views counter cookie value.
  • Install the schema file located in /data directory in this package tree. You might need to alter it a bit. The schema file is for MySQL v5.1.
  • Since the widget will try to store the IP address of the user, it needs to be able to tell it somehow. I recommend using another extension that I've written: PcMaxmindGeoIP that has a powerful method for retriving the remote IP address. In fact, the code already includes a usage of this extension so you need to either download and install it, or update the line of code. This is line 51 (as of writing these documentation lines):
$this->_clientIpAddress = Yii::app()->geoip->getRemoteIpAddress();

Configurable parameters passed to the widget instance:

  • modelClassName: string. the model class name this widget instance refers to
  • modelId: integer. the model object id this widget instance refers to
  • uniqueMode: boolean. Whether this widget instance should run, count and display unique or non-unique views. Defaults to true, meaning works in unique mode.
  • dontCount: boolean. Whether this widget should run in 'read only' mode, meaning no impression count would be performed - just showing existing views count is done.

In the view file

Sample code:

$this->widget("ViewsCountWidget", array('modelId' => $model->id, 'modelClassName' => get_class($model)));

API Methods

(Since v1.3)

  • int PcViewsCounterModule::getViewsCount($model_name, $model_id, $unique = true) : Static method that is used to retrieve the views counter of a certain model, programmatically on server side. This is useful if other components of your application needs to make decisions based on the views count. Note that unique/non-unique count is easily distinguished by the $unique boolean parameter.

Design Notes

  • Some information is saved in a cookie. Due to cookies limitations on client side (i.e. browsers), we save this info in a single cookie. This cookie data structure is as follows: (serialized and encrypted) array('modelname-modelId' => UTC_timestamp_when_viewed)
  • More? That's my kind of attitude! Use the force - read the source! The code is full with useful (hopefully) documentation notes.
  • The following is the algorithm for impression request handling (the AJAX request). Notice that the check for an impression cookie is performed for BOTH guest and authenticated users:
if impression-cookie exists (this is for guest)
    do nothing.
else
    if authenticated
        search PageViewBookeeping record for this user (based on user_id, PageViewsStat->id (which is an FK in the former class/table))
        if not found search for PageViewBookeeping record based on ip address.
        if found in either of the 2 checks above
            add this information into the impression cookie.
            return
        else
            add impression record for this user.
            add this information into the impression cookie.
    else (this is guest user)
        add cookie for this guest user unique impression
        search for PageViewBookeeping record based on ip address.
        if NOT found
            add impression record for guest (record his ip address)
        else 
            return existing counter
    add +1 to unique stats counter for this model name + id. 
  • The following is a drawing of the schema used in this extension:

schema drawing

Resources

Change Log

  • 26 March 2014 v1.3: Added getViewsCount(), a static method in the module class for getting stats on server side, no rendering etc. This can be utilized by other components that can read the stats and act accordingly (for example, render stuff differently if above certain views count). Also, a few modifications for PHP v5.4
  • 10 Jan 2013 v1.2 : Bugfix. Now supports rendering multiple instances of widget on the same page.
  • 6 July 2012 v1.1 : Attached missing schema file.

Total 18 comments

#16229 report it
Dawpro at 2014/02/01 10:38am
Alternative cache method

Hey,

Instead of using memcache, I have used the file cache

In the config file:

'cache' => array(
       'class' => 'CFileCache'
),

Thank you for the extension Boaz ;)

#14716 report it
Boaz at 2013/09/05 02:53pm
@jengtong

Hi,

Thanks for taking the time to provide a feedback!

Unfortunately, I don't have much spare time these days and I haven't been into this package internals lately so I cannot technically comment in a wise manner.

I think it would be the best to move the (good and encouraged!) design discussion to a relevant forum thread. I'll do so when I find the time. Hopefully by then you'll be able to respond. I always love debating a design - I typically find myself wiser at its end, no matter what conclusions were drawn.

I'll open a forum thread when I can and let you know. There, we could further discuss the points you've raised.

Boaz.

#14708 report it
jengtong at 2013/09/05 02:25am
Good but can be better

Hello Boaz

First of all, thank you for this well made and well documented extension, I finally got it to work correctly. However, I have done a lot of huge modifications, and would like to share with you a bit.

Firstly, I changed the whole package. I always prefer a handy-dandy extension rather than a module or controller combination that might change my system structure. So I turned your controller file into an CAction instance, so I can directly use it in my controller as an action.

Besides, your extension seems like requiring too much prerequisites. You are using a different CActiveRecord abstract file which you have created in other extension, and you require a cache component to run your extension too (if I don't override your _set and _getPersistantCounterConfig). Nonetheless, your 2 models requires separate db tables to run correctly. This would be quite troublesome for some newbie programmers, maybe. Some other minor things, which were stated by Drini before, would also need some fixes.

To deal with all these problems, it takes quite some time to test and correct. Hopefully you can try to include everything in one package in the future, and make sure it can work out-of-the-box. I always like work-out-of-the-box, or do the configurations in my main config file or set the params directly at the place I use it.

In short, you concepts and logics will save us a lot of work, but on the other hand, to include your extension, it takes some other work too. Anyway, thank you for this view counter.

#11421 report it
beesho at 2013/01/13 08:54am
Not sure why it's not working..

I have a problem getting this to work.. I am adding this: $this->widget("ViewsCountWidget", array('modelId' => $model->id, 'modelClassName' => get_class($model))); to the view file of the User(yii-user) model, and I always get: "views: 0".

What do u mean by: •Requires 'cache' application component. Be sure to either have it working or rewrite ViewsCountWidget._setPersistentCounterConfig() and ViewsCountWidget._getPersistentCounterConfig(). See those methods for more information.

Could it be not working because of this? Or what do you think?

#11350 report it
Boaz at 2013/01/08 02:37am
@Janoo

Exactly: making a page submit AJAX calls is an effective search engine filter. Ever seen a search engine that after it loads the page will runs its JS and execute everything in it inc. AJAX calls? AFAIK no search engine does this. You're invited to check it on the web (Google etc).

#11349 report it
Janoo at 2013/01/08 02:30am
thanks

thanks for your answer Boaz, i diden't understood exactly, thats way i asked it :)

but it's interesting for me, the ajax request the way to filter search robots?!

a pharse in my language: one can always learn something

:-)

#11345 report it
Boaz at 2013/01/07 09:58am
@Janoo

Regarding Google Analytics - its totally different. This one only records page views and that's it. GA is much more. This extension is designed to show a nice counter on each page (or resource you render).

It should skip robots, as noted in the documentation above, since it uses AJAX to mark a view, and searching engines will not send the needed AJAX calls after page has been loaded.

#11344 report it
Janoo at 2013/01/07 09:54am
is it as good...

is it as good, as google analytics??

is it counting search robots too, or how is it avoid that??

thanks for answers, and sorry bad english

#9449 report it
Boaz at 2012/08/12 08:15am
@livermore

Hi,

For a start, you're rendering the widget in a way that it always marks its impressions on model id =1. This is not what you want... . Notice the example I gave above for rendering of the widget. It uses the following code: array('modelId' => $model->id and not array('modelId' => 1...) ... :-)

Lets see how you're doing after this...

#9448 report it
livermore at 2012/08/12 05:13am
Not working. Did I miss something?

I think I have followed all your steps but it does not work.

1 - I have put PcBaseArModel.php in /protected/components

2 - I have updated my main.php like:

'import'=>array(
        'application.modules.PcViewsCounter.*',
        'application.modules.PcViewsCounter.models.*',
        'application.modules.PcViewsCounter.controllers.*',
        'application.modules.PcViewsCounter.components.*',
        'application.modules.PcViewsCounter.extensions.ViewsCountWidget.*',
'components'=>array(
                  'geoip' => array(
            'class' => 'ext.PcMaxmindGeoIp.PcMaxmindGeoIp',
          ),
'modules'=>array(
        'contentViewsCounter' => array(
                            'class' => 'application.modules.PcViewsCounter.PcViewsCounterModule',
                            ),

3- I have unpacked into /extensions/PcMaxmindGeoIp:

./PcMaxmindGeoIp.php
./maxmind/GeoLiteCity.dat
./maxmind/geoip_maxmind_pure_php_API__php-1.11/*

4- And I have put in my view file this code:

echo $this->widget("ViewsCountWidget", array('modelId' => 1, 'modelClassName' => get_class($model)));

modelId is like a unique id for everymodel I have, isn't it?

5- and I have created into my database both tables:

- page_views_bookeeping
- page_views_stats

Am I doing something wrong?? Did I miss something??

Because, if a call my/website/my/view/file.php I always see

Views: 0

And in my database, the table page_views_stats has been updated, but nothing happen with the table page_views_bookeeping (this one is always empty, why??)

Boaz, could you guide me with my installation?

Thank you for this tool!

#8972 report it
Boaz at 2012/07/11 03:20am
@Drini
  1. Always - "use the force - read the source" :-)

  2. I wonder about the warning or maybe even the total explosion of your PHP env on the use of gmmktime(). WTF? What's the problem with it? Although as a side note I must say that developing on as pedantic as possible environment is a good practice, and it appears that mine is not, at the moment.

I'm glad you find it useful after all! :)

#8971 report it
Sampa at 2012/07/11 03:09am
@boaz

"->> and the use of gmttime() instead of time.

I'm not sure what's the issue here. If you want, please elaborate more on this."

I have no clue, but yii halted on the lines using gmttime() telling me to use time() instead.

Thank you for telling me where to look, it helped, now I have the extension completely working.

#8964 report it
Boaz at 2012/07/10 08:10am
@Drini

->> Oh well, I finally have it working, kinda. It still says 0 for unique visitors but not unique ->> visits is updated. Should say 1 on unique visitors... shouldnt it?

Well it says whatever is in its page_views_stats.count_uniq (if your widget is set to uniq mode). If it comes to a decision that you already voted it wont count the uniq count column and simply show what's inside. And how does it decide if you viewed this content or not? I actually started writing here but its too long and too clear in the code. Have a look at ViewsCountWidget.php, line 136 (in the run() method).

->> There was many errors thrown by yii disliking things in the code, declaration of the findByAttributes method must match the parent declaration for example

I guess you're right. It would be nice if you could submit a bug on this on the github project page (see link in 'resources' section above.

->> and the use of gmttime() instead of time.

I'm not sure what's the issue here. If you want, please elaborate more on this.

->> name of module in the main config and the name of folder didnt match, gave url fails.

Hmm... you're probably right here as well. The folder name for this module should be PcViewsCounter. A bug on this would be nice as well... :)

Thanks for the report!

#8963 report it
Sampa at 2012/07/10 07:05am
@boaz

Yes I noticed that and installed that extension from the start, else how would Yii know the methods?:p

Oh well, I finally have it working, kinda. It still says 0 for unique visitors but not unique visits is updated. Should say 1 on unique visitors... shouldnt it?

There was many errors thrown by yii disliking things in the code, declaration of the findByAttributes method must match the parent declaration for example, and the use of gmttime() instead of time.

name of module in the main config and the name of folder didnt match, gave url fails. Thats the errors I haven't mentioned that I had to solve during installation.

Thanks for sharing this btw.

#8922 report it
Boaz at 2012/07/07 04:28pm
@Drini

Yes, this extension requires PcBaseArModel - which is another extension I've written, and that I use. Its noted in the requirements section. That extension provides a class that requires a couple of its methods (which are abstract) - be implemented. This is really not a big deal. Read that extension's documentation and see how easy it is to implement those methods. This is relevant only if you're extending PcBaseArModel - which is yet again, a matter for the other extension, not this one (unless there's some bug that I'll be happy to hear about).

You're right about the 'geoip' app component I didn't mention. I've updated the documentation to reflect this need. Please see above... .

The views counting is not taken from the bookeeping table but rather from the 'main' stats table. But anyhow if you got logging enabled - please check the logs to get a better insights for problems this extension is facing.

Thanks for the feedback

#8921 report it
Sampa at 2012/07/07 04:02pm
Views not updating.

I had some troubles installing, php threw errors over the abstract static methods in PcBaseArModel extension, and didnt find Yii::app()->geoip so tried changing to

$this->_clientIpAddress = Yii::app()->request->userHostAddress; also define tablenames '{{tablename}}' so users with table prefix doesnt have to change it.

It creates a row for the model whos view i visit in the db, but nothing in the page_views_bookeeping table. So the view count stays at 0 and updated at null. Maybe I'm just to tired now but what am I missing?:p Thx

#8901 report it
Boaz at 2012/07/06 09:52am
You mean here or in the code?

@WebDevPT,

If you mean what's here, then I also recommend peeking at the source files inline documentation. That's where I leave my thoughts - typically for myself, but obviously other developers are welcomes to sync in, for a joyful and fruitful outcome.

thanks anyhow :) Boaz.

#8890 report it
WebDevPT at 2012/07/06 06:41am
Thank you!

Very good documentation!

Leave a comment

Please to leave your comment.

Create extension