Back to index

plone3  3.1.7
QuickInstallerTool.py
Go to the documentation of this file.
00001 import logging
00002 import os
00003 
00004 from zope.component import getSiteManager
00005 from zope.component import getAllUtilitiesRegisteredFor
00006 from zope.interface import implements
00007 from zope.annotation.interfaces import IAnnotatable
00008 from zope.i18nmessageid import MessageFactory
00009 
00010 from AccessControl import ClassSecurityInfo
00011 from AccessControl.requestmethod import postonly
00012 from Acquisition import aq_base, aq_parent, aq_get
00013 
00014 from Globals import DevelopmentMode
00015 from Globals import InitializeClass
00016 from Globals import INSTANCE_HOME
00017 from OFS.SimpleItem import SimpleItem
00018 from OFS.ObjectManager import ObjectManager
00019 from zExceptions import NotFound
00020 
00021 from Products.CMFCore.permissions import ManagePortal
00022 from Products.CMFCore.utils import UniqueObject, getToolByName
00023 from Products.ExternalMethod.ExternalMethod import ExternalMethod
00024 from Products.GenericSetup import EXTENSION
00025 from Products.GenericSetup.utils import _getDottedName
00026 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00027 
00028 from Products.CMFQuickInstallerTool.interfaces import INonInstallable
00029 from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool
00030 from Products.CMFQuickInstallerTool.InstalledProduct import InstalledProduct
00031 _ = MessageFactory("plone")
00032 
00033 
00034 logger = logging.getLogger('CMFQuickInstallerTool')
00035 
00036 
00037 class AlreadyInstalled(Exception):
00038     """ Would be nice to say what Product was trying to be installed """
00039     pass
00040 
00041 def addQuickInstallerTool(self, REQUEST=None):
00042     """ """
00043     qt = QuickInstallerTool()
00044     self._setObject('portal_quickinstaller', qt, set_owner=False)
00045     if REQUEST:
00046         return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00047 
00048 
00049 class HiddenProducts(object):
00050     implements(INonInstallable)
00051 
00052     def getNonInstallableProducts(self):
00053         return ['CMFQuickInstallerTool', 'Products.CMFQuickInstallerTool']
00054 
00055 
00056 class QuickInstallerTool(UniqueObject, ObjectManager, SimpleItem):
00057     """
00058       Let's make sure that this implementation actually fulfills the
00059       'IQuickInstallerTool' API.
00060 
00061       >>> from zope.interface.verify import verifyClass
00062       >>> verifyClass(IQuickInstallerTool, QuickInstallerTool)
00063       True
00064     """
00065     implements(IQuickInstallerTool)
00066 
00067     meta_type = 'CMF QuickInstaller Tool'
00068     id = 'portal_quickinstaller'
00069 
00070     security = ClassSecurityInfo()
00071 
00072     manage_options=(
00073         {'label':'Install', 'action':'manage_installProductsForm'},
00074         ) + ObjectManager.manage_options
00075 
00076     security.declareProtected(ManagePortal, 'manage_installProductsForm')
00077     manage_installProductsForm = PageTemplateFile(
00078         'forms/install_products_form', globals(),
00079         __name__='manage_installProductsForm')
00080 
00081     security = ClassSecurityInfo()
00082 
00083     def __init__(self):
00084         self.id = 'portal_quickinstaller'
00085 
00086     security.declareProtected(ManagePortal, 'getInstallProfiles')
00087     def getInstallProfiles(self, productname):
00088         """ Return the installer profile id
00089         """
00090         portal_setup = getToolByName(self, 'portal_setup')
00091         profiles = portal_setup.listProfileInfo()
00092 
00093         # We are only interested in extension profiles for the product
00094         # TODO Remove the manual Products.* check here. It is still needed.
00095         profiles = [prof['id'] for prof in profiles if
00096             prof['type'] == EXTENSION and
00097             (prof['product'] == productname or 
00098              prof['product'] == 'Products.%s' % productname)]
00099 
00100         return profiles
00101 
00102     security.declareProtected(ManagePortal, 'getInstallProfile')
00103     def getInstallProfile(self, productname):
00104         """ Return the installer profile
00105         """
00106         portal_setup = getToolByName(self, 'portal_setup')
00107         profiles = portal_setup.listProfileInfo()
00108 
00109         # We are only interested in extension profiles for the product
00110         profiles = [prof for prof in profiles if
00111             prof['type'] == EXTENSION and
00112             (prof['product'] == productname or 
00113              prof['product'] == 'Products.%s' % productname)]
00114 
00115         # XXX Currently QI always uses the first profile
00116         if profiles:
00117             return profiles[0]
00118         return None
00119 
00120     security.declareProtected(ManagePortal, 'getInstallMethod')
00121     def getInstallMethod(self, productname):
00122         """ Return the installer method
00123         """
00124         import_error_announced = False
00125         for mod, func in (('Install','install'),
00126                           ('Install','Install'),
00127                           ('install','install'),
00128                           ('install','Install')):
00129             if productname in self.Control_Panel.Products.objectIds():
00130                 productInCP = self.Control_Panel.Products[productname]
00131 
00132                 if mod in productInCP.objectIds():
00133                     modFolder = productInCP[mod]
00134                     if func in modFolder.objectIds():
00135                         return modFolder[func]
00136 
00137                 try:
00138                     try:
00139                         return ExternalMethod('temp',
00140                                               'temp',
00141                                               productname+'.'+mod,
00142                                               func)
00143                     except NotFound:
00144                         continue
00145                     except ImportError, e:
00146                         if not import_error_announced:
00147                             # do not announce the error 4 times
00148                             import_error_announced = True
00149                             msg = "%s, ImportError: %s" % (productname, str(e))
00150                             logger.log(logging.ERROR, msg)
00151                         continue
00152                 except RuntimeError, msg:
00153                     # external method can throw a bunch of these
00154                     msg = "%s, RuntimeError: %s" % (productname, msg)
00155                     logger.log(logging.ERROR, msg)
00156 
00157         raise AttributeError, ('No Install method found for '
00158                                'product %s' % productname)
00159 
00160     security.declareProtected(ManagePortal, 'getBrokenInstalls')
00161     def getBrokenInstalls(self):
00162         """ Return all the broken installs """
00163         errs = getattr(self, "errors", {})
00164         return errs.values()
00165     
00166     security.declareProtected(ManagePortal, 'isProductInstallable')
00167     def isProductInstallable(self, productname):
00168         """Asks wether a product is installable by trying to get its install
00169            method or an installation profile.
00170         """
00171         not_installable = []
00172         utils = getAllUtilitiesRegisteredFor(INonInstallable)
00173         for util in utils:
00174             not_installable.extend(util.getNonInstallableProducts())
00175         if productname in not_installable:
00176             return False
00177         try:
00178             meth=self.getInstallMethod(productname)
00179             return True
00180         except AttributeError:
00181             profiles = self.getInstallProfiles(productname)
00182             if not profiles:
00183                 return False
00184             setup_tool = getToolByName(self, 'portal_setup')
00185             try:
00186                 # XXX Currently QI always uses the first profile 
00187                 setup_tool.getProfileDependencyChain(profiles[0])
00188             except KeyError, e:
00189                 if not getattr(self, "errors", {}):
00190                     self.errors = {}
00191                 # Don't show twice the same error: old install and profile
00192                 # oldinstall is test in first in other methods we may have an
00193                 # extra 'Products.' in the namespace
00194                 checkname = productname
00195                 if checkname.startswith('Products.'):
00196                     checkname = checkname[9:]
00197                 else:
00198                     checkname = 'Products.' + checkname
00199                 if self.errors.has_key(checkname):
00200                     if self.errors[checkname]['value'] == e.args[0]:
00201                         return False
00202                     else:
00203                         # A new error is found, register it
00204                         self.errors[productname] = dict(
00205                             type= _(u"dependency_missing", default=u"Missing dependency"),
00206                             value = e.args[0],
00207                             productname = productname)
00208                 else:
00209                     self.errors[productname] = dict(
00210                         type= _(u"dependency_missing", default=u"Missing dependency"),
00211                         value = e.args[0],
00212                         productname = productname)
00213 
00214                 return False
00215 
00216             return True
00217 
00218     security.declareProtected(ManagePortal, 'isProductAvailable')
00219     isProductAvailable = isProductInstallable
00220 
00221     security.declareProtected(ManagePortal, 'listInstallableProfiles')
00222     def listInstallableProfiles(self):
00223         """List candidate products which have a GS profiles.
00224         """
00225         portal_setup = getToolByName(self, 'portal_setup')
00226         profiles = portal_setup.listProfileInfo()
00227 
00228         # We are only interested in extension profiles
00229         profiles = [prof['product'] for prof in profiles if
00230             prof['type'] == EXTENSION]
00231         return set(profiles)
00232 
00233     security.declareProtected(ManagePortal, 'listInstallableProducts')
00234     def listInstallableProducts(self, skipInstalled=True):
00235         """List candidate CMF products for installation -> list of dicts
00236            with keys:(id,title,hasError,status)
00237         """
00238         # reset the list of broken products
00239         self.errors = {}
00240 
00241         # Get product list from control panel
00242         pids = self.Control_Panel.Products.objectIds()
00243         pids = [p for p in pids if self.isProductInstallable(p)]
00244 
00245         # Get product list from the extension profiles
00246         profile_pids = self.listInstallableProfiles()
00247         profile_pids = [p for p in profile_pids if self.isProductInstallable(p)]
00248         for p in profile_pids:
00249             if p.startswith('Products.'):
00250                 p = p[9:]
00251             if p not in pids:
00252                 pids.append(p)
00253 
00254         if skipInstalled:
00255             installed=[p['id'] for p in self.listInstalledProducts(showHidden=True)]
00256             pids=[r for r in pids if r not in installed]
00257 
00258         res=[]
00259         for r in pids:
00260             p=self._getOb(r,None)
00261             name = r
00262             profile = self.getInstallProfile(r)
00263             if profile:
00264                 name = profile['title']
00265             if p:
00266                 res.append({'id':r, 'title':name, 'status':p.getStatus(),
00267                             'hasError':p.hasError()})
00268             else:
00269                 res.append({'id':r, 'title':name,'status':'new', 'hasError':False})
00270         res.sort(lambda x,y: cmp(x.get('title', x.get('id', None)),
00271                                  y.get('title', y.get('id', None))))
00272         return res
00273 
00274     security.declareProtected(ManagePortal, 'listInstalledProducts')
00275     def listInstalledProducts(self, showHidden=False):
00276         """Returns a list of products that are installed -> list of
00277         dicts with keys:(id, title, hasError, status, isLocked, isHidden,
00278         installedVersion)
00279         """
00280         pids = [o.id for o in self.objectValues()
00281                 if o.isInstalled() and (o.isVisible() or showHidden)]
00282         pids = [pid for pid in pids if self.isProductInstallable(pid)]
00283 
00284         res=[]
00285 
00286         for r in pids:
00287             p = self._getOb(r,None)
00288             name = r
00289             profile = self.getInstallProfile(r)
00290             if profile:
00291                 name = profile['title']
00292             
00293             res.append({'id':r,
00294                         'title':name,
00295                         'status':p.getStatus(),
00296                         'hasError':p.hasError(),
00297                         'isLocked':p.isLocked(),
00298                         'isHidden':p.isHidden(),
00299                         'installedVersion':p.getInstalledVersion()})
00300         res.sort(lambda x,y: cmp(x.get('title', x.get('id', None)),
00301                                  y.get('title', y.get('id', None))))
00302         return res
00303 
00304     security.declareProtected(ManagePortal, 'getProductFile')
00305     def getProductFile(self,p,fname='readme.txt'):
00306         """Return the content of a file of the product
00307         case-insensitive, if it does not exist -> None
00308         """
00309         try:
00310             prodpath=self.Control_Panel.Products._getOb(p).home
00311         except AttributeError:
00312             return None
00313 
00314         #now list the directory to get the readme.txt case-insensitive
00315         try:
00316             files=os.listdir(prodpath)
00317         except OSError:
00318             return None
00319 
00320         for f in files:
00321             if f.lower()==fname:
00322                 text = open(os.path.join(prodpath,f)).read()
00323                 try:
00324                     return unicode(text)
00325                 except UnicodeDecodeError:
00326                     try:
00327                         return unicode(text, 'utf-8')
00328                     except UnicodeDecodeError:
00329                         return unicode(text, 'utf-8', 'replace')
00330         return None
00331 
00332     security.declareProtected(ManagePortal, 'getProductReadme')
00333     getProductReadme=getProductFile
00334 
00335     security.declareProtected(ManagePortal, 'getProductDescription')
00336     def getProductDescription(self, p):
00337         """Returns the profile description for a given product.
00338         """
00339         profile = self.getInstallProfile(p)
00340         if profile is None:
00341             return None
00342         return profile.get('description', None)
00343 
00344     security.declareProtected(ManagePortal, 'getProductVersion')
00345     def getProductVersion(self,p):
00346         """Return the version string stored in version.txt or the one found
00347         in the GS profile.
00348         """
00349         res = self.getProductFile(p, 'version.txt')
00350         if res is not None:
00351             res = res.strip()
00352         else:
00353             # Try to look up the version from the GS profile
00354             profiles = self.getInstallProfiles(p)
00355             if profiles:
00356                 profile = profiles[0]
00357                 portal_setup = getToolByName(self, 'portal_setup')
00358                 version = portal_setup.getVersionForProfile(profile)
00359                 if isinstance(version, basestring):
00360                     res = str(version.strip())
00361         return res
00362 
00363 
00364     security.declareProtected(ManagePortal, 'snapshotPortal')
00365     def snapshotPortal(self, portal):
00366         portal_types=getToolByName(portal,'portal_types')
00367         portal_skins=getToolByName(portal,'portal_skins')
00368         portal_actions=getToolByName(portal,'portal_actions')
00369         portal_workflow=getToolByName(portal,'portal_workflow')
00370         type_registry=getToolByName(portal,'content_type_registry')
00371 
00372         state={}
00373         state['leftslots']=getattr(portal,'left_slots',[])
00374         if callable(state['leftslots']):
00375             state['leftslots']=state['leftslots']()
00376         state['rightslots']=getattr(portal,'right_slots',[])
00377         if callable(state['rightslots']):
00378             state['rightslots']=state['rightslots']()
00379         state['registrypredicates']=[pred[0] for pred in type_registry.listPredicates()]
00380 
00381         state['types']=portal_types.objectIds()
00382         state['skins']=portal_skins.objectIds()
00383         actions= set()
00384         for category in portal_actions.objectIds():
00385             for action in portal_actions[category].objectIds():
00386                 actions.add((category, action))
00387         state['actions']=actions
00388         state['workflows']=portal_workflow.objectIds()
00389         state['portalobjects']=portal.objectIds()
00390         state['adapters']= tuple(getSiteManager().registeredAdapters())
00391         state['utilities']= tuple(getSiteManager().registeredUtilities())
00392 
00393         jstool = getToolByName(portal,'portal_javascripts', None)
00394         if jstool is not None:
00395             state['resources_js']= jstool.getResourceIds()
00396         else:
00397             state['resources_js']= []
00398         csstool = getToolByName(portal,'portal_css', None)
00399         if jstool is not None:
00400             state['resources_css']= csstool.getResourceIds()
00401         else:
00402             state['resources_css']= []
00403 
00404         return state
00405 
00406 
00407     security.declareProtected(ManagePortal, 'deriveSettingsFromSnapshots')
00408     def deriveSettingsFromSnapshots(self, before, after):
00409         actions = [a for a in (after['actions'] - before['actions'])]
00410 
00411         adapters = []
00412         if len(after['adapters']) > len(before['adapters']):
00413             registrations = [reg for reg in after['adapters']
00414                                   if reg not in before['adapters']]
00415             # TODO: expand this to actually cover adapter registrations
00416 
00417         utilities = []
00418         if len(after['utilities']) > len(before['utilities']):
00419             registrations = [reg for reg in after['utilities']
00420                                   if reg not in before['utilities']]
00421 
00422             for registration in registrations:
00423                 reg = (_getDottedName(registration.provided), registration.name)
00424                 utilities.append(reg)
00425 
00426         settings=dict(
00427             types=[t for t in after['types'] if t not in before['types']],
00428             skins=[s for s in after['skins'] if s not in before['skins']],
00429             actions=actions,
00430             workflows=[w for w in after['workflows'] if w not in before['workflows']],
00431             portalobjects=[a for a in after['portalobjects']
00432                            if a not in before['portalobjects']],
00433             leftslots=[s for s in after['leftslots'] if s not in before['leftslots']],
00434             rightslots=[s for s in after['rightslots'] if s not in before['rightslots']],
00435             adapters=adapters,
00436             utilities=utilities,
00437             registrypredicates=[s for s in after['registrypredicates']
00438                                 if s not in before['registrypredicates']],
00439             )
00440 
00441         jstool = getToolByName(self,'portal_javascripts', None)
00442         if jstool is not None:
00443             settings['resources_js']=[r for r in after['resources_js'] if r not in before['resources_js']]
00444             settings['resources_css']=[r for r in after['resources_css'] if r not in before['resources_css']]
00445 
00446         return settings
00447 
00448 
00449     security.declareProtected(ManagePortal, 'installProduct')
00450     def installProduct(self, p, locked=False, hidden=False,
00451                        swallowExceptions=None, reinstall=False,
00452                        forceProfile=False, omitSnapshots=True,
00453                        profile=None):
00454         """Install a product by name
00455         """
00456         __traceback_info__ = (p,)
00457 
00458         if profile is not None:
00459             forceProfile = True
00460 
00461         if self.isProductInstalled(p):
00462             prod = self._getOb(p)
00463             msg = ('This product is already installed, '
00464                    'please uninstall before reinstalling it.')
00465             prod.log(msg)
00466             return msg
00467 
00468         portal=aq_parent(self)
00469 
00470         before=self.snapshotPortal(portal)
00471 
00472         if hasattr(self, "REQUEST"):
00473             reqstorage=IAnnotatable(self.REQUEST, None)
00474             if reqstorage is not None:
00475                 key="Products.CMFQUickInstaller.Installing"
00476                 if reqstorage.has_key(key):
00477                     installing=reqstorage[key]
00478                 else:
00479                     installing=reqstorage[key]=set()
00480                 installing.add(p)
00481         else:
00482             reqstorage=None
00483 
00484         # XXX We can not use getToolByName since that returns a utility
00485         # without a RequestContainer. This breaks import steps that need
00486         # to run tools which request self.REQUEST.
00487         portal_setup = aq_get(portal, 'portal_setup', None, 1)
00488         status=None
00489         res=''
00490 
00491         # Create a snapshot before installation
00492         before_id = portal_setup._mangleTimestampName('qi-before-%s' % p)
00493         if not omitSnapshots:
00494             portal_setup.createSnapshot(before_id)
00495 
00496         install = False
00497         if not forceProfile:
00498             try:
00499                 # Install via external method
00500                 install = self.getInstallMethod(p).__of__(portal)
00501             except AttributeError:
00502                 # No classic install method found
00503                 pass
00504 
00505         if install and not forceProfile:
00506             try:
00507                 res=install(portal, reinstall=reinstall)
00508             except TypeError:
00509                 res=install(portal)
00510             status='installed'
00511         else:
00512             profiles = self.getInstallProfiles(p)
00513             if profiles:
00514                 if profile is None:
00515                     profile = profiles[0]
00516                     if len(profiles) > 1:
00517                         logger.log(logging.INFO,
00518                                    'Multiple extension profiles found for product '
00519                                    '%s. Used profile: %s' % (p, profile))
00520 
00521                 portal_setup.runAllImportStepsFromProfile('profile-%s' % profile)
00522                 status='installed'
00523             else:
00524                 # No install method and no profile, log / abort?
00525                 pass
00526 
00527         if reqstorage is not None:
00528             installing.remove(p)
00529 
00530         # Create a snapshot after installation
00531         after_id = portal_setup._mangleTimestampName('qi-after-%s' % p)
00532         if not omitSnapshots:
00533             portal_setup.createSnapshot(after_id)
00534 
00535         after=self.snapshotPortal(portal)
00536 
00537         settings=self.deriveSettingsFromSnapshots(before, after)
00538 
00539         jstool = getToolByName(self,'portal_javascripts', None)
00540         if jstool is not None:
00541             if len(settings['types']) > 0:
00542                 rr_css=getToolByName(portal,'portal_css')
00543                 rr_css.cookResources()
00544 
00545         msg=str(res)
00546         version=self.getProductVersion(p)
00547 
00548         # add the product
00549         self.notifyInstalled(p,
00550                       settings=settings,
00551                       installedversion=version,
00552                       logmsg=res,
00553                       status=status,
00554                       error=False,
00555                       locked=locked,
00556                       hidden=hidden,
00557                       afterid=after_id,
00558                       beforeid=before_id)
00559 
00560         prod = getattr(self, p)
00561         afterInstall = prod.getAfterInstallMethod()
00562         if afterInstall is not None:
00563             afterInstall = afterInstall.__of__(portal)
00564             afterRes=afterInstall(portal, reinstall=reinstall, product=prod)
00565             if afterRes:
00566                 res = res + '\n' + str(afterRes)
00567         return res
00568 
00569     security.declareProtected(ManagePortal, 'installProducts')
00570     def installProducts(self, products=[], stoponerror=True, reinstall=False,
00571                         REQUEST=None, forceProfile=False, omitSnapshots=True):
00572         """ """
00573         res = """
00574     Installed Products
00575     ====================
00576     """
00577         ok = True
00578         # return products
00579         for p in products:
00580             res += p +':'
00581             r=self.installProduct(p, swallowExceptions=not stoponerror,
00582                                   reinstall=reinstall,
00583                                   forceProfile=forceProfile,
00584                                   omitSnapshots=omitSnapshots)
00585             res +='ok:\n'
00586             if r:
00587                 res += str(r)+'\n'
00588         if REQUEST :
00589             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00590 
00591         return res
00592 
00593     def isProductInstalled(self, productname):
00594         """Check wether a product is installed (by name)
00595         """
00596         o = self._getOb(productname, None)
00597         return o is not None and o.isInstalled()
00598 
00599     security.declareProtected(ManagePortal, 'notifyInstalled')
00600     def notifyInstalled(self,p,locked=True,hidden=False,settings={},**kw):
00601         """Marks a product that has been installed
00602         without QuickInstaller as installed
00603         """
00604 
00605         if not p in self.objectIds():
00606             ip = InstalledProduct(p)
00607             self._setObject(p,ip)
00608             
00609         p = getattr(self, p)
00610         p.update(settings,locked=locked, hidden=hidden, **kw)
00611 
00612     security.declareProtected(ManagePortal, 'uninstallProducts')
00613     def uninstallProducts(self, products=[],
00614                           cascade=InstalledProduct.default_cascade,
00615                           reinstall=False,
00616                           REQUEST=None):
00617         """Removes a list of products
00618         """
00619         for pid in products:
00620             prod=getattr(self,pid)
00621             prod.uninstall(cascade=cascade, reinstall=reinstall)
00622 
00623         if REQUEST:
00624             return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00625     uninstallProducts = postonly(uninstallProducts)
00626 
00627     security.declareProtected(ManagePortal, 'reinstallProducts')
00628     def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True):
00629         """Reinstalls a list of products, the main difference to
00630         uninstall/install is that it does not remove portal objects
00631         created during install (e.g. tools, etc.)
00632         """
00633         if isinstance(products, basestring):
00634             products=[products]
00635 
00636         # only delete everything EXCEPT portalobjects (tools etc) for reinstall
00637         cascade=[c for c in InstalledProduct.default_cascade
00638                  if c != 'portalobjects']
00639         self.uninstallProducts(products, cascade, reinstall=True)
00640         self.installProducts(products,
00641                              stoponerror=True,
00642                              reinstall=True,
00643                              omitSnapshots=omitSnapshots)
00644 
00645         if REQUEST:
00646             return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00647 
00648     reinstallProducts = postonly(reinstallProducts)
00649 
00650     def getQIElements(self):
00651         res = ['types', 'skins', 'actions', 'portalobjects', 'workflows', 
00652                   'leftslots', 'rightslots', 'registrypredicates',
00653                   'resources_js', 'resources_css']
00654         return res
00655 
00656     def getAlreadyRegistered(self):
00657         """Get a list of already registered elements
00658         """
00659         result = {}
00660         products = [p for p in self.objectValues() if p.isInstalled() ]
00661         for element in self.getQIElements():
00662             v = result.setdefault(element, [])
00663             for product in products:
00664                 pv = getattr(aq_base(product), element, None)
00665                 if pv:
00666                     v.extend(list(pv))
00667         return result
00668 
00669     security.declareProtected(ManagePortal, 'isDevelopmentMode')
00670     def isDevelopmentMode(self):
00671         """Is the Zope server in debug mode?
00672         """
00673         return not not DevelopmentMode
00674 
00675     security.declareProtected(ManagePortal, 'getInstanceHome')
00676     def getInstanceHome(self):
00677         """Return location of $INSTANCE_HOME
00678         """
00679         return INSTANCE_HOME
00680 
00681 InitializeClass(QuickInstallerTool)