CatalogTool.py 45 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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.
#
##############################################################################

29
import sys
30
from copy import deepcopy
31
from collections import defaultdict
32
from math import ceil
Jean-Paul Smets's avatar
Jean-Paul Smets committed
33 34
from Products.CMFCore.CatalogTool import CatalogTool as CMFCoreCatalogTool
from Products.ZSQLCatalog.ZSQLCatalog import ZCatalog
35
from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery
36
from Products.ERP5Type import Permissions
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37
from AccessControl import ClassSecurityInfo, getSecurityManager
38
from AccessControl.User import system as system_user
Aurel's avatar
Aurel committed
39 40
from Products.CMFCore.utils import UniqueObject, _getAuthenticatedUser, getToolByName
from Products.ERP5Type.Globals import InitializeClass, DTMLFile
41
from Acquisition import aq_base, aq_inner, aq_parent, ImplicitAcquisitionWrapper
42
from Products.CMFActivity.ActiveObject import ActiveObject
43
from Products.CMFActivity.ActivityTool import GroupedMessage
44
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
Jean-Paul Smets's avatar
Jean-Paul Smets committed
45 46 47

from AccessControl.PermissionRole import rolesForPermissionOn

48
from MethodObject import Method
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49

50
from Products.ERP5Security import mergedLocalRoles
51
from Products.ERP5Security.ERP5UserManager import SUPER_USER
52
from Products.ZSQLCatalog.Utils import sqlquote
53

Aurel's avatar
Aurel committed
54
import warnings
55
from zLOG import LOG, PROBLEM, WARNING, INFO
Jean-Paul Smets's avatar
Jean-Paul Smets committed
56

57
ACQUIRE_PERMISSION_VALUE = []
58
DYNAMIC_METHOD_NAME = 'z_related_'
59
DYNAMIC_METHOD_NAME_LEN = len(DYNAMIC_METHOD_NAME)
60
STRICT_DYNAMIC_METHOD_NAME = DYNAMIC_METHOD_NAME + 'strict_'
61
STRICT_DYNAMIC_METHOD_NAME_LEN = len(STRICT_DYNAMIC_METHOD_NAME)
62
RELATED_DYNAMIC_METHOD_NAME = '_related'
63 64
# Negative as it's used as a slice end offset
RELATED_DYNAMIC_METHOD_NAME_LEN = -len(RELATED_DYNAMIC_METHOD_NAME)
65
ZOPE_SECURITY_SUFFIX = '__roles__'
Aurel's avatar
Aurel committed
66

67
class IndexableObjectWrapper(object):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
68

69
    def __init__(self, ob):
70 71
        self.__ob = ob

72 73 74 75
    def __getattr__(self, name):
        return getattr(self.__ob, name)

    # We need to update the uid during the cataloging process
76
    uid = property(lambda self: self.__ob.getUid(),
77
                   lambda self, value: setattr(self.__ob, 'uid', value))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
78

79
    def _getSecurityParameterList(self):
80 81
      result = self.__dict__.get('_cache_result', None)
      if result is None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
82
        ob = self.__ob
83 84 85 86 87
        # For each group or user, we have a list of roles, this list
        # give in this order : [roles on object, roles acquired on the parent,
        # roles acquired on the parent of the parent....]
        # So if we have ['-Author','Author'] we should remove the role 'Author'
        # but if we have ['Author','-Author'] we have to keep the role 'Author'
88 89
        localroles = {}
        skip_role_set = set()
90 91
        skip_role = skip_role_set.add
        clear_skip_role = skip_role_set.clear
92
        for key, role_list in mergedLocalRoles(ob).iteritems():
93 94 95 96 97 98 99 100
          new_role_list = []
          new_role = new_role_list.append
          clear_skip_role()
          for role in role_list:
            if role[:1] == '-':
              skip_role(role[1:])
            elif role not in skip_role_set:
              new_role(role)
101 102
          if new_role_list:
            localroles[key] = [new_role_list, False]
103

104
        portal = ob.getPortalObject()
105 106
        role_dict = dict(portal.portal_catalog.getSQLCatalog().\
                                              getSQLCatalogRoleKeysList())
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
        for user_info in portal.acl_users.searchUsers(id=tuple(localroles), exact_match=True):
          key = user_info['id']
          try:
            localroles[key][1] = True
          except KeyError:
            # We found a bug, report it but do not make indexation fail.
            LOG(
              'CatalogTool.IndexableObjectWrapper',
              PROBLEM,
              'searchUser(id=%r, exact_match=True) returned an entry with '
              'id=%r. This is very likely a bugin a PAS plugin !' % (
                tuple(localroles),
                key,
              ),
            )
122

123
        allowed_dict = {}
124

125 126 127 128 129 130
        # For each local role of a user:
        #   If the local role grants View permission, add it.
        # Every addition implies 2 lines:
        #   user:<user_id>
        #   user:<user_id>:<role_id>
        # A line must not be present twice in final result.
131
        allowed_role_set = set(rolesForPermissionOn('View', ob))
132 133 134 135 136
        # XXX the permission name is included by default for verbose
        # logging of security errors, but the catalog does not need to
        # index it. Unfortunately, rolesForPermissionOn does not have
        # an option to disable this behavior at calling time, so
        # discard it explicitly.
137
        allowed_role_set.discard('_View_Permission')
138 139
        # XXX Owner is hardcoded, in order to prevent searching for user on the
        # site root.
140 141 142
        allowed_role_set.discard('Owner')

        # XXX make this a method of base ?
143
        local_roles_group_id_dict = deepcopy(getattr(ob,
144
          '__ac_local_roles_group_id_dict__', {}))
145 146 147 148 149 150 151 152 153 154

        # If we acquire a permission, then we also want to acquire the local
        # roles group ids
        local_roles_container = ob
        while getattr(local_roles_container, 'isRADContent', 0):
          if local_roles_container._getAcquireLocalRoles():
            local_roles_container = local_roles_container.aq_parent
            for role_definition_group, user_and_role_list in \
                getattr(local_roles_container,
                        '__ac_local_roles_group_id_dict__',
155
                        {}).items():
156
              local_roles_group_id_dict.setdefault(role_definition_group, set()
157 158 159
                ).update(user_and_role_list)
          else:
            break
160

161
        allowed_by_local_roles_group_id = {}
162 163
        allowed_by_local_roles_group_id[''] = allowed_role_set

164
        optimized_role_set = set()
165
        for role_definition_group, user_and_role_list in local_roles_group_id_dict.iteritems():
166 167
          group_allowed_set = allowed_by_local_roles_group_id.setdefault(
            role_definition_group, set())
168
          for user, role in user_and_role_list:
169 170 171 172
            if role in allowed_role_set:
              prefix = 'user:' + user
              group_allowed_set.update((prefix, '%s:%s' % (prefix, role)))
              optimized_role_set.add((user, role))
173 174
        user_role_dict = {}
        user_view_permission_role_dict = {}
175
        for user, (roles, user_exists) in localroles.iteritems():
176
          prefix = 'user:' + user
Jérome Perrin's avatar
Jérome Perrin committed
177
          for role in roles:
178
            is_not_in_optimised_role_set = (user, role) not in optimized_role_set
179
            if user_exists and role in role_dict:
180 181 182 183
              # User is a user (= not a group) and role is configured as
              # monovalued.
              if is_not_in_optimised_role_set:
                user_role_dict[role] = user
184
              if role in allowed_role_set:
185
                # ...and local role grants view permission.
186 187
                user_view_permission_role_dict[role] = user
            elif role in allowed_role_set:
188 189
              # User is a group and local role grants view permission.
              for group in local_roles_group_id_dict.get(user, ('', )):
190 191
                group_allowed_set = allowed_by_local_roles_group_id.setdefault(
                  group, set())
192 193 194
                if is_not_in_optimised_role_set:
                  group_allowed_set.add(prefix)
                  group_allowed_set.add('%s:%s' % (prefix, role))
195

196
        # sort `allowed` principals
197
        sorted_allowed_by_local_roles_group_id = {}
198
        for local_roles_group_id, allowed in \
199
                allowed_by_local_roles_group_id.iteritems():
200 201 202 203 204
          sorted_allowed_by_local_roles_group_id[local_roles_group_id] = tuple(
            sorted(allowed))

        self._cache_result = result = (sorted_allowed_by_local_roles_group_id,
                                       user_role_dict,
205 206
                                       user_view_permission_role_dict)
      return result
207

208 209 210
    def getLocalRolesGroupIdDict(self):
      """Returns a mapping of local roles group id to roles and users with View
      permission.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
      """
      return self._getSecurityParameterList()[0]

    def getAssignee(self):
      """Returns the user ID of the user with 'Assignee' local role on this
      document.

      If there is more than one Assignee local role, the result is undefined.
      """
      return self._getSecurityParameterList()[1].get('Assignee', None)

    def getViewPermissionAssignee(self):
      """Returns the user ID of the user with 'Assignee' local role on this
      document, if the Assignee role has View permission.

      If there is more than one Assignee local role, the result is undefined.
      """
      return self._getSecurityParameterList()[2].get('Assignee', None)

    def getViewPermissionAssignor(self):
      """Returns the user ID of the user with 'Assignor' local role on this
      document, if the Assignor role has View permission.

      If there is more than one Assignor local role, the result is undefined.
      """
      return self._getSecurityParameterList()[2].get('Assignor', None)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
237

238 239 240 241 242 243 244 245
    def getViewPermissionAssociate(self):
      """Returns the user ID of the user with 'Associate' local role on this
      document, if the Associate role has View permission.

      If there is more than one Associate local role, the result is undefined.
      """
      return self._getSecurityParameterList()[2].get('Associate', None)

246 247 248 249
    def __repr__(self):
      return '<Products.ERP5Catalog.CatalogTool.IndexableObjectWrapper'\
          ' for %s>' % ('/'.join(self.__ob.getPhysicalPath()), )

250

251
class RelatedBaseCategory(Method):
252 253
    """A Dynamic Method to act as a related key.
    """
254
    def __init__(self, id, strict_membership=0, related=0):
255
      self._id = id
256 257 258 259 260 261 262
      if strict_membership:
        strict = 'AND %(category_table)s.category_strict_membership = 1\n'
      else:
        strict = ''
      # From the point of view of query_table, we are looking up objects...
      if related:
        # ... which have a relation toward us
263 264 265 266
        # query_table's uid = category table's category_uid
        query_table_side = 'category_uid'
        # category table's uid = foreign_table's uid
        foreign_side = 'uid'
267 268
      else:
        # ... toward which we have a relation
269 270 271 272
        # query_table's uid = category table's uid
        query_table_side = 'uid'
        # category table's category_uid = foreign_table's uid
        foreign_side = 'category_uid'
273 274
      self._template = """\
%%(category_table)s.base_category_uid = %%(base_category_uid)s
275
%(strict)sAND %%(foreign_catalog)s.uid = %%(category_table)s.%(foreign_side)s
276 277
%%(RELATED_QUERY_SEPARATOR)s
%%(category_table)s.%(query_table_side)s = %%(query_table)s.uid""" % {
278
          'strict': strict,
279 280
          'foreign_side': foreign_side,
          'query_table_side': query_table_side,
281
      }
282 283 284 285 286 287
      self._monotable_template = """\
%%(category_table)s.base_category_uid = %%(base_category_uid)s
%(strict)sAND %%(category_table)s.%(query_table_side)s = %%(query_table)s.uid""" % {
          'strict': strict,
          'query_table_side': query_table_side,
      }
288

289
    def __call__(self, instance, table_0, table_1=None, query_table='catalog',
290
        RELATED_QUERY_SEPARATOR=' AND ', **kw):
291
      """Create the sql code for this related key."""
292 293
      # Note: in normal conditions, our category's uid will not change from
      # one invocation to the next.
294 295 296
      return (
        self._monotable_template if table_1 is None else self._template
      ) % {
297 298
        'base_category_uid': instance.getPortalObject().portal_categories.\
          _getOb(self._id).getUid(),
299
        'query_table': query_table,
300
        'category_table': table_0,
301
        'foreign_catalog': table_1,
302
        'RELATED_QUERY_SEPARATOR': RELATED_QUERY_SEPARATOR,
303
      }
304

305
class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
306 307 308 309 310 311 312
    """
    This is a ZSQLCatalog that filters catalog queries.
    It is based on ZSQLCatalog
    """
    id = 'portal_catalog'
    meta_type = 'ERP5 Catalog'
    security = ClassSecurityInfo()
Aurel's avatar
Aurel committed
313

314
    default_result_limit = None
315
    default_count_limit = 1
Aurel's avatar
Aurel committed
316

Vincent Pelletier's avatar
Vincent Pelletier committed
317
    manage_options = ({ 'label' : 'Overview', 'action' : 'manage_overview' },
Jean-Paul Smets's avatar
Jean-Paul Smets committed
318 319 320 321 322
                     ) + ZCatalog.manage_options

    def __init__(self):
        ZCatalog.__init__(self, self.getId())

323
    # Explicit Inheritance
Jean-Paul Smets's avatar
Jean-Paul Smets committed
324 325 326
    __url = CMFCoreCatalogTool.__url
    manage_catalogFind = CMFCoreCatalogTool.manage_catalogFind

Vincent Pelletier's avatar
Vincent Pelletier committed
327 328 329
    security.declareProtected(Permissions.ManagePortal
                , 'manage_schema')
    manage_schema = DTMLFile('dtml/manageSchema', globals())
Jean-Paul Smets's avatar
Jean-Paul Smets committed
330

331
    security.declarePublic('getPreferredSQLCatalogId')
Aurel's avatar
Aurel committed
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
    def getPreferredSQLCatalogId(self, id=None):
      """
      Get the SQL Catalog from preference.
      """
      if id is None:
        # Check if we want to use an archive
        #if getattr(aq_base(self.portal_preferences), 'uid', None) is not None:
        archive_path = self.portal_preferences.getPreferredArchive(sql_catalog_id=self.default_sql_catalog_id)
        if archive_path not in ('', None):
          try:
            archive = self.restrictedTraverse(archive_path)
          except KeyError:
            # Do not fail if archive object has been removed,
            # but preference is not up to date
            return None
          if archive is not None:
            catalog_id = archive.getCatalogId()
            if catalog_id not in ('', None):
              return catalog_id
        return None
      else:
        return id
354

355
    def _listAllowedRolesAndUsers(self, user):
356
        # We use ERP5Security PAS based authentication
357 358 359
        try:
          # check for proxy role in stack
          eo = getSecurityManager()._context.stack[-1]
360
          proxy_roles = getattr(eo, '_proxy_roles',None)
361 362 363 364 365
        except IndexError:
          proxy_roles = None
        if proxy_roles:
          # apply proxy roles
          user = eo.getOwner()
Vincent Pelletier's avatar
Vincent Pelletier committed
366
          result = list(proxy_roles)
367
        else:
Vincent Pelletier's avatar
Vincent Pelletier committed
368 369 370
          result = list(user.getRoles())
        result.append('Anonymous')
        result.append('user:%s' % user.getId())
371 372 373
        # deal with groups
        getGroups = getattr(user, 'getGroups', None)
        if getGroups is not None:
374
            groups = list(user.getGroups())
375 376 377 378 379 380
            groups.append('role:Anonymous')
            if 'Authenticated' in result:
                groups.append('role:Authenticated')
            for group in groups:
                result.append('user:%s' % group)
        # end groups
Jérome Perrin's avatar
Jérome Perrin committed
381
        return result
382

Jean-Paul Smets's avatar
Jean-Paul Smets committed
383
    # Schema Management
384
    security.declareProtected(Permissions.ManagePortal, 'editColumn')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
385 386 387 388 389 390 391 392 393 394 395 396 397
    def editColumn(self, column_id, sql_definition, method_id, default_value, REQUEST=None, RESPONSE=None):
      """
        Modifies a schema column of the catalog
      """
      new_schema = []
      for c in self.getIndexList():
        if c.id == index_id:
          new_c = {'id': index_id, 'sql_definition': sql_definition, 'method_id': method_id, 'default_value': default_value}
        else:
          new_c = c
        new_schema.append(new_c)
      self.setColumnList(new_schema)

398
    security.declareProtected(Permissions.ManagePortal, 'setColumnList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
399 400 401 402 403
    def setColumnList(self, column_list):
      """
      """
      self._sql_schema = column_list

404
    security.declarePublic('getColumnList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
405 406 407 408 409 410
    def getColumnList(self):
      """
      """
      if not hasattr(self, '_sql_schema'): self._sql_schema = []
      return self._sql_schema

411
    security.declarePublic('getColumn')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
412 413 414 415 416 417 418 419
    def getColumn(self, column_id):
      """
      """
      for c in self.getColumnList():
        if c.id == column_id:
          return c
      return None

420
    security.declareProtected(Permissions.ManagePortal, 'editIndex')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
421 422 423 424 425 426 427 428 429 430 431 432 433
    def editIndex(self, index_id, sql_definition, REQUEST=None, RESPONSE=None):
      """
        Modifies the schema of the catalog
      """
      new_index = []
      for c in self.getIndexList():
        if c.id == index_id:
          new_c = {'id': index_id, 'sql_definition': sql_definition}
        else:
          new_c = c
        new_index.append(new_c)
      self.setIndexList(new_index)

434
    security.declareProtected(Permissions.ManagePortal, 'setIndexList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
435 436 437 438 439
    def setIndexList(self, index_list):
      """
      """
      self._sql_index = index_list

440
    security.declarePublic('getIndexList')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
441 442 443 444 445 446
    def getIndexList(self):
      """
      """
      if not hasattr(self, '_sql_index'): self._sql_index = []
      return self._sql_index

447
    security.declarePublic('getIndex')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
448 449 450 451 452 453 454 455 456
    def getIndex(self, index_id):
      """
      """
      for c in self.getIndexList():
        if c.id == index_id:
          return c
      return None


Vincent Pelletier's avatar
Vincent Pelletier committed
457
    security.declarePublic('getAllowedRolesAndUsers')
458
    def getAllowedRolesAndUsers(self, sql_catalog_id=None, local_roles=None):
459 460
      """
        Return allowed roles and users.
461

462
        This is supposed to be used with Z SQL Methods to check permissions
463
        when you list up documents. It is also able to take into account
464
        a parameter named local_roles so that listed documents only include
465 466
        those documents for which the user (or the group) was
        associated one of the given local roles.
Aurel's avatar
Aurel committed
467

468 469
        The use of getAllowedRolesAndUsers is deprecated, you should use
        getSecurityQuery instead
470 471
      """
      user = _getAuthenticatedUser(self)
472
      user_str = str(user)
473
      user_is_superuser = (user == system_user) or (user_str == SUPER_USER)
474
      allowedRolesAndUsers = self._listAllowedRolesAndUsers(user)
475
      role_column_dict = {}
476 477 478
      local_role_column_dict = {}
      catalog = self.getSQLCatalog(sql_catalog_id)
      column_map = catalog.getColumnMap()
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
      # We only consider here the Owner role (since it was not indexed)
      # since some objects may only be visible by their owner
      # which was not indexed
      for role, column_id in catalog.getSQLCatalogRoleKeysList():
        # XXX This should be a list
        if not user_is_superuser:
          try:
            # if called by an executable with proxy roles, we don't use
            # owner, but only roles from the proxy.
            eo = getSecurityManager()._context.stack[-1]
            proxy_roles = getattr(eo, '_proxy_roles', None)
            if not proxy_roles:
              role_column_dict[column_id] = user_str
          except IndexError:
            role_column_dict[column_id] = user_str

496 497
      # Patch for ERP5 by JP Smets in order
      # to implement worklists and search of local roles
498
      if local_roles:
499 500
        local_role_dict = dict(catalog.getSQLCatalogLocalRoleKeysList())
        role_dict = dict(catalog.getSQLCatalogRoleKeysList())
501
        # XXX user is not enough - we should also include groups of the user
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        new_allowedRolesAndUsers = []
        new_role_column_dict = {}
        # Turn it into a list if necessary according to ';' separator
        if isinstance(local_roles, str):
          local_roles = local_roles.split(';')
        # Local roles now has precedence (since it comes from a WorkList)
        for user_or_group in allowedRolesAndUsers:
          for role in local_roles:
            # Performance optimisation
            if local_role_dict.has_key(role):
              # XXX This should be a list
              # If a given role exists as a column in the catalog,
              # then it is considered as single valued and indexed
              # through the catalog.
              if not user_is_superuser:
517
                # XXX This should be a list
518 519 520 521 522 523 524 525 526
                # which also includes all user groups
                column_id = local_role_dict[role]
                local_role_column_dict[column_id] = user_str
            if role_dict.has_key(role):
              # XXX This should be a list
              # If a given role exists as a column in the catalog,
              # then it is considered as single valued and indexed
              # through the catalog.
              if not user_is_superuser:
527
                # XXX This should be a list
528 529 530 531 532 533 534
                # which also includes all user groups
                column_id = role_dict[role]
                new_role_column_dict[column_id] = user_str
            new_allowedRolesAndUsers.append('%s:%s' % (user_or_group, role))
        if local_role_column_dict == {}:
          allowedRolesAndUsers = new_allowedRolesAndUsers
          role_column_dict = new_role_column_dict
535 536

      return allowedRolesAndUsers, role_column_dict, local_role_column_dict
537

538
    security.declarePublic('getSecurityUidDictAndRoleColumnDict')
539
    def getSecurityUidDictAndRoleColumnDict(self, sql_catalog_id=None, local_roles=None):
540
      """
541 542
        Return a dict of local_roles_group_id -> security Uids and a
        dictionnary containing available role columns.
543 544 545 546

        XXX: This method always uses default catalog. This should not break a
        site as long as security uids are considered consistent among all
        catalogs.
547
      """
548
      allowedRolesAndUsers, role_column_dict, local_role_column_dict = \
549 550 551 552
          self.getAllowedRolesAndUsers(
            sql_catalog_id=sql_catalog_id,
            local_roles=local_roles,
          )
Aurel's avatar
Aurel committed
553
      catalog = self.getSQLCatalog(sql_catalog_id)
554
      method = getattr(catalog, catalog.sql_search_security, None)
555
      if allowedRolesAndUsers:
556
        allowedRolesAndUsers.sort()
557
        cache_key = tuple(allowedRolesAndUsers)
558
        tv = getTransactionalVariable()
559
        try:
560
          security_uid_cache = tv['getSecurityUidDictAndRoleColumnDict']
561
        except KeyError:
562
          security_uid_cache = tv['getSecurityUidDictAndRoleColumnDict'] = {}
563
        try:
564
          security_uid_dict = security_uid_cache[cache_key]
565
        except KeyError:
566 567 568 569
          if method is None:
            warnings.warn("The usage of allowedRolesAndUsers is "\
                          "deprecated. Please update your catalog "\
                          "business template.", DeprecationWarning)
570
            security_uid_dict = {None: [x.security_uid for x in \
571 572 573
              self.unrestrictedSearchResults(
                allowedRolesAndUsers=allowedRolesAndUsers,
                select_expression="security_uid",
574
                group_by_expression="security_uid")] }
575 576
          else:
            # XXX: What with this string transformation ?! Souldn't it be done in
577
            # dtml instead ? ... yes, but how to be bw compatible ?
578
            allowedRolesAndUsers = [sqlquote(role) for role in allowedRolesAndUsers]
579

580
            security_uid_dict = defaultdict(list)
581
            for brain in method(security_roles_list=allowedRolesAndUsers):
582 583
              security_uid_dict[getattr(brain, 'local_roles_group_id', '')
                ].append(brain.uid)
584 585

          security_uid_cache[cache_key] = security_uid_dict
586
      else:
587 588
        security_uid_dict = []
      return security_uid_dict, role_column_dict, local_role_column_dict
589

Vincent Pelletier's avatar
Vincent Pelletier committed
590
    security.declarePublic('getSecurityQuery')
591
    def getSecurityQuery(self, query=None, sql_catalog_id=None, local_roles=None, **kw):
592
      """
593 594 595
        Build a query based on allowed roles or on a list of security_uid
        values. The query takes into account the fact that some roles are
        catalogued with columns.
596
      """
597 598 599 600 601 602
      user = _getAuthenticatedUser(self)
      user_str = str(user)
      user_is_superuser = (user == system_user) or (user_str == SUPER_USER)
      if user_is_superuser:
        # We need no security check for super user.
        return query
603
      original_query = query
604
      security_uid_dict, role_column_dict, local_role_column_dict = \
605 606 607 608
          self.getSecurityUidDictAndRoleColumnDict(
            sql_catalog_id=sql_catalog_id,
            local_roles=local_roles,
          )
609 610 611

      role_query = None
      security_uid_query = None
612 613 614 615 616
      if role_column_dict:
        query_list = []
        for key, value in role_column_dict.items():
          new_query = Query(**{key : value})
          query_list.append(new_query)
617
        operator_kw = {'operator': 'OR'}
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
        role_query = ComplexQuery(*query_list, **operator_kw)
      if security_uid_dict:
        catalog_security_uid_groups_columns_dict = \
            self.getSQLCatalog().getSQLCatalogSecurityUidGroupsColumnsDict()

        query_list = []
        for local_roles_group_id, security_uid_list in\
                 security_uid_dict.iteritems():
          assert security_uid_list
          query_list.append(Query(**{
            catalog_security_uid_groups_columns_dict[local_roles_group_id]:
                  security_uid_list,
            'operator': 'IN'}))

        security_uid_query = ComplexQuery(*query_list, operator='OR')

      if role_query:
        if security_uid_query:
          # merge
          query = ComplexQuery(security_uid_query, role_query, operator='OR')
        else:
          query = role_query
      elif security_uid_query:
        query = security_uid_query

643
      else:
Aurel's avatar
Aurel committed
644
        # XXX A false query has to be generated.
645 646 647 648 649 650
        # As it is not possible to use SQLKey for now, pass impossible value
        # on uid (which will be detected as False by MySQL, as it is not in the
        # column range)
        # Do not pass security_uid_list as empty in order to prevent useless
        # overhead
        query = Query(uid=-1)
651 652 653 654 655 656 657 658 659 660

      if local_role_column_dict:
        query_list = []
        for key, value in local_role_column_dict.items():
          new_query = Query(**{key : value})
          query_list.append(new_query)
        operator_kw = {'operator': 'AND'}
        local_role_query = ComplexQuery(*query_list, **operator_kw)
        query = ComplexQuery(query, local_role_query, operator='AND')

661 662 663
      if original_query is not None:
        query = ComplexQuery(query, original_query, operator='AND')
      return query
664

Jean-Paul Smets's avatar
Jean-Paul Smets committed
665
    # searchResults has inherited security assertions.
666
    def searchResults(self, query=None, sql_catalog_id=None, local_roles=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
667
        """
668 669
        Calls ZCatalog.searchResults with extra arguments that
        limit the results to what the user is allowed to see.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
670
        """
671 672 673 674 675
        #if not _checkPermission(
        #    Permissions.AccessInactivePortalContent, self):
        #    now = DateTime()
        #    kw[ 'effective' ] = { 'query' : now, 'range' : 'max' }
        #    kw[ 'expires'   ] = { 'query' : now, 'range' : 'min' }
Jean-Paul Smets's avatar
Jean-Paul Smets committed
676

677 678 679 680 681 682
        catalog_id = self.getPreferredSQLCatalogId(sql_catalog_id)
        query = self.getSecurityQuery(
          query=query,
          sql_catalog_id=catalog_id,
          local_roles=local_roles,
        )
683 684
        if query is not None:
          kw['query'] = query
685
        kw.setdefault('limit', self.default_result_limit)
Aurel's avatar
Aurel committed
686 687 688
        # get catalog from preference
        #LOG("searchResult", INFO, catalog_id)
        #         LOG("searchResult", INFO, ZCatalog.searchResults(self, query=query, sql_catalog_id=catalog_id, src__=1, **kw))
689
        return ZCatalog.searchResults(self, sql_catalog_id=catalog_id, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
690 691 692

    __call__ = searchResults

693
    security.declarePrivate('unrestrictedSearchResults')
694
    def unrestrictedSearchResults(self, **kw):
695 696
        """Calls ZSQLCatalog.searchResults directly without restrictions.
        """
697
        kw.setdefault('limit', self.default_result_limit)
698
        return ZCatalog.searchResults(self, **kw)
699

700 701
    # We use a string for permissions here due to circular reference in import
    # from ERP5Type.Permissions
702
    security.declareProtected('Search ZCatalog', 'getResultValue')
703
    def getResultValue(self, **kw):
704 705 706 707
        """
        A method to factor common code used to search a single
        object in the database.
        """
708
        kw.setdefault('limit', 1)
709
        result = self.searchResults(**kw)
710 711 712 713
        try:
          return result[0].getObject()
        except IndexError:
          return None
714 715

    security.declarePrivate('unrestrictedGetResultValue')
716
    def unrestrictedGetResultValue(self, **kw):
717 718 719 720 721
        """
        A method to factor common code used to search a single
        object in the database. Same as getResultValue but without
        taking into account security.
        """
722
        kw.setdefault('limit', 1)
723
        result = self.unrestrictedSearchResults(**kw)
724 725 726 727 728
        try:
          return result[0].getObject()
        except IndexError:
          return None

729
    def countResults(self, query=None, sql_catalog_id=None, local_roles=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
730 731 732 733
        """
            Calls ZCatalog.countResults with extra arguments that
            limit the results to what the user is allowed to see.
        """
734
        # XXX This needs to be set again
735
        #if not _checkPermission(
Vincent Pelletier's avatar
Vincent Pelletier committed
736 737
        #    Permissions.AccessInactivePortalContent, self):
        #    base = aq_base(self)
738 739 740
        #    now = DateTime()
        #    #kw[ 'effective' ] = { 'query' : now, 'range' : 'max' }
        #    #kw[ 'expires'   ] = { 'query' : now, 'range' : 'min' }
741 742 743 744 745 746
        catalog_id = self.getPreferredSQLCatalogId(sql_catalog_id)
        query = self.getSecurityQuery(
          query=query,
          sql_catalog_id=catalog_id,
          local_roles=local_roles,
        )
747 748
        if query is not None:
          kw['query'] = query
749
        kw.setdefault('limit', self.default_count_limit)
Aurel's avatar
Aurel committed
750
        # get catalog from preference
751
        return ZCatalog.countResults(self, sql_catalog_id=catalog_id, **kw)
Aurel's avatar
Aurel committed
752

753 754 755 756 757
    security.declarePrivate('unrestrictedCountResults')
    def unrestrictedCountResults(self, REQUEST=None, **kw):
        """Calls ZSQLCatalog.countResults directly without restrictions.
        """
        return ZCatalog.countResults(self, REQUEST, **kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
758

759 760 761 762 763 764 765 766 767 768
    def wrapObject(self, object, sql_catalog_id=None, **kw):
        """
          Return a wrapped object for reindexing.
        """
        catalog = self.getSQLCatalog(sql_catalog_id)
        if catalog is None:
          # Nothing to do.
          LOG('wrapObject', 0, 'Warning: catalog is not available')
          return (None, None)

769 770 771
        document_object = aq_inner(object)
        w = IndexableObjectWrapper(document_object)

772
        wf = getToolByName(self, 'portal_workflow')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
773
        if wf is not None:
774
          w.__dict__.update(wf.getCatalogVariablesFor(object))
775

776 777 778
        # Find the parent definition for security
        is_acquired = 0
        while getattr(document_object, 'isRADContent', 0):
Aurel's avatar
Aurel committed
779
          # This condition tells which object should acquire
780 781
          # from their parent.
          # XXX Hardcode _View_Permission for a performance point of view
782 783
          if getattr(aq_base(document_object), '_View_Permission', ACQUIRE_PERMISSION_VALUE) == ACQUIRE_PERMISSION_VALUE\
             and document_object._getAcquireLocalRoles():
784
            document_object = document_object.aq_parent
785 786 787 788
            is_acquired = 1
          else:
            break
        if is_acquired:
789
          document_w = IndexableObjectWrapper(document_object)
790 791 792
        else:
          document_w = w

793 794 795 796
        (security_uid_dict, optimised_roles_and_users) = \
              catalog.getSecurityUidDict(document_w)


797
        w.optimised_roles_and_users = optimised_roles_and_users
798 799 800 801 802 803 804 805 806 807 808

        catalog_security_uid_groups_columns_dict = \
            catalog.getSQLCatalogSecurityUidGroupsColumnsDict()
        default_security_uid_column = catalog_security_uid_groups_columns_dict['']
        for local_roles_group_id, security_uid in security_uid_dict.items():
          catalog_column = catalog_security_uid_groups_columns_dict.get(
                local_roles_group_id, default_security_uid_column)
          setattr(w, catalog_column, security_uid)

        # XXX we should build vars begore building the wrapper

809 810
        predicate_property_dict = catalog.getPredicatePropertyDict(object)
        if predicate_property_dict is not None:
811
          w.predicate_property_dict = predicate_property_dict
812
        else:
Aurel's avatar
Aurel committed
813
          w.predicate_property_dict = {}
814

815 816 817
        (subject_set_uid, optimised_subject_list) = catalog.getSubjectSetUid(document_w)
        w.optimised_subject_list = optimised_subject_list
        w.subject_set_uid = subject_set_uid
818 819

        return ImplicitAcquisitionWrapper(w, aq_parent(document_object))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
820 821

    security.declarePrivate('reindexObject')
822
    def reindexObject(self, object, idxs=None, sql_catalog_id=None,**kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
823 824 825 826
        '''Update catalog after object data has changed.
        The optional idxs argument is a list of specific indexes
        to update (all of them by default).
        '''
827
        if idxs is None: idxs = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
828
        url = self.__url(object)
829
        self.catalog_object(object, url, idxs=idxs, sql_catalog_id=sql_catalog_id,**kw)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
830

831

832 833
    def catalogObjectList(self, object_list, *args, **kw):
        """Catalog a list of objects"""
834
        m = object_list[0]
835 836 837
        if isinstance(m, GroupedMessage):
          tmp_object_list = [x.object for x in object_list]
          super(CatalogTool, self).catalogObjectList(tmp_object_list, **m.kw)
838
          if tmp_object_list:
839 840
            exc_info = sys.exc_info()
          for x in object_list:
841 842
            if x.object in tmp_object_list:
              x.raised(exc_info)
843
            else:
844
              x.result = None
845 846 847
        else:
          super(CatalogTool, self).catalogObjectList(object_list, *args, **kw)

848 849 850
    security.declarePrivate('uncatalogObjectList')
    def uncatalogObjectList(self, message_list):
      """Uncatalog a list of objects"""
851
      # TODO: this is currently only a placeholder for further optimization
852 853
      try:
        for m in message_list:
854
          m.result = self.unindexObject(*m.args, **m.kw)
855
      except Exception:
856
        m.raised()
857

Jean-Paul Smets's avatar
Jean-Paul Smets committed
858
    security.declarePrivate('unindexObject')
859
    def unindexObject(self, object=None, path=None, uid=None,sql_catalog_id=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
860 861 862
        """
          Remove from catalog.
        """
863
        if path is None and uid is None:
864 865
          if object is None:
            raise TypeError, 'One of uid, path and object parameters must not be None'
866
          path = self.__url(object)
867 868
        if uid is None:
          raise TypeError, "unindexObject supports only uid now"
869
        self.uncatalog_object(path=path, uid=uid, sql_catalog_id=sql_catalog_id)
870

871 872 873 874 875 876 877 878 879
    security.declarePrivate('beforeUnindexObject')
    def beforeUnindexObject(self, object, path=None, uid=None,sql_catalog_id=None):
        """
          Remove from catalog.
        """
        if path is None and uid is None:
          path = self.__url(object)
        self.beforeUncatalogObject(path=path,uid=uid, sql_catalog_id=sql_catalog_id)

880 881 882
    security.declarePrivate('getUrl')
    def getUrl(self, object):
      return self.__url(object)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
883

Jean-Paul Smets's avatar
Jean-Paul Smets committed
884
    security.declarePrivate('moveObject')
885
    def moveObject(self, object, idxs=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
886 887 888 889 890 891
        """
          Reindex in catalog, taking into account
          peculiarities of ERP5Catalog / ZSQLCatalog

          Useless ??? XXX
        """
892
        if idxs is None: idxs = []
Jean-Paul Smets's avatar
Jean-Paul Smets committed
893 894
        url = self.__url(object)
        self.catalog_object(object, url, idxs=idxs, is_object_moved=1)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
895

896 897 898 899 900 901
    security.declarePublic('getPredicatePropertyDict')
    def getPredicatePropertyDict(self, object):
      """
      Construct a dictionnary with a list of properties
      to catalog into the table predicate
      """
902
      if not object.providesIPredicate():
903 904 905
        return None
      object = object.asPredicate()
      if object is None:
906 907 908 909 910 911 912 913 914 915 916
        return None
      property_dict = {}
      identity_criterion = getattr(object,'_identity_criterion',None)
      range_criterion = getattr(object,'_range_criterion',None)
      if identity_criterion is not None:
        for property, value in identity_criterion.items():
          if value is not None:
            property_dict[property] = value
      if range_criterion is not None:
        for property, (min, max) in range_criterion.items():
          if min is not None:
917
            property_dict['%s_range_min' % property] = min
918
          if max is not None:
919
            property_dict['%s_range_max' % property] = max
920
      property_dict['membership_criterion_category_list'] = object.getMembershipCriterionCategoryList()
921 922
      return property_dict

923
    security.declarePrivate('getDynamicRelatedKeyList')
924
    def getDynamicRelatedKeyList(self, key_list, sql_catalog_id=None):
925
      """
926
      Return the list of dynamic related keys.
927 928
      This method will try to automatically generate new related key
      by looking at the category tree.
929 930

      For exemple it will generate:
931 932 933
      destination_title | category,catalog/title/z_related_destination
      default_destination_title | category,catalog/title/z_related_destination
      strict_destination_title | category,catalog/title/z_related_strict_destination
934 935 936

      strict_ related keys only returns documents which are strictly member of
      the category.
937 938
      """
      related_key_list = []
939 940 941 942
      base_cat_id_set = set(
        self.getPortalObject().portal_categories.getBaseCategoryList()
      )
      base_cat_id_set.discard('parent')
943
      default_string = 'default_'
944
      strict_string = 'strict_'
945
      related_string = 'related_'
946
      column_map = self.getSQLCatalog(sql_catalog_id).getColumnMap()
947
      for key in key_list:
948
        prefix = ''
949
        strict = 0
950 951 952
        if key.startswith(default_string):
          key = key[len(default_string):]
          prefix = default_string
953 954 955 956
        if key.startswith(strict_string):
          strict = 1
          key = key[len(strict_string):]
          prefix = prefix + strict_string
957 958 959
        split_key = key.split('_')
        for i in xrange(len(split_key) - 1, 0, -1):
          expected_base_cat_id = '_'.join(split_key[0:i])
960
          if expected_base_cat_id in base_cat_id_set:
961
            # We have found a base_category
962
            end_key = '_'.join(split_key[i:])
963 964 965
            related = end_key.startswith(related_string)
            if related:
              end_key = end_key[len(related_string):]
966
            # XXX: joining with non-catalog tables is not trivial and requires
967 968 969
            # ZSQLCatalog's ColumnMapper cooperation, so only allow catalog
            # columns.
            if 'catalog' in column_map.get(end_key, ()):
970 971 972 973 974 975 976 977 978 979 980 981 982
              is_uid = end_key == 'uid'
              if is_uid:
                end_key = 'uid' if related else 'category_uid'
              related_key_list.append(
                prefix + key + ' | category' +
                ('' if is_uid else ',catalog') +
                '/' +
                end_key +
                '/z_related_' +
                ('strict_' if strict else '') +
                expected_base_cat_id +
                ('_related' if related else '')
              )
983 984 985 986 987 988

      return related_key_list

    def _aq_dynamic(self, name):
      """
      Automatic related key generation.
989
      Will generate z_related_[base_category_id] if possible
990
      """
991 992 993
      result = None
      if name.startswith(DYNAMIC_METHOD_NAME) and \
          not name.endswith(ZOPE_SECURITY_SUFFIX):
994
        kw = {}
995
        if name.endswith(RELATED_DYNAMIC_METHOD_NAME):
996 997
          end_offset = RELATED_DYNAMIC_METHOD_NAME_LEN
          kw['related'] = 1
998
        else:
999 1000 1001 1002 1003 1004 1005
          end_offset = None
        if name.startswith(STRICT_DYNAMIC_METHOD_NAME):
          start_offset = STRICT_DYNAMIC_METHOD_NAME_LEN
          kw['strict_membership'] = 1
        else:
          start_offset = DYNAMIC_METHOD_NAME_LEN
        method = RelatedBaseCategory(name[start_offset:end_offset], **kw)
1006
        setattr(self.__class__, name, method)
1007 1008 1009 1010 1011 1012
        # This getattr has 2 purposes:
        # - wrap in acquisition context
        #   This alone should be explicitly done rather than through getattr.
        # - wrap (if needed) class attribute on the instance
        #   (for the sake of not relying on current implementation details
        #   "too much")
1013 1014
        result = getattr(self, name)
      return result
1015

1016
    def _searchAndActivate(self, method_id, method_args=(), method_kw={},
1017
                           activate_kw={}, min_uid=None, group_kw={}, **kw):
1018 1019
      """Search the catalog and run a script by activity on all found objects

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
      In order to not generate too many activities, this method limits the
      number of rows to fetch from the catalog, and if the catalog would return
      more results, it resumes by calling itself by activity.

      'activate_kw' is for common activate parameters between all generated
      activities and is usually used for priority and dependencies.

      Common usage is to call this method without 'select_method_id'.
      In this case, found objects are processed via a CMFActivity grouping,
      and this can be configured via 'group_kw', for additional parameters to
      pass to CMFActivity (in particular: 'activity' and 'group_method_*').
      A generic grouping method is used if none is given.
      group_method_cost default to 30 objects per packet.

1034 1035 1036 1037
      'select_method_id', if provided, will be called with partial catalog
      results and returned value will be provided to the callable identified by
      'method_id' (which will no longer be invoked in the context of a given
      document returned by catalog) as first positional argument.
1038
      Use 'packet_size' parameter to limit the size of each group (default: 30).
1039

1040 1041 1042 1043 1044
      'activity_count' parameter is deprecated.
      Its value should be hardcoded because CMFActivity can now handle many
      activities efficiently and any tweak should benefit to everyone.
      However, there are still rare cases where one want to limit the number
      of processing nodes, to minimize latency of high-priority activities.
1045
      """
1046
      catalog_kw = kw.copy()
1047
      select_method_id = catalog_kw.pop('select_method_id', None)
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
      if select_method_id:
        packet_size = catalog_kw.pop('packet_size', 30)
        limit = packet_size * catalog_kw.pop('activity_count', 100)
      elif 'packet_size' in catalog_kw: # BBB
        assert not group_kw, (kw, group_kw)
        packet_size = catalog_kw.pop('packet_size')
        group_method_cost = 1. / packet_size
        limit = packet_size * catalog_kw.pop('activity_count', 100)
      else:
        group_method_cost = group_kw.get('group_method_cost', .034) # 30 objects
        limit = catalog_kw.pop('activity_count', None) or \
          100 * int(ceil(1 / group_method_cost))
1060
      if min_uid:
1061 1062
        catalog_kw['min_uid'] = SimpleQuery(uid=min_uid,
                                            comparison_operator='>')
1063 1064 1065 1066 1067 1068 1069 1070
      if catalog_kw.pop('restricted', False):
        search = self
      else:
        search = self.unrestrictedSearchResults
      r = search(sort_on=(('uid','ascending'),), limit=limit, **catalog_kw)
      result_count = len(r)
      if result_count:
        if result_count == limit:
1071 1072
          next_kw = activate_kw.copy()
          next_kw['priority'] = 1 + next_kw.get('priority', 1)
1073
          self.activate(activity='SQLQueue', **next_kw) \
1074
              ._searchAndActivate(method_id,method_args, method_kw,
1075 1076
                                  activate_kw, r[-1].getUid(),
                                  group_kw=group_kw, **kw)
1077 1078 1079 1080
        if select_method_id:
          portal_activities = self.getPortalObject().portal_activities
          active_portal_activities = portal_activities.activate(
            activity='SQLQueue', **activate_kw)
1081 1082
          r = getattr(portal_activities, select_method_id)(r)
          activate = getattr(active_portal_activities, method_id)
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
          for i in xrange(0, result_count, packet_size):
            activate(r[i:i+packet_size], *method_args, **method_kw)
        else:
          kw = activate_kw.copy()
          kw['activity'] = 'SQLQueue'
          if group_method_cost < 1:
            kw['group_method_cost'] = group_method_cost
            kw['group_method_id'] = None
            kw.update(group_kw)
          for r in r:
            getattr(r.activate(**kw), method_id)(*method_args, **method_kw)
1094 1095 1096 1097 1098 1099

    security.declarePublic('searchAndActivate')
    def searchAndActivate(self, *args, **kw):
      """Restricted version of _searchAndActivate"""
      return self._searchAndActivate(restricted=True, *args, **kw)

1100 1101
    security.declareProtected(Permissions.ManagePortal, 'upgradeSchema')
    def upgradeSchema(self, sql_catalog_id=None, src__=0):
1102
      """Upgrade all catalog tables, with ALTER or CREATE queries"""
1103 1104 1105
      catalog = self.getSQLCatalog(sql_catalog_id)
      connection_id = catalog.z_create_catalog.connection_id
      src = []
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
      db = self.getPortalObject()[connection_id]()
      with db.lock():
        for clear_method in catalog.sql_clear_catalog:
          r = catalog[clear_method]._upgradeSchema(
            connection_id, create_if_not_exists=1, src__=1)
          if r:
            src.append(r)
        if not src__:
          for r in src:
            db.query(r)
1116 1117 1118
      return src


1119
InitializeClass(CatalogTool)