Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/Install.py
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/Install.py	(revision 963)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/Install.py	(revision 963)
@@ -0,0 +1,7 @@
+import transaction
+from Products.CMFCore.utils import getToolByName
+
+def uninstall(self):
+    portal_setup = getToolByName(self, 'portal_setup')
+    portal_setup.runAllImportStepsFromProfile('profile-quintagroup.plonecaptchas:uninstall', purge_old=False)
+    transaction.savepoint()
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/__init__.py
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/__init__.py	(revision 963)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/Extensions/__init__.py	(revision 963)
@@ -0,0 +1,1 @@
+#
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/config.py
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/config.py	(revision 962)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/config.py	(revision 963)
@@ -13,5 +13,5 @@
 TOOL_ID = 'portal_captchas'
 CONFIGLET_ID = "prefs_captchas_setup_form"
-CONFIGLET_NAME = "qPloneCaptchas setup"
+PROPERTY_SHEET = 'qPloneCaptchas'
 
 DEFAULT_IMAGE_SIZE = 27
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/configure.zcml
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/configure.zcml	(revision 962)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/configure.zcml	(revision 963)
@@ -18,9 +18,9 @@
     <cmf:registerDirectory name="captchas_join_form" />
     <cmf:registerDirectory name="captchas_sendto_form" />
-    <cmf:registerDirectory name="plone_captchas" />
+    <cmf:registerDirectory name="plone_captchas" recursive="true" />
 
     <gs:registerProfile
         name="default"
-        title="quintagroup.plonecaptchas"
+        title="Plone Captchas"
         directory="profiles/default"
         description="This product adds captcha support to Plone."
@@ -29,3 +29,12 @@
         />
 
+    <gs:registerProfile
+        name="uninstall"
+        title="Uninstall Plone Captchas"
+        directory="profiles/uninstall"
+        description="Uninstall Plone Captchas product."
+        for="Products.CMFPlone.interfaces.IPloneSiteRoot"
+        provides="Products.GenericSetup.interfaces.EXTENSION"
+        />
+
 </configure>
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/default/import_steps.xml
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/default/import_steps.xml	(revision 962)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/default/import_steps.xml	(revision 963)
@@ -1,5 +1,5 @@
 <?xml version="1.0"?>
 <import-steps>
-  <import-step id="quintagroup.plonecaptchas.various"
+  <import-step id="quintagroup.plonecaptchas-various"
                version="20090403-01"
                handler="quintagroup.plonecaptchas.setuphandlers.setupVarious"
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/import_steps.xml
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/import_steps.xml	(revision 963)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/import_steps.xml	(revision 963)
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<import-steps>
+ <import-step id="quintagroup.plonecaptchas-uninstall"
+              version="20090403-01"
+              handler="quintagroup.plonecaptchas.setuphandlers.uninstall"
+              title="quintagroup.plonecaptchas: uninstall steps.">
+    Uninstalls quintagroup.plonecaptchas product.
+ </import-step>
+</import-steps>
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/quintagroup.plonecaptchas_uninstall.txt
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/quintagroup.plonecaptchas_uninstall.txt	(revision 963)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/profiles/uninstall/quintagroup.plonecaptchas_uninstall.txt	(revision 963)
@@ -0,0 +1,1 @@
+This file is here to run uninstall steps for quintagroup.plonecaptchas package.
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/setuphandlers.py
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/setuphandlers.py	(revision 962)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/setuphandlers.py	(revision 963)
@@ -1,5 +1,8 @@
 from random import randint
 
-from quintagroup.plonecaptchas.config import CAPTCHA_KEY
+from Products.CMFCore.utils import getToolByName
+
+from quintagroup.plonecaptchas.config import CAPTCHA_KEY, CONFIGLET_ID, \
+    ALL_LAYERS, PROPERTY_SHEET
 
 def generateKey(length):
@@ -21,2 +24,28 @@
     else:
         site._setProperty(CAPTCHA_KEY, value, 'string')
+
+def uninstall(context):
+    # Only run step if a flag file is present (e.g. not an extension profile)
+    if context.readDataFile('quintagroup.plonecaptchas_uninstall.txt') is None:
+        return
+
+    site = context.getSite()
+
+    # remove our layers from skin path
+    skinstool = getToolByName(site, 'portal_skins')
+    for skinName in skinstool.getSkinSelections():
+        path = skinstool.getSkinPath(skinName)
+        path = [i.strip() for i in  path.split(',')]
+        pth  = [x for x in path if not ((x in ALL_LAYERS) or
+                filter(lambda y: x.startswith(y), ALL_LAYERS))]
+        skinstool.addSkinSelection(skinName, ','.join(pth))
+
+    # remove configlet
+    cpt = getToolByName(site, 'portal_controlpanel')
+    if CONFIGLET_ID in [o.id for o in cpt.listActions()]:
+        cpt.unregisterConfiglet(CONFIGLET_ID)
+
+    # remove property sheet
+    pp = getToolByName(site, 'portal_properties')
+    if PROPERTY_SHEET in pp.objectIds():
+        pp.manage_delObjects(ids=[PROPERTY_SHEET])
Index: /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/tests.py
===================================================================
--- /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/tests.py	(revision 962)
+++ /quintagroup.plonecaptchas/trunk/quintagroup/plonecaptchas/tests.py	(revision 963)
@@ -32,5 +32,5 @@
     def afterSetUp(self):
         self.loginAsPortalOwner()
-        self.addProduct('quintagroup.plonecaptchas')
+        self.addProduct(PRODUCT_NAME)
         self.portal.invokeFactory('Document', 'index_html')
         self.portal['index_html'].allowDiscussion(True)
@@ -47,10 +47,10 @@
 
     def testImage(self):
-        path = '%s/discussion_reply_form'%self.absolute_url
-        resp1 = self.publish(path, self.basic_auth, request_method='GET').getBody()
-        patt = re.compile('\s+src="%s(/getCaptchaImage/[0-9a-fA-F]+)"'%self.portal.absolute_url())
-        match_obj = patt.search(resp1)
+        path = '%s/discussion_reply_form' % self.absolute_url
+        response = self.publish(path, self.basic_auth, request_method='GET').getBody()
+        patt = re.compile('\s+src="%s(/getCaptchaImage/[0-9a-fA-F]+)"' % self.portal.absolute_url())
+        match_obj = patt.search(response)
         img_url = match_obj.group(1)
-        content_type = self.publish('/plone'+img_url, self.basic_auth).getHeader('content-type')
+        content_type = self.publish('/plone' + img_url, self.basic_auth).getHeader('content-type')
         self.assert_(content_type.startswith('image'))
 
@@ -58,11 +58,10 @@
         hashkey = self.portal.getCaptcha()
         key = getWord(int(parseKey(decrypt(self.captcha_key, hashkey))['key']))
-        parameters = 'form.submitted=1&Creator=test_user&key=%s'%key
-        path = '%s/discussion_reply_form?%s'%(self.absolute_url, parameters)
+        parameters = 'form.submitted=1&Creator=test_user&key=%s' % key
+        path = '%s/discussion_reply_form?%s' % (self.absolute_url, parameters)
         extra = {'hashkey': hashkey,
                  'subject': 'testing',
                  'body_text': 'Text in Comment',
                  'discussion_reply:method': 'Save'}
-
         response = self.publish(path, self.basic_auth, extra=extra, request_method='GET').getBody()
         patt = re.compile("Please re\-enter validation code")
@@ -73,10 +72,9 @@
         hashkey = self.portal.getCaptcha()
         parameters = 'form.submitted=1&Creator=test_user&key=fdfgh'
-        path = '%s/discussion_reply_form?%s'%(self.absolute_url, parameters)
+        path = '%s/discussion_reply_form?%s' % (self.absolute_url, parameters)
         extra = {'hashkey': hashkey,
                  'subject': 'testing',
                  'body_text': 'Text in Comment',
                  'discussion_reply:method': 'Save'}
-
         response = self.publish(path, self.basic_auth, extra=extra, request_method='GET').getBody()
         patt = re.compile("Please re\-enter validation code")
@@ -93,5 +91,4 @@
                  'body_text': 'Text in Comment',
                  'discussion_reply:method': 'Save'}
-
         self.publish(path, self.basic_auth, extra=extra, request_method='GET')
         response = self.publish(path, self.basic_auth, extra=extra, request_method='GET').getBody()
@@ -112,33 +109,52 @@
         return LAYERS + [LAYER_STATIC_CAPTCHAS]
 
-    def test_configlet_install(self):
+    def testPropertysheetInstall(self):
+        pp = getToolByName(self.portal, 'portal_properties')
+        self.assert_(PROPERTY_SHEET in pp.objectIds(), 'Property sheet isn\'t found')
+
+    def testPropertysheetUninstall(self):
+        self.qi.uninstallProducts([PRODUCT_NAME])
+        pp = getToolByName(self.portal, 'portal_properties')
+        self.assert_(not PROPERTY_SHEET in pp.objectIds(),
+            'Property sheet found after uninstallation')
+
+    def testConfigletInstall(self):
         self.assert_(CONFIGLET_ID in [a.getId() for a in self.cp.listActions()], 'Configlet not found')
 
-    def test_skins_install(self):
+    def testConfigletUninstall(self):
+        self.qi.uninstallProducts([PRODUCT_NAME])
+        self.assertNotEqual(self.qi.isProductInstalled(PRODUCT_NAME), True,'%s is already installed' % PRODUCT_NAME)
+        self.assert_(not CONFIGLET_ID in [a.getId() for a in self.cp.listActions()], 'Configlet found after uninstallation')
+
+    def testSkinsInstall(self):
         skinstool = self.st
-        Layers = self.getLayers()
+        layers = self.getLayers()
         for skin in skinstool.getSkinSelections():
             path = skinstool.getSkinPath(skin)
             path = map(str.strip, path.split(','))
-            for layer in Layers:
+            for layer in layers:
                 self.assert_(layer.split('/')[0] in skinstool.objectIds(), '%s directory view not found in portal_skins after installation' % layer)
                 self.assert_(layer in path, '%s layer not found in %s' % (PRODUCT_NAME, skin))
 
-    def test_skins_uninstall(self):
+    def testSkinsUninstall(self):
         self.qi.uninstallProducts([PRODUCT_NAME])
         self.assertNotEqual(self.qi.isProductInstalled(PRODUCT_NAME), True,'%s is already installed' % PRODUCT_NAME)
         skinstool = self.st
-        Layers = self.getLayers()
+        layers = self.getLayers()
         for skin in skinstool.getSkinSelections():
             path = skinstool.getSkinPath(skin)
             path = map(str.strip, path.split(','))
-            for layer in Layers:
+            for layer in layers:
                 self.assert_(not layer.split('/')[0] in skinstool.objectIds(), '%s directory view found in portal_skins after uninstallation' % layer)
                 self.assert_(not layer in path, '%s layer found in %s after uninstallation' % (layer, skin))
 
-    def test_configlet_uninstall(self):
+    def testToolInstall(self):
+        self.assert_(TOOL_ID in self.portal.objectIds())
+
+    def testToolUninstall(self):
         self.qi.uninstallProducts([PRODUCT_NAME])
-        self.assertNotEqual(self.qi.isProductInstalled(PRODUCT_NAME), True,'%s is already installed' % PRODUCT_NAME)
-        self.assert_(not CONFIGLET_ID in [a.getId() for a in self.cp.listActions()], 'Configlet found after uninstallation')
+        self.assertNotEqual(self.qi.isProductInstalled(PRODUCT_NAME), True, 
+            '%s is already installed' % PRODUCT_NAME)
+        self.assert_(not TOOL_ID in self.portal.objectIds())
 
     def testCaptchaKey(self):
@@ -147,7 +163,4 @@
         self.assertEqual(len(ck), 8)
 
-    def testCaptchaTool(self):
-        self.assert_('portal_captchas' in self.portal.objectIds())
-
 def test_suite():
     suite = unittest.TestSuite()
