"""
This script is used to convert a list of categories into an security
identifier (security ID). It is invoked by two classes in ERP5:

- ERP5Type.py to convert security definitions made of
  multiple categories into security ID strings

- ERP5GroupManager.py to convert an assignment definition
  into a single security ID string. It should be noted here
  that ERP5GroupManager.py also tries to invoke ERP5Type_asSecurityGroupIdList
  (DEPRECATED) in order associate a user to multiple security groups.
  In this case ERP5Type_asSecurityGroupId is not invoked.

The script takes the following parameters:

category_order - list of base_categories we want to use to generate the group id
kw             - keys should be base categories, values should be value
                 of corresponding relative urls (obtained by getBaseCategory())

Example call:
  context.ERP5TypeSecurity_asGroupId(category_order=('site', 'group', 'function'),
               site='france/lille', group='nexedi', function='accounting/accountant')

This will generate a string like 'LIL_NXD_ACT' where "LIL", "NXD" and "ACT" are
the codification of respecively "france/lille", "nexedi" and "accounting/accountant" categories

If the category points to a document portal type (ex. trade condition, project, etc.),
and if no codification property is defined for this type of object,
the security ID group is generated by considering the object reference or
the object ID.

ERP5Type_asSecurityGroupId can also return a list of users whenever a category points
to a Person instance. This is useful to implement user based local role assignments
instead of abstract security based local roles.
"""
portal = context.getPortalObject()
getCategoryValue = portal.portal_categories.getCategoryValue

# sort the category list lexicographically
# this prevents us to choose the exact order we want,
# but also prevents some human mistake to break everything by creating site_function instead of function_site
if category_order not in (None, ''):
  category_order = list(category_order)
  category_order.sort()
else:
  category_order = []

# Prepare a cartesian product
from Products.ERP5Type.Utils import cartesianProduct
list_of_list = []
user_list = []
for base_category in category_order:
  # It is acceptable for a category not to be defined
  try:
    category_list = kw[base_category]
  except KeyError:
    continue

  associative_list = []
  if isinstance(category_list, str):
    category_list = [category_list]
  for category in category_list:
    if category[-1] == '*':
      category = category[:-1]
      is_child_category = 1
    else:
      is_child_category = 0
    category_path = '%s/%s' % (base_category, category)
    category_object = getCategoryValue(category_path)
    if category_object is None:
      raise RuntimeError("Security definition error (category %r not found)" % (category_path,))
    portal_type = category_object.getPortalType()
    if portal_type == 'Person':
      # We define a person here
      user_name = category_object.Person_getUserId()
      if user_name is not None:
        user_list.append(user_name)
    else:
      category_code = (category_object.getProperty('codification') or
                        category_object.getProperty('reference') or
                        category_object.getId())
      if is_child_category:
        category_code += '*'
      associative_list.append(category_code)
  # Prevent making a cartesian product with an empty set
  if associative_list:
    list_of_list.append(associative_list)

# Return a list of users if any was defined
if user_list:
  return user_list

# Compute the cartesian product and return the codes
# return filter(lambda x: x, map(lambda x: '_'.join(x), cartesianProduct(list_of_list)))
return ['_'.join(x) for x in cartesianProduct(list_of_list) if x]
