From 87b9f75ee8d6a7bbe2a821c6b3032883305aedfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Sun, 20 Dec 2020 22:23:41 +0100
Subject: [PATCH] sms: remove all but passerelle provider (#39088)

---
 tests/admin_pages/test_workflow.py |   4 +-
 tests/backoffice_pages/test_all.py |   4 +-
 tests/test_workflows.py            |   2 +-
 tests/utilities.py                 |  21 ++-
 wcs/admin/settings.py              |  95 +------------
 wcs/backoffice/management.py       |   5 +-
 wcs/qommon/sms.py                  | 207 +----------------------------
 wcs/workflows.py                   |   7 +-
 8 files changed, 30 insertions(+), 315 deletions(-)

diff --git a/tests/admin_pages/test_workflow.py b/tests/admin_pages/test_workflow.py
index 5f53712d0..08e1e0769 100644
--- a/tests/admin_pages/test_workflow.py
+++ b/tests/admin_pages/test_workflow.py
@@ -486,7 +486,7 @@ def test_workflows_check_available_actions(pub):
     with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
         pub.site_options.write(fd)
 
-    pub.cfg['sms'] = {'mode': 'foobar'}
+    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
     pub.write_cfg()
     workflow.criticality_levels = [WorkflowCriticalityLevel(name='green')]
     workflow.store()
@@ -814,7 +814,7 @@ def test_workflows_edit_sms_action(pub):
     workflow.add_status(name='baz')
     workflow.store()
 
-    pub.cfg['sms'] = {'mode': 'foobar'}
+    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
     pub.write_cfg()
 
     app = login(get_app(pub))
diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index e97c232f9..ff805d083 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -3419,7 +3419,7 @@ def test_per_user_view_tracking_code(pub, emails, sms_mocking):
     user_view_resp = app.get('/backoffice/management/users/%s/' % user.id)
     assert 'Send tracking code' in user_view_resp.text
 
-    pub.cfg['sms'] = {'mode': 'none'}
+    pub.cfg['sms'] = {}
     pub.write_cfg()
     resp = user_view_resp.click('Send tracking code')
     assert not 'sms' in resp.form.fields
@@ -3431,7 +3431,7 @@ def test_per_user_view_tracking_code(pub, emails, sms_mocking):
     assert emails.get('Tracking Code reminder')['email_rcpt'] == [user.email]
     assert form_class.get(number31.id).tracking_code in emails.get('Tracking Code reminder')['payload']
 
-    pub.cfg['sms'] = {'mode': 'xx'}
+    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
     pub.write_cfg()
     resp = user_view_resp.click('Send tracking code', index=0)
     resp.form['method'].value = 'SMS'
diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index f81ff3648..46f165463 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -2341,7 +2341,7 @@ def test_jump_missing_previous_mark(two_pubs):
 
 
 def test_sms(pub, sms_mocking):
-    pub.cfg['sms'] = {'mode': 'xxx'}
+    pub.cfg['sms'] = {'sender': 'xxx', 'passerelle_url': 'http://passerelle.invalid/'}
     formdef = FormDef()
     formdef.name = 'baz'
     formdef.fields = []
diff --git a/tests/utilities.py b/tests/utilities.py
index ebcf1edee..1b81c1e28 100644
--- a/tests/utilities.py
+++ b/tests/utilities.py
@@ -398,21 +398,16 @@ class HttpRequestsMocking(object):
         return len(self.requests)
 
 
-class SMSMocking(wcs.qommon.sms.MobytSMS):
-    def get_sms_class(self, mode):
-        if mode == 'none':
-            return None
-        return self
-
-    def send(self, sender, destinations, text, quality=None):
+class SMSMocking(wcs.qommon.sms.PasserelleSMS):
+    def get_sms_class(self):
+        sms_cfg = get_publisher().cfg.get('sms', {})
+        if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
+            return self
+        return None
+
+    def send(self, sender, destinations, text):
         self.sms.append({'sender': sender, 'destinations': destinations, 'text': text})
 
-    def get_sms_left(self, type='standard'):
-        raise NotImplementedError
-
-    def get_money_left(self):
-        raise NotImplementedError
-
     def __enter__(self):
         self.sms = []
         self.wcs_get_sms_class = wcs.qommon.sms.SMS.get_sms_class
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index 2f33cc9ad..9b388149a 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -425,7 +425,7 @@ class SettingsDirectory(QommonSettingsDirectory):
             'template', 'emails', 'debug_options', 'language',
             ('import', 'p_import'), 'export', 'identification', 'sitename',
             'sms', 'certificates', 'texts', 'install_theme',
-            'session', 'download_theme', 'smstest', 'postgresql',
+            'session', 'download_theme', 'postgresql',
             ('admin-permissions', 'admin_permissions'), 'geolocation',
             'theme_preview', 'filetypes',
             ('user-template', 'user_template'),
@@ -1101,103 +1101,22 @@ class SettingsDirectory(QommonSettingsDirectory):
         r = TemplateIO(html=True)
         r += htmltext('<h2>%s</h2>') % _('SMS Options')
         sms_cfg = get_cfg('sms', {})
-        mode = sms_cfg.get('mode', 'none')
-        sms = SMS.get_sms_class(mode)
-        if sms:
-            r += htmltext('<ul>')
-            try:
-                try:
-                    r += htmltext('<li>%s %s</li>') % (_('SMS Credit:'), sms.get_money_left())
-                except NotImplementedError:
-                    pass
-                try:
-                    r += htmltext('<li>%s %s</li>') % (_('SMS Left:'), sms.get_sms_left())
-                except NotImplementedError:
-                    pass
-            except errors.SMSError:
-                r += htmltext("<p>%s</li>") % _("Connection with SMS provider failed")
-            r += htmltext('</ul>')
-
         form = Form(enctype='multipart/form-data')
-        form.add(SingleSelectWidget, 'mode', title = _('SMS Mode'),
-                value = mode,
-                options = [(str('none'), _('No support'), str('none'))]+
-                          [(str(k), _(SMS.providers.get(k)[0]), str(k)) for k in SMS.providers.keys()])
-
+        form.add(StringWidget, 'sender', title=_('Sender (number of name)'),
+                value=sms_cfg.get('sender'))
+        form.add(StringWidget, 'passerelle_url', title=_('URL'),
+                value=sms_cfg.get('passerelle_url'))
         form.add_submit('submit', _('Submit'))
         form.add_submit('cancel', _('Cancel'))
 
         if form.get_widget('cancel').parse():
             return redirect('.')
 
-        if sms:
-            for widget, name, title in sms.parameters:
-                form.add(widget, name, title=_(title),
-                         value=sms_cfg.get(name, ''),
-                         required=True)
-            if form.get_submit() and not form.has_errors():
-                cfg_submit(form, 'sms', ['mode'] + [x[1] for x in sms.parameters])
-                if mode != form.get_widget('mode').parse():
-                    return redirect('sms')
-                else:
-                    return redirect('.')
-            elif mode != form.get_widget('mode').parse():
-                cfg_submit(form, 'sms', ['mode',])
-                return redirect('sms')
-        else:
-            if form.get_submit() and form.get_widget('mode').parse() == str('none'):
-                return redirect('.')
-
         if form.get_submit() and not form.has_errors():
-            cfg_submit(form, 'sms', ['mode',])
-            return redirect('sms')
-        else:
-            r += form.render()
-
-        if mode != 'none':
-            r += htmltext('<p><a href="smstest">%s</a></p>') % _('SMS Test')
-
-        return r.getvalue()
-
-
-    def smstest(self):
-        form = Form(enctype='multipart/form-data', action='smstest')
-        form.add(StringWidget, 'sender', title=_('Sender'), required=True)
-        form.add(StringWidget, 'destinations', title=_('Destinations'), required=True)
-        form.add(StringWidget, 'text', title=_('Text'), required=True)
-        form.add_submit('submit', _('Submit'))
-        form.add_submit('cancel', _('Cancel'))
-
-        if form.get_widget('cancel').parse():
-            return redirect('sms')
+            cfg_submit(form, 'sms', ['sender', 'passerelle_url'])
+            return redirect('.')
 
-        get_response().breadcrumb.append(('sms', _('SMS')))
-        get_response().breadcrumb.append(('smstest', _('SMS Test')))
-        html_top('settings', title = _('SMS Test'))
-        r = TemplateIO(html=True)
-        r += htmltext('<h2>%s</h2>') % _('SMS Test')
         r += form.render()
-
-        if form.get_submit() and not form.has_errors():
-            sms_cfg = get_cfg('sms', {})
-            mode = sms_cfg.get('mode', 'none')
-            sms = SMS.get_sms_class(mode)
-
-            sender = str(form.get_widget('sender').parse())
-            destinations = str(form.get_widget('destinations').parse()).split(str(','))
-            text = str(form.get_widget('text').parse())
-
-            try:
-                sms.send(sender, destinations, text)
-            except Exception as e:
-                r += htmltext('<pre>')
-                r += repr(e)
-                r += htmltext('</pre>')
-            else:
-                r += htmltext('<p>')
-                r += _('The SMS has been sent successfully.')
-                r += htmltext('</p>')
-
         return r.getvalue()
 
     def postgresql(self):
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 2bdd3abdb..2decb2203 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -146,9 +146,7 @@ class SendCodeFormdefDirectory(Directory):
                 value=submitter_email)
         sms_class = None
         if get_publisher().use_sms_feature:
-            sms_cfg = get_cfg('sms', {})
-            mode = sms_cfg.get('mode', 'none')
-            sms_class = sms.SMS.get_sms_class(mode)
+            sms_class = sms.SMS.get_sms_class()
             if sms_class:
                 form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
                 form.add(RadiobuttonsWidget, 'method',
@@ -180,6 +178,7 @@ class SendCodeFormdefDirectory(Directory):
         if sms_class and form.get_widget('method').parse() == 'sms':
             # send sms
             sitename = get_cfg('misc', {}).get('sitename') or 'w.c.s.'
+            sms_cfg = get_cfg('sms', {})
             sender = sms_cfg.get('sender', sitename)[:11]
             message = Template(message).render(data)
             try:
diff --git a/wcs/qommon/sms.py b/wcs/qommon/sms.py
index 91f532f19..84db2b280 100644
--- a/wcs/qommon/sms.py
+++ b/wcs/qommon/sms.py
@@ -24,188 +24,7 @@ from .form import StringWidget, PasswordWidget
 from wcs.wscalls import call_webservice
 
 
-class MobytSMS(object):
-    """This class allows to send a SMS using Mobyt provider"""
-    parameters = [
-         (StringWidget, 'sender', N_('Sender (number or name)')),
-         (StringWidget, 'mobyt_username', N_('Username')),
-         (PasswordWidget, 'mobyt_password', N_('Password')),
-    ]
-
-    def __init__(self):
-        sms_cfg = get_cfg('sms', {})
-        self.user = sms_cfg.get('mobyt_username', '')
-        self.password = sms_cfg.get('mobyt_password', '')
-
-    def send(self, sender, destinations, text, quality='l'):
-        """ Send a sms using Mobyt provider"""
-        rcpt = ""
-        for dest in destinations:
-            rcpt += "%s," % dest
-        rcpt = rcpt[:len(rcpt) - 1]
-        params = urllib.urlencode({
-            'user': self.user,
-            'pass': self.password,
-            'rcpt': rcpt,
-            'data': text,
-            'sender': sender,
-            'qty': quality
-            })
-        try:
-            r = misc.urlopen("http://multilevel.mobyt.fr/sms/batch.php", params)
-        except misc.ConnectionError as e:
-            raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
-        answer = r.read()
-        r.close()
-        if answer[:2] == "KO":
-            raise errors.SMSError(answer[3:])
-
-    def get_credit(self, type):
-        params = urllib.urlencode({
-            'user': self.user,
-            'pass': self.password,
-            'type': type
-            })
-        try:
-            r = misc.urlopen("http://multilevel.mobyt.fr/sms/credit.php", params)
-        except misc.ConnectionError as e:
-            raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
-        answer = r.read()
-        r.close()
-        if answer[:2] == "KO":
-            raise errors.SMSError(answer[3:])
-        else:
-            return answer[3:]
-
-    def get_sms_left(self, type="standard"):
-        """
-        type (mobyt provider): standard, lowcost or top
-        """
-        if type == "standard":
-            return self.get_credit("l")
-        elif type == "lowcost":
-            return self.get_credit("ll")
-        elif type == "top":
-            return self.get_credit("n")
-        else:
-            raise errors.SMSError("%s is invalid type for provider Mobyt" % type)
-
-    def get_money_left(self):
-        """ return money left in euros """
-        return self.get_credit("credit")
-
-
-class OxydSMS(object):
-    """This class allows to send a SMS using Oxyd provider"""
-    parameters = [
-         (StringWidget, 'oxyd_username', N_('Username')),
-         (PasswordWidget, 'oxyd_password', N_('Password')),
-         (StringWidget, 'oxyd_default_country_code', N_('Default Country Code')),
-    ]
-
-    def __init__(self):
-        sms_cfg = get_cfg('sms', {})
-        self.user = sms_cfg.get('oxyd_username', '')
-        self.password = sms_cfg.get('oxyd_password', '')
-        self.default_country_code = sms_cfg.get('oxyd_default_country_code')
-        if not self.default_country_code:
-            self.default_country_code = '33' # arbitrary
-
-    def send(self, sender, destinations, text, quality=None):
-        """Send a SMS using Oxyd provider"""
-        # unfortunately it lacks a batch API...
-        for dest in destinations:
-            # oxyd needs the number prefixed by the country code, this is
-            # really unfortunate.
-            number = ''.join(re.findall(r'\d', dest))
-            if dest.startswith('+'):
-                pass # it already is fully qualified
-            elif number.startswith('00'):
-                # assumes 00 is international access code, remove it
-                number = number[2:]
-            elif number.startswith('0'):
-                # local prefix, remove 0 and add default country code
-                number = self.default_country_code + number[1:]
-            params = urllib.urlencode({
-                'id': self.user,
-                'pass': self.password,
-                'num': number,
-                'sms': text,
-                'flash': '0'
-                })
-            try:
-                r = misc.urlopen('http://sms.oxyd.fr/send.php', params)
-            except misc.ConnectionError as e:
-                # XXX: add proper handling of errors
-                raise errors.SMSError("failed to POST to oxyd.fr (%s)" % e)
-            r.close()
-
-    def get_sms_left(self, type='standard'):
-        raise NotImplementedError
-
-    def get_money_left(self):
-        raise NotImplementedError
-
-
-class ChoositSMS(object):
-    """This class allows to send a SMS using the Choosit provider
-
-    http://sms.choosit.com/documentation_technique.html
-    """
-    parameters = [
-         (StringWidget, 'choosit_key', N_('Key')),
-         (StringWidget, 'choosit_default_country_code', N_('Default Country Code')),
-    ]
-
-    def __init__(self):
-        sms_cfg = get_cfg('sms', {})
-        self.key = sms_cfg.get('choosit_key', '')
-        self.default_country_code = sms_cfg.get('choosit_default_country_code')
-        if not self.default_country_code:
-            self.default_country_code = '33' # arbitrary
-
-    def send(self, sender, destinations, text, quality=None):
-        """Send a SMS using the Choosit provider"""
-        # unfortunately it lacks a batch API...
-        for dest in destinations:
-            # choosit needs the number prefixed by the country code, this is
-            # really unfortunate.
-            number = ''.join(re.findall(r'\d', dest))
-            if dest.startswith('+'):
-                pass # it already is fully qualified
-            elif number.startswith('00'):
-                # assumes 00 is international access code, remove it
-                number = number[2:]
-            elif number.startswith('0'):
-                # local prefix, remove 0 and add default country code
-                number = self.default_country_code + number[1:]
-            params = urllib.urlencode({
-                'key': self.key,
-                'recipient': number,
-                'content': text[:160],
-                })
-            try:
-                r = misc.urlopen('http://sms.choosit.com/webservice', params)
-            except misc.ConnectionError as e:
-                # XXX: add proper handling of errors
-                raise errors.SMSError("failed to POST to choosit.com (%s)" % e)
-            r.close()
-
-    def get_sms_left(self, type='standard'):
-        raise NotImplementedError
-
-    def get_money_left(self):
-        raise NotImplementedError
-
-
 class PasserelleSMS(object):
-    """This class allows to send a SMS using Passerelle
-    """
-    parameters = [
-         (StringWidget, 'sender', N_('Sender (number or name)')),
-         (StringWidget, 'passerelle_url', N_('URL')),
-    ]
-
     TIMEOUT = 10
 
     def __init__(self):
@@ -214,7 +33,6 @@ class PasserelleSMS(object):
         self.url = sms_cfg.get('passerelle_url', '')
 
     def send(self, sender, destinations, text, quality=None):
-        """Send a SMS using the Choosit provider"""
         sender = sender or self.sender
         payload = {
               'from': sender,
@@ -227,27 +45,12 @@ class PasserelleSMS(object):
         get_logger().debug('sms %r sent using passerelle to %r, result: %r',
                 text, destinations, data)
 
-    def get_sms_left(self, type='standard'):
-        raise NotImplementedError
-
-    def get_money_left(self):
-        raise NotImplementedError
-
 
 class SMS(object):
-    providers = {
-        'mobyt': (N_('Mobyt provider'), MobytSMS),
-        'oxyd': (N_('Oxyd provider'), OxydSMS),
-        'choosit': (N_('Choosit provider'), ChoositSMS),
-        'passerelle': (N_('Passerelle provider'), PasserelleSMS),
-    }
 
     @classmethod
-    def get_sms_class(cls, provider_id):
-        if not provider_id:
-            sms_cfg = get_cfg('sms', {})
-            provider_id = sms_cfg.get('mode', '')
-        if provider_id in cls.providers:
-            return cls.providers.get(provider_id)[1]()
-        else:
-            return None
+    def get_sms_class(cls):
+        sms_cfg = get_cfg('sms', {})
+        if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
+            return PasserelleSMS()
+        return None
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 6fb0ef045..fc2675d21 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -2826,8 +2826,8 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
 
     @classmethod
     def is_available(cls, workflow=None):
-        sms_mode = get_cfg('sms', {}).get('mode') or 'none'
-        return sms_mode != 'none'
+        sms_cfg = get_cfg('sms', {})
+        return bool(sms_cfg.get('sender') and sms_cfg.get('passerelle_url'))
 
     def get_parameters(self):
         return ('to', 'body', 'condition')
@@ -2868,9 +2868,8 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
 
         sms_cfg = get_cfg('sms', {})
         sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
-        mode = sms_cfg.get('mode', 'none')
         try:
-            sms.SMS.get_sms_class(mode).send(sender, destinations, sms_body)
+            sms.SMS.get_sms_class().send(sender, destinations, sms_body)
         except errors.SMSError as e:
             get_logger().error(e)
 
-- 
2.29.2

