Index: quintagroup.analytics/branches/mylan/MANIFEST.in
===================================================================
--- quintagroup.analytics/branches/mylan/MANIFEST.in	(revision 3057)
+++ quintagroup.analytics/branches/mylan/MANIFEST.in	(revision 3057)
@@ -0,0 +1,4 @@
+recursive-include docs *
+recursive-include quintagroup *
+global-exclude *.pyc
+global-exclude *.mo
Index: quintagroup.analytics/branches/mylan/README.txt
===================================================================
--- quintagroup.analytics/branches/mylan/README.txt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/README.txt	(revision 3057)
@@ -0,0 +1,59 @@
+Quintagroup Analytics Tool for Plone
+====================================
+
+.. figure:: http://quintagroup.com/services/plone-development/products/quintagroup.analytics/plone-analytics.png/
+
+quintagroup.analytics provides statistic information about your Plone site. It 
+adds few content stats views of plone content workflow states, ownership and 
+portlets registered on different contexts. 
+
+Information provided by Quintagroup Analytics Tool allows you to see Plone site 
+content from different perspectives. This information can be very useful while 
+migrating your site into newer Plone version, or into another CMS.
+
+With its help you can visually audit the content setup in Plone site before migration 
+and compare it with the migrated website structure.
+
+Usage
+-----
+
+To see your Plone site statistic information - navigate to 'Quintagroup Analytics' item 
+under Add-on Products Configuarion. Browse through all configlet tabs to see all statistic
+information, generated by Quintagroup Analytics Tool:
+
+* Content Ownership by Type - information about most popular content types on your site. Here you
+  can see the most frequently created content types on your site and their owners.
+* Content Ownership by State - information about site's content workflow states. Here you can see
+  how many content object are published/submitted for review/etc. and their owners.
+* Content Types by State - information about site's most frequently created content types and their
+  workflow states.
+* Site Portlets - information about site portlets assigned throughout site sections. This information
+  can be exported into .csv format. You can see all portlets assigned on your site and edit them. 
+* Legacy Portlets - information about legacy assigned throughout site sections. This information
+  can be exported into .csv format.
+* Properties stats - information on certain property values for all site objects, such as
+  titles, descriptions, etc. This information can be exported into .csv format.
+
+Compatibility
+-------------
+
+Plone 3.x, Plone 4.0
+
+Links
+-----
+
+* Product Homepage: http://quintagroup.com/services/plone-development/products/quintagroup.analytics
+* Documentation Area: http://projects.quintagroup.com/products/wiki/quintagroup.analytics
+* Repository: http://svn.quintagroup.com/products/quintagroup.analytics
+* Releases: http://plone.org/products/quintagroup.analytics/releases
+
+Authors and contributors
+------------------------
+
+* Myroslav Opyr
+* Volodymyr Cherepanyak
+* Taras Melnychuk
+* Bohdan Koval'
+* Roman Kozlovskyi
+* Vitaliy Podoba
+
Index: quintagroup.analytics/branches/mylan/docs/HISTORY.txt
===================================================================
--- quintagroup.analytics/branches/mylan/docs/HISTORY.txt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/docs/HISTORY.txt	(revision 3057)
@@ -0,0 +1,8 @@
+Changelog
+=========
+
+1.0 - 2010-11-19
+----------------
+
+* Initial release
+
Index: quintagroup.analytics/branches/mylan/docs/INSTALL.txt
===================================================================
--- quintagroup.analytics/branches/mylan/docs/INSTALL.txt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/docs/INSTALL.txt	(revision 3057)
@@ -0,0 +1,31 @@
+quintagroup.analytics Installation
+----------------------------------
+
+To install quintagroup.analytics to your buildout-based project:
+
+* Add ``quintagroup.analytics`` to the list of eggs to install::
+
+    [buildout]
+    ...
+    eggs =
+        ...
+        quintagroup.analytics
+       
+* Tell the plone.recipe.zope2instance recipe to install a ZCML slug::
+
+    [instance]
+    recipe = plone.recipe.zope2instance
+    ...
+    zcml =
+        quintagroup.analytics
+
+* Re-run buildout, e.g. with::
+
+    $ ./bin/buildout
+
+* Restart the Zope server, e.g with the following command in the terminal::
+
+   $ ./bin/instance restart
+   
+* Install ``Quintagroup Analytics`` with Quickinstaller in Plone (Site Setup -> Add/Remove Products) 
+
Index: quintagroup.analytics/branches/mylan/docs/LICENSE.GPL
===================================================================
--- quintagroup.analytics/branches/mylan/docs/LICENSE.GPL	(revision 3057)
+++ quintagroup.analytics/branches/mylan/docs/LICENSE.GPL	(revision 3057)
@@ -0,0 +1,225 @@
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
Index: quintagroup.analytics/branches/mylan/docs/LICENSE.txt
===================================================================
--- quintagroup.analytics/branches/mylan/docs/LICENSE.txt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/docs/LICENSE.txt	(revision 3057)
@@ -0,0 +1,16 @@
+  quintagroup.analytics is copyright Quintagroup
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
+  MA 02111-1307 USA.
Index: quintagroup.analytics/branches/mylan/quintagroup/__init__.py
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/__init__.py	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/__init__.py	(revision 3057)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/README.txt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/README.txt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/README.txt	(revision 3057)
@@ -0,0 +1,58 @@
+Introduction
+============
+
+Quintagroup Analytics Tool (quintagroup.analytics) provides statistic information
+about your Plone site. It adds few content stats views of plone content workflow
+states, ownership and portlets registered on different contexts. 
+
+Information provided by Quintagroup Analytics Tool allows you to see Plone site 
+content from different perspectives. This information can be very useful while 
+migrating your site into newer Plone version, or into another CMS.
+
+With its help you can visually audit the content setup in Plone site before migration 
+and compare it with the migrated website structure.
+
+Usage
+-----
+
+To see your Plone site statistic information - navigate to Quintagroup Analytics item 
+under Add-on Products Configuarion. Browse through all configlet tabs to see all statistic
+information, generated by Quintagroup Analytics Tool:
+
+* Ownership by type - information about most popular content types on your site. Here you
+  can see the most frequently created content types on your site and their owners.
+* Ownership by state - information about site's content workflow states. Here you can see
+  how many content object are published/submitted for review/etc. and their owners.
+* Types by state - information about site's most frequently created content types and their
+  workflow states.
+* Portlets stats - information about site portlets and their location. You can see all 
+  portlets assigned on your site and edit them. 
+* Legacy portlets - information about legacy portlets and their location. You can see all 
+  legacy portlets assigned on your site and edit them. This information can be exported into
+  .csv format.
+* Properties stats - information on certain property values for all site objects, such as
+  titles, descriptions, etc. This information can be exported into .csv format.
+
+Compatibility
+-------------
+
+Plone 3.x, Plone 4.0
+
+Links
+-----
+
+* Product Homepage: http://quintagroup.com/services/plone-development/products/quintagroup.analytics
+* Documentation Area: http://projects.quintagroup.com/products/wiki/quintagroup.analytics
+* Repository: http://svn.quintagroup.com/products/quintagroup.analytics
+* Releases: http://plone.org/products/quintagroup.analytics/releases
+
+Authors and contributors
+------------------------
+
+* Myroslav Opyr
+* Volodymyr Cherepanyak
+* Taras Melnychuk
+* Bohdan Koval'
+* Roman Kozlovskyi
+* Vitaliy Podoba
+
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/configure.zcml
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/configure.zcml	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/configure.zcml	(revision 3057)
@@ -0,0 +1,70 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser"
+           i18n_domain="quintagroup.analytics">
+
+    <browser:page
+        name="qa_overview"
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        permission="zope2.ViewManagementScreens"
+        template="qa_overview.pt"
+        />
+
+    <browser:page
+        name="ownership_by_type"
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        permission="zope2.ViewManagementScreens"
+        class=".views.OwnershipByType"
+        template="ownership_by_type.pt"
+        />
+
+    <browser:page
+        name="ownership_by_state"
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        permission="zope2.ViewManagementScreens"
+        class=".views.OwnershipByState"
+        template="ownership_by_state.pt"
+        />
+
+    <browser:page
+        name="type_by_state"
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        permission="zope2.ViewManagementScreens"
+        class=".views.TypeByState"
+        template="type_by_state.pt"
+        />
+
+    <browser:page
+        name="legacy_portlets"
+        for="*"
+        permission="zope2.ViewManagementScreens"
+        class=".views.LegacyPortlets"
+        template="legacy_portlets.pt"
+        />
+
+    <browser:page
+        name="properties_stats"
+        for="*"
+        permission="zope2.ViewManagementScreens"
+        class=".views.PropertiesStats"
+        template="properties_stats.pt"
+        />
+
+    <browser:page
+        name="portlets_stats"
+        for="*"
+        permission="zope2.ViewManagementScreens"
+        class=".views.PortletsStats"
+        template="portlets_stats.pt"
+        />
+
+    <browser:resource
+        name="stats_style.css"
+        file="resources/stats_style.css"
+        />
+
+    <browser:resource
+        name="qaicon.png"
+        image="resources/qaicon.png"
+     />
+
+</configure>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/legacy_portlets.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/legacy_portlets.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/legacy_portlets.pt	(revision 3057)
@@ -0,0 +1,89 @@
+    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main" i18n:domain="quintagroup.analytics"
+            tal:define="infos view/getPortlets">
+ <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a class="selected" href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+            </ul>
+            <p>The following list can display site's legacy portlets, assigned as left and right slots on different
+               site contexts. Specify the search depth: portlets on what level(s) to be included. Select CSV to render
+               this information in .csv format.</p>
+            <form action="" method="get">
+                Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):
+                <input type="text" name="level" size="5" value="1" tal:attributes="value request/level|python:1" />
+                CSV
+                <input type="checkbox" name="csv" tal:attributes="checked request/csv|python:0" />
+                <input type="submit" name="submit" value="Search" />
+            </form>
+        </div>
+        <div tal:condition="request/csv|nothing">
+            <nobr>path,left_slots,right_slots</nobr><br>
+            <nobr tal:repeat="info infos">
+                <span tal:content="info/path">path</span>,
+                <span tal:condition="info/left_slots">"
+                    <span tal:repeat="slot info/left_slots">
+                        <span tal:content="slot" />;
+                    </span>"
+                </span>
+                <span tal:condition="python:info['left_slots'] is None">None</span>,
+                <span tal:condition="info/right_slots">"
+                    <span tal:repeat="slot info/right_slots">
+                        <span tal:content="slot" />;
+                    </span>"
+                </span>
+                <span tal:condition="python:info['right_slots'] is None">None</span>
+                <br>
+            </nobr>
+        </div>
+        <table class="listing"
+               tal:condition="not:request/csv|nothing">
+            <tr>
+                <th></th>
+                <th>left_slots</th>
+                <th>right_slots</th>
+            </tr>
+            <tr tal:repeat="info infos">
+                <th><a href="#" tal:content="info/path" tal:attributes="href info/href">path</a></th>
+                <td>
+                    <ul tal:condition="info/left_slots">
+                        <li tal:repeat="slot info/left_slots" tal:content="slot">slot</li>
+                    </ul>
+                    <span tal:condition="python:info['left_slots'] is None">doesn't exist</span>
+                </td>
+                <td>
+                    <ul tal:condition="info/right_slots">
+                        <li tal:repeat="slot info/right_slots" tal:content="slot">slot</li>
+                    </ul>
+                    <span tal:condition="python:info['right_slots'] is None">doesn't exist</span>
+                </td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td colspan="2" tal:content="view/getTotal">123</td>
+            </tr>
+            <tr>
+                <th>Expressions</th>
+                <td colspan="2">
+                    <ul>
+                        <li tal:repeat="expr view/getAllPortletExpressions" tal:content="expr">expressions</li>
+                    </ul>
+                </td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_state.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_state.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_state.pt	(revision 3057)
@@ -0,0 +1,50 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main" i18n:domain="quintagroup.analytics">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a class="selected" href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+            </ul>
+        </div>
+        <p>The following chart displays workflow states of site's content objects in relation to their owners
+           (up to 10 most active site content contributors are displayed).</p>
+        <p>The table under the chart displays number of content objects in all site's workflow states in relation
+           to content owners.
+        </p>
+        <div id="chart" tal:content="structure view/getChart">
+            Chart will be here
+        </div>
+        <table class="listing">
+            <tr>
+                <th></th>
+                <th tal:repeat="user view/getUsers" tal:content="user">User</th>
+            </tr>
+            <tr tal:repeat="type view/getStates">
+                <th tal:content="type">Document</th>
+                <td tal:repeat="count python:view.getContent(type)" tal:content="count">123</td>
+            </tr>
+            <tr>
+                <th tal:content="view/getNoWFContentTitle">Document</th>
+                <td tal:repeat="count view/getNoWFContent" tal:content="count">123</td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td tal:repeat="total view/getTotal" tal:content="total">123</td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_type.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_type.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/ownership_by_type.pt	(revision 3057)
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main" i18n:domain="quintagroup.analytics">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a class="selected" href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+            </ul>
+        </div>
+        <p>The following chart displays the most frequently created content types in relation
+            to their owners (up to 10 most active site content contributors and up to 10 most popular
+            content types are displayed).
+        </p>
+        <p>The table under the chart displays number of content objects of all site's content types
+           in relation to their owners. Besides, the total number of created objects by every contributor
+           is provided.
+        </p>
+        <div id="chart" tal:content="structure view/getChart">
+            Chart will be here
+        </div>
+        <table class="listing">
+            <tr>
+                <th></th>
+                <th tal:repeat="user view/getUsers" tal:content="user">User</th>
+            </tr>
+            <tr tal:repeat="type python:view.getTypes(all=True)">
+                <th tal:content="type">Document</th>
+                <td tal:repeat="count python:view.getContent(type)" tal:content="count">123</td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td tal:repeat="total view/getTotal" tal:content="total">123</td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/portlets_stats.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/portlets_stats.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/portlets_stats.pt	(revision 3057)
@@ -0,0 +1,104 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main"
+            i18n:domain="quintagroup.analytics"
+            tal:define="infos view/getPropsList">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a class="selected" href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+            </ul>
+            <p>The following list can displays site's portlets, assigned to left and right columns on different 
+               contexts. Specify the search depth: portlets on what level(s) to be included. Click on the porltet
+               title to land on the porltet edit form. Select CSV to render this information in .csv format.</p>
+            <form action="" method="get">
+                Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):
+                <input type="text" name="level" size="5" value="1" tal:attributes="value request/level|python:1" />
+                CSV
+                <input type="checkbox" name="csv" size="40" tal:attributes="checked request/csv|python:0" />
+                <input type="submit" name="submit" value="Search" />
+            </form>
+        </div>
+        <div>
+             <span tal:repeat="prop view/proplist">
+                 <span tal:replace="prop"/>,
+             </span>
+             <hr>
+        </div>
+        <div tal:condition="request/csv|nothing">
+            <nobr>path,</nobr>
+            <nobr>left_slots,</nobr>
+            <nobr>right_slots,</nobr><br/>
+            <nobr tal:repeat="info infos">
+              <span tal:replace="info/path">path</span>,
+              <span tal:condition="info/left_slots">
+                <tal:left_slot repeat="item info/left_slots">
+                  <span tal:replace="item/title" />
+                </tal:left_slot>
+              </span>,
+              <span tal:condition="info/right_slots">
+                <tal:left_slot repeat="item info/right_slots">
+                  <span tal:replace="item/title" />
+                </tal:left_slot>
+              </span>,<br/>
+            </nobr>
+        </div>
+        <table class="listing" tal:condition="not:request/csv|nothing">
+            <tr>
+                <th></th>
+                <th>left_slots</th>
+                <th>right_slots</th>
+            </tr>
+            <tr tal:repeat="info view/getPropsList">
+                <th>
+                    <a href="#"
+                       tal:content="info/path"
+                       tal:attributes="href string:${info/href}/@@manage-portlets">path</a>
+                </th>
+                <td>
+                    <ul tal:condition="info/left_slots">
+                        <li tal:repeat="slot info/left_slots">
+                            <span tal:condition="python:slot['editview'] is ''"
+                                  tal:content="slot/title"></span>
+                            <a tal:condition="python:slot['editview'] is not ''"
+                               tal:content="slot/title"
+                               tal:attributes="href string:${slot/editview};
+                                               title slot/title">title</a>
+                        </li>
+                    </ul>
+                    <span tal:condition="python:info['left_slots'] is None">doesn't exist</span>
+                </td>
+                <td>
+                    <ul tal:condition="info/right_slots">
+                        <li tal:repeat="slot info/right_slots">
+                            <span tal:condition="python:slot['editview'] is ''"
+                                  tal:content="slot/title"></span>
+                            <a tal:condition="python:slot['editview'] is not ''"
+                               tal:content="slot/title"
+                               tal:attributes="href string:${slot/editview};
+                                               title slot/title">title</a>
+                        </li>
+                    </ul>
+                    <span tal:condition="python:info['right_slots'] is None">doesn't exist</span>
+                </td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td colspan="2" tal:content="view/getTotal">123</td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/properties_stats.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/properties_stats.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/properties_stats.pt	(revision 3057)
@@ -0,0 +1,93 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main"
+            i18n:domain="quintagroup.analytics"
+            tal:define="infos view/getPropsList">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a class="selected" href="@@properties_stats">Properties stats</a></li>
+            </ul>
+            <p>The following list can display values for certain property for site objects. To generate the 
+               list - specify the search depth (objects on what level(s) will be included) and type in 
+               property name (a list of all available properties is provided below). For example: to display
+               all site objects titles type '-1' into 'Search depth' field and 'title' into 'Property name' field.
+                Select CSV to render this information in .csv format.
+            </p>
+            <form action="" method="get">
+                Search depth (-1 means no limit, 1 means that objects in the site root only will be searched):
+                <input type="text" name="level" size="5" value="1" tal:attributes="value request/level|python:1" />
+                <br>
+                Property name
+                <input type="text" name="propname" size="10" tal:attributes="value request/propname|string:" />
+                CSV
+                <input type="checkbox" name="csv" size="40" tal:attributes="checked request/csv|python:0" />
+                <input type="submit" name="submit" value="Search" />
+            </form>
+        </div>
+        <div>
+             <span tal:repeat="prop view/proplist">
+                 <span tal:replace="prop"/>
+                 <span tal:condition="not:repeat/prop/end">,</span>
+             </span>
+             <hr>
+        </div>
+        <div tal:condition="request/csv|nothing">
+            <nobr>path,<span tal:replace="view/propname" /><br /></nobr>
+            <nobr tal:repeat="info infos">
+                <span tal:replace="info/path">
+                    path
+                </span>,<span
+                tal:condition="info/slots">"<span
+                tal:condition="python:not isinstance(info['slots'],basestring)"
+                tal:repeat="slot info/slots"><span
+                tal:replace="slot" />;</span><span
+                tal:condition="python:isinstance(info['slots'],basestring)"
+                tal:replace="info/slots">string</span>"</span><span
+                tal:condition="python:info['slots'] is None">None</span><br />
+            </nobr>
+        </div>
+        <table class="listing"
+               tal:condition="not:request/csv|nothing">
+            <tr>
+                <th></th>
+                <th tal:content="view/propname">Property</th>
+            </tr>
+            <tr tal:repeat="info view/getPropsList">
+                <th><a href="#" tal:content="info/path" tal:attributes="href info/href">path</a></th>
+                <td>
+                    <ul tal:condition="info/slots">
+                        <li tal:condition="python:not isinstance(info['slots'],basestring)" tal:repeat="slot info/slots" tal:content="slot">slot</li>
+                        <li tal:condition="python:isinstance(info['slots'],basestring)" tal:content="info/slots">string</li>
+                    </ul>
+                    <span class="none" tal:condition="python:info['slots'] is None">doesn't exist</span>
+                </td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td tal:content="view/getTotal">123</td>
+            </tr>
+            <tr>
+                <th>Expressions</th>
+                <td>
+                    <ul>
+                        <li tal:repeat="expr view/getAllPortletExpressions" tal:content="expr">expressions</li>
+                    </ul>
+                </td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/qa_overview.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/qa_overview.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/qa_overview.pt	(revision 3057)
@@ -0,0 +1,66 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+  <metal:main metal:fill-slot="prefs_configlet_main" i18n:domain="quintagroup.analytics">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a class="selected" href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+
+            </ul>
+            <h3>Welcome to Quintagroup Analytics Tool</h3>
+            <p>Quintagroup Analytics Tool provides statistic information about your Plone site.
+               You can see how many content objects are on you site, who are content owners,
+               what are content workflow states, and how many portlets are assigned on different contexts.
+            </p>
+            <p>Information provided by Quintagroup Analytics Tool allows you to see your site's overall
+               content from different perspectives. This can be very useful while migrating your site into
+               newer Plone version, or into another CMS. With its help you can visually audit the content 
+               setup in Plone site before migration and compare it with the migrated website structure.
+            </p>
+            <p>Visit the following pages to see your site's statistics:</p>
+            <dl>
+              <dt>
+                <a href="@@ownership_by_type">Content Ownership by Type</a>
+              </dt>
+              <dd>Information about most popular content types on your site. Here you can see the most frequently 
+                  created content types on your site and their owners. </dd>
+              <dt>
+                <a href="@@ownership_by_state">Content Ownership by State</a>
+              </dt>
+              <dd>Information about site's content workflow states. Here you can see how many content object
+                  are published/submitted for review/etc. and their owners.</dd>
+              <dt>
+                <a href="@@type_by_state">Content Types by State</a>
+              </dt>
+              <dd>Information about site's most frequently created content types and their workflow states.</dd>
+              <dt>
+                <a href="@@portlets_stats">Site Portlets</a>
+              </dt>
+              <dd>Information about site portlets assigned throughout site sections. This information 
+                  can be exported into .csv format. </dd>
+              <dt>
+                <a href="@@legacy_portlets">Legacy Portlets</a>
+              </dt>
+              <dd>Information about legacy portlets assigned throughout site sections. This information 
+                  can be exported into .csv format.</dd>
+              <dt>
+                <a href="@@properties_stats">Properties Stats</a>
+              </dt>
+              <dd>Information on certain property values for all site objects.</dd>
+            </dl>
+        </div>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/resources/stats_style.css
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/resources/stats_style.css	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/resources/stats_style.css	(revision 3057)
@@ -0,0 +1,65 @@
+body {
+    font-family: Helvetica,Arial,sans-serif;
+}
+h1 {
+    text-align: center;
+}
+#wrapper {
+/*     width: 1000px; */
+    margin: 0 auto;
+}
+#chart img {
+    display: block;
+    margin: 0 auto 1em;
+}
+#navigation {
+    background-color: #E5ECF9;
+    padding: 5px;
+    text-align: center;
+    margin-bottom: 1em;
+}
+#navigation a {
+    color: #3366CC;
+    text-decoration: none;
+    font-weight: bold;
+}
+#navigation a:hover {
+    color: #3366CC;
+    text-decoration: underline;
+}
+#navigation a.current {
+    color: #000000;
+}
+form {
+    background-color:#E5ECF9;
+    border-top: 1px solid #3366CC;
+    margin-bottom: 1em;
+    padding: 0.5em;
+    text-align: center;
+}
+table {
+    font-size: 75%;
+    border: 1px solid #3366CC;
+    border-collapse: collapse;
+    border-spacing: 0;
+}
+th {
+    padding: 5px;
+    background-color: #E5ECF9;
+    border:1px solid #3366CC;
+}
+td {
+    padding: 5px;
+    text-align: center;
+    border:1px solid #3366CC;
+}
+
+table ul {
+    list-style: none;
+    padding: 0;
+    margin: 10px;
+}
+
+table td span.none {
+    color: red;
+}
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/type_by_state.pt
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/type_by_state.pt	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/type_by_state.pt	(revision 3057)
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+      xmlns:tal="http://xml.zope.org/namespaces/tal"
+      xmlns:metal="http://xml.zope.org/namespaces/metal"
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+      lang="en"
+      metal:use-macro="context/prefs_main_template/macros/master">
+
+<metal:main metal:fill-slot="prefs_configlet_main" i18n:domain="quintagroup.analytics">
+    <body>
+        <div id="wrapper">
+            <h1>Quintagroup Analytics Tool for Plone</h1>
+            <ul class="formTabs" id="navigation">
+                <li class="formTab"><a href="@@qa_overview">Overview</a></li>
+                <li class="formTab"><a href="@@ownership_by_type">Ownership by type</a></li>
+                <li class="formTab"><a href="@@ownership_by_state">Ownership by state</a></li>
+                <li class="formTab"><a class="selected" href="@@type_by_state">Types by state</a></li>
+                <li class="formTab"><a href="@@portlets_stats">Portlets stats</a></li>
+                <li class="formTab"><a href="@@legacy_portlets">Legacy portlets</a></li>
+                <li class="formTab"><a href="@@properties_stats">Properties stats</a></li>
+            </ul>
+        </div>
+        <p>The following chart and table display workflow states of the site's most frequently created content types. 
+           You can see the total number of site's content objects of every content type.</p>
+        <div id="chart" tal:content="structure view/getChart">
+            Chart will be here
+        </div>
+        <table class="listing">
+            <tr>
+                <th></th>
+                <th tal:repeat="user view/getTypes" tal:content="user">User</th>
+            </tr>
+            <tr tal:repeat="type view/getStates">
+                <th tal:content="type">Document</th>
+                <td tal:repeat="count python:view.getContent(type)" tal:content="count">123</td>
+            </tr>
+            <tr>
+                <th tal:content="view/getNoWFContentTitle">No workflow</th>
+                <td tal:repeat="count view/getNoWFContent" tal:content="count">123</td>
+            </tr>
+            <tr>
+                <th>Total</th>
+                <td tal:repeat="total view/getTotal" tal:content="total">123</td>
+            </tr>
+        </table>
+    </body>
+  </metal:main>
+</html>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/views.py
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/views.py	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/browser/views.py	(revision 3057)
@@ -0,0 +1,528 @@
+from Acquisition import aq_base
+from zope.component import getUtility, getMultiAdapter, queryMultiAdapter
+
+from OFS.interfaces import IPropertyManager
+from Products.Five.browser import BrowserView
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.interfaces import IFolderish
+from Products.Archetypes.interfaces import IBaseFolder
+from plone.portlets.interfaces import IPortletManager
+from plone.portlets.interfaces import IPortletAssignmentMapping
+from plone.portlets.interfaces import ILocalPortletAssignmentManager
+try:
+    from plone.portlets.interfaces import IPortletAssignmentSettings
+except ImportError:
+    "Before plon4 we don't have an annotation storage for settings."
+    IPortletAssignmentSettings = lambda assignment:{}
+
+from GChartWrapper import VerticalBarStack
+
+from quintagroup.analytics.config import COLORS, OTHER_TYPES, NO_WF_BIND
+
+class OwnershipByType(BrowserView):
+    MAX = 10
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.cat = getToolByName(self.context, 'portal_catalog')
+        self.users = None
+        self.total = None
+        self.types = None
+        self.data = {}
+
+    def getUsers(self):
+        if self.users is None:
+            index = self.cat._catalog.getIndex('Creator')
+            data = {}
+            for k in index._index.keys():
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            data = data[:self.MAX]
+            self.users = [i[0] for i in data]
+            self.total = [i[1] for i in data]
+        return self.users
+
+    def getTypes(self, all=False):
+        if self.types is None:
+            index = self.cat._catalog.getIndex('portal_type')
+            data = {}
+            for k in index._index.keys():
+                if not k:
+                    continue
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            self.types = [i[0] for i in data]
+        return all and self.types or self.types[:self.MAX]
+
+    def getContent(self, type_):
+        if type_ not in self.data:
+            data = self.data[type_] = []
+            for user in self.getUsers():
+                res = self.cat(portal_type=type_, Creator=user)
+                l = len(res)
+                if l == 0:
+                    data.append(0)
+                else:
+                    data.append(l)
+        return self.data[type_]
+
+    def getTotal(self):
+        return self.total
+
+    def getChart(self):
+        data = []
+        types = self.getTypes()
+        for type_ in types:
+            data.append(self.getContent(type_))
+        other = [self.getContent(t) for t in self.getTypes(all=True)[self.MAX:]]
+        if other:
+            data.append([sum(l) for l in zip(*other)])
+        max_value = max(self.getTotal())
+        chart = VerticalBarStack(data, encoding='text')
+        types = other and types+OTHER_TYPES or types
+        chart.title('Content ownership by type').legend(*(types))
+        chart.bar('a', 10, 0).legend_pos("b")
+        chart.color(*COLORS)
+        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.users)
+        chart.axes.type("y")
+        chart.axes.range(0,0,max_value)
+        return chart.img()
+
+
+class OwnershipByState(BrowserView):
+    MAX = 10
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.cat = getToolByName(self.context, 'portal_catalog')
+        self.users = None
+        self.states = None
+        self.total = None
+        self.data = {}
+
+    def getUsers(self):
+        if self.users is None:
+            index = self.cat._catalog.getIndex('Creator')
+            data = {}
+            for k in index._index.keys():
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            data = data[:self.MAX]
+            self.users = [i[0] for i in data]
+            self.total = [i[1] for i in data]
+        return self.users
+
+    def getStates(self):
+        if self.states is None:
+            index = self.cat._catalog.getIndex('review_state')
+            data = {}
+            for k in index._index.keys():
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            self.states = [i[0] for i in data]
+        return self.states
+
+    def getContent(self, type_):
+        if type_ not in self.data:
+            if NO_WF_BIND not in self.data:
+                self.data[NO_WF_BIND] = self.getTotal()
+            data = self.data[type_] = []
+            for user in self.getUsers():
+                res = self.cat(review_state=type_, Creator=user)
+                l = len(res)
+                if l == 0:
+                    data.append(0)
+                else:
+                    data.append(l)
+            if len(data) > 0:
+                self.data[NO_WF_BIND] = map(lambda t,d:t-d, self.data[NO_WF_BIND], data)
+        return self.data[type_]
+
+    def getNoWFContentTitle(self):
+        return NO_WF_BIND
+
+    def getNoWFContent(self):
+        return self.getContent(NO_WF_BIND)
+
+    def getTotal(self):
+        if self.total is None:
+            self.getUsers()
+        return self.total
+
+    def getChart(self):
+        data = []
+        for state in self.getStates():
+            data.append(self.getContent(state))
+        data.append(self.getNoWFContent())
+        max_value = max(self.getTotal())
+        chart = VerticalBarStack(data, encoding='text')
+        chart.title('Content ownership by state').legend(*self.states+[NO_WF_BIND])
+        chart.bar('a', 10, 0).legend_pos("b")
+        chart.color(*COLORS)
+        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.users)
+        chart.axes.type("y")
+        chart.axes.range(0,0,max_value)
+        return chart.img()
+
+
+class TypeByState(BrowserView):
+    MAX = 10
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.cat = getToolByName(self.context, 'portal_catalog')
+        self.types = None
+        self.states = None
+        self.total = None
+        self.data = {}
+
+    def getTypes(self):
+        if self.types is None:
+            index = self.cat._catalog.getIndex('portal_type')
+            data = {}
+            for k in index._index.keys():
+                if not k:
+                    continue
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            self.types = [i[0] for i in data[:self.MAX]]
+            self.total = [i[1] for i in data[:self.MAX]]
+        return self.types
+
+    def getStates(self):
+        if self.states is None:
+            index = self.cat._catalog.getIndex('review_state')
+            data = {}
+            for k in index._index.keys():
+                haslen = hasattr(index._index[k], '__len__')
+                if haslen:
+                    data[k] = len(index._index[k])
+                else:
+                    data[k] = 1
+            data = data.items()
+            data.sort(lambda a, b: a[1] - b[1])
+            data.reverse()
+            self.states = [i[0] for i in data]
+        return self.states
+
+    def getContent(self, state):
+        if state not in self.data:
+            if NO_WF_BIND not in self.data:
+                self.data[NO_WF_BIND] = self.getTotal()
+            data = self.data[state] = []
+            for type_ in self.getTypes():
+                res = self.cat(portal_type=type_, review_state=state)
+                l = len(res)
+                if l == 0:
+                    data.append(0)
+                else:
+                    data.append(l)
+            if len(data) > 0:
+                self.data[NO_WF_BIND] = map(lambda t,d:t-d, self.data[NO_WF_BIND], data)
+        return self.data[state]
+
+    def getTotal(self):
+        if self.total is None:
+            self.getTypes()
+        return self.total
+
+    def getNoWFContentTitle(self):
+        return NO_WF_BIND
+
+    def getNoWFContent(self):
+        return self.getContent(NO_WF_BIND)
+
+    def getChart(self):
+        data = []
+        for state in self.getStates():
+            data.append(self.getContent(state))
+        data.append(self.getContent(NO_WF_BIND))
+        max_value = max(self.getTotal())
+        chart = VerticalBarStack(data, encoding='text')
+        chart.title('Content type by state').legend(*self.states+[NO_WF_BIND])
+        chart.bar('a', 10, 0).legend_pos("b")
+        chart.color(*COLORS)
+        chart.size(800, 375).scale(0,max_value).axes('xy').label(*self.types)
+        chart.axes.type("y")
+        chart.axes.range(0,0,max_value)
+        return chart.img()
+
+
+class LegacyPortlets(BrowserView):
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.total = None
+        self.DEBUG = False
+        self.expressions = set()
+
+    def _getInfo(self, obj):
+        href = obj.absolute_url()
+        path = '/'.join(obj.getPhysicalPath())
+        info = {
+            'path': path,
+            'href': href,
+            'left_slots': None,
+            'right_slots': None,
+        }
+        if IPropertyManager.providedBy(obj):
+            obj = aq_base(obj)
+            if obj.hasProperty('left_slots'):
+                info['left_slots'] = obj.getProperty('left_slots')
+                self.expressions = self.expressions.union(set(info['left_slots']))
+            if obj.hasProperty('right_slots'):
+                info['right_slots'] = obj.getProperty('right_slots')
+                self.expressions = self.expressions.union(set(info['right_slots']))
+        return info
+
+    def _walk(self, obj, level=-1):
+        yield self._getInfo(obj)
+        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
+            for v in obj.contentValues():
+                for i in self._walk(v, level-1):
+                    yield i
+
+    def getPortlets(self):
+        level = self.request.form.get('level', 1)
+        try:
+            level = level and int(level) or 1
+        except ValueError:
+            level = 1
+        infos = []
+        for i in self._walk(self.context, level):
+            if self.DEBUG or i['left_slots'] is not None or i['right_slots'] is not None:
+                infos.append(i)
+        self.total = len(infos)
+        return infos
+
+    def getTotal(self):
+        return self.total
+
+    def getAllPortletExpressions(self):
+        exprs = []
+        for name in self.expressions:
+            name = name.split('|')[0]
+            if not '/macros/' in name:
+                name = name.split('/')[-1]
+            name = name.strip()
+            if name not in exprs:
+                exprs.append(name)
+        exprs.sort()
+        return exprs
+
+class PropertiesStats(BrowserView):
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.total = None
+        self.DEBUG = False
+        self.expressions = set()
+        self.proplist = []
+        self.propname = self.request.form.get('propname') or ""
+
+    def _getInfo(self, obj):
+
+        href = obj.absolute_url()
+        path = '/'.join(obj.getPhysicalPath())
+        info = {
+            'path': path,
+            'href': href,
+            'slots': None,
+        }
+        if IPropertyManager.providedBy(obj):
+            obj = aq_base(obj)
+            self.proplist.extend([i for i in obj.propertyIds() if i not in self.proplist])
+            if obj.hasProperty(self.propname):
+                info['slots'] = obj.getProperty(self.propname)
+                if isinstance(info['slots'], int):
+                    info['slots'] = str(info['slots'])
+                if not isinstance(info['slots'], basestring):
+                    self.expressions = self.expressions.union(set(info['slots']))
+                else:
+                    self.expressions = self.expressions.union(set([info['slots']]))
+        return info
+
+    def _walk(self, obj, level=-1):
+        yield self._getInfo(obj)
+        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
+            for v in obj.contentValues():
+                for i in self._walk(v, level-1):
+                    yield i
+
+    def getPropsList(self):
+        level = self.request.form.get('level', 1)
+        try:
+            level = level and int(level) or 1
+        except ValueError:
+            level = 1
+        infos = []
+        for i in self._walk(self.context, level):
+            if self.DEBUG or i['slots'] is not None:
+                infos.append(i)
+        self.total = len(infos)
+        return infos
+
+    def getTotal(self):
+        return self.total
+
+    def getAllPortletExpressions(self):
+        exprs = []
+        for name in self.expressions:
+            name = name.strip()
+            if name not in exprs:
+                exprs.append(name)
+        #exprs.sort()
+        return exprs
+
+class PortletsStats(BrowserView):
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.total = None
+        self.DEBUG = False
+        self.expressions = set()
+        self.proplist = []
+        self.propname = self.request.form.get('propname') or ""
+
+    def getAssignmentMappingUrl(self, context, manager):
+        baseUrl = str(getMultiAdapter((context, self.request), name='absolute_url'))
+        return '%s/++contextportlets++%s' % (baseUrl, manager.__name__)
+
+    def getAssignmentsForManager(self, context, manager):
+        assignments = getMultiAdapter((context, manager), IPortletAssignmentMapping)
+        return assignments.values()
+
+    def getPortletsMapping(self, context):
+        leftcolumn = getUtility(IPortletManager, name=u'plone.leftcolumn', context=context)
+        rightcolumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=context)
+        leftmapping = getMultiAdapter((context, leftcolumn,), IPortletAssignmentMapping)
+        rightmapping = getMultiAdapter((context, rightcolumn,), IPortletAssignmentMapping)
+        return (leftmapping, rightmapping)
+
+    def getLocalPortletsManager(self, context):
+        leftcolumn = getUtility(IPortletManager, name='plone.leftcolumn', context=context)
+        rightcolumn = getUtility(IPortletManager, name='plone.rightcolumn', context=context)
+        leftmanager = getMultiAdapter((context, leftcolumn,), ILocalPortletAssignmentManager)
+        rightmanager = getMultiAdapter((context, rightcolumn,), ILocalPortletAssignmentManager)
+        return (leftmanager, rightmanager)
+
+    def getPortletsManager(self, context):
+        left = getUtility(IPortletManager, name='plone.leftcolumn', context=context)
+        right = getUtility(IPortletManager, name='plone.rightcolumn', context=context)
+        return (left, right)
+
+    def portlets_for_assignments(self, assignments, manager, base_url):
+        data = []
+        for idx in range(len(assignments)):
+            name = assignments[idx].__name__
+
+            editview = queryMultiAdapter(
+                (assignments[idx], self.request), name='edit', default=None)
+
+            if editview is None:
+                editviewName = ''
+            else:
+                editviewName = '%s/%s/edit' % (base_url, name)
+
+            settings = IPortletAssignmentSettings(assignments[idx])
+
+            data.append({
+                'title'      : assignments[idx].title,
+                'editview'   : editviewName,
+                'visible'    : settings.get('visible', True),
+                })
+        return data
+
+    def getPortlets(self, context, mapping, manager):
+        #import pdb; pdb.set_trace()
+        return mapping.keys()
+
+    def _getInfo(self, obj):
+        href = obj.absolute_url()
+        path = '/'.join(obj.getPhysicalPath())
+        info = {
+            'path': path,
+            'href': href,
+            'left_slots': None,
+            'right_slots': None,
+        }
+        left, right = self.getPortletsManager(obj)
+        #leftmapping, rightmapping = self.getPortletsMapping(obj)
+        #leftmanager, rightmanager = self.getLocalPortletsManager(obj)
+        #info['left_slots'] = self.getPortlets(obj, leftmapping, leftmanager)
+        #info['right_slots'] = self.getPortlets(obj, rightmapping, rightmanager)
+        lass = self.getAssignmentsForManager(obj, left)
+        rass = self.getAssignmentsForManager(obj, right)
+        lurl = self.getAssignmentMappingUrl(obj, left)
+        rurl = self.getAssignmentMappingUrl(obj, right)
+        plass = self.portlets_for_assignments(lass, left, lurl)
+        prass = self.portlets_for_assignments(rass, right, rurl)
+        #print obj, plass, prass
+        info['left_slots'] = plass #[i['title'] for i in plass]
+        info['right_slots'] = prass #[i['title'] for i in prass]
+        return info
+
+    def _walk(self, obj, level=-1):
+        try:
+            yield self._getInfo(obj)
+        except:
+            pass
+        if level != 0 and (IFolderish.providedBy(obj) or IBaseFolder.providedBy(obj)):
+            for v in obj.contentValues():
+                for i in self._walk(v, level-1):
+                    yield i
+
+    def getPropsList(self):
+        level = self.request.form.get('level', 1)
+        try:
+            level = level and int(level) or 1
+        except ValueError:
+            level = 1
+        infos = []
+        for i in self._walk(self.context, level):
+            if self.DEBUG or i['left_slots'] is not None or i['right_slots'] is not None:
+                infos.append(i)
+        self.total = len(infos)
+        return infos
+
+    def getTotal(self):
+        return self.total
+
+    def getAllPortletExpressions(self):
+        exprs = []
+        for name in self.expressions:
+            name = name.strip()
+            if name not in exprs:
+                exprs.append(name)
+        #exprs.sort()
+        return exprs
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/config.py
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/config.py	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/config.py	(revision 3057)
@@ -0,0 +1,3 @@
+COLORS = ['669933', 'CC9966', '993300', 'FF6633', 'E8E4E3', 'A9A486', 'DCB57E', 'FFCC99', '996633', '333300', '00FF00']
+OTHER_TYPES = ['Other types']
+NO_WF_BIND = 'No workflow'
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/configure.zcml
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/configure.zcml	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/configure.zcml	(revision 3057)
@@ -0,0 +1,19 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:five="http://namespaces.zope.org/five"
+    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+    i18n_domain="quintagroup.analytics">
+
+  <include package=".browser" />
+
+  <five:registerPackage package="."/>
+
+  <genericsetup:registerProfile
+      name="QAnalytics"
+      title="Quintagroup Analytics"
+      directory="profiles/default"
+      description="Profile for a Quintagroup Analytics."
+      provides="Products.GenericSetup.interfaces.EXTENSION"
+      />
+
+</configure>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/profiles/default/controlpanel.xml
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/profiles/default/controlpanel.xml	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/profiles/default/controlpanel.xml	(revision 3057)
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<object name="portal_controlpanel" meta_type="Plone Control Panel Tool"
+  i18n:domain="plone" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+ <configlet title="Quintagroup Analytics" action_id="QAnalytics" appId="QAnalytics"
+    category="Products" condition_expr=""
+    icon_expr="string:${portal_url}/++resource++qaicon.png"
+    url_expr="string:${portal_url}/@@qa_overview"
+    visible="True" i18n:attributes="title">
+  <permission>Manage portal</permission>
+ </configlet>
+</object>
Index: quintagroup.analytics/branches/mylan/quintagroup/analytics/tests.py
===================================================================
--- quintagroup.analytics/branches/mylan/quintagroup/analytics/tests.py	(revision 3057)
+++ quintagroup.analytics/branches/mylan/quintagroup/analytics/tests.py	(revision 3057)
@@ -0,0 +1,473 @@
+import unittest
+import transaction
+
+from AccessControl.SecurityManagement import newSecurityManager
+from Testing import ZopeTestCase as ztc
+from Products.Five import zcml
+from Products.Five import fiveconfigure
+from zope.component import testing, queryMultiAdapter, getUtility
+
+from Products.PloneTestCase import PloneTestCase as ptc
+from Products.PloneTestCase.version import PLONE40
+from Products.PloneTestCase import setup as ptc_setup
+from Products.PloneTestCase.layer import PloneSite
+from plone.portlets.interfaces import IPortletType
+
+import quintagroup.analytics
+
+ptc.setupPloneSite()
+
+class Installed(PloneSite):
+
+    @classmethod
+    def setUp(cls):
+        fiveconfigure.debug_mode = True
+        zcml.load_config('configure.zcml',
+                         quintagroup.analytics)
+        fiveconfigure.debug_mode = False
+        ztc.installPackage('quintagroup.analytics')
+        app = ztc.app()
+        portal = app[ptc_setup.portal_name]
+
+        # Sets the local site/manager
+        ptc_setup._placefulSetUp(portal)
+
+        qi = getattr(portal, 'portal_quickinstaller', None)
+        qi.installProduct('quintagroup.analytics')
+        transaction.commit()
+
+    @classmethod
+    def tearDown(cls):
+            pass
+
+class SetUpContent(Installed):
+
+    max = 10
+    types_ = ['Document', 'Event', 'Folder']
+    users = [('user%s'%i, 'user%s'%i, 'Member', None)
+             for i in xrange(max)]
+
+    @classmethod
+    def setupUsers(cls, portal):
+        """ Creates users."""
+        acl_users = portal.acl_users
+        mp = portal.portal_membership
+        map(acl_users._doAddUser, *zip(*cls.users))
+        if not mp.memberareaCreationFlag:
+            mp.setMemberareaCreationFlag()
+        map(mp.createMemberArea, [u[0] for u in cls.users])
+
+    @classmethod
+    def setupContent(cls, portal):
+        """ Creates test content."""
+        uf = portal.acl_users
+        pm = portal.portal_membership
+        pc = portal.portal_catalog
+        users = [u[0] for u in cls.users]
+        for u in users:
+            folder = pm.getHomeFolder(u)
+            user = uf.getUserById(u)
+            if not hasattr(user, 'aq_base'):
+                user = user.__of__(uf)
+            newSecurityManager(None, user)
+            for i in xrange(users.index(u)+cls.max):
+                map(folder.invokeFactory, cls.types_, [t+str(i) for t in cls.types_])
+        transaction.commit()
+
+
+    @classmethod
+    def setUp(cls):
+        app = ztc.app()
+        portal = app[ptc_setup.portal_name]
+        cls.setupUsers(portal)
+        cls.setupContent(portal)
+
+    @classmethod
+    def tearDown(cls):
+        pass
+
+class TestCase(ptc.PloneTestCase):
+    layer = Installed
+
+
+class TestQAInstallation(TestCase):
+    """ This class veryfies registrations of all needed views and
+        actions.
+    """
+
+    def test_cp_action_installation(self):
+        """This test validates control panel action. """
+        control_panel = self.portal.portal_controlpanel
+        self.assert_('QAnalytics' in [a.id for a in control_panel.listActions()],
+                     "Configlet for quintagroup.analitycs isn't registered.")
+
+    def test_OwnershipByType(self):
+        """ This test validates registration of
+            ownership_by_type view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="ownership_by_type")
+
+        self.assert_(view, "Ownership by type view isn't registered")
+
+    def test_OwnershipByState(self):
+        """ This test validates registration of
+            ownership_by_state view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="ownership_by_state")
+
+        self.assert_(view, "Ownership by state view isn't registered")
+
+    def test_TypeByState(self):
+        """ This test validates registration of
+            type_by_state view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="type_by_state")
+
+        self.assert_(view, "Type by state view isn't registered")
+
+    def test_LegacyPortlets(self):
+        """ This test validates registration of
+            legacy_portlets view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="legacy_portlets")
+
+        self.assert_(view, "Legacy Portlets view isn't registered")
+
+    def test_PropertiesStats(self):
+        """ This test validates registration of
+            properties_stats view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="properties_stats")
+
+        self.assert_(view, "Properties Stats view isn't registered")
+
+
+    def test_PortletsStats(self):
+        """ This test validates registration of
+            portlets_stats view.
+        """
+        view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="portlets_stats")
+
+        self.assert_(view, "Portlets Stats view isn't registered")
+
+class TestOwnershipByType(TestCase):
+    """Tests all ownership by type view methods."""
+
+    layer = SetUpContent
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="ownership_by_type")
+        self.pc = self.portal.portal_catalog
+
+    def test_getUsers(self):
+        """ Tests method that returns ordered list of users."""
+        users = [u[0] for u in self.layer.users]
+        users.reverse()
+        self.assert_(False not in map(lambda u1, u2:u1==u2,
+                     users, self.view.getUsers()))
+
+    def test_getTypes(self):
+        """ Tests method that returns ordered list of types."""
+        data = {}
+        index = self.pc._catalog.getIndex('portal_type')
+        for k in index._index.keys():
+            if not k:
+                continue
+            haslen = hasattr(index._index[k], '__len__')
+            if haslen:
+                data[k] = len(index._index[k])
+            else:
+                data[k] = 1
+        data = data.items()
+        data.sort(lambda a, b: a[1] - b[1])
+        data.reverse()
+        types = [i[0] for i in data]
+        self.assert_(False not in map(lambda t1, t2:t1==t2,
+                     self.view.getTypes(), types))
+
+    def test_getContent(self):
+        """ This test verifies method that returns list of numbers.
+            Each number is amount of specified content type objects
+            that owned  by particular user.
+        """
+        # we need to login in to the site as Manager to be able to
+        # see catalog results
+        self.loginAsPortalOwner()
+
+        for type_ in self.layer.types_:
+            self.assert_(False not in \
+            map(lambda i, j:i==j, [len(self.pc(portal_type=type_, Creator=user))
+                                   for user in self.view.getUsers()],
+                                  self.view.getContent(type_)))
+
+    def test_getChart(self):
+        """ This test verifies creation of chart image tag."""
+        plone33chart_tag = \
+          """<imgsrc="http://chart.apis.google.com/chart?chxt=y&amp;chds=0,
+             57&amp;chd=t:19.0,18.0,17.0,16.0,15.0,14.0,13.0,12.0,11.0,
+             10.0|19.0,18.0,17.0,16.0,15.0,14.0,13.0,12.0,11.0,10.0|
+             19.0,18.0,17.0,16.0,15.0,14.0,13.0,12.0,11.0,10.0|0.0,
+             0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0|0.0,0.0,0.0,0.0,
+             0.0,0.0,0.0,0.0,0.0,0.0&amp;chxr=0,0,57&amp;chco=669933,cc9966,
+             993300,ff6633,e8e4e3,a9a486,dcb57e,ffcc99,996633,333300,00ff00&amp;
+             chl=user9|user8|user7|user6|user5|user4|user3|user2|user1|
+             user0&amp;chbh=a,10,0&amp;chs=800x375&amp;cht=bvs&amp;
+             chtt=Content+ownership+by+type&amp;chdl=Folder|Document|Event
+             |Large+Plone+Folder|Topic&amp;chdlp=b"/>"""
+        plone4chart_tag = \
+          """<img src="http://chart.apis.google.com/chart?chxt=y&amp;
+             chds=0,57&amp;chd=t:19.0,18.0,17.0,16.0,15.0,14.0,
+             13.0,12.0,11.0,10.0|19.0,18.0,17.0,16.0,15.0,14.0,13.0,
+             12.0,11.0,10.0|19.0,18.0,17.0,16.0,15.0,14.0,13.0,12.0,
+             11.0,10.0|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0&amp;
+             chxr=0,0,57&amp;chco=669933,cc9966,993300,ff6633,e8e4e3,
+             a9a486,dcb57e,ffcc99,996633,333300,00ff00&amp;chl=user9|
+             user8|user7|user6|user5|user4|user3|user2|user1|user0&amp;
+             chbh=a,10,0&amp;chs=800x375&amp;cht=bvs&amp;
+             chtt=Content+ownership+by+type&amp;chdl=Folder|Document|
+             Event|Topic&amp;chdlp=b" />"""
+        chart_tag = plone4chart_tag
+        if not PLONE40:
+            chart_tag = plone33chart_tag
+
+        self.loginAsPortalOwner()
+        self.assertEqual(*map(lambda s:''.join(s.split()),
+                              [chart_tag, self.view.getChart()]))
+
+class TestOwnershipByState(TestCase):
+    """Tests all ownership by state view methods."""
+
+    layer = SetUpContent
+
+    states = ['private', 'published', 'pending']
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="ownership_by_state")
+        self.pc = self.portal.portal_catalog
+
+    def test_getUsers(self):
+        """ Tests method that returns ordered list of users."""
+        users = [u[0] for u in self.layer.users]
+        users.reverse()
+        self.assert_(False not in map(lambda u1, u2:u1==u2,
+                     users, self.view.getUsers()))
+
+    def test_getStates(self):
+        """ Tests method that returns ordered list of states."""
+        self.assert_(False not in map(lambda s1, s2:s1==s2,
+                     ['private', 'published'], self.view.getStates()))
+
+    def test_getContent(self):
+        """ This test verifies method that returns list of numbers.
+            Each number is amount of specified content type objects
+            that are in particular workflow state.
+        """
+        # we need to login in to the site as Manager to be able to
+        # see catalog results
+        self.loginAsPortalOwner()
+
+        for state in self.states:
+            self.assert_(False not in \
+            map(lambda i, j:i==j,[len(self.pc(review_state=state, Creator=user))
+                                  for user in self.view.getUsers()],
+                                 self.view.getContent(state)))
+
+    def test_getChart(self):
+        """ This test verifies creation of chart image tag."""
+        chart_tag = """<imgsrc="http://chart.apis.google.com/chart?chxt=y&amp;
+                       chds=0,57&amp;chd=t:57.0,54.0,51.0,48.0,45.0,42.0,39.0,
+                       36.0,33.0,30.0|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
+                       0.0|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0&amp;chxr=0,
+                       0,57&amp;chco=669933,cc9966,993300,ff6633,e8e4e3,a9a486,
+                       dcb57e,ffcc99,996633,333300,00ff00&amp;chl=user9|user8|
+                       user7|user6|user5|user4|user3|user2|user1|user0&amp;
+                       chbh=a,10,0&amp;chs=800x375&amp;cht=bvs&amp;
+                       chtt=Content+ownership+by+state&amp;chdl=private|
+                       published|No+workflow&amp;chdlp=b"/>"""
+        self.loginAsPortalOwner()
+        self.assertEqual(*map(lambda s:''.join(s.split()),
+                              [chart_tag, self.view.getChart()]))
+
+class TestTypeByState(TestCase):
+    """Tests all type_by_state view methods."""
+    layer = SetUpContent
+    states = ['private', 'published', 'pending']
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                 name="type_by_state")
+        self.pc = self.portal.portal_catalog
+
+    def test_getTypes(self):
+        """ Tests method that returns ordered list of types."""
+        index = self.pc._catalog.getIndex('portal_type')
+        data = {}
+        for k in index._index.keys():
+            if not k:
+                continue
+            haslen = hasattr(index._index[k], '__len__')
+            if haslen:
+                data[k] = len(index._index[k])
+            else:
+                data[k] = 1
+        data = data.items()
+        data.sort(lambda a, b: a[1] - b[1])
+        data.reverse()
+        types = [i[0] for i in data]
+        self.assert_(False not in map(lambda t1, t2:t1==t2, types,
+                                      self.view.getTypes()))
+
+    def test_getStates(self):
+        """ Tests method that returns ordered list of states."""
+        self.assert_(False not in map(lambda s1, s2:s1==s2,
+                     ['private', 'published'], self.view.getStates()))
+
+    def test_getContent(self):
+        """ This test verifies method that returns list of numbers.
+            Each number is amount of specified content type objects
+            that owned  by particular user.
+        """
+        # we need to login in to the site as Manager to be able to
+        # see catalog results
+        self.loginAsPortalOwner()
+
+        for state in self.states:
+            self.assert_(False not in \
+            map(lambda i, j:i==j, [len(self.pc(portal_type=type_,
+                                               review_state=state))
+                                   for type_ in self.view.getTypes()],
+                                  self.view.getContent(state)))
+
+    def test_getChart(self):
+        """ This test verifies creation of chart image tag."""
+        plone33chart_tag = \
+          """<imgsrc="http://chart.apis.google.com/chart?chxt=y&amp;chds=0,
+             156&amp;chd=t:156.0,145.0,145.0,0.0,0.0|0.0,1.0,0.0,3.0,3.0|
+             0.0,0.0,0.0,0.0,0.0&amp;chxr=0,0,156&amp;chco=669933,cc9966,
+             993300,ff6633,e8e4e3,a9a486,dcb57e,ffcc99,996633,333300,
+             00ff00&amp;chl=Folder|Document|Event|Large+Plone+Folder|
+             Topic&amp;chbh=a,10,0&amp;chs=800x375&amp;cht=bvs&amp;
+             chtt=Content+type+by+state&amp;chdl=private|published|
+             No+workflow&amp;chdlp=b"/>"""
+        plone4chart_tag = \
+          """<imgsrc="http://chart.apis.google.com/chart?chxt=y&amp;
+             chds=0,159&amp;chd=t:156.0,145.0,145.0,0.0|3.0,1.0,0.0,
+             3.0|0.0,0.0,0.0,0.0&amp;chxr=0,0,159&amp;chco=669933,
+             cc9966,993300,ff6633,e8e4e3,a9a486,dcb57e,ffcc99,996633,
+             333300,00ff00&amp;chl=Folder|Document|Event|Topic&amp;
+             chbh=a,10,0&amp;chs=800x375&amp;cht=bvs&amp;
+             chtt=Content+type+by+state&amp;chdl=private|published|
+             No+workflow&amp;chdlp=b"/>"""
+
+        chart_tag = plone4chart_tag
+        if not PLONE40:
+            chart_tag = plone33chart_tag
+
+        self.loginAsPortalOwner()
+        self.assertEqual(*map(lambda s:''.join(s.split()),
+                              [chart_tag, self.view.getChart()]))
+
+class LegacyPortlets(TestCase):
+    """Test all legasy_portlets view methods."""
+
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                       name='legacy_portlets')
+
+    def test_getPortlets(self):
+        """Tests method that returns portlets info."""
+
+        # this is true for Plone 4
+        self.assert_(self.view.getPortlets() == [])
+
+    def test_getAllPortletExpressions(self):
+        """Tests method that returns portlets expressions."""
+
+        # this is true for Plone 4
+        self.assert_(self.view.getAllPortletExpressions() == [])
+
+class TestPropertiesStats(TestCase):
+    """Tests all properties_stats view methods."""
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                       name='properties_stats')
+
+    def test_getPropsList(self):
+        self.view.propname = 'title'
+        result = [u'Plone site', u'Welcome to Plone',
+                  u'News', u'Events', u'Users']
+
+        for title in result:
+            self.assert_(title in [prop_info['slots']
+                     for prop_info in self.view.getPropsList()])
+
+
+class TestPortletsStats(TestCase):
+    """Tests all properties_stats view methods."""
+
+    def afterSetUp(self):
+        self.view = queryMultiAdapter((self.portal, self.portal.REQUEST),
+                                       name='portlets_stats')
+
+    def test_getPropsList(self):
+        """Tests method that collects portlet information from site."""
+
+        self.loginAsPortalOwner()
+        portlet = getUtility(IPortletType, name='portlets.Calendar')
+        mapping = \
+          self.portal.restrictedTraverse('++contextportlets++plone.leftcolumn')
+        mapping.restrictedTraverse('+/' + portlet.addview)()
+
+        plone_portlets_info = filter(lambda info:info['path'] == '/plone',
+                                     self.view.getPropsList())
+        lslots = plone_portlets_info[0]['left_slots']
+        self.assert_(filter(lambda info: info['title'] == 'Calendar', lslots))
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+
+    test_suite = unittest.TestSuite([
+
+        # Unit tests
+        #doctestunit.DocFileSuite(
+        #    'README.txt', package='quintagroup.contentstats',
+        #    setUp=testing.setUp, tearDown=testing.tearDown),
+
+        #doctestunit.DocTestSuite(
+        #    module='quintagroup.contentstats.mymodule',
+        #    setUp=testing.setUp, tearDown=testing.tearDown),
+
+
+        # Integration tests that use PloneTestCase
+        #ztc.ZopeDocFileSuite(
+        #    'README.txt', package='quintagroup.contentstats',
+        #    test_class=TestCase),
+
+        #ztc.FunctionalDocFileSuite(
+        #    'browser.txt', package='quintagroup.contentstats',
+        #    test_class=TestCase),
+
+        ])
+
+    test_suite.addTest(makeSuite(TestQAInstallation))
+    test_suite.addTest(makeSuite(TestOwnershipByType))
+    test_suite.addTest(makeSuite(TestOwnershipByState))
+    test_suite.addTest(makeSuite(TestTypeByState))
+    test_suite.addTest(makeSuite(LegacyPortlets))
+    test_suite.addTest(makeSuite(TestPropertiesStats))
+    test_suite.addTest(makeSuite(TestPortletsStats))
+    return test_suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
Index: quintagroup.analytics/branches/mylan/setup.cfg
===================================================================
--- quintagroup.analytics/branches/mylan/setup.cfg	(revision 3057)
+++ quintagroup.analytics/branches/mylan/setup.cfg	(revision 3057)
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
Index: quintagroup.analytics/branches/mylan/setup.py
===================================================================
--- quintagroup.analytics/branches/mylan/setup.py	(revision 3057)
+++ quintagroup.analytics/branches/mylan/setup.py	(revision 3057)
@@ -0,0 +1,33 @@
+from setuptools import setup, find_packages
+import os
+
+version = '1.0'
+
+setup(name='quintagroup.analytics',
+      version=version,
+      description="Plone site's statistics",
+      long_description=open("README.txt").read() + "\n" +
+                       open(os.path.join("docs", "HISTORY.txt")).read(),
+      # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+      classifiers=[
+        "Framework :: Plone",
+        "Programming Language :: Python",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        ],
+      keywords='',
+      author='Quintagroup',
+      author_email='support@quintagroup.com',
+      url='http://svn.quintagroup.com/products/quintagroup.analytics',
+      license='GPL',
+      packages=find_packages(exclude=['ez_setup']),
+      namespace_packages=['quintagroup'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          'setuptools',
+          'GChartWrapper'
+          # -*- Extra requirements: -*-
+      ],
+      entry_points="""
+      """,
+      )
