PEAR(); if (is_array($args)) { if ($args['option']=='compress') { $this->_OPT = 'compress'; $this->_SPACER = ''; $this->_CRLF = ''; } } $this->_SESSID = getmypid(); $this->_MSGCNT = 0; $this->_log('raw','i','OPS Raw Log:'); $this->_log('raw','i','Initialized '.date('r') ); $this->_log('xml','i','OPS XML Log:'); $this->_log('xml','i','Initialized '.date('r') ); } /** * Writes a message to a socket (buffered IO) * * @param int socket handle * * @param string message to write * */ function writeData(&$fh,$msg) { $len = strlen($msg); fputs($fh, 'Content-Length: ' . $len . $this->CRLF . $this->CRLF); fputs($fh, $msg, $len ); $this->_log('raw', 'w', $msg, $len); } /** * Encodes and writes a message to a socket * * @param int socket handle * * @param string message to encode and write * */ function writeMessage(&$fh, $hr ) { $msg = $this->encode( $hr ); $this->writeData($fh, $msg ); } /** * Reads data from a socket * * @param int socket handle * * @param int timeout for read * * @return mixed buffer with data, or an error for a short read * */ function readData(&$fh, $timeout=5) { $len = 0; /* PHP doesn't have timeout for fread ... we just set the timeout for the socket */ socket_set_timeout($fh, $timeout); $line = fgets($fh, 4000); if ($this->socketStatus($fh)) { return false; } if (!$len && preg_match('/^\s*Content-Length:\s+(\d+)\s*\r\n/i', $line, $matches ) ) { $len = (int)$matches[1]; } else { $this->_log('raw', 'e', 'UNEXPECTED READ: No Content-Length' ); $this->_log('raw', 'r', $line); return false; } /* read the empty line */ $line = fread($fh, 2); if ($this->socketStatus($fh)) { return false; } if ($line!=$this->CRLF) { $this->_log('raw', 'e', 'UNEXPECTED READ: No CRLF'); $this->_log('raw', 'r', $line); return false; } $line = ''; while (strlen($line) < $len) { $line .= fread($fh, $len); if ($this->socketStatus($fh)) { return false; } } if ($line) { $buf = $line; $this->_log('raw', 'r', $line); } else { $buf = false; $this->_log('raw', 'e', 'NEXT LINE SHORT READ (should be '.$len.')' ); $this->_log('raw', 'r', $line); } return $buf; } /** * Reads and decodes data from a socket * * @param int socket handle * * @param int timeout for read * * @return mixed associative array of data, or an error * */ function readMessage(&$fh, $timeout=5) { $buf = $this->readData($fh, $timeout); return ( $buf ? $this->decode($buf) : false ); } /** * Checks a socket for timeout or EOF * * @param int socket handle * * @return boolean true if the socket has timed out or is EOF * */ function socketStatus(&$fh) { $return = false; if (is_resource($fh)) { $temp = socket_get_status($fh); if ($temp['timed_out']) { $this->_log('raw', 'e', 'SOCKET TIMED OUT'); $return = true; } if ($temp['eof']) { $this->_log('raw', 'e', 'SOCKET EOF'); $return = true; } unset($temp); } return $return; } /** * Internal method to generate error codes hashes * * @param int error code * * @param string error message * * @return array error hash * */ function _opsError($err_code,$err_text) { return array( 'response_code' => $err_code, 'response_text' => $err_text, 'is_success' => 0 ); } # # DECODING METHODS # Converts XML OPS messages into PHP data # /** * Accepts an OPS protocol message or an file handle * and decodes the data into a PHP array * * @param string OPS message * * @return mixed PHP array, or error * */ function decode($in) { $ops_msg = ''; /* determine if we were passed a string or file handle */ if (is_resource($in)) { # read the file into a string, then process as usual while (!feof($in)) { $ops_msg .= fgets($in, 400); } } else { $ops_msg = $in; } /* log it first */ $this->_log('xml', 'r', $ops_msg); /* decode and return */ return $this->XML2PHP($ops_msg); } /** * XML Parser that converts an OPS protocol message into a PHP array * * @param string OPS message * * @return mixed PHP array, or error * */ function XML2PHP($msg) { $this->_data = NULL; $xp = xml_parser_create(); xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false); xml_parser_set_option($xp, XML_OPTION_SKIP_WHITE, true); xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, 'ISO-8859-1'); if (!xml_parse_into_struct($xp,$msg,$vals,$index)) { $error = sprintf('XML error: %s at line %d', xml_error_string(xml_get_error_code($xp)), xml_get_current_line_number($xp) ); xml_parser_free($xp); return $this->raiseError($error); } xml_parser_free($xp); $temp = $depth = array(); foreach($vals as $value) { switch ($value['tag']) { case 'OPS_envelope': case 'header': case 'body': case 'data_block': break; case 'version': case 'msg_id': case 'msg_type': $key = '_OPS_' . $value['tag']; $temp[$key] = $value['value']; break; case 'item': $key = $value['attributes']['key']; switch ($value['type']) { case 'open': array_push($depth, $key); break; case 'complete': array_push($depth, $key); $p = join('::',$depth); $temp[$p] = $value['value']; array_pop($depth); break; case 'close': array_pop($depth); break; } break; case 'dt_assoc': case 'dt_array': break; } } foreach ($temp as $key=>$value) { $levels = explode('::',$key); $num_levels = count($levels); if ($num_levels==1) { $this->_data[$levels[0]] = $value; } else { $pointer = &$this->_data; for ($i=0; $i<$num_levels; $i++) { if ( !isset( $pointer[$levels[$i]] ) ) { $pointer[$levels[$i]] = array(); } $pointer = &$pointer[$levels[$i]]; } $pointer = $value; } } return ($this->_data); } # # ENCODING METHODS # Converts PHP data into XML OPS messages # /** * Converts a PHP array into an OPS message * * @param array PHP array * * @return string OPS XML message * */ function encode($array) { $this->_MSGCNT++; $msg_id = $this->_SESSID + $this->_MSGCNT; /* addition removes the leading zero */ $msg_type = $this->_MSGTYPE_STD; if ($array['protocol']) { $array['protocol'] = strtoupper($array['protocol']); } if ($array['action']) { $array['action'] = strtoupper($array['action']); } if ($array['object']) { $array['object'] = strtoupper($array['object']); } $xml_data_block = $this->PHP2XML($array); $ops_msg = '' . $this->_CRLF . '' . $this->_CRLF . '' . $this->_CRLF . $this->_SPACER . '
' . $this->_CRLF . $this->_SPACER . $this->_SPACER . '' . $this->_OPS_VERSION . '' . $this->_CRLF . $this->_SPACER . $this->_SPACER . '' . $msg_id . '' . $this->_CRLF . $this->_SPACER . $this->_SPACER . '' . $msg_type . '' . $this->_CRLF . $this->_SPACER . '
' . $this->_CRLF . $this->_SPACER . '' . $this->_CRLF . $xml_data_block . $this->_CRLF . $this->_SPACER . '' . $this->_CRLF . '
'; # log it $this->_log('xml', 'w', $ops_msg); return $ops_msg; } /** * Converts a PHP array into an OPS data_block tag * * @param array PHP array * * @return string OPS data_block tag * */ function PHP2XML($data) { return str_repeat($this->_SPACER,2) . '' . $this->_convertData($data, 3) . $this->_CRLF . str_repeat($this->_SPACER,2) . ''; } /** * Recursivly converts PHP data into XML * * @param mixed PHP array or data * * @param int ident level * * @return string XML string * */ function _convertData(&$array, $indent=0) { $string = ''; $IND = str_repeat($this->_SPACER,$indent); if (is_array($array)) { if ($this->_is_assoc($array)) { # HASH REFERENCE $string .= $this->_CRLF . $IND . ''; $end = ''; } else { # ARRAY REFERENCE $string .= $this->_CRLF . $IND . ''; $end = ''; } foreach ($array as $k=>$v) { $indent++; /* don't encode some types of stuff */ if ((gettype($v)=='resource') || (gettype($v)=='user function') || (gettype($v)=='unknown type')) { continue; } $string .= $this->_CRLF . $IND . '_convertData($v, $indent+1); $string .= $this->_CRLF . $IND . ''; } else { $string .= $this->_quoteXMLChars($v) . ''; } $indent--; } $string .= $this->_CRLF . $IND . $end; } else { # SCALAR $string .= $this->_CRLF . $IND . '' . $this->_quoteXMLChars($array) . ''; } return $string; } /** * Quotes special XML characters * * @param string string to quote * * @return string quoted string * */ function _quoteXMLChars($string) { $search = array ('&', '<', '>', "'", '"'); $replace = array ('&', '<', '>', ''', '"'); $string = str_replace($search, $replace, $string); $string = utf8_encode($string); return $string; } /** * Determines if an array is associative or not, since PHP * doesn't really distinguish between the two, but Perl/OPS does * * @param array array to check * * @return boolean true if the array is associative * */ function _is_assoc(&$array) { if (is_array($array)) { foreach ($array as $k=>$v) { if (!is_int($k)) { return true; } } } return false; } /** * Internal loggging method * * @param string which log to log to * * @param string type of log message ('r'ead, 'w'rite, 'i'nfo or 'e'rror) * * @param int message * */ function _log($log, $type, $msg) { $types = array( 'r' => 'read', 'w' => 'write', 'e' => 'error', 'i' => 'info' ); if ($log=='xml') { $this->log[$log][] = sprintf("[% 6s:%06d] %s\n", strtoupper($types[$type]), ($type=='e' || $type=='i') ? 0 : strlen($msg), $msg ); } else { $this->log[$log][] = sprintf("[% 6s:%06d] %s\n", strtoupper($types[$type]), ($type=='e' || $type=='i') ? 0 : strlen($msg), ($type=='e' || $type=='i') ? $msg : bin2hex($msg) ); } } /** * Show internal log * * @param string which log to log show, 'raw' or 'xml' * * @param string format to display: 'html' (default) or 'raw' * */ function showLog($log, $format='html') { echo '
';
		foreach ($this->log[$log] as $line) {
			switch ($format) {
			  case 'raw':
				echo $line . "\n";
				break;
			  case 'html':
			  default:
				echo htmlEntities($line) . "\n";
				break;
			}
		}
		echo '
'; } }