Telephone.py 15.1 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.
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 29 30 31 32 33
#
# 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.
#
##############################################################################

from AccessControl import ClassSecurityInfo

from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5Type.Base import Base

34
from Products.ERP5.Document.Coordinate import Coordinate
Jean-Paul Smets's avatar
Jean-Paul Smets committed
35 36 37 38

import re

class Telephone(Coordinate, Base):
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
  """
    A telephone is a coordinate which stores a telephone number
    The telephone class may be used by multiple content types (ex. Fax,
    Mobile Phone, Fax, Remote Access, etc.).

    A telephone is a terminating leaf
    in the OFS. It can not contain anything.

    Telephone inherits from Base and
    from the mix-in Coordinate

    A list of I18N telephone codes can be found here::
      http://kropla.com/dialcode.htm
  """

  meta_type = 'ERP5 Telephone'
  portal_type = 'Telephone'
  add_permission = Permissions.AddPortalContent
  isPortalContent = 1
  isRADContent = 1

  # Declarative security
  security = ClassSecurityInfo()
  security.declareObjectProtected(Permissions.AccessContentsInformation)

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.SimpleItem
                    , PropertySheet.Telephone
                    )
  # This is a list of regex.
  # Each regex into the list handle a single (or should handle) input.
  # The list is a priority list,
  # be carefull to add a new regex.
  regex_list = [
    # Country, Area, Number, Extension*
75
    # +11(0)1-11111111/111      or +11(0)1-11111111/      or +11(0)1-11111111
Rafael Monnerat's avatar
Rafael Monnerat committed
76
    # +11(0)1-1111-1111/111      or +11(0)1-1111-1111/      or +11(0)1-1111-1111
77 78
    # + 11 (0)1-11 11 01 01/111 or + 11 (0)1-11 11 01 01/ or + 11 (0)1-11 11 01 01
    # +11 (0)11 1011 1100/111   or +11 (0)11 1011 1100/   or +11 (0)11 1011 1100
Rafael Monnerat's avatar
Rafael Monnerat committed
79
    "\+(?P<country>[\d\ ]*)\(0\)(?P<area>\d+)(\-|\ )(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
80 81

    # Country, Area, Number, Extension*
82
    # +11-1-11111111/111 or +11-1-11111111/ or +11-1-11111111
83 84 85
    "\+(?P<country>\d+)-(?P<area>\d+)-(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing area
86
    # +11(0)-11111111/111" or +11(0)-11111111/ or +11(0)-11111111
87 88 89
    "\+(?P<country>\d+)\(0\)\-(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

    # Country, Area, Number, Extension*
90
    # +11(1)11111111/111 or +11(1)11111111/ or +11(1)11111111
91 92 93
    "\+(?P<country>\d+)\((?P<area>\d+)\)(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

    # Country, Area, Number, Extension*
94
    # +111-1-11111111/111 or +111-1-11111111/ or +111-1-11111111
95 96 97
    "\+(?P<country>\d+)-(?P<area>\d+)-(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing country
98
    # +(0)1-11111111/111" or +(0)1-11111111/ or +(0)1-11111111
99 100 101
    "\+\(0\)(?P<area>\d+)\-(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

    # Missing Country and Area 
102
    # +(0)-11111111/111" or +(0)-11111111/ or +(0)-11111111
103 104 105 106
    "\+\(0\)\-(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

    # Area, Number Extension*
    # Area between parenthesis.
107
    # (11)11111111/111 or (11)11111111/ or (11)11111111
108 109 110
    "\((?P<area>\d+)\)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing country
111
    # +(1)11111111/111" or +(1)11111111/ or +(1)11111111
112 113 114 115
    "\+\((?P<area>\d+)\)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country, Area, Number and Extension*
    # Country space area space number slash extension or not
116 117 118
    # +11 1 011111111/1    or +11 1 011111111/  or +11 1 011111111
    # + 111 1 1101 101/111 or + 111 1 1101 101/ or + 111 1 1101 101/111
    "\+(?P<space>[\ ]*)(?P<country>\d+)\ (?P<area>\d+)\ (?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",
119 120 121

    # This regex is to handle two inputs very similar
    # but with different behavior
122 123
    # 111 11 11/111 or 111 11 11 or 111 11 11 
    # will result in {'area':'', 'number':'111 11 11', 'ext':'111 or empty'}
124
    #
125 126
    # 111-11 11/111 or 111-11 11 or 111-11 11 
    # will result in {'area':'111', 'number':'11 11', 'ext':'111 or empty'} 
127 128 129 130 131
    "^(?:0)?((?P<area>\d+)-)?(?P<number>[\d\-\ ]*)(?:\/)?(?P<ext>\d+|)$",

    # Area, Number, Extension*
    # It is a common input in France 
    # and in Japan but with different behavior.
132 133 134 135 136
    # 011-111-1111/111 or 011-111-1111/ or 011-111-1111
    # will result in {'area':'11', 'number':'111-1111', \
    #                  'ext':'111 or empty'} <= France
    # will result in {'area':'011', 'number':'111-1111', 
    #                  'ext':'111 or empty'} <= Japan
137 138 139 140 141 142 143
    # so we have here two regex:
    # To France: "^0(?P<area>\d+)-(?P<number>[\d\-\ ]*)(?:\/)?(?P<ext>\d+|)$",
    # To Japan: "^(?P<area>\d+)-(?P<number>[\d\-\ ]*)(?:\/)?(?P<ext>\d+|)$",
    "^0(?P<area>\d+)-(?P<number>[\d\-\ ]*)(?:\/)?(?P<ext>\d+|)$",

    # Area, Number, Extension*
    # It is a common input in France and in Japan but with different behavior.
144 145 146 147 148
    # 011(111)1111/111 or 011(111)1111/ or 011(111)1111
    # will result in {'area':'11', 'number':'111)1111', 
    #                  'ext':'111 or empty'} <= France
    # will result in {'area':'011', 'number':'111)1111', 
    #                  'ext':'111 or empty'} <= Japan
149 150 151 152 153 154 155 156
    # so we have here two regex:
    #To France: 
    # "^0(?P<area>\d+)\((?P<number>[\d\)\(\ \-]*)(?:\/)?(?P<ext>\d+|)$",
    #To Japan: 
    # "^(?P<area>\d+)\((?P<number>[\d\)\(\ \-]*)(?:\/)?(?P<ext>\d+|)$",
    "^0(?P<area>\d+)\((?P<number>[\d\)\(\ \-]*)(?:\/)?(?P<ext>\d+|)$",

    # Missing area
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    # +11()11111111/111" or +11()11111111/ or +11()11111111
    "\+(?P<country>\d+)\(\)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country, area, number, extension*
    # Space between country and (0).
    # Space between (0) and area.
    # Space between area and number.
    # +111 (0) 1 111 11011/111 or +111 (0) 1 111 11011/ or +111 (0) 1 111 11011
    "\+(?P<country>\d+)\ \(0\)\ (?P<area>\d+)\ (?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country and number
    # (0) between country and number
    # +111 (0) 111111101-01/111 or +111 (0) 111111101-01/ or +111 (0) 111111101-01
    "\+(?P<country>\d+)\ \(0\)\ (?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country, area, number and extension*
    # +11 (11) 1111 1111/111 or +11 (11) 1111 1111/ or +11 (11) 1111 1111
    # +11 (11)-10111111/111  or +11 (11)-10111111/  or +11 (11)-10111111
    # +11(11)-10111111/111   or +11(11)-10111111/   or +11(11)-10111111
    # 1 (111) 1101-101/111   or 1 (111) 1101-101/   or 1 (111) 1101-101/
    "(\+|)(?P<country>\d+)\ \((?P<area>\d+)\)(\ |\-|)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # +110 1111111/111 or +110 1111111/ or +110 1111111
    "\+(?P<country>\d+)\ (?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing country
    # 111/111-1111/111 or 111/111-1111/ or 111/111-1111
    "(?P<area>\d+)\/(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
    
    # Country, area, number, extension*
    # Hyphen between country and area.
    # +11-1 11 11 01 11/111 or +11-1 11 11 01 11/ or +11-1 11 11 01 11
    "\+(?P<country>\d+)\-(?P<area>\d+)\ (?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing area
    # +111-1101110/111 or +111-1101110/ or +111-1101110
    "\+(?P<country>\d+)\-(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Missing country
    # Dot between area number and telephone number.
    # 111.111.1111/111 or 111.111.1111/ or 111.111.1111
    "(?P<area>\d+)\.(?P<number>[\d\ \-\.]*)(?:\/)?(?P<ext>\d+|)",

    # Country, area, number and extensioin*
    # (111 11) 111111/111     or (111 11) 111111/    or (111 11) 111111
    # (111 11) 111-11-11/111  or (111 11) 111-11-11/ or (111 11) 111-11-11
    # (111 11)101011/111      or (111 11)101011/     or (111 11)101011
    # +(111 11) 100-11-11/111 or +(111 11) 100-11-11 or +(111 11) 100-11-11
    "(\+|)\((?P<country>\d+)\ (?P<area>\d+)\)(\ |)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country and number
    # (+111)101111111/111  or (+111)101111111/  or (+111)101111111
    # (+111) 101111111/111 or (+111) 101111111/ or (+111) 101111111
    "\(\+(?P<country>\d+)\)(\ |)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country, area, number and extension*
    # (+11-111) 1111111/111 or (+11-111) 1111111/ or (+11-111) 1111111
    # (11-11) 111-1111/111  or (11-11) 111-1111/  or (11-11) 111-1111
    # (11-1) 1.111.111/111  or (11-1) 1.111.111/  or (11-1) 1.111.111
    "\((\+|)(?P<country>\d+)\-(?P<area>\d+)\)(\ |\-|)(?P<number>[\d\ \-\.]*)(?:\/)?(?P<ext>\d+|)",

    # Country, area, number and extersion*
    # + 111-11-1110111/111 or + 111-11-1110111/ or + 111-11-1110111
    # +111-11-1110111/111  or +111-11-1110111/  or +111-11-1110111
    # +111/1/1111 1100/111 or +111/1/1111 1100/ or +111/1/1111 1100
    "\+(?P<spaces>[\ ]*)(?P<country>\d+)(\-|\/)(?P<area>\d+)(\-|\/)(?P<number>[\d\ \-\.]*)(?:\/)?(?P<ext>\d+|)",

    # + (111) 111-111/111 or + (111) 111-111/  or + (111) 111-111
    "\+(?P<spaces>[\ ]*)\((?P<country>\d+)\)\ (?P<number>[\d\ \-\.]*)(?:\/)?(?P<ext>\d+|)"
226 227 228 229 230 231 232 233 234 235 236 237
  ]
  
  security.declareProtected(Permissions.ModifyPortalContent, 'fromText')
  def fromText(self, coordinate_text):
    """ See ICoordinate.fromText """
    method = self._getTypeBasedMethod('fromText')
    if method is not None:
      return method(text=coordinate_text)
    
    if coordinate_text is None:
      coordinate_text = ''

238 239 240
    # Removing the spaces of the begin and end.
    coordinate_text = coordinate_text.strip()
    
241 242 243 244 245 246 247 248 249 250 251 252
    # This regexp get the coordinate text 
    # and extract number and letters
    input_regex_without_markup = '[0-9A-Za-z]'
    input_without_markup = ''.join(re.findall(input_regex_without_markup,\
                                              coordinate_text))
    # Test if coordinate_text has or not markups.
    if len(coordinate_text) > len(input_without_markup):
      number_match = None
      for regex in self._getRegexList():
        possible_number_match = re.match(regex, coordinate_text)
        if possible_number_match not in [None]:
          number_match = possible_number_match
253
          number_dict = number_match.groupdict()
254 255 256 257 258
          break
      if number_match == None:
        from zLOG import LOG, WARNING
        msg = "Doesn't exist a regex to handle this telephone: ", \
                                                               coordinate_text
259
        LOG('Telephone.fromText', WARNING, msg) 
260
        number_dict = {'number' : input_without_markup}
261 262 263 264 265 266 267 268 269 270 271 272 273 274
    else:
      number_dict = {'number' : coordinate_text}

    country = number_dict.get('country','')
    area = number_dict.get('area','')
    number = number_dict.get('number','')
    extension = number_dict.get('ext','')

    if ((country in ['', None]) and \
        (area in ['', None]) and \
        (number in ['', None]) and \
        (extension in ['', None])):
      country = area = number = extension = ''
    else:
275
      # Trying to get the country and area from dict, 
276 277 278 279 280 281 282 283 284 285 286 287 288
      # but if it fails must be get from preference
      preference_tool = self.portal_preferences
      if country in ['', None]:
        country = preference_tool.getPreferredTelephoneDefaultCountryNumber('')
      if area in ['', None]:
        area = preference_tool.getPreferredTelephoneDefaultAreaNumber('')

      country =  country.strip()
      area = area.strip()
      number = number.strip()
      extension = extension.strip()

      # Formating the number.
289 290 291
      # Removing any ")", "(", "-", "." and " "
      for token in [")", "(", "-" ,"." ," "]:
        country = country.replace(token, '')
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
        number = number.replace(token, '')

    self.edit(telephone_country = country,
              telephone_area = area,
              telephone_number = number, 
              telephone_extension = extension)
   
  security.declareProtected(Permissions.ModifyPortalContent, '_setText')
  _setText = fromText

  security.declareProtected(Permissions.View, 'asText')
  def asText(self):
    """
      Returns the telephone number in standard format
    """
    script = self._getTypeBasedMethod('asText')
    if script is not None:
      return script()
    
    country = self.getTelephoneCountry('')
    area = self.getTelephoneArea('')
    number = self.getTelephoneNumber('')
    extension = self.getTelephoneExtension('')
   
    # If country, area, number, extension are blank 
    # the method should to return blank.
    if ((country == '') and \
        (area == '') and \
        (number == '') and \
        (extension == '')): 
      return ''

    # Define the notation
    notation = self._getNotation()
    if notation not in [None, '']:
      notation = notation.replace('<country>',country)
      notation = notation.replace('<area>',area)
      notation = notation.replace('<number>',number)
      notation = notation.replace('<ext>',extension)

    if extension == '':
      notation = notation.replace('/','')

    return notation

  security.declareProtected(Permissions.AccessContentsInformation,
                            'asURL')
  def asURL(self):
    """Returns a text representation of the Url if defined
    or None else.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
342
    """
343 344 345 346 347
    telephone_country = self.getTelephoneCountry()
    if telephone_country is not None:
      url_string = '+%s' % telephone_country
    else :
      url_string = '0'
Jean-Paul Smets's avatar
Jean-Paul Smets committed
348

349 350 351
    telephone_area = self.getTelephoneArea()
    if telephone_area is not None:
      url_string += telephone_area
Jean-Paul Smets's avatar
Jean-Paul Smets committed
352

353 354 355
    telephone_number = self.getTelephoneNumber()
    if telephone_number is not None:
      url_string += telephone_number
Jean-Paul Smets's avatar
Jean-Paul Smets committed
356

357 358 359 360 361 362 363 364 365
    if url_string == '0':
      return None
    return 'tel:%s' % (url_string.replace(' ',''))

  security.declareProtected(Permissions.View, 'getText')
  getText = asText

  security.declareProtected(Permissions.View, 'standardTextFormat')
  def standardTextFormat(self):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
366
    """
367 368 369
      Returns the standard text formats for telephone numbers
    """
    return ("+33(0)6-62 05 76 14",)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
370

371 372 373 374 375 376 377 378 379 380 381 382 383
  def _getNotation(self):
    """
      Returns the notation that will be used by asText method.
    """
    # The notation can be changed.
    # But needs to have <country>, <area>, <number> and <ext>
    return "+<country>(0)<area>-<number>/<ext>"

  def _getRegexList(self):
    """
      Returns the regex list that will be used by fromText method.
    """
    return self.regex_list