Telephone.py 16.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
#
# 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

31
from Products.ERP5Type import Permissions, PropertySheet
Jean-Paul Smets's avatar
Jean-Paul Smets committed
32 33
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
import re

class Telephone(Coordinate, Base):
38 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
  """
    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

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

  # Declarative properties
  property_sheets = ( PropertySheet.Base
                    , PropertySheet.SimpleItem
64
                    , PropertySheet.CategoryCore
65
                    , PropertySheet.SortIndex
66 67 68 69 70 71 72
                    , 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 = [
73
    # Country, Area, City, Number, Extension*
74
    "\+(?P<country>[\d ]+)(\(0\)|\ |\-)(?P<area>\d+)(\-|\ )(?P<city>\d+)(\-|\ )(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
75 76 77

    # Area, City, Number, Extension*
    "^(\(0\)|0)?(?P<area>\d+)(\-|\ |\/)(?P<city>\d+)(\-|\ )(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
78
    "^\+(\(0\)|0)+(?P<area>\d+)(\-|\ |\/)(?P<city>\d+)(\-|\ )(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
79
    # Country, Area, Number, Extension*
80
    # +11(0)1-11111111/111      or +11(0)1-11111111/      or +11(0)1-11111111
Rafael Monnerat's avatar
Rafael Monnerat committed
81
    # +11(0)1-1111-1111/111      or +11(0)1-1111-1111/      or +11(0)1-1111-1111
82 83
    # + 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
84
    "\+(?P<country>[\d\ ]*)\(0\)(?P<area>\d+)(\-|\ )(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",
85

86

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

    # Missing area
92
    # +11(0)-11111111/111" or +11(0)-11111111/ or +11(0)-11111111
93 94 95
    "\+(?P<country>\d+)\(0\)\-(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

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

    # Country, Area, Number, Extension*
100
    # +111-1-11111111/111 or +111-1-11111111/ or +111-1-11111111
101 102 103
    "\+(?P<country>\d+)-(?P<area>\d+)-(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

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

107
    # Missing Country and Area
108
    # +(0)-11111111/111" or +(0)-11111111/ or +(0)-11111111
109 110 111 112
    "\+\(0\)\-(?P<number>[\d\ ]*)(?:\/)?(?P<ext>\d+|)",

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

    # Missing country
117
    # +(1)11111111/111" or +(1)11111111/ or +(1)11111111
118 119 120 121
    "\+\((?P<area>\d+)\)(?P<number>[\d\ \-]*)(?:\/)?(?P<ext>\d+|)",

    # Country, Area, Number and Extension*
    # Country space area space number slash extension or not
122 123 124
    # +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+|)",
125 126 127

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

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

    # Missing area
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
    # +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+|)",
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
    # 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+|)",

224
    # Country, area, number and extension*
225 226 227 228 229 230 231
    # + 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+|)"
232
  ]
233

234 235 236 237 238 239
  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)
240

241 242 243
    if coordinate_text is None:
      coordinate_text = ''

244
    # Removing the spaces of the begin and end.
245
    coordinate_text = str(coordinate_text).strip()
246 247

    # This regexp get the coordinate text
248 249 250 251 252 253 254 255 256 257 258
    # 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
259
          number_dict = number_match.groupdict()
260
          break
Fabien Morin's avatar
Fabien Morin committed
261
      if number_match is None:
262 263 264
        from zLOG import LOG, WARNING
        msg = "Doesn't exist a regex to handle this telephone: ", \
                                                               coordinate_text
265
        LOG('Telephone.fromText', WARNING, msg)
266
        number_dict = {'number' : input_without_markup}
267 268 269 270 271
    else:
      number_dict = {'number' : coordinate_text}

    country = number_dict.get('country','')
    area = number_dict.get('area','')
272
    city = number_dict.get('city','')
273 274 275 276
    number = number_dict.get('number','')
    extension = number_dict.get('ext','')
    if ((country in ['', None]) and \
        (area in ['', None]) and \
277
        (city in ['', None]) and \
278 279
        (number in ['', None]) and \
        (extension in ['', None])):
280
      country = area = city = number = extension = ''
281
    else:
282
      # Trying to get the country and area from dict,
283 284
      # but if it fails must be get from preference
      preference_tool = self.portal_preferences
285
      import pdb; pdb.set_trace()
286 287 288 289
      if country in ['', None]:
        country = preference_tool.getPreferredTelephoneDefaultCountryNumber('')
      if area in ['', None]:
        area = preference_tool.getPreferredTelephoneDefaultAreaNumber('')
290 291
      if city in ['', None]:
        city = preference_tool.getPreferredTelephoneDefaultCityNumber('')
292 293 294

      country =  country.strip()
      area = area.strip()
295
      city = city.strip()
296 297 298 299
      number = number.strip()
      extension = extension.strip()

      # Formating the number.
300 301 302
      # Removing any ")", "(", "-", "." and " "
      for token in [")", "(", "-" ,"." ," "]:
        country = country.replace(token, '')
303 304 305 306
        number = number.replace(token, '')

    self.edit(telephone_country = country,
              telephone_area = area,
307
              telephone_city = city,
308
              telephone_number = number,
309
              telephone_extension = extension)
310

311 312 313 314 315 316 317 318 319 320 321
  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()
322

323 324
    country = self.getTelephoneCountry('')
    area = self.getTelephoneArea('')
325
    city = self.getTelephoneCity('')
326 327
    number = self.getTelephoneNumber('')
    extension = self.getTelephoneExtension('')
328 329

    # If country, area, number, extension are blank
330 331 332
    # the method should to return blank.
    if ((country == '') and \
        (area == '') and \
333
        (city == '') and \
334
        (number == '') and \
335
        (extension == '')):
336 337 338 339 340 341 342
      return ''

    # Define the notation
    notation = self._getNotation()
    if notation not in [None, '']:
      notation = notation.replace('<country>',country)
      notation = notation.replace('<area>',area)
343 344
      if city == "":
        notation = notation.replace('<city>-', '')
345
      else:
346
        notation = notation.replace('<city>',city)
347 348 349 350 351 352 353 354 355 356 357 358 359
      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
360
    """
361 362 363 364 365
    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
366

367 368 369
    telephone_area = self.getTelephoneArea()
    if telephone_area is not None:
      url_string += telephone_area
370

371 372 373
    telephone_city = self.getTelephoneCity()
    if telephone_city is not None:
      url_string += telephone_city
Jean-Paul Smets's avatar
Jean-Paul Smets committed
374

375 376 377
    telephone_number = self.getTelephoneNumber()
    if telephone_number is not None:
      url_string += telephone_number
Jean-Paul Smets's avatar
Jean-Paul Smets committed
378

379 380 381 382 383 384 385 386 387
    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
388
    """
389 390 391
      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
392

393 394 395 396 397
  def _getNotation(self):
    """
      Returns the notation that will be used by asText method.
    """
    # The notation can be changed.
398 399
    # But needs to have <country>, <area>, <city>, <number> and <ext>
    return "+<country>(0)<area>-<city>-<number>/<ext>"
400 401 402 403 404 405

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