Test Setup Failed
Push — master ( ec638a...cb9435 )
by Julito
51:10
created

Clockwork::send()   F

Complexity

Conditions 34
Paths > 20000

Size

Total Lines 144
Code Lines 98

Duplication

Lines 144
Ratio 100 %

Importance

Changes 0
Metric Value
cc 34
eloc 98
nc 31201
nop 1
dl 144
loc 144
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
* Clockwork PHP API
4
*
5
* @package     Clockwork
6
* @copyright   Mediaburst Ltd 2012
7
* @license     ISC
8
* @link        http://www.clockworksms.com
9
* @version     1.3.0
10
*/
11
12
if ( !class_exists('ClockworkException') ) {
13
  require_once('exception.php');
14
}
15
16
/**
17
* Main Clockwork API Class
18
* 
19
* @package     Clockwork
20
* @since       1.0
21
*/
22
class Clockwork {
23
24
  /*
25
  * Version of this class
26
  */
27
  const VERSION           = '1.3.1';
28
29
  /**
30
  * All Clockwork API calls start with BASE_URL
31
  * @author  Martin Steel
32
  */
33
  const API_BASE_URL      = 'api.clockworksms.com/xml/';
34
35
  /**
36
  * string to append to API_BASE_URL to check authentication
37
  * @author  Martin Steel
38
  */
39
  const API_AUTH_METHOD   = 'authenticate';
40
41
  /**
42
  * string to append to API_BASE_URL for sending SMS
43
  * @author  Martin Steel
44
  */
45
  const API_SMS_METHOD    = 'sms';
46
47
  /**
48
  * string to append to API_BASE_URL for checking message credit
49
  * @author  Martin Steel
50
  */
51
  const API_CREDIT_METHOD = 'credit';
52
53
  /**
54
  * string to append to API_BASE_URL for checking account balance
55
  * @author  Martin Steel
56
  */
57
  const API_BALANCE_METHOD = 'balance';
58
59
  /** 
60
  * Clockwork API Key
61
  * 
62
  * @var string
63
  * @author  Martin Steel
64
  */
65
  public $key;
66
67
  /**
68
  * Use SSL when making HTTP requests
69
  *
70
  * If this is not set, SSL will be used where PHP supports it
71
  *
72
  * @var bool
73
  * @author  Martin Steel
74
  */
75
  public $ssl;
76
77
  /**
78
  * Proxy server hostname (Optional)
79
  *
80
  * @var string
81
  * @author  Martin Steel
82
  */
83
  public $proxy_host;
84
85
  /**
86
  * Proxy server port (Optional)
87
  *
88
  * @var integer
89
  * @author  Martin Steel
90
  */
91
  public $proxy_port;
92
93
  /**
94
  * From address used on text messages
95
  *
96
  * @var string (11 characters or 12 numbers)
97
  * @author  Martin Steel
98
  */
99
  public $from;
100
101
  /**
102
  * Allow long SMS messages (Cost up to 3 credits)
103
  *
104
  * @var bool
105
  * @author  Martin Steel
106
  */
107
  public $long;
108
109
  /**
110
  * Truncate message text if it is too long
111
  *
112
  * @var bool
113
  * @author  Martin Steel
114
  */
115
  public $truncate;
116
117
  /**
118
  * Enables various logging of messages when true.
119
  *
120
  * @var bool
121
  * @author  Martin Steel
122
  */
123
  public $log;
124
125
  /**
126
  * What Clockwork should do if you send an invalid character
127
  *
128
  * Possible values:
129
  *      'error'     - Return an error (Messasge is not sent)
130
  *      'remove'    - Remove the invalid character(s)
131
  *      'replace'   - Replace invalid characters where possible, remove others 
132
  * @author  Martin Steel
133
  */
134
  public $invalid_char_action;
135
136
  /**
137
  * Create a new instance of the Clockwork wrapper
138
  *
139
  * @param   string  key         Your Clockwork API Key
140
  * @param   array   options     Optional parameters for sending SMS
141
  * @author  Martin Steel
142
  */
143
  public function __construct($key, array $options = array()) {
144
    if (empty($key)) {
145
      throw new ClockworkException("Key can't be blank");      
146
    } else {
147
      $this->key = $key;
148
    }
149
        
150
    $this->ssl                  = (array_key_exists('ssl', $options)) ? $options['ssl'] : null;
151
    $this->proxy_host           = (array_key_exists('proxy_host', $options)) ? $options['proxy_host'] : null;
152
    $this->proxy_port           = (array_key_exists('proxy_port', $options)) ? $options['proxy_port'] : null;
153
    $this->from                 = (array_key_exists('from', $options)) ? $options['from'] : null;
154
    $this->long                 = (array_key_exists('long', $options)) ? $options['long'] : null;
155
    $this->truncate             = (array_key_exists('truncate', $options)) ? $options['truncate'] : null;
156
    $this->invalid_char_action  = (array_key_exists('invalid_char_action', $options)) ? $options['invalid_char_action'] : null;
157
    $this->log                  = (array_key_exists('log', $options)) ? $options['log'] : false;
158
  }
159
160
  /**
161
  * Send some text messages
162
  * 
163
  *
164
  * @author  Martin Steel
165
  */
166
  public function send(array $sms) {
167
    if (!is_array($sms)) {
168
      throw new ClockworkException("sms parameter must be an array");
169
    }
170
    $single_message = $this->is_assoc($sms);
171
172
    if ($single_message) {
173
      $sms = array($sms);
174
    }
175
176
    $req_doc = new DOMDocument('1.0', 'UTF-8');
177
    $root = $req_doc->createElement('Message');
178
    $req_doc->appendChild($root);
179
180
    $user_node = $req_doc->createElement('Key');
181
    $user_node->appendChild($req_doc->createTextNode($this->key));
182
    $root->appendChild($user_node);
183
184
    for ($i = 0; $i < count($sms); $i++) {
185
      $single = $sms[$i];
186
187
      $sms_node = $req_doc->createElement('SMS');
188
           
189
      // Phone number
190
      $sms_node->appendChild($req_doc->createElement('To', $single['to'])); 
191
            
192
      // Message text
193
      $content_node = $req_doc->createElement('Content');
194
      $content_node->appendChild($req_doc->createTextNode($single['message']));
195
      $sms_node->appendChild($content_node);
196
197
      // From
198
      if (array_key_exists('from', $single) || isset($this->from)) {
199
        $from_node = $req_doc->createElement('From');
200
        $from_node->appendChild($req_doc->createTextNode(array_key_exists('from', $single) ? $single['from'] : $this->from));
201
        $sms_node->appendChild($from_node);
202
      }
203
204
      // Client ID
205
      if (array_key_exists('client_id', $single)) {
206
        $client_id_node = $req_doc->createElement('ClientID');
207
        $client_id_node->appendChild($req_doc->createTextNode($single['client_id']));
208
        $sms_node->appendChild($client_id_node);
209
      }
210
211
      // Long
212
      if (array_key_exists('long', $single) || isset($this->long)) {
213
        $long = array_key_exists('long', $single) ? $single['long'] : $this->long;
214
        $long_node = $req_doc->createElement('Long');
215
        $long_node->appendChild($req_doc->createTextNode($long ? 1 : 0));
216
        $sms_node->appendChild($long_node);
217
      }
218
219
      // Truncate
220
      if (array_key_exists('truncate', $single) || isset($this->truncate)) {
221
        $truncate = array_key_exists('truncate', $single) ? $single['truncate'] : $this->truncate;
222
        $trunc_node = $req_doc->createElement('Truncate');
223
        $trunc_node->appendChild($req_doc->createTextNode($truncate ? 1 : 0));
224
        $sms_node->appendChild($trunc_node);
225
      }
226
227
      // Invalid Char Action
228
      if (array_key_exists('invalid_char_action', $single) || isset($this->invalid_char_action)) {
229
        $action = array_key_exists('invalid_char_action', $single) ? $single['invalid_char_action'] : $this->invalid_char_action;
230
        switch (strtolower($action)) {
231
          case 'error':
232
          $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 1));
233
          break;
234
          case 'remove':
235
          $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 2));
236
          break;
237
          case 'replace':
238
          $sms_node->appendChild($req_doc->createElement('InvalidCharAction', 3));
239
          break;
240
          default:
241
          break;
242
        }
243
      }
244
245
      // Wrapper ID
246
      $sms_node->appendChild($req_doc->createElement('WrapperID', $i));
247
248
      $root->appendChild($sms_node);
249
    }
250
251
    $req_xml = $req_doc->saveXML();
252
     
253
    $resp_xml = $this->postToClockwork(self::API_SMS_METHOD, $req_xml);
254
    $resp_doc = new DOMDocument();
255
    $resp_doc->loadXML($resp_xml);   
256
257
    $response = array();
258
    $err_no = null;
259
    $err_desc = null;
260
261
    foreach($resp_doc->documentElement->childNodes AS $doc_child) {
262
      switch(strtolower($doc_child->nodeName)) {
263
        case 'sms_resp':
264
        $resp = array();
265
        $wrapper_id = null;
266
        foreach($doc_child->childNodes AS $resp_node) {
267
          switch(strtolower($resp_node->nodeName)) {
268
            case 'messageid':
269
            $resp['id'] = $resp_node->nodeValue;
270
            break;
271
            case 'errno':
272
            $resp['error_code'] = $resp_node->nodeValue;
273
            break;
274
            case 'errdesc':
275
            $resp['error_message'] = $resp_node->nodeValue;
276
            break;
277
            case 'wrapperid':
278
            $wrapper_id = $resp_node->nodeValue;
279
            break;
280
          }
281
        }
282
        if( array_key_exists('error_code', $resp ) ) 
283
        {
284
          $resp['success'] = 0;
285
        } else {
286
          $resp['success'] = 1;
287
        }
288
        $resp['sms'] = $sms[$wrapper_id];
289
        array_push($response, $resp);
290
        break;
291
        case 'errno':
292
        $err_no = $doc_child->nodeValue;
293
        break;
294
        case 'errdesc':
295
        $err_desc = $doc_child->nodeValue;
296
        break;
297
      }
298
    }
299
300
    if (isset($err_no)) {
301
      throw new ClockworkException($err_desc, $err_no);      
302
    }
303
        
304
    if ($single_message) {
305
      return $response[0];
306
    } else {
307
      return $response;
308
    }
309
  }
310
311
  /**
312
  * Check how many SMS credits you have available
313
  *
314
  * @return  integer   SMS credits remaining
315
  * @deprecated Use checkBalance() instead
316
  * @author  Martin Steel
317
  */
318
  public function checkCredit() {
319
    // Create XML doc for request
320
    $req_doc = new DOMDocument('1.0', 'UTF-8');
321
    $root = $req_doc->createElement('Credit');
322
    $req_doc->appendChild($root);
323
    $root->appendChild($req_doc->createElement('Key', $this->key));
324
    $req_xml = $req_doc->saveXML();
325
326
    // POST XML to Clockwork
327
    $resp_xml = $this->postToClockwork(self::API_CREDIT_METHOD, $req_xml);
328
329
    // Create XML doc for response
330
    $resp_doc = new DOMDocument();
331
    $resp_doc->loadXML($resp_xml);
332
333
    // Parse the response to find credit value
334
    $credit;
335
    $err_no = null;
336
    $err_desc = null;
337
        
338
    foreach ($resp_doc->documentElement->childNodes AS $doc_child) {
339
      switch ($doc_child->nodeName) {
340
        case "Credit":
341
        $credit = $doc_child->nodeValue;
342
        break;
343
        case "ErrNo":
344
        $err_no = $doc_child->nodeValue;
345
        break;
346
        case "ErrDesc":
347
        $err_desc = $doc_child->nodeValue;
348
        break;
349
        default:
350
        break;
351
      }
352
    }
353
354
    if (isset($err_no)) {
355
      throw new ClockworkException($err_desc, $err_no);
356
    }
357
    return $credit;
358
  }
359
360
  /**
361
  * Check your account balance
362
  *
363
  * @return  array   Array of account balance: 
364
  * @author  Martin Steel
365
  */
366
  public function checkBalance() {
367
    // Create XML doc for request
368
    $req_doc = new DOMDocument('1.0', 'UTF-8');
369
    $root = $req_doc->createElement('Balance');
370
    $req_doc->appendChild($root);
371
    $root->appendChild($req_doc->createElement('Key', $this->key));
372
    $req_xml = $req_doc->saveXML();
373
    
374
    // POST XML to Clockwork
375
    $resp_xml = $this->postToClockwork(self::API_BALANCE_METHOD, $req_xml);
376
377
    // Create XML doc for response
378
    $resp_doc = new DOMDocument();
379
    $resp_doc->loadXML($resp_xml);
380
    
381
    // Parse the response to find balance value
382
    $balance = null;
383
    $err_no = null;
384
    $err_desc = null;
385
        
386
    foreach ($resp_doc->documentElement->childNodes as $doc_child) {
387
      switch ($doc_child->nodeName) {
388
        case "Balance":
389
        $balance = number_format(floatval($doc_child->nodeValue), 2);
390
        break;
391
        case "Currency":
392
        foreach ($doc_child->childNodes as $resp_node) {
393
          switch ($resp_node->tagName) {
394
            case "Symbol":
395
            $symbol = $resp_node->nodeValue; 
396
            break;
397
            case "Code":
398
            $code = $resp_node->nodeValue; 
399
            break;
400
          }
401
        }
402
        break;
403
        case "ErrNo":
404
        $err_no = $doc_child->nodeValue;
405
        break;
406
        case "ErrDesc":
407
        $err_desc = $doc_child->nodeValue;
408
        break;
409
        default:
410
        break;
411
      }
412
    }
413
414
    if (isset($err_no)) {
415
      throw new ClockworkException($err_desc, $err_no);
416
    }
417
        
418
    return array( 'symbol' => $symbol, 'balance' => $balance, 'code' => $code );
419
  }
420
421
  /**
422
  * Check whether the API Key is valid
423
  *
424
  * @return  bool    True indicates a valid key
425
  * @author  Martin Steel
426
  */
427
  public function checkKey() {
428
    // Create XML doc for request
429
    $req_doc = new DOMDocument('1.0', 'UTF-8');
430
    $root = $req_doc->createElement('Authenticate');
431
    $req_doc->appendChild($root);
432
    $root->appendChild($req_doc->createElement('Key', $this->key));
433
    $req_xml = $req_doc->saveXML();
434
435
    // POST XML to Clockwork
436
    $resp_xml = $this->postToClockwork(self::API_AUTH_METHOD, $req_xml);
437
438
    // Create XML doc for response
439
    $resp_doc = new DOMDocument();
440
    $resp_doc->loadXML($resp_xml);
441
        
442
    // Parse the response to see if authenticated
443
    $cust_id;
444
    $err_no = null;
445
    $err_desc = null;
446
447
    foreach ($resp_doc->documentElement->childNodes AS $doc_child) {
448
      switch ($doc_child->nodeName) {
449
        case "CustID":
450
        $cust_id = $doc_child->nodeValue;
451
        break;
452
        case "ErrNo":
453
        $err_no = $doc_child->nodeValue;
454
        break;
455
        case "ErrDesc":
456
        $err_desc = $doc_child->nodeValue;
457
        break;
458
        default:
459
        break;
460
      }
461
    }
462
463
    if (isset($err_no)) {
464
      throw new ClockworkException($err_desc, $err_no);
465
    }
466
    return isset($cust_id);   
467
  }
468
469
  /**
470
  * Make an HTTP POST to Clockwork
471
  *
472
  * @param   string   method Clockwork method to call (sms/credit)
473
  * @param   string   data   Content of HTTP POST
474
  *
475
  * @return  string          Response from Clockwork
476
  * @author  Martin Steel
477
  */
478
  protected function postToClockwork($method, $data) {
479
    if ($this->log) {
480
      $this->logXML("API $method Request XML", $data);
481
    }
482
    
483
    if( isset( $this->ssl ) ) {
484
      $ssl = $this->ssl;
485
    } else {
486
      $ssl = $this->sslSupport();
487
    }
488
489
    $url = $ssl ? 'https://' : 'http://';
490
    $url .= self::API_BASE_URL . $method;
491
492
    $response = $this->xmlPost($url, $data);
493
494
    if ($this->log) {
495
      $this->logXML("API $method Response XML", $response);
496
    }
497
498
    return $response;
499
  }
500
501
  /**
502
  * Make a HTTP POST
503
  *
504
  * cURL will be used if available, otherwise tries the PHP stream functions
505
  *
506
  * @param   string url      URL to send to
507
  * @param   string data     Data to POST
508
  * @return  string          Response returned by server
509
  * @author  Martin Steel
510
  */
511
  protected function xmlPost($url, $data) {
512
    if(extension_loaded('curl')) {
513
      $ch = curl_init($url);
514
      curl_setopt($ch, CURLOPT_POST, 1);
515
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
516
      curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type: text/xml"));
517
      curl_setopt($ch, CURLOPT_USERAGENT, 'Clockwork PHP Wrapper/1.0' . self::VERSION);
518
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
519
      if (isset($this->proxy_host) && isset($this->proxy_port)) {
520
        curl_setopt($ch, CURLOPT_PROXY, $this->proxy_host);
521
        curl_setopt($ch, CURLOPT_PROXYPORT, $this->proxy_port);
522
      }
523
524
      $response = curl_exec($ch);
525
      $info = curl_getinfo($ch);
526
527
      if ($response === false || $info['http_code'] != 200) {
528
        throw new Exception('HTTP Error calling Clockwork API - HTTP Status: ' . $info['http_code'] . ' - cURL Erorr: ' . curl_error($ch));
529
      } elseif (curl_errno($ch) > 0) {
530
        throw new Exception('HTTP Error calling Clockwork API - cURL Error: ' . curl_error($ch));
531
      }
532
533
      curl_close($ch);
534
535
      return $response;
536
    } elseif (function_exists('stream_get_contents')) {
537
      // Enable error Track Errors
538
      $track = ini_get('track_errors');
539
      ini_set('track_errors',true);
540
541
      $params = array('http' => array(
542
      'method'  => 'POST',
543
      'header'  => "Content-Type: text/xml\r\nUser-Agent: mediaburst PHP Wrapper/" . self::VERSION . "\r\n",
544
      'content' => $data
545
      ));
546
547
      if (isset($this->proxy_host) && isset($this->proxy_port)) {
548
        $params['http']['proxy'] = 'tcp://'.$this->proxy_host . ':' . $this->proxy_port;
549
        $params['http']['request_fulluri'] = True;
550
      }
551
552
      $ctx = stream_context_create($params);
553
      $fp = @fopen($url, 'rb', false, $ctx);
554
      if (!$fp) {
555
        ini_set('track_errors',$track);
556
        throw new Exception("HTTP Error calling Clockwork API - fopen Error: $php_errormsg");
557
      }
558
      $response = @stream_get_contents($fp);
559
      if ($response === false) {
560
        ini_set('track_errors',$track);
561
        throw new Exception("HTTP Error calling Clockwork API - stream Error: $php_errormsg");
562
      }
563
      ini_set('track_errors',$track);
564
      return $response;
565
    } else {
566
      throw new Exception("Clockwork requires PHP5 with cURL or HTTP stream support");
567
    }
568
  }
569
570
  /**
571
  * Does the server/HTTP wrapper support SSL
572
  *
573
  * This is a best guess effort, some servers have weird setups where even
574
  * though cURL is compiled with SSL support is still fails to make
575
  * any requests.
576
  *
577
  * @return bool     True if SSL is supported
578
  * @author  Martin Steel
579
  */
580
  protected function sslSupport() {
581
    $ssl = false;
582
    // See if PHP is compiled with cURL
583
    if (extension_loaded('curl')) {
584
      $version = curl_version();
585
      $ssl = ($version['features'] & CURL_VERSION_SSL) ? true : false;
586
    } elseif (extension_loaded('openssl')) {
587
      $ssl = true;
588
    }
589
    return $ssl;
590
  }
591
592
  /**
593
  * Log some XML, tidily if possible, in the PHP error log
594
  *
595
  * @param   string  log_msg The log message to prepend to the XML
596
  * @param   string  xml     An XML formatted string
597
  *
598
  * @return  void
599
  * @author  Martin Steel
600
  */
601
  protected function logXML($log_msg, $xml) {
602
    // Tidy if possible
603
    if (class_exists('tidy')) {
604
      $tidy = new tidy;
605
      $config = array(
606
      'indent'     => true,
607
      'input-xml'  => true,
608
      'output-xml' => true,
609
      'wrap'       => 200
610
      );
611
      $tidy->parseString($xml, $config, 'utf8');
612
      $tidy->cleanRepair();
613
      $xml = $tidy;
614
    }
615
    // Output
616
    error_log("Clockwork $log_msg: $xml");
617
  }
618
619
  /**
620
  * Check if an array is associative
621
  *
622
  * @param   array $array Array to check
623
  * @return  bool
624
  * @author  Martin Steel
625
  */
626
  protected function is_assoc($array) {
627
    return (bool)count(array_filter(array_keys($array), 'is_string'));
628
  }
629
  
630
  /**
631
   * Check if a number is a valid MSISDN
632
   *
633
   * @param string $val Value to check
634
   * @return bool True if valid MSISDN
635
   * @author James Inman
636
   * @since 1.3.0
637
   * @todo Take an optional country code and check that the number starts with it
638
   */
639
  public static function is_valid_msisdn($val) {
640
    return preg_match( '/^[1-9][0-9]{7,12}$/', $val );
641
  }
642
643
}
644