Advertisement
Guest User

Neufbox4 PHP class (0.1.1)

a guest
Feb 2nd, 2012
461
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 24.89 KB | None | 0 0
  1. <?php
  2. /**
  3.  * Provides access and control over Neufbox4 devices.
  4.  * Tested with firmware NB4-MAIN-R3.1.10.
  5.  *
  6.  * @require PHP > 5.1.2
  7.  * @author Anael Ollier <nanawel -nospam- {at} gmail {dot} com>
  8.  * @version 0.1.1
  9.  * @since 2011-12-25
  10.  */
  11. class Neufbox4 {
  12.     const CLASS_VERSION = '0.1.1';
  13.    
  14.     const DEFAULT_HOST = '192.168.1.1';
  15.    
  16.     const STATUS_CONNECTED = 0;
  17.     const STATUS_CONNECTING = 1;
  18.     const STATUS_UNUSED = 2;
  19.     const STATUS_NOT_CONNECTED = 3;
  20.    
  21.     const REQUEST_TIMEOUT = 5;
  22.    
  23.     const LOG_DEBUG = 10;
  24.     const LOG_NOTICE = 20;
  25.     const LOG_WARNING = 30;
  26.     const LOG_ERROR = 40;
  27.    
  28.     /** @var string */
  29.     protected $_host;
  30.     /** @var string */
  31.     protected $_login;
  32.     /** @var string */
  33.     protected $_password;
  34.    
  35.     /** @var resource */
  36.     protected $_curl;
  37.     /** @var string */
  38.     protected $_sessionId;
  39.     /** @var string */
  40.     protected $_cookie;
  41.    
  42.     /** @var boolean */
  43.     public $debug = false;
  44.     /** @var int */
  45.     public $logLevel = self::LOG_DEBUG;
  46.    
  47.     /**
  48.      *
  49.      * @param string $host The Neufbox4 IP to connect to.
  50.      */
  51.     public function __construct($host = self::DEFAULT_HOST, $logLevel = self::LOG_NOTICE) {
  52.         $this->_host = $host;
  53.         $this->logLevel = $logLevel;
  54.         $this->_curl = curl_init($this->_getUrl('/'));
  55.         $this->log("Initialized new connection to host $host", self::LOG_DEBUG);
  56.     }
  57.    
  58.     public function __destruct() {
  59.         curl_close($this->_curl);
  60.     }
  61.    
  62.     protected function _getUrl($path) {
  63.         return 'http://' . $this->_host . $path;
  64.     }
  65.    
  66.     /**
  67.      * Creates a new session with stored login/password if
  68.      * there's no current session.
  69.      *
  70.      * @param boolean $force
  71.      */
  72.     protected function _login($force = false) {
  73.         if ($force || !$this->_sessionId) {
  74.            
  75.             ///////////////////
  76.             // 1. Retrieve challenge (session ID)
  77.             $res = $this->_sendRawRequest('/login', 'post', array('action' => 'challenge'),
  78.                 array(
  79.                     'X-Requested-With: XMLHttpRequest',
  80.                     'X-Requested-Handler: ajax',
  81.                 )
  82.             );
  83.             if ($res === false) {
  84.                 self::throwException('Cannot log in: challenge request failed');
  85.             }
  86.            
  87.             if (200 != ($code = $res['info']['http_code'])) {
  88.                 self::throwException("Cannot log in: unexpected code HTTP $code returned");
  89.             }
  90.             elseif ('text/xml' != ($contentType = $res['info']['content_type'])) {
  91.                 self::throwException("Cannot log in: unexpected content type \"$contentType\" returned (text/xml expected)");
  92.             }
  93.             $xml = new SimpleXMLElement($res['body']);
  94.             if (! $sid = (string) $xml->challenge) {
  95.                 self::throwException('Cannot log in: no challenge found in response body');
  96.             }
  97.             $this->_sessionId = trim($sid);
  98.            
  99.             ///////////////////
  100.             // 2. Generate hash for authentication
  101.             if (0 == strlen($this->_login) || 0 == strlen($this->_password)) {
  102.                 self::log("Missing or empty login/password", self::LOG_WARNING);
  103.             }
  104.             $hash = $this->_genLoginHash($this->_sessionId, $this->_login, $this->_password);
  105.            
  106.             ///////////////////
  107.             // 3. Log in with calculated hash
  108.             try {
  109.                 $res = $this->_sendRawRequest('/login', 'post',
  110.                     array(
  111.                         'hash' => $hash,
  112.                         'login' => '',
  113.                         'method' => 'passwd',
  114.                         'password' => '',
  115.                         'zsid' => $this->_sessionId,
  116.                     ),
  117.                     null,
  118.                     'zsid=' . $this->_sessionId
  119.                 );
  120.             }
  121.             catch (Exception $e) {
  122.                 self::throwException("Cannot log in: authentication request failed. ({$e->getMessage()})");
  123.             }
  124.            
  125.             if (!isset($res['headers']['Set-Cookie'])) {
  126.                 self::throwException("Cannot log in: invalid login/password?");
  127.             }
  128.             $this->_cookie = $res['headers']['Set-Cookie'];
  129.            
  130.             $this->log('Login successful! Session ID: ' . $this->_sessionId, self::LOG_DEBUG);
  131.         }
  132.     }
  133.    
  134.     /**
  135.      * Generates the authentication hash based on session ID,
  136.      * login and password.
  137.      *
  138.      * @param string $challenge
  139.      * @param string $login
  140.      * @param string $password
  141.      */
  142.     protected function _genLoginHash($challenge, $login, $password) {
  143.         return hash_hmac('sha256', hash('sha256', $login), $challenge)
  144.             . hash_hmac('sha256', hash('sha256', $password), $challenge);
  145.     }
  146.    
  147.     /**
  148.      * Helper for building raw cURL requests.
  149.      *
  150.      * @param string $path
  151.      * @param string $method
  152.      * @param array $data
  153.      * @return array
  154.      * @throws Exception if the request failed.
  155.      */
  156.     protected function _sendRawRequest($path = '/', $method = 'get', $data = array(), $headers = null, $cookie = '') {
  157.         $url = $this->_getUrl($path);
  158.        
  159.         // Common
  160.         curl_setopt($this->_curl, CURLOPT_URL, $url);
  161.         curl_setopt($this->_curl, CURLOPT_TIMEOUT, self::REQUEST_TIMEOUT);
  162.         curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
  163.         curl_setopt($this->_curl, CURLOPT_HEADER, true);
  164.        
  165.         // Cookie
  166.         if ($cookie) {
  167.             curl_setopt($this->_curl, CURLOPT_COOKIE, $cookie);
  168.         }
  169.        
  170.         // Headers
  171.         if (is_array($headers)) {
  172.             curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
  173.         }
  174.         else {
  175.             curl_setopt($this->_curl, CURLOPT_HTTPHEADER, array());
  176.         }
  177.        
  178.         // GET/POST
  179.         if ($method == 'get') {
  180.             curl_setopt($this->_curl, CURLOPT_HTTPGET, true);
  181.             self::log("Sending GET request to \"$url\"", self::LOG_DEBUG);
  182.         }
  183.         elseif ($method == 'post') {
  184.             curl_setopt($this->_curl, CURLOPT_POST, true);
  185.             $postfields = array();
  186.             foreach($data as $key => $value) {
  187.                 $postfields[] = $key . '=' . $value;
  188.             }
  189.             $postfields = implode('&', $postfields);
  190.             curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $postfields);
  191.             self::log("Sending POST request to \"$url\" with data: $postfields", self::LOG_DEBUG);
  192.         }
  193.        
  194.         // Debug
  195.         curl_setopt($this->_curl, CURLOPT_VERBOSE, $this->debug ? true : false);
  196.        
  197.         $result = curl_exec($this->_curl);
  198.         if ($result === false) {
  199.             self::throwException('cURL error ' . curl_errno($this->_curl));
  200.         }
  201.        
  202.         // Extract headers
  203.         list($rawHeaders, $body) = explode("\r\n\r\n", $result, 2);
  204.         $headers = self::_parseHttpHeaders($rawHeaders);
  205.        
  206.         return array(
  207.             'info'      => curl_getinfo($this->_curl),
  208.             'headers'   => $headers,
  209.             'body'      => $body,
  210.         );
  211.     }
  212.    
  213.     /**
  214.      * Helper for building cURL requests after authentication with
  215.      * the Neufbox.
  216.      *
  217.      * @param string $path
  218.      * @param string $method
  219.      * @param array $data
  220.      * @return array
  221.      * @throws Exception if the request failed.
  222.      */
  223.     protected function _sendRequest($path = '/', $method = 'get', $data = array(), $headers = null) {
  224.         if (!$this->_sessionId) {
  225.             $this->log("No session, initializing...", self::LOG_DEBUG);
  226.             $this->_login();
  227.         }
  228.         $res = $this->_sendRawRequest($path, $method, $data, $headers, $this->_cookie);
  229.        
  230.         // Redirect means that session is invalid
  231.         if ($res['info']['http_code'] == 302 || false !== strpos($res['body'], 'access_lock')) {
  232.             $this->log("Session lost, attempting to renew...", self::LOG_DEBUG);
  233.            
  234.             // Force new login
  235.             $this->_login(true);
  236.            
  237.             // Then try the original request again
  238.             $res = $this->_sendRawRequest($path, $method, $data, $headers, $this->_cookie);
  239.             if ($res['info']['http_code'] != 200) {
  240.                 self::throwException('Cannot reconnect to Neufbox. Aborting');
  241.             }
  242.         }
  243.         return $res;
  244.     }
  245.    
  246.     /**
  247.      *
  248.      * @param string $html
  249.      * @param string $encoding
  250.      * @return DOMXPath
  251.      */
  252.     protected function _htmlToDOMXPath($html, $encoding = 'iso-8859-1') {
  253.         $dom = new DOMDocument('1.0', $encoding);
  254.         @$dom->loadHTML($html);
  255.         $xpathDom = new DOMXPath($dom);
  256.        
  257.         return $xpathDom;
  258.     }
  259.    
  260.     public function login($login, $password) {
  261.         $this->_login = $login;
  262.         $this->_password = $password;
  263.         //$this->_login(true);
  264.     }
  265.    
  266.     public function logout() {
  267.         $this->log('Logging out', self::LOG_NOTICE);
  268.         $this->_sessionId = null;
  269.     }
  270.    
  271.     public function getHost() {
  272.         return $this->_host;
  273.     }
  274.    
  275.     /**
  276.      *
  277.      * @return int
  278.      */
  279.     public function getIpv4Status() {
  280.         $this->log("Retrieving IPv4 status...");
  281.         $res = $this->_sendRequest('/state');
  282.         return $this->_getStatusFromNodeCss($res['body'], '//td[@id="internet_status"]');
  283.     }
  284.    
  285.     /**
  286.      *
  287.      * @return int
  288.      */
  289.     public function getIpv6Status() {
  290.         $this->log("Retrieving IPv6 status...");
  291.         $res = $this->_sendRequest('/state');
  292.         return $this->_getStatusFromNodeCss($res['body'], '//td[@id="internet_status_v6"]');
  293.     }
  294.    
  295.     /**
  296.      *
  297.      * @return int
  298.      */
  299.     public function getPhoneStatus() {
  300.         $this->log("Retrieving phone status...");
  301.         $res = $this->_sendRequest('/state');
  302.         return $this->_getStatusFromNodeCss($res['body'], '//td[@id="voip_status"]');
  303.     }
  304.    
  305.     /**
  306.      *
  307.      * @return int
  308.      */
  309.     public function getWifiStatus() {
  310.         $this->log("Retrieving Wifi status...");
  311.         $res = $this->_sendRequest('/wifi');
  312.         return $this->_getStatusFromNodeCss($res['body'], '//table[@id="wifi_info"]/*/td[1]');
  313.     }
  314.    
  315.     /**
  316.      *
  317.      * @return int
  318.      */
  319.     public function getHotspotStatus() {
  320.         $this->log("Retrieving hotspot status...");
  321.         $res = $this->_sendRequest('/hotspot');
  322.         return $this->_getStatusFromNodeCss($res['body'], '//td[@id="hotspot_status"]');
  323.     }
  324.    
  325.     /**
  326.      *
  327.      * @return int
  328.      */
  329.     public function getTelevisionStatus() {
  330.         $this->log("Retrieving TV status...");
  331.         $res = $this->_sendRequest('/state');
  332.         return $this->_getStatusFromNodeCss($res['body'], '//td[@id="tv_status"]');
  333.     }
  334.    
  335.     /**
  336.      *
  337.      * @return array
  338.      */
  339.     public function getModemInfo() {
  340.         $this->log("Retrieving modem info...");
  341.         $res = $this->_sendRequest('/state');
  342.         return $this->_getTableDataAsArray($res['body'], '//table[@id="modem_infos"]');
  343.     }
  344.    
  345.     /**
  346.      *
  347.      * @return array
  348.      */
  349.     public function getIpv4ConnectionInfo() {
  350.         $this->log("Retrieving IPv4 info...");
  351.         $res = $this->_sendRequest('/state/wan');
  352.         return $this->_getTableDataAsArray($res['body'], '//table[@id="wan_info"]');
  353.     }
  354.    
  355.     /**
  356.      *
  357.      * @return array
  358.      */
  359.     public function getIpv6ConnectionInfo() {
  360.         $this->log("Retrieving IPv6 info...");
  361.         $res = $this->_sendRequest('/state/wan');
  362.         return $this->_getTableDataAsArray($res['body'], '//table[@id="ipv6_info"]');
  363.     }
  364.    
  365.     /**
  366.      *
  367.      * @return array
  368.      */
  369.     public function getAdslInfo() {
  370.         $this->log("Retrieving ADSL info...");
  371.         $res = $this->_sendRequest('/state/wan');
  372.         return $this->_getTableDataAsArray($res['body'], '//table[@id="adsl_info"]');
  373.     }
  374.    
  375.     /**
  376.      *
  377.      * @return array
  378.      */
  379.     public function getPppInfo() {
  380.         $this->log("Retrieving PPP info...");
  381.         $res = $this->_sendRequest('/state/wan');
  382.         return $this->_getTableDataAsArray($res['body'], '//table[@id="ppp_info"]');
  383.     }
  384.    
  385.     /**
  386.      *
  387.      * @return array
  388.      */
  389.     public function getConnectedHosts() {
  390.         $this->log("Retrieving connected hosts list...");
  391.         $res = $this->_sendRequest('/network');
  392.         return $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="network_clients"]');
  393.     }
  394.    
  395.     /**
  396.      *
  397.      * @return array
  398.      */
  399.     public function getPortsInfo() {
  400.         $this->log("Retrieving ports info...");
  401.         $res = $this->_sendRequest('/network');
  402.         return $this->_getTableDataAsArray($res['body'], '//table[@id="network_status"]');
  403.     }
  404.    
  405.     /**
  406.      *
  407.      * @return array
  408.      */
  409.     public function getWifiInfo() {
  410.         $this->log("Retrieving Wifi info...");
  411.         $res = $this->_sendRequest('/wifi');
  412.         return $this->_getTableDataAsArray($res['body'], '//table[@id="wifi_info"]');
  413.     }
  414.    
  415.     /**
  416.      *
  417.      * @return array
  418.      */
  419.     public function getNatConfig() {
  420.         $this->log("Retrieving NAT configuration...");
  421.         $res = $this->_sendRequest('/network/nat');
  422.         $return = $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="nat_config"]');
  423.        
  424.         //Remove last line (used to add a new NAT rule from the GUI)
  425.         array_pop($return);
  426.        
  427.         // Remove the last two columns from rows (used to enable/disable and delete rules from the GUI)
  428.         foreach($return as &$row) {
  429.             array_pop($row);
  430.             array_pop($row);
  431.         }
  432.        
  433.         return $return;
  434.     }
  435.    
  436.     /**
  437.      *
  438.      * @return array
  439.      */
  440.     public function getPhoneCallHistory() {
  441.         $this->log("Retrieving phone call history...");
  442.         $res = $this->_sendRequest('/state/voip');
  443.         return $this->_getTableDataAsArrayWithHeaders($res['body'], '//table[@id="call_history_list"]');
  444.     }
  445.    
  446.     public function getFullReport() {
  447.         $report = array();
  448.         $myMethods = get_class_methods($this);
  449.         $excludedMethods = array('getHost', 'getFullReport', 'getStatusAsString');
  450.         sort($myMethods);
  451.        
  452.         foreach($myMethods as $methodName) {
  453.             if (substr($methodName, 0, 3) == 'get' && !in_array($methodName, $excludedMethods)) {
  454.                 $key = self::_uncamelize(substr($methodName, 3));
  455.                 $report[$key] = call_user_func(array($this, $methodName));
  456.             }
  457.         }
  458.    
  459.         return $report;
  460.     }
  461.    
  462.     public function reboot() {
  463.         $this->log("Rebooting device...", self::LO);
  464.         $res = $this->_sendRequest('/reboot', 'post', array('submit' => ''));
  465.         if (200 != ($code = $res['info']['http_code'])) {
  466.             self::throwException("Reboot may have failed: unexpected code HTTP $code returned");
  467.         }
  468.         $this->log("Reboot command sent successfully");
  469.     }
  470.    
  471.     /**
  472.      * Enable or disable Wifi.
  473.      *
  474.      * @param boolean $enable
  475.      */
  476.     public function enableWifi($enable = true) {
  477.         $res = $this->_sendRequest('/wifi/config', 'get');
  478.         $formData = $this->_getFormValues($res['body'], '//table[@id="access_point_config"]');
  479.        
  480.         // Override enable/disable value
  481.         if ($enable) {
  482.             $this->log("Enabling Wifi...");
  483.             $formData['ap_active'] = 'on';
  484.         }
  485.         else {
  486.             $this->log("Disabling Wifi...");
  487.             $formData['ap_active'] = 'off';
  488.         }
  489.        
  490.         $res = $this->_sendRequest('/wifi/config', 'post', $formData);
  491.         if (200 != ($code = $res['info']['http_code'])) {
  492.             self::throwException("Wifi " . ($enable ? '' : 'de') . "activation may have failed: unexpected code HTTP $code returned");
  493.         }
  494.         $this->log("Wifi " . ($enable ? '' : 'de') . "activation command sent successfully");
  495.     }
  496.    
  497.     public function disableWifi() {
  498.         $this->enableWifi(false);
  499.     }
  500.    
  501.     /**
  502.      * Set Wifi config.
  503.      *
  504.      * @param array $config Available values:
  505.      *      ap_active   => on/off           [You should use enableWifi() instead]
  506.      *      ap_ssid     => (string)
  507.      *      ap_closed   => 0/1
  508.      *      ap_channel  => auto/0/1/2/.../13
  509.      *      ap_mode     => auto/11b/11g
  510.      */
  511.     public function setWifiConfig($config) {
  512.         $res = $this->_sendRequest('/wifi/config', 'get');
  513.         $formData = $this->_getFormValues($res['body'], '//table[@id="access_point_config"]');
  514.        
  515.         $this->log("Setting Wifi configuration...");
  516.         $formData = array_merge($formData, $config);
  517.        
  518.         $res = $this->_sendRequest('/wifi/config', 'post', $formData);
  519.         if (200 != ($code = $res['info']['http_code'])) {
  520.             self::throwException("Wifi configuration may have failed: unexpected code HTTP $code returned");
  521.         }
  522.         $this->log("Wifi configuration set successfully");
  523.     }
  524.    
  525.     /**
  526.      * Set Wifi security config.
  527.      *
  528.      * @param array $config Available values:
  529.      *      wlan_encryptiontype => OPEN/WEP/WPA-PSK/WPA2-PSK/WPA-WPA2-PSK
  530.      *      wlan_keytype        => ascii/hexa
  531.      *      wlan_wepkey         => (string)
  532.      *      wlan_wpakey         => (string)
  533.      */
  534.     public function setWifiSecurity($config) {
  535.         $res = $this->_sendRequest('/wifi/security', 'get');
  536.         $formData = $this->_getFormValues($res['body'], '//table[@id="wlan_encryption"]');
  537.        
  538.         $this->log("Setting Wifi configuration...");
  539.         $formData = array_merge($formData, $config);
  540.        
  541.         $res = $this->_sendRequest('/wifi/security', 'post', $formData);
  542.         if (200 != ($code = $res['info']['http_code'])) {
  543.             self::throwException("Wifi configuration may have failed: unexpected code HTTP $code returned");
  544.         }
  545.         $this->log("Wifi configuration set successfully");
  546.     }
  547.  
  548.     /**
  549.      *
  550.      * @param boolean $enable
  551.      * @param string $mode "sfr" or "sfr_fon"
  552.      */
  553.     public function enableHotspot($enable = true, $mode = 'sfr') {
  554.         $res = $this->_sendRequest('/hotspot/config', 'get');
  555.         $formData = $this->_getFormValues($res['body'], '//table[@id="hotspot_config"]');
  556.        
  557.         // Override enable/disable value
  558.         if ($enable) {
  559.             $this->log("Enabling hotspot...");
  560.             $formData['hotspot_active'] = 'on';
  561.             $formData['hotspot_mode'] = $mode;
  562.             $formData['hotspot_conditions'] = 'accept';
  563.         }
  564.         else {
  565.             $this->log("Disabling hotspot...");
  566.             $formData['hotspot_active'] = 'off';
  567.         }
  568.        
  569.         $res = $this->_sendRequest('/hotspot/config', 'post', $formData);
  570.         if (200 != ($code = $res['info']['http_code'])) {
  571.             self::throwException("Hotspot " . ($enable ? '' : 'de') . "activation may have failed: unexpected code HTTP $code returned");
  572.         }
  573.         $this->log("Hotspot " . ($enable ? '' : 'de') . "activation command sent successfully");
  574.        
  575.         if ($enable && $this->getWifiStatus() != self::STATUS_CONNECTED) {
  576.             $this->log('Hotspot cannot be active if Wifi is off', self::LOG_WARNING);
  577.         }
  578.     }
  579.    
  580.     public function disableHotspot() {
  581.         $this->enableHotspot(false);
  582.     }
  583.    
  584.     /**
  585.      * Exports the user configuration to the specified file.
  586.      *
  587.      * @param string $filename
  588.      */
  589.     public function exportUserConfig($filename) {
  590.         $this->log('Exporting user config...', self::LOG_NOTICE);
  591.         $res = $this->_sendRequest('/maintenance/system', 'post', array(
  592.             'action'    => 'config_user_export',
  593.         ));
  594.         if (200 != ($code = $res['info']['http_code'])) {
  595.             self::throwException("Cannot export user config: unexpected code HTTP $code returned");
  596.         }
  597.         if (false === file_put_contents($filename, $res['body'])) {
  598.             self::throwException("Cannot export user config: unable to write data to $filename");
  599.         }
  600.         $this->log("User config exported successfully to $filename", self::LOG_NOTICE);
  601.     }
  602.    
  603.    
  604.     ///////////////////////////////////////////////////////////////////////////
  605.     //          HELPERS
  606.     ///////////////////////////////////////////////////////////////////////////
  607.    
  608.     /**
  609.      *
  610.      * @param string $html
  611.      * @param string $xpath
  612.      * @return int
  613.      */
  614.     protected function _getStatusFromNodeCss($html, $xpath) {
  615.         $dom = $this->_htmlToDOMXPath($html);
  616.         $entries = $dom->query($xpath);
  617.         if (null === $entries->item(0)) {
  618.             self::throwException('Cannot find node at XPath "' . $xpath . '"');
  619.         }
  620.         $entry = $entries->item(0);
  621.        
  622.         $status = null;
  623.         switch($entry->attributes->getNamedItem('class')->nodeValue) {
  624.             case 'enabled':
  625.                 $status = self::STATUS_CONNECTED;
  626.                 break;
  627.                
  628.             case 'disabled':
  629.                 $status = self::STATUS_NOT_CONNECTED;
  630.                 break;
  631.                
  632.             case 'unused':
  633.                 $status = self::STATUS_UNUSED;
  634.                 break;
  635.         }
  636.         return $status;
  637.     }
  638.    
  639.     /**
  640.      *
  641.      * @param string $html
  642.      * @param string $xpath XPath to the table holding data to be retrieved
  643.      * @return array
  644.      */
  645.     protected function _getTableDataAsArray($html, $xpath) {
  646.         $dom = $this->_htmlToDOMXPath($html);
  647.         $entries = $dom->query($xpath);
  648.         if (null === $entries->item(0) || $entries->item(0)->nodeName != 'table') {
  649.             self::throwException('Cannot find <table> node at XPath "' . $xpath . '"');
  650.         }
  651.         $entry = $entries->item(0);
  652.        
  653.         $data = array();
  654.         foreach($entry->childNodes as $childNode) {
  655.             /** $childNode <tr> */
  656.             $label = '';
  657.             $value = '';
  658.             foreach($childNode->childNodes as $node) {
  659.                 if ($node->nodeName == 'th') {
  660.                     $label = self::_normalizeText($node->textContent);
  661.                 }
  662.                 if ($node->nodeName == 'td') {
  663.                     $value = self::_normalizeText($node->textContent);
  664.                 }
  665.             }
  666.             if ($label && $value) {
  667.                 $data[$label] = $value;
  668.             }
  669.         }
  670.         return $data;
  671.     }
  672.    
  673.     /**
  674.      *
  675.      * @param string $html
  676.      * @param string $xpath XPath to the table holding data to be retrieved
  677.      * @return array
  678.      */
  679.     protected function _getTableDataAsArrayWithHeaders($html, $xpath) {
  680.         $dom = $this->_htmlToDOMXPath($html);
  681.         $entries = $dom->query($xpath);
  682.         if (null === $entries->item(0) || $entries->item(0)->nodeName != 'table') {
  683.             self::throwException('Cannot find <table> node at XPath "' . $xpath . '"');
  684.         }
  685.         $entry = $entries->item(0);
  686.        
  687.         $data = array();
  688.        
  689.         // Cols
  690.         $cols = array();
  691.         $nodeList = $dom->query($xpath . '/thead/tr/th');
  692.         foreach($nodeList as $node) {
  693.             if ($node->nodeName == 'th') {
  694.                 $cols[] = self::_normalizeText($node->textContent);
  695.             }
  696.         }
  697.        
  698.         // Rows
  699.         $rows = array();
  700.         $rowNodeList = $dom->query($xpath . '/tbody/tr');
  701.         $i = 0;
  702.         foreach($rowNodeList as $rowNode) {
  703.             $i++;
  704.             $j = 0;
  705.             foreach($rowNode->childNodes as $cellNode) {
  706.                 if ($cellNode->nodeName == 'td') {
  707.                     $colName = isset($cols[$j]) ? $cols[$j] : "{Column $j}";
  708.                     $j++;
  709.                    
  710.                     // Normal text node
  711.                     if ($value = self::_normalizeText($cellNode->textContent)) {
  712.                         $data[$i][$colName] = $value;
  713.                     }
  714.                     else {
  715.                         foreach($cellNode->childNodes as $subCellNode) {
  716.                             // Image node: retrieve "alt" attribute as text value
  717.                             if ($subCellNode->nodeName == 'img') {
  718.                                 if ($value = $subCellNode->getAttribute('alt')) {
  719.                                     $data[$i][$colName] = self::_normalizeText($value);
  720.                                 }
  721.                             }
  722.                         }
  723.                     }
  724.                    
  725.                     // Fallback
  726.                     if (!isset($data[$i][$colName])) {
  727.                         $data[$i][$colName] = '';
  728.                     }
  729.                 }
  730.             }
  731.         }
  732.         return $data;
  733.     }
  734.    
  735.     /**
  736.      *
  737.      * @param string $html
  738.      * @param string $xpath XPath to the form
  739.      * @return DOMNodeList
  740.      */
  741.     protected function _getFormValues($html, $xpath) {
  742.         $dom = $this->_htmlToDOMXPath($html);
  743.         $inputNodes = $dom->query($xpath . '//input | ' . $xpath . '//select');
  744.        
  745.         $formData = array();
  746.         foreach($inputNodes as $inputNode) {
  747.             $attributes = $inputNode->attributes;
  748.             if ($inputNode->nodeName == 'input') {
  749.                 switch($attributes->getNamedItem('type')->nodeValue) {
  750.                     case 'radio':
  751.                         if ($attributes->getNamedItem('checked') && $attributes->getNamedItem('checked')->nodeValue == 'checked') {
  752.                             $formData[$attributes->getNamedItem('name')->nodeValue] = $attributes->getNamedItem('value')->nodeValue;
  753.                         }
  754.                         break;
  755.                        
  756.                     default:
  757.                         $formData[$attributes->getNamedItem('name')->nodeValue] = $attributes->getNamedItem('value')->nodeValue;
  758.                         break;
  759.                 }
  760.             }
  761.             elseif($inputNode->nodeName == 'select') {
  762.                 $optionNodes = $inputNode->getElementsByTagName('option');
  763.                 foreach($optionNodes as $optionNode) {
  764.                     if ($optionNode->attributes->getNamedItem('selected') && $optionNode->attributes->getNamedItem('selected')->nodeValue == 'selected') {
  765.                         $formData[$attributes->getNamedItem('name')->nodeValue] = $optionNode->attributes->getNamedItem('value')->nodeValue;
  766.                     }
  767.                 }
  768.             }
  769.         }
  770.         return $formData;
  771.     }
  772.    
  773.     public function log($msg, $level = self::LOG_NOTICE) {
  774.         if ($level >= $this->logLevel) {
  775.             echo self::formatLog($msg, $level);
  776.         }
  777.     }
  778.    
  779.     public static function formatLog($msg, $level = self::LOG_DEBUG) {
  780.         switch($level) {
  781.             case self::LOG_NOTICE:
  782.                 $level = 'NOTICE';
  783.                 break;
  784.            
  785.             case self::LOG_WARNING:
  786.                 $level = 'WARN';
  787.                 break;
  788.            
  789.             case self::LOG_ERROR:
  790.                 $level = 'ERROR';
  791.                 break;
  792.                
  793.             default:
  794.                 $level = 'DEBUG';
  795.                 break;
  796.         }
  797.         return date('Y-m-d H:i:s') . " [$level] " . print_r($msg, true) . "\n";
  798.     }
  799.    
  800.     public function throwException($msg) {
  801.         $this->log($msg, self::LOG_ERROR);
  802.         throw new Exception($msg);
  803.     }
  804.    
  805.     /**
  806.      * @see http://www.php.net/manual/en/function.http-parse-headers.php#77241
  807.      * @param string $rawHeaders
  808.      * @return array
  809.      */
  810.     protected static function _parseHttpHeaders($rawHeaders) {
  811.          $retVal = array();
  812.          $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $rawHeaders));
  813.          foreach( $fields as $field ) {
  814.              if( preg_match('/([^:]+): (.+)/m', $field, $match) ) {
  815.                  $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
  816.                  if( isset($retVal[$match[1]]) ) {
  817.                      $retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
  818.                  } else {
  819.                      $retVal[$match[1]] = trim($match[2]);
  820.                  }
  821.              }
  822.          }
  823.          return $retVal;
  824.     }
  825.    
  826.     /**
  827.      *
  828.      * @param int $status
  829.      * @return string
  830.      */
  831.     public static function getStatusAsString($status) {
  832.         $string = null;
  833.         switch ($status) {
  834.             case self::STATUS_CONNECTED:
  835.                 $string = 'Connected';
  836.                 break;
  837.             case self::STATUS_CONNECTING:
  838.                 $string = 'Connecting';
  839.                 break;
  840.             case self::STATUS_UNUSED:
  841.                 $string = 'Uused';
  842.                 break;
  843.             case self::STATUS_NOT_CONNECTED:
  844.                 $string = 'Not Connected';
  845.                 break;
  846.         }
  847.         return $string;
  848.     }
  849.    
  850.     protected static function _normalizeText($text) {
  851.         return trim(preg_replace('/\s+/', ' ', str_replace(array("\n", "\r\n"), '', $text)));
  852.     }
  853.    
  854.     protected function _uncamelize($string) {
  855.        return strtolower(preg_replace('/(.)([A-Z])/', '$1_$2', $string));
  856.     }
  857.    
  858.     public static function checkRequirements() {
  859.         $classes = array(
  860.             'DOMDocument',
  861.             'DOMXPath',
  862.             'SimpleXMLElement'
  863.         );
  864.         $functions = array(
  865.             'curl_init'
  866.         );
  867.        
  868.         if (-1 == version_compare(phpversion(), '5.1.2')) {
  869.             echo "WARNING: PHP 5.1.2 or above is required.\n";
  870.         }
  871.         foreach($classes as $class) {
  872.             if (!class_exists($class)) {
  873.                 throw new Exception("Missing required class/library: '$class'. Please check your PHP configuration");
  874.             }
  875.         }
  876.         foreach($functions as $function) {
  877.             if (!function_exists($function)) {
  878.                 throw new Exception("Missing required function/library: '$function'. Please check your PHP configuration");
  879.             }
  880.         }
  881.     }
  882. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement