addParam($pars[$i]);
+ }
+ }
+ }
+
+ function xml_header()
+ {
+ return "\n\n";
+ }
+
+ function xml_footer()
+ {
+ return "";
+ }
+
+ function kindOf()
+ {
+ return 'msg';
+ }
+
+ function createPayload($charset_encoding='')
+ {
+ if ($charset_encoding != '')
+ $this->content_type = 'text/xml; charset=' . $charset_encoding;
+ else
+ $this->content_type = 'text/xml';
+ $this->payload=$this->xml_header();
+ $this->payload.='' . $this->methodname . "\n";
+ // if(sizeof($this->params)) {
+ $this->payload.="\n";
+ for($i=0; $iparams); $i++)
+ {
+ $p=$this->params[$i];
+ $this->payload.="\n" . $p->serialize($charset_encoding) .
+ "\n";
+ }
+ $this->payload.="\n";
+ // }
+ $this->payload.=$this->xml_footer();
+ //$this->payload=str_replace("\n", "\r\n", $this->payload);
+ }
+
+ /*
+ * Gets/sets the xmlrpc method to be invoked
+ * @param string $meth the method to be set (leave empty not to set it)
+ * @return string the method that will be invoked
+ * @access public
+ */
+ function method($meth='')
+ {
+ if($meth!='')
+ {
+ $this->methodname=$meth;
+ }
+ return $this->methodname;
+ }
+
+ /*
+ * @return string the xml representation of the message
+ */
+ function serialize($charset_encoding='')
+ {
+ $this->createPayload($charset_encoding);
+ return $this->payload;
+ }
+
+ /*
+ * Add a parameter to the list of parameters to be used upon method invocation
+ * @param xmlrpcval $par
+ * @return boolean false on failure
+ */
+ function addParam($par)
+ {
+ // add check: do not add to self params which are not xmlrpcvals
+ if(is_object($par) && is_a($par, 'xmlrpcval'))
+ {
+ $this->params[]=$par;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /*
+ * @param integer $i the index of the parameter to fetch (zero based)
+ * @return xmlrpcval the i-th parameter
+ */
+ function getParam($i) { return $this->params[$i]; }
+
+ /*
+ * @return integer the number of parameters currently set
+ */
+ function getNumParams() { return sizeof($this->params); }
+
+ /*
+ * @access private
+ * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
+ */
+ function &parseResponseFile($fp)
+ {
+ $ipd='';
+ while($data=fread($fp, 32768))
+ {
+ $ipd.=$data;
+ }
+ //fclose($fp);
+ $r =& $this->parseResponse($ipd);
+ return $r;
+ }
+
+ /**
+ * Parses HTTP headers and separates them from data.
+ * @access private
+ */
+ function &parseResponseHeaders(&$data, $headers_processed=false)
+ {
+ // Strip HTTP 1.1 100 Continue header if present
+ while(ereg('^HTTP/1\.1 1[0-9]{2} ', $data))
+ {
+ $pos = strpos($data, 'HTTP', 12);
+ // server sent a Continue header without any (valid) content following...
+ // give the client a chance to know it
+ if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
+ {
+ break;
+ }
+ $data = substr($data, $pos);
+ }
+ if(!ereg('^HTTP/[0-9.]+ 200 ', $data))
+ {
+ $errstr= substr($data, 0, strpos($data, "\n")-1);
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr);
+ $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
+ return $r;
+ }
+
+ $GLOBALS['_xh']['headers'] = array();
+ $GLOBALS['_xh']['cookies'] = array();
+
+ // be tolerant to usage of \n instead of \r\n to separate headers and data
+ // (even though it is not valid http)
+ $pos = strpos($data,"\r\n\r\n");
+ if($pos || is_int($pos))
+ {
+ $bd = $pos+4;
+ }
+ else
+ {
+ $pos = strpos($data,"\n\n");
+ if($pos || is_int($pos))
+ {
+ $bd = $pos+2;
+ }
+ else
+ {
+ // No separation between response headers and body: fault?
+ $bd = 0;
+ }
+ }
+ // be tolerant to line endings, and extra empty lines
+ $ar = split("\r?\n", trim(substr($data, 0, $pos)));
+ while(list(,$line) = @each($ar))
+ {
+ // take care of multi-line headers and cookies
+ $arr = explode(':',$line,2);
+ if(count($arr) > 1)
+ {
+ $header_name = strtolower(trim($arr[0]));
+ /// @todo some other headers (the ones that allow a CSV list of values)
+ /// do allow many values to be passed using multiple header lines.
+ /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
+ /// instead of replacing it for those...
+ if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
+ {
+ if ($header_name == 'set-cookie2')
+ {
+ // version 2 cookies:
+ // there could be many cookies on one line, comma separated
+ $cookies = explode(',', $arr[1]);
+ }
+ else
+ {
+ $cookies = array($arr[1]);
+ }
+ foreach ($cookies as $cookie)
+ {
+ // glue together all received cookies, using a comma to separate them
+ // (same as php does with getallheaders())
+ if (isset($GLOBALS['_xh']['headers'][$header_name]))
+ $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
+ else
+ $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
+ // parse cookie attributes, in case user wants to coorectly honour then
+ // feature creep: only allow rfc-compliant cookie attributes?
+ $cookie = explode(';', $cookie);
+ foreach ($cookie as $pos => $val)
+ {
+ $val = explode('=', $val, 2);
+ $tag = trim($val[0]);
+ $val = trim(@$val[1]);
+ /// @todo with version 1 cookies, we should strip leading and trailing " chars
+ if ($pos == 0)
+ {
+ $cookiename = $tag;
+ $GLOBALS['_xh']['cookies'][$tag] = array();
+ $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
+ }
+ else
+ {
+ $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
+ }
+ }
+ }
+ }
+ else
+ {
+ $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
+ }
+ }
+ elseif(isset($header_name))
+ {
+ /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
+ $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
+ }
+ }
+ // rebuild full cookie set
+ /*if (isset($GLOBALS['_xh']['headers']['set-cookie']))
+ {
+ $cookies = array();
+ $received = explode(';', $GLOBALS['_xh']['headers']['set-cookie']);
+ foreach($received as $cookie)
+ {
+ list($name, $value) = explode('=', $cookie);
+ $name = trim($name);
+ $value = trim($value);
+ // these values are in fact attributes
+ if ($name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment' && $name != 'Comment')
+ {
+ $cookies[$name] = $value;
+ }
+ }
+ }*/
+
+ $data = substr($data, $bd);
+
+ if($this->debug && count($GLOBALS['_xh']['headers']))
+ {
+ print '';
+ foreach($GLOBALS['_xh']['headers'] as $header => $value)
+ {
+ print "HEADER: $header: $value\n";
+ }
+ foreach($GLOBALS['_xh']['cookies'] as $header => $value)
+ {
+ print "COOKIE: $header={$value['value']}\n";
+ }
+ print "\n";
+ }
+
+ // if CURL was used for the call, http headers have been processed,
+ // and dechunking + reinflating have been carried out
+ if(!$headers_processed)
+ {
+ // Decode chunked encoding sent by http 1.1 servers
+ if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
+ {
+ if(!$data = decode_chunked($data))
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server');
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
+ return $r;
+ }
+ }
+
+ // Decode gzip-compressed stuff
+ // code shamelessly inspired from nusoap library by Dietrich Ayala
+ if(isset($GLOBALS['_xh']['headers']['content-encoding']))
+ {
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
+ {
+ // if decoding works, use it. else assume data wasn't gzencoded
+ if(function_exists('gzinflate'))
+ {
+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzinflate($data))
+ {
+ $data = $degzdata;
+ if($this->debug)
+ print "---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
";
+ }
+ elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
+ {
+ $data = $degzdata;
+ if($this->debug)
+ print "---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
";
+ }
+ else
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server');
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
+ return $r;
+ }
+ }
+ else
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
+ return $r;
+ }
+ }
+ }
+ } // end of 'if needed, de-chunk, re-inflate response'
+
+ // real stupid hack to avoid PHP 4 complaining about returning NULL by ref
+ $r = null;
+ $r =& $r;
+ return $r;
+ }
+
+ /*
+ * @param string $data the xmlrpc response, eventually including http headers
+ * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and conseuqent decoding
+ * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
+ * @access private
+ */
+ function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
+ {
+ //$hdrfnd = 0;
+ if($this->debug)
+ {
+ //by maHo, replaced htmlspecialchars with htmlentities
+ print "---GOT---\n" . htmlentities($data) . "\n---END---\n
";
+ $start = strpos($data, '', $start);
+ $comments = substr($data, $start, $end-$start);
+ print "---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n";
+ }
+ }
+
+ if($data == '')
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.');
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
+ return $r;
+ }
+
+ $GLOBALS['_xh']=array();
+
+ // parse the HTTP headers of the response, if present, and separate them from data
+ if(ereg("^HTTP",$data))
+ {
+ $r =& $this->parseResponseHeaders($data, $headers_processed);
+ if ($r)
+ {
+ return $r;
+ }
+ }
+ else
+ {
+ $GLOBALS['_xh']['headers'] = array();
+ $GLOBALS['_xh']['cookies'] = array();
+ }
+
+
+ // be tolerant of extra whitespace in response body
+ $data = trim($data);
+
+ /// @todo return an error msg if $data=='' ?
+
+ // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
+ // idea from Luca Mariano originally in PEARified version of the lib
+ $bd = false;
+ // Poor man's version of strrpos for php 4...
+ $pos = strpos($data, '');
+ while($pos || is_int($pos))
+ {
+ $bd = $pos+17;
+ $pos = strpos($data, '', $bd);
+ }
+ if($bd)
+ {
+ $data = substr($data, 0, $bd);
+ }
+
+ // if user wants back raw xml, give it to him
+ if ($return_type == 'xml')
+ {
+ $r =& new xmlrpcresp($data, 0, '', 'xml');
+ $r->hdrs = $GLOBALS['_xh']['headers'];
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
+ return $r;
+ }
+
+ // try to 'guestimate' the character encoding of the received response
+ $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
+
+ $GLOBALS['_xh']['stack'] = array();
+ $GLOBALS['_xh']['valuestack'] = array();
+ $GLOBALS['_xh']['isf']=0;
+ $GLOBALS['_xh']['isf_reason']='';
+ $GLOBALS['_xh']['ac']='';
+ $GLOBALS['_xh']['qt']='';
+
+ // if response charset encoding is not known / supported, try to use
+ // the default encoding and parse the xml anyway, but log a warning...
+ if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
+ // the following code might be better for mb_string enabled installs, but
+ // makes the lib about 200% slower...
+ //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
+ {
+ error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding);
+ $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
+ }
+ $parser = xml_parser_create($resp_encoding);
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
+ // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
+ // the xml parser to give us back data in the expected charset
+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
+
+ if ($return_type == 'phpvals')
+ {
+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
+ }
+ else
+ {
+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
+ }
+
+ xml_set_character_data_handler($parser, 'xmlrpc_cd');
+ xml_set_default_handler($parser, 'xmlrpc_dh');
+
+ if(!xml_parse($parser, $data, sizeof($data)))
+ {
+ // thanks to Peter Kocks
+ if((xml_get_current_line_number($parser)) == 1)
+ {
+ $errstr = 'XML error at line 1, check URL';
+ }
+ else
+ {
+ $errstr = sprintf('XML error: %s at line %d',
+ xml_error_string(xml_get_error_code($parser)),
+ xml_get_current_line_number($parser));
+ }
+ error_log($errstr);
+ $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
+ xml_parser_free($parser);
+ if($this->debug)
+ {
+ print $errstr;
+ }
+ $r->hdrs = $GLOBALS['_xh']['headers'];
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
+ return $r;
+ }
+ xml_parser_free($parser);
+ if ($GLOBALS['_xh']['isf'] > 1)
+ {
+ if ($this->debug)
+ {
+ /// @todo echo something for user?
+ }
+
+ $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
+ $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
+ }
+ elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
+ {
+ // then something odd has happened
+ // and it's time to generate a client side error
+ // indicating something odd went on
+ $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
+ $GLOBALS['xmlrpcstr']['invalid_return']);
+ }
+ else
+ {
+ if ($this->debug)
+ {
+ print "---PARSED---\n" ;
+ var_export($GLOBALS['_xh']['value']);
+ print "\n---END---
";
+ } // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
+
+ $v =& $GLOBALS['_xh']['value'];
+
+ if($GLOBALS['_xh']['isf'])
+ {
+ if ($return_type == 'xmlrpcvals')
+ {
+ $errno_v = $v->structmem('faultCode');
+ $errstr_v = $v->structmem('faultString');
+ $errno = $errno_v->scalarval();
+ $errstr = $errstr_v->scalarval();
+ }
+ else
+ {
+ $errno = $v['faultCode'];
+ $errstr = $v['faultString'];
+ }
+
+ if($errno == 0)
+ {
+ // FAULT returned, errno needs to reflect that
+ $errno = -1;
+ }
+
+ $r =& new xmlrpcresp($v, $errno, $errstr);
+ }
+ else
+ {
+ $r=&new xmlrpcresp($v, 0, '', 'phpvals');
+ }
+ }
+
+ $r->hdrs = $GLOBALS['_xh']['headers'];
+ $r->_cookies = $GLOBALS['_xh']['cookies'];
+ return $r;
+ }
+ }
+
+ class xmlrpcval
+ {
+ var $me=array();
+ var $mytype=0;
+ var $_php_class=null;
+
+ function xmlrpcval($val=-1, $type='')
+ {
+ //$this->me=array();
+ //$this->mytype=0;
+ if($val!==-1 || $type!='')
+ {
+ if($type=='')
+ {
+ $type='string';
+ }
+ if($GLOBALS['xmlrpcTypes'][$type]==1)
+ {
+ $this->addScalar($val,$type);
+ }
+ elseif($GLOBALS['xmlrpcTypes'][$type]==2)
+ {
+ $this->addArray($val);
+ }
+ elseif($GLOBALS['xmlrpcTypes'][$type]==3)
+ {
+ $this->addStruct($val);
+ }
+ }
+ }
+
+ function addScalar($val, $type='string')
+ {
+ $typeof=@$GLOBALS['xmlrpcTypes'][$type];
+ if($typeof!=1)
+ {
+ error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($typeof)");
+ return 0;
+ }
+
+ // coerce booleans into correct values
+ // NB: shall we do it for datetimes, integers and doubles, too?
+ if($type==$GLOBALS['xmlrpcBoolean'])
+ {
+ if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
+ {
+ $val=true;
+ }
+ else
+ {
+ $val=false;
+ }
+ }
+
+ switch($this->mytype)
+ {
+ case 1:
+ error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value');
+ return 0;
+ case 3:
+ error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval');
+ return 0;
+ case 2:
+ // we're adding a scalar value to an array here
+ //$ar=$this->me['array'];
+ //$ar[]=&new xmlrpcval($val, $type);
+ //$this->me['array']=$ar;
+ // Faster (?) avoid all the costly array-copy-by-val done here...
+ $this->me['array'][]=&new xmlrpcval($val, $type);
+ return 1;
+ default:
+ // a scalar, so set the value and remember we're scalar
+ $this->me[$type]=$val;
+ $this->mytype=$typeof;
+ return 1;
+ }
+ }
+
+ /// @todo add some checking for $vals to be an array of xmlrpcvals?
+ function addArray($vals)
+ {
+ if($this->mytype==0)
+ {
+ $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
+ $this->me['array']=$vals;
+ return 1;
+ }
+ elseif($this->mytype==2)
+ {
+ // we're adding to an array here
+ $this->me['array'] = array_merge($this->me['array'], $vals);
+ }
+ else
+ {
+ error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']');
+ return 0;
+ }
+ }
+
+ /// @todo add some checking for $vals to be an array?
+ function addStruct($vals)
+ {
+ if($this->mytype==0)
+ {
+ $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
+ $this->me['struct']=$vals;
+ return 1;
+ }
+ elseif($this->mytype==3)
+ {
+ // we're adding to a struct here
+ $this->me['struct'] = array_merge($this->me['struct'], $vals);
+ }
+ else
+ {
+ error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']');
+ return 0;
+ }
+ }
+
+ // poor man's version of print_r ???
+ // DEPRECATED!
+ function dump($ar)
+ {
+ foreach($ar as $key => $val)
+ {
+ echo "$key => $val
";
+ if($key == 'array')
+ {
+ while(list($key2, $val2) = each($val))
+ {
+ echo "-- $key2 => $val2
";
+ }
+ }
+ }
+ }
+
+ function kindOf()
+ {
+ switch($this->mytype)
+ {
+ case 3:
+ return 'struct';
+ break;
+ case 2:
+ return 'array';
+ break;
+ case 1:
+ return 'scalar';
+ break;
+ default:
+ return 'undef';
+ }
+ }
+
+ function serializedata($typ, $val, $charset_encoding='')
+ {
+ $rs='';
+ switch(@$GLOBALS['xmlrpcTypes'][$typ])
+ {
+ case 3:
+ // struct
+ if ($this->_php_class)
+ {
+ $rs.='\n";
+ }
+ else
+ {
+ $rs.="\n";
+ }
+ foreach($val as $key2 => $val2)
+ {
+ $rs.="${key2}\n";
+ //$rs.=$this->serializeval($val2);
+ $rs.=$val2->serialize();
+ $rs.="\n";
+ }
+ $rs.='';
+ break;
+ case 2:
+ // array
+ $rs.="\n\n";
+ for($i=0; $iserializeval($val[$i]);
+ $rs.=$val[$i]->serialize();
+ }
+ $rs.="\n";
+ break;
+ case 1:
+ switch($typ)
+ {
+ case $GLOBALS['xmlrpcBase64']:
+ $rs.="<${typ}>" . base64_encode($val) . "${typ}>";
+ break;
+ case $GLOBALS['xmlrpcBoolean']:
+ $rs.="<${typ}>" . ($val ? '1' : '0') . "${typ}>";
+ break;
+ case $GLOBALS['xmlrpcString']:
+ // G. Giunta 2005/2/13: do NOT use htmlentities, since
+ // it will produce named html entities, which are invalid xml
+ $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "${typ}>";
+ // $rs.="<${typ}>" . htmlentities($val). "${typ}>";
+ break;
+ case $GLOBALS['xmlrpcInt']:
+ case $GLOBALS['xmlrpcI4']:
+ $rs.="<${typ}>".(int)$val."${typ}>";
+ break;
+ case $GLOBALS['xmlrpcDouble']:
+ $rs.="<${typ}>".(double)$val."${typ}>";
+ break;
+ default:
+ // no standard type value should arrive here, but provide a possibility
+ // for xmlrpcvals of unknown type...
+ $rs.="<${typ}>${val}${typ}>";
+ }
+ break;
+ default:
+ break;
+ }
+ return $rs;
+ }
+
+ /**
+ * Return xml representation of the value
+ * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
+ */
+ function serialize($charset_encoding='')
+ {
+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
+ //{
+ reset($this->me);
+ list($typ, $val) = each($this->me);
+ return '' . $this->serializedata($typ, $val, $charset_encoding) . "\n";
+ //}
+ }
+
+ // DEPRECATED
+ function serializeval($o)
+ {
+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
+ //{
+ $ar=$o->me;
+ reset($ar);
+ list($typ, $val) = each($ar);
+ return '' . $this->serializedata($typ, $val) . "\n";
+ //}
+ }
+
+ /**
+ * Checks wheter a struct member with a given name is present.
+ * Works only on xmlrpcvals of type struct.
+ * @param string $m the name of the struct member to be looked up
+ * @return boolean
+ */
+ function structmemexists($m)
+ {
+ return array_key_exists($this->me['struct'][$m]);
+ }
+
+ /*
+ * Returns the value of a given struct member (an xmlrpcval object in itself).
+ * Will raise a php warning if struct member of given name does not exist
+ * @param string $m the name of the struct member to be looked up
+ * @return xmlrpcval
+ */
+ function structmem($m)
+ {
+ return $this->me['struct'][$m];
+ }
+
+ function structreset()
+ {
+ reset($this->me['struct']);
+ }
+
+ function structeach()
+ {
+ return each($this->me['struct']);
+ }
+
+ // DEPRECATED! this code looks like it is very fragile and has not been fixed
+ // for a long long time. Shall we remove it for 2.0?
+ function getval()
+ {
+ // UNSTABLE
+ reset($this->me);
+ list($a,$b)=each($this->me);
+ // contributed by I Sofer, 2001-03-24
+ // add support for nested arrays to scalarval
+ // i've created a new method here, so as to
+ // preserve back compatibility
+
+ if(is_array($b))
+ {
+ @reset($b);
+ while(list($id,$cont) = @each($b))
+ {
+ $b[$id] = $cont->scalarval();
+ }
+ }
+
+ // add support for structures directly encoding php objects
+ if(is_object($b))
+ {
+ $t = get_object_vars($b);
+ @reset($t);
+ while(list($id,$cont) = @each($t))
+ {
+ $t[$id] = $cont->scalarval();
+ }
+ @reset($t);
+ while(list($id,$cont) = @each($t))
+ {
+ //@eval('$b->'.$id.' = $cont;');
+ @$b->$id = $cont;
+ }
+ }
+ // end contrib
+ return $b;
+ }
+
+ /**
+ * Returns the value of a scalar xmlrpcval
+ * @return mixed
+ */
+ function scalarval()
+ {
+ reset($this->me);
+ list(,$b)=each($this->me);
+ return $b;
+ }
+
+ /**
+ * Returns the type of the xmlrpcval.
+ * For integers, 'int' is always returned in place of 'i4'
+ * @return string
+ */
+ function scalartyp()
+ {
+ reset($this->me);
+ list($a,$b)=each($this->me);
+ if($a==$GLOBALS['xmlrpcI4'])
+ {
+ $a=$GLOBALS['xmlrpcInt'];
+ }
+ return $a;
+ }
+
+ /**
+ * Returns the m-th member of an xmlrpcval of struct type
+ * @param integer $m the index of the value to be retrieved (zero based)
+ * @return xmlrpcval
+ */
+ function arraymem($m)
+ {
+ return $this->me['array'][$m];
+ }
+
+ /**
+ * Returns the number of members in an xmlrpcval of array type
+ * @return integer
+ */
+ function arraysize()
+ {
+ return count($this->me['array']);
+ }
+
+ /**
+ * Returns the number of members in an xmlrpcval of struct type
+ * @return integer
+ */
+ function structsize()
+ {
+ return count($this->me['struct']);
+ }
+ }
+
+
+ // date helpers
+ function iso8601_encode($timet, $utc=0)
+ {
+ // return an ISO8601 encoded string
+ // really, timezones ought to be supported
+ // but the XML-RPC spec says:
+ //
+ // "Don't assume a timezone. It should be specified by the server in its
+ // documentation what assumptions it makes about timezones."
+ //
+ // these routines always assume localtime unless
+ // $utc is set to 1, in which case UTC is assumed
+ // and an adjustment for locale is made when encoding
+ if(!$utc)
+ {
+ $t=strftime("%Y%m%dT%H:%M:%S", $timet);
+ }
+ else
+ {
+ if(function_exists('gmstrftime'))
+ {
+ // gmstrftime doesn't exist in some versions
+ // of PHP
+ $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
+ }
+ else
+ {
+ $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
+ }
+ }
+ return $t;
+ }
+
+ function iso8601_decode($idate, $utc=0)
+ {
+ // return a timet in the localtime, or UTC
+ $t=0;
+ if(ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs))
+ {
+ if($utc)
+ {
+ $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
+ }
+ else
+ {
+ $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
+ }
+ }
+ return $t;
+ }
+
+ /**
+ * Takes an xmlrpc value in PHP xmlrpcval object format
+ * and translates it into native PHP types.
+ * Works with xmlrpc message objects as input, too.
+ *
+ * @author Dan Libby (dan@libby.com)
+ *
+ * @param xmlrpcval $xmlrpc_val
+ * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects
+ * @return mixed
+ */
+ function php_xmlrpc_decode($xmlrpc_val, $options=array())
+ {
+ switch($xmlrpc_val->kindOf())
+ {
+ case 'scalar':
+ return $xmlrpc_val->scalarval();
+ case 'array':
+ $size = $xmlrpc_val->arraysize();
+ $arr = array();
+ for($i = 0; $i < $size; $i++)
+ {
+ $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
+ }
+ return $arr;
+ case 'struct':
+ $xmlrpc_val->structreset();
+ // If user said so, try to rebuild php objects for specific struct vals.
+ /// @todo should we raise a warning for class not found?
+ // shall we check for proper subclass of xmlrpcval instead of
+ // presence of _php_class to detect what we can do?
+ if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
+ && class_exists($xmlrpc_val->_php_class))
+ {
+ $obj = @new $xmlrpc_val->_php_class;
+ while(list($key,$value)=$xmlrpc_val->structeach())
+ {
+ $obj->$key = php_xmlrpc_decode($value, $options);
+ }
+ return $obj;
+ }
+ else
+ {
+ $arr = array();
+ while(list($key,$value)=$xmlrpc_val->structeach())
+ {
+ $arr[$key] = php_xmlrpc_decode($value, $options);
+ }
+ return $arr;
+ }
+ case 'msg':
+ $paramcount = $xmlrpc_val->getNumParams();
+ $arr = array();
+ for($i = 0; $i < $paramcount; $i++)
+ {
+ $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
+ }
+ return $arr;
+ }
+ }
+
+ if(function_exists('xmlrpc_decode'))
+ {
+ define('XMLRPC_EPI_ENABLED','1');
+ }
+ else
+ {
+ define('XMLRPC_EPI_ENABLED','0');
+ }
+
+ /**
+ * Takes native php types and encodes them into xmlrpc PHP object format.
+ * It will not re-encode xmlrpcval objects.
+ * Feature creep -- could support more types via optional type argument
+ * (string => datetime support has been added, ??? => base64 not yet)
+ *
+ * @author Dan Libby (dan@libby.com)
+ *
+ * @param mixed $php_val the value to be converted into an xmlrpcval object
+ * @param array $options can include 'encode_php_objs' and 'auto_dates'
+ * @return xmlrpcval
+ */
+ function &php_xmlrpc_encode($php_val, $options=array())
+ {
+ $type = gettype($php_val);
+ $xmlrpc_val =& new xmlrpcval;
+
+ switch($type)
+ {
+ case 'array':
+ // PHP arrays can be encoded to either xmlrpc structs or arrays,
+ // depending on wheter they are hashes or plain 0..n integer indexed
+ // A shorter one-liner would be
+ // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
+ // but execution time skyrockets!
+ $j = 0;
+ $arr = array();
+ $ko = false;
+ foreach($php_val as $key => $val)
+ {
+ $arr[$key] =& php_xmlrpc_encode($val, $options);
+ if(!$ko && $key !== $j)
+ {
+ $ko = true;
+ }
+ $j++;
+ }
+ if($ko)
+ {
+ $xmlrpc_val->addStruct($arr);
+ }
+ else
+ {
+ $xmlrpc_val->addArray($arr);
+ }
+ break;
+ case 'object':
+ if(is_a($php_val, 'xmlrpcval'))
+ {
+ $xmlrpc_val = $php_val;
+ }
+ else
+ {
+ $arr = array();
+ while(list($k,$v) = each($php_val))
+ {
+ $arr[$k] = php_xmlrpc_encode($v, $options);
+ }
+ $xmlrpc_val->addStruct($arr);
+ if (in_array('encode_php_objs', $options))
+ {
+ // let's save original class name into xmlrpcval:
+ // might be useful later on...
+ $xmlrpc_val->_php_class = get_class($php_val);
+ }
+ }
+ break;
+ case 'integer':
+ $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcInt']);
+ break;
+ case 'double':
+ $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDouble']);
+ break;
+ case 'string':
+ if (in_array('auto_dates', $options) && ereg("^[0-9]{8}\T{1}[0-9]{2}\:[0-9]{2}\:[0-9]{2}$", $php_val))
+ $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcDateTime']);
+ else
+ $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcString']);
+ break;
+ //
+ // Add support for encoding/decoding of booleans, since they are supported in PHP
+ case 'boolean':
+ $xmlrpc_val->addScalar($php_val, $GLOBALS['xmlrpcBoolean']);
+ break;
+ //
+ // catch "resource", "NULL", "user function", "unknown type"
+ //case 'unknown type':
+ default:
+ // giancarlo pinerolo
+ // it has to return
+ // an empty object in case (which is already
+ // at this point), not a boolean.
+ break;
+ }
+ return $xmlrpc_val;
+ }
+
+ /**
+ * decode a string that is encoded w/ "chunked" transfer encoding
+ * as defined in rfc2068 par. 19.4.6
+ * code shamelessly stolen from nusoap library by Dietrich Ayala
+ *
+ * @param string $buffer the string to be decoded
+ * @return string
+ */
+ function decode_chunked($buffer)
+ {
+ // length := 0
+ $length = 0;
+ $new = '';
+
+ // read chunk-size, chunk-extension (if any) and crlf
+ // get the position of the linebreak
+ $chunkend = strpos($buffer,"\r\n") + 2;
+ $temp = substr($buffer,0,$chunkend);
+ $chunk_size = hexdec( trim($temp) );
+ $chunkstart = $chunkend;
+ // while(chunk-size > 0) {
+ while($chunk_size > 0)
+ {
+ $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
+
+ // just in case we got a broken connection
+ if($chunkend == false)
+ {
+ $chunk = substr($buffer,$chunkstart);
+ // append chunk-data to entity-body
+ $new .= $chunk;
+ $length += strlen($chunk);
+ break;
+ }
+
+ // read chunk-data and crlf
+ $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
+ // append chunk-data to entity-body
+ $new .= $chunk;
+ // length := length + chunk-size
+ $length += strlen($chunk);
+ // read chunk-size and crlf
+ $chunkstart = $chunkend + 2;
+
+ $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
+ if($chunkend == false)
+ {
+ break; //just in case we got a broken connection
+ }
+ $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
+ $chunk_size = hexdec( trim($temp) );
+ $chunkstart = $chunkend;
+ }
+ return $new;
+ }
+
+ /**
+ * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
+ * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
+ * NB: for php 'resource' types returns empty string, since resources cannot be serialized;
+ * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
+ * @param string $phptype
+ * @return string
+ */
+ function php_2_xmlrpc_type($phptype)
+ {
+ switch(strtolower($phptype))
+ {
+ case 'string':
+ return $GLOBALS['xmlrpcString'];
+ case 'integer':
+ case $GLOBALS['xmlrpcInt']: // 'int'
+ case $GLOBALS['xmlrpcI4']:
+ return $GLOBALS['xmlrpcInt'];
+ case 'double':
+ return $GLOBALS['xmlrpcDouble'];
+ case 'boolean':
+ return $GLOBALS['xmlrpcBoolean'];
+ case 'array':
+ return $GLOBALS['xmlrpcArray'];
+ case 'object':
+ return $GLOBALS['xmlrpcStruct'];
+ case $GLOBALS['xmlrpcBase64']:
+ case $GLOBALS['xmlrpcStruct']:
+ return strtolower($phptype);
+ case 'resource':
+ return '';
+ default:
+ if(class_exists($phptype))
+ {
+ return $GLOBALS['xmlrpcStruct'];
+ }
+ else
+ {
+ // unknown: might be any xmlrpc type
+ return $GLOBALS['xmlrpcValue'];
+ }
+ }
+ }
+
+ /**
+ * Given a user-defined PHP function, create a PHP 'wrapper' function that can
+ * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
+ * clients.
+ *
+ * Since php is a typeless language, to infer types of input and output parameters,
+ * it relies on parsing the javadoc-style comment block associated with the given
+ * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
+ * in the @param tag is also allowed, if you need the php function to receive/send
+ * data in that particular format (note that base64 enncoding/decoding is transparently
+ * carried out by the lib, while datetime vals are passed around as strings)
+ *
+ * Known limitations:
+ * - requires PHP 5.0.3 +
+ * - only works for user-defined functions, not for PHP internal functions
+ * (reflection does not support retrieving number/type of params for those)
+ * - functions returning php objects will generate special xmlrpc responses:
+ * when the xmlrpc decoding of those responses is carried out by this same lib, using
+ * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
+ * In short: php objects can be serialized, too (except for their resource members),
+ * using this function.
+ * Other libs might choke on the very same xml that will be generated in this case
+ * (i.e. it has a nonstandard attribute on struct element tags)
+ * - usage of javadoc @param tags using param names in a different order from the
+ * function prototype is not considered valid (to be fixed?)
+ *
+ * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
+ * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
+ * is by making use of the functions_parameters_type class member.
+ *
+ * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
+ * @return false on error, or an array containing the name of the new php function,
+ * its signature and docs, to be used in the server dispatch map
+ *
+ * @todo decide how to deal with params passed by ref: bomb out or allow?
+ * @todo finish using javadoc info to build method sig if all params are named but out of order
+ * @done switch to some automagic object encoding scheme
+ * @todo add a check for params of 'resource' type
+ * @todo add some trigger_errors when returning false?
+ * @todo what to do when the PHP function returns NULL? we are currently returning bogus responses!!!
+ */
+ function wrap_php_function($funcname, $newfuncname='')
+ {
+ if(version_compare(phpversion(), '5.0.3') == -1)
+ {
+ // up to php 5.0.3 some useful reflection methods were missing
+ return false;
+ }
+ if((is_array($funcname) && !method_exists($funcname[0], $funcname[1])) || !function_exists($funcname))
+ {
+ return false;
+ }
+ else
+ {
+ // determine name of new php function
+ if($newfuncname == '')
+ {
+ if(is_array($funcname))
+ {
+ $xmlrpcfuncname = "xmlrpc_".implode('_', $funcname);
+ }
+ else
+ {
+ $xmlrpcfuncname = "xmlrpc_$funcname";
+ }
+ }
+ else
+ {
+ $xmlrpcfuncname = $newfuncname;
+ }
+ while(function_exists($xmlrpcfuncname))
+ {
+ $xmlrpcfuncname .= 'x';
+ }
+ $code = "function $xmlrpcfuncname(\$msg) {\n";
+
+ // start to introspect PHP code
+ $func =& new ReflectionFunction($funcname);
+ if($func->isInternal())
+ {
+ // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
+ // instead of getparameters to fully reflect internal php functions ?
+ return false;
+ }
+
+ // retrieve parameter names, types and description from javadoc comments
+
+ // function description
+ $desc = '';
+ // type of return val: by default 'any'
+ $returns = $GLOBALS['xmlrpcValue'];
+ // type + name of function parameters
+ $paramDocs = array();
+
+ $docs = $func->getDocComment();
+ if($docs != '')
+ {
+ $docs = explode("\n", $docs);
+ $i = 0;
+ foreach($docs as $doc)
+ {
+ $doc = trim($doc, " \r\t/*");
+ if(strlen($doc) && strpos($doc, '@') !== 0 && !$i)
+ {
+ if($desc)
+ {
+ $desc .= "\n";
+ }
+ $desc .= $doc;
+ }
+ elseif(strpos($doc, '@param') === 0)
+ {
+ // syntax: @param type [$name] desc
+ if(preg_match('/@param\s+(\S+)(\s+\$\S+)?\s+(.+)/', $doc, $matches))
+ {
+ if(strpos($matches[1], '|'))
+ {
+ //$paramDocs[$i]['type'] = explode('|', $matches[1]);
+ $paramDocs[$i]['type'] = 'mixed';
+ }
+ else
+ {
+ $paramDocs[$i]['type'] = $matches[1];
+ }
+ $paramDocs[$i]['name'] = trim($matches[2]);
+ $paramDocs[$i]['doc'] = $matches[3];
+ }
+ $i++;
+ }
+ elseif(strpos($doc, '@return') === 0)
+ {
+ $returns = preg_split("/\s+/", $doc);
+ if(isset($returns[1]))
+ {
+ $returns = php_2_xmlrpc_type($returns[1]);
+ }
+ }
+ }
+ }
+
+ // start introspection of actual function prototype and building of PHP code
+ // to be eval'd
+ $params = $func->getParameters();
+
+ $innercode = '';
+ $i = 0;
+ $parsvariations = array();
+ $pars = array();
+ $pnum = count($params);
+ foreach($params as $param)
+ {
+ if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != '$'.strtolower($param->getName()))
+ {
+ // param name from phpdoc info does not match param definition!
+ $paramDocs[$i]['type'] = 'mixed';
+ }
+
+ if($param->isOptional())
+ {
+ // this particular parameter is optional. save as valid previous list of parameters
+ $innercode .= "if (\$paramcount > $i) {\n";
+ $parsvariations[] = $pars;
+ }
+ $innercode .= "\$p$i = \$msg->getParam($i);\n";
+ $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p$i = \$p{$i}->scalarval(); else \$p$i = php_xmlrpc_decode(\$p$i);\n";
+ $pars[] = "\$p$i";
+ $i++;
+ if($param->isOptional())
+ {
+ $innercode .= "}\n";
+ }
+ if($i == $pnum)
+ {
+ // last allowed parameters combination
+ $parsvariations[] = $pars;
+ }
+ }
+
+ $sigs = array();
+ if(count($parsvariations) == 0)
+ {
+ // only known good synopsis = no parameters
+ $parsvariations[] = array();
+ $minpars = 0;
+ }
+ else
+ {
+ $minpars = count($parsvariations[0]);
+ }
+
+ if($minpars)
+ {
+ // add to code the check for min params number
+ // NB: this check needs to be done BEFORE decoding param values
+ $innercode = "\$paramcount = \$msg->getNumParams();\n" .
+ "if (\$paramcount < $minpars) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
+ }
+ else
+ {
+ $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
+ }
+
+ $innercode .= "\$np = false;";
+ foreach($parsvariations as $pars)
+ {
+ $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = $funcname(" . implode(',', $pars) . "); else\n";
+ // build a 'generic' signature (only use an appropriate return type)
+ $sig = array($returns);
+ for($i=0; $i < count($pars); $i++)
+ {
+ if (isset($paramDocs[$i]['type']))
+ {
+ $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
+ }
+ else
+ {
+ $sig[] = $GLOBALS['xmlrpcValue'];
+ }
+ }
+ $sigs[] = $sig;
+ }
+ $innercode .= "\$np = true;\n";
+ $innercode .= "if (\$np) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else\n";
+ //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
+ if($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64'])
+ {
+ $innercode .= "return new xmlrpcresp(new xmlrpcval(\$retval, '$returns'));";
+ }
+ else
+ {
+ $innercode .= "return new xmlrpcresp(php_xmlrpc_encode(\$retval, array('encode_php_objs')));";
+ }
+ // shall we exclude functions returning by ref?
+ // if($func->returnsReference())
+ // return false;
+ $code = $code . $innercode . "\n}\n \$allOK=1;";
+ //print_r($code);
+ $allOK = 0;
+ eval($code);
+ // alternative
+ //$xmlrpcfuncname = create_function('$m', $innercode);
+
+ if(!$allOK)
+ {
+ return false;
+ }
+
+ /// @todo examine if $paramDocs matches $parsvariations and build array for
+ /// usage as method signature, plus put together a nice string for docs
+
+ $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc);
+ return $ret;
+ }
+ }
+
+ /**
+ * Given an xmlrpc client and a method name, register a php wrapper function
+ * that will call it and return results using native php types for both
+ * params and results. The generated php function will return an xmlrpcresp
+ * oject for failed xmlrpc calls
+ *
+ * Known limitations:
+ * - server must support system.methodsignature for the wanted xmlrpc method
+ * - for methods that expose many signatures, only one can be picked (we
+ * could in priciple check if signatures differ only by number of params
+ * and not by type, but it would be more complication than we can spare time)
+ * - nested xmlrpc params: the caller of the generated php function has to
+ * encode on its own the params passed to the php function if these are structs
+ * or arrays whose (sub)members include values of type datetime or base64
+ *
+ * Notes: the connection properties of the given client will be copied
+ * and reused for the connection used during the call to the generated
+ * php function.
+ * Calling the generated php function 'might' be slow: a new xmlrpc client
+ * is created on every invocation and an xmlrpc-connection opened+closed.
+ * An extra 'debug' param is appended to param list of xmlrpc method, useful
+ * for debugging purposes.
+ *
+ * @param xmlrpc_client $client an xmlrpc client set up correctly to communicate with target server
+ * @param string $methodname the xmlrpc method to be mapped to a php function
+ * @param integer $signum the index of the method signature to use in mapping (if method exposes many sigs)
+ * @return string the name of the generated php function (or false)
+ */
+ function wrap_xmlrpc_method($client, $methodname, $signum=0, $timeout=0, $protocol='', $newfuncname='')
+ {
+ $msg =& new xmlrpcmsg('system.methodSignature');
+ $msg->addparam(new xmlrpcval($methodname));
+ $response =& $client->send($msg, $timeout, $protocol);
+ if(!$response || $response->faultCode())
+ {
+ return false;
+ }
+ else
+ {
+ $desc = $response->value();
+ if($desc->kindOf() != 'array' || $desc->arraysize() <= $signum)
+ {
+ return false;
+ }
+ else
+ {
+ if($newfuncname != '')
+ {
+ $xmlrpcfuncname = $newfuncname;
+ }
+ else
+ {
+ $xmlrpcfuncname = 'xmlrpc_'.str_replace('.', '_', $methodname);
+ }
+ while(function_exists($xmlrpcfuncname))
+ {
+ $xmlrpcfuncname .= 'x';
+ }
+ $desc = $desc->arraymem($signum);
+ $code = "function $xmlrpcfuncname (";
+ $innercode = "\$client =& new xmlrpc_client('$client->path', '$client->server');\n";
+ // copy all client fields to the client that will be generated runtime
+ // (this provides for future expansion of client obj)
+ foreach($client as $fld => $val)
+ {
+ if($fld != 'debug' && $fld != 'return_type')
+ {
+ $val = var_export($val, true);
+ $innercode .= "\$client->$fld = $val;\n";
+ }
+ }
+ $innercode .= "\$client->setDebug(\$debug);\n";
+ $innercode .= "\$client->return_type = 'xmlrpcvals';\n";
+ $innercode .= "\$msg =& new xmlrpcmsg('$methodname');\n";
+
+ // param parsing
+ $plist = array();
+ $pcount = $desc->arraysize();
+ for($i = 1; $i < $pcount; $i++)
+ {
+ $plist[] = "\$p$i";
+ $ptype = $desc->arraymem($i);
+ $ptype = $ptype->scalarval();
+ if($ptype == 'dateTime.iso8601' || $ptype == 'base64')
+ {
+ $innercode .= "\$p$i =& new xmlrpcval(\$p$i, '$ptype');\n";
+ }
+ else
+ {
+ $innercode .= "\$p$i =& php_xmlrpc_encode(\$p$i);\n";
+ }
+ $innercode .= "\$msg->addparam(\$p$i);\n";
+ }
+ $plist[] = '$debug = 0';
+ $plist = implode(',', $plist);
+
+ $innercode .= "\$res =& \$client->send(\$msg, $timeout, '$protocol');\n";
+ $innercode .= "if (\$res->faultcode()) return \$res; else return php_xmlrpc_decode(\$res->value(), array('decode_php_objs'));";
+
+ $code = $code . $plist. ") {\n" . $innercode . "\n}\n\$allOK=1;";
+ //print_r($code);
+ $allOK = 0;
+ eval($code);
+ // alternative
+ //$xmlrpcfuncname = create_function('$m', $innercode);
+ if($allOK)
+ {
+ return $xmlrpcfuncname;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ /**
+ * xml charset encoding guessing helper function.
+ * Tries to determine the charset encoding of an XML chunk
+ * received over HTTP.
+
+ * NB: according to the spec (RFC 3023, if text/xml content-type is received over HTTP without a content-type,
+
+ * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
+
+ * which will be most probably using UTF-8 anyway...
+ *
+ * @param string $httpheaders the http Content-type header
+ * @param string $xmlchunk xml content buffer
+ * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
+ *
+ * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
+ */
+ function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
+ {
+ // discussion: see http://www.yale.edu/pclt/encoding/
+ // 1 - test if encoding is specified in HTTP HEADERS
+
+ //Details:
+ // LWS: (\13\10)?( |\t)+
+ // token: (any char but excluded stuff)+
+ // header: Content-type = ...; charset=value(; ...)*
+ // where value is of type token, no LWS allowed between 'charset' and value
+ // Note: we do not check for invalid chars in VALUE:
+ // this had better be done using pure ereg as below
+
+ /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
+ if(eregi(";((\\xD\\xA)?[ \\x9]+)*charset=", $httpheader))
+ {
+ /// @BUG if charset is received uppercase, this line will fail!
+ $in = strpos($httpheader, 'charset=')+8;
+ $out = strpos($httpheader, ';', $in) ? strpos($httpheader, ';', $in) : strlen($httpheader);
+ return strtoupper(trim(substr($httpheader, $in, $out-$in)));
+ }
+
+ // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
+ // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
+ // NOTE: actually, according to the spec, even if we find the BOM and determine
+ // an encoding, we should check if there is an encoding specified
+ // in the xml declaration, and verify if they match.
+ /// @todo implement check as described above?
+ /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
+ if(@ereg("^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)", $xmlchunk))
+ // if (preg_match("/^(\\x00\\x00\\xFE\\xFF|\\xFF\\xFE\\x00\\x00|\\x00\\x00\\xFF\\xFE|\\xFE\\xFF\\x00\\x00)/", $xmlchunk))
+ {
+ return 'UCS-4';
+ }
+ elseif(ereg("^(\\xFE\\xFF|\\xFF\\xFE)", $xmlchunk))
+ {
+ return 'UTF-16';
+ }
+ elseif(ereg("^(\\xEF\\xBB\\xBF)", $xmlchunk))
+ {
+ return 'UTF-8';
+ }
+
+ // 3 - test if encoding is specified in the xml declaration
+ // Details:
+ // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
+ // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
+ if (ereg("^<\?xml".
+ "[ \\x9\\xD\\xA]+" . "version" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[a-zA-Z0-9_.:-]+\")|('[a-zA-Z0-9_.:-]+'))".
+ "[ \\x9\\xD\\xA]+" . "encoding" . "[ \\x9\\xD\\xA]*=[ \\x9\\xD\\xA]*" . "((\"[A-Za-z][A-Za-z0-9._-]*\")|('[A-Za-z][A-Za-z0-9._-]*'))",
+ $xmlchunk, $regs))
+ {
+ return strtoupper(substr($regs[4], 1, strlen($regs[4])-2));
+ }
+
+ // 4 - if mbstring is available, let it do the guesswork
+ // NB: we favour finding an encoding that is compatible with what we can process
+ if(extension_loaded('mbstring'))
+ {
+ if($encoding_prefs)
+ {
+ $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
+ }
+ else
+ {
+ $enc = mb_detect_encoding($xmlchunk);
+ }
+ // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
+ // IANA also likes better US-ASCII, so go with it
+ if($enc == 'ASCII')
+ {
+ $enc = 'US-'.$enc;
+ }
+ return $enc;
+ }
+ else
+ {
+ // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
+ // Both RFC 2616 (HTTP 1.1) and 1945(http 1.0) clearly state that for text/xxx content types
+ // this should be the standard. And we should be getting text/xml as request and response.
+ // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
+ return $GLOBALS['xmlrpc_defencoding'];
+ }
+ }
+
+/**
+* Checks if a given charset encoding is present in a list of encodings or
+* if it is a valid subset of any encoding in the list
+* @param string $encoding charset to be tested
+* @param mixed $validlist comma separated list of valid charsets (or array of charsets)
+*/
+function is_valid_charset($encoding, $validlist)
+{
+ $charset_supersets = array(
+ 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
+ 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
+ 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
+ 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
+ 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
+ );
+ if (is_string($validlist))
+ $validlist = split(',', $validlist);
+ if (@in_array(strtoupper($encoding), $validlist))
+ return true;
+ else
+ {
+ if (array_key_exists($encoding, $charset_supersets))
+ foreach ($validlist as $allowed)
+ if (in_array($allowed, $charset_supersets[$encoding]))
+ return true;
+ return false;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 37a36fc5504..f78044ea38e 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -3,30 +3,21 @@
"access_crm_segmentation_line","crm.segmentation.line","model_crm_segmentation_line","crm.group_crm_manager",1,1,1,1
"access_crm_case_section","crm.case.section","model_crm_case_section","crm.group_crm_user",1,0,0,0
"access_crm_case_categ","crm.case.categ","model_crm_case_categ","crm.group_crm_user",1,0,0,0
-"access_crm_case_manger","crm.case manager","model_crm_case","crm.group_crm_user",1,1,1,1
-"access_crm_case","crm.case","model_crm_case","crm.group_crm_manager",1,1,1,1
"access_crm_meeting","crm.meeting","model_crm_meeting","crm.group_crm_manager",1,1,1,1
"access_crm_lead","crm.lead","model_crm_lead","crm.group_crm_manager",1,1,1,1
-"access_crm_opportunity","crm.opportunity","model_crm_opportunity","crm.group_crm_manager",1,1,1,1
"access_crm_phonecall","crm.phonecall","model_crm_phonecall","crm.group_crm_manager",1,1,1,1
"access_crm_case_log","crm.case.log","model_crm_case_log","crm.group_crm_user",1,1,1,1
"access_crm_case_history","crm.case.history","model_crm_case_history","crm.group_crm_user",1,1,1,1
-"access_crm_case_section_manager","crm.case.section.manager","model_crm_case_section","crm.group_crm_manager",1,1,1,1
+"o","crm.case.section.manager","model_crm_case_section","crm.group_crm_manager",1,1,1,1
"access_crm_case_categ_manager","crm.case.categ.manager","model_crm_case_categ","crm.group_crm_manager",1,1,1,1
"access_crm_case_log_manager","crm.case.log manager","model_crm_case_log","crm.group_crm_manager",1,1,1,1
"access_crm_case_history_manager","crm.case.history manager","model_crm_case_history","crm.group_crm_manager",1,1,1,1
-"access_crm_email_add_cc_manager","crm.email.add.cc","model_crm_email_add_cc","crm.group_crm_manager",1,1,1,1
-"access_crm_email_add_cc_user","crm.email.add.cc","model_crm_email_add_cc","crm.group_crm_user",1,0,0,0
"access_crm_case_stage","crm.case.stage","model_crm_case_stage","crm.group_crm_user",1,0,0,0
"access_crm_case_stage_manager","crm.case.stage","model_crm_case_stage","crm.group_crm_manager",1,1,1,1
"access_crm_case_resource_type_user","crm_case_resource_type user","model_crm_case_resource_type","crm.group_crm_user",1,0,0,0
"access_crm_case_resource_type_manager","crm_case_resource_type manager","model_crm_case_resource_type","crm.group_crm_manager",1,1,1,1
-"access_crm_case_report_user","crm.case.report","model_crm_case_report","crm.group_crm_user",1,0,0,0
-"access_crm_case_report_manager","crm.case.report manager","model_crm_case_report","crm.group_crm_manager",1,1,1,1
"access_crm_lead_report_user","crm.lead.report","model_crm_lead_report","crm.group_crm_user",1,0,0,0
"access_crm_phonecall_report_user","crm.phonecall.report","model_crm_phonecall_report","crm.group_crm_user",1,0,0,0
-"access_crm_opportunity_report_user","crm.opportunity.report","model_crm_opportunity_report","crm.group_crm_user",1,0,0,0
-"access_report_crm_case_service_dashboard","report.crm.case.service.dashboard","model_report_crm_case_service_dashboard","crm.group_crm_user",1,0,0,0
"access_crm_lead2partner","crm.lead2partner","model_crm_lead2partner","crm.group_crm_user",1,1,1,1
"access_crm_lead2opportunity","crm.lead2opportunity","model_crm_lead2opportunity","crm.group_crm_user",1,1,1,1
"access_crm_opportunity2phonecall","crm.opportunity2phonecall","model_crm_opportunity2phonecall","crm.group_crm_user",1,1,1,1
@@ -34,5 +25,8 @@
"access_crm_phonecall2partner","crm.phonecall2partner","model_crm_phonecall2partner","crm.group_crm_user",1,1,1,1
"access_crm_phonecall2opportunity","crm.phonecall2opportunity","model_crm_phonecall2opportunity","crm.group_crm_user",1,1,1,1
"access_crm_send_mail","crm.send.mail","model_crm_send_mail","crm.group_crm_user",1,1,1,1
+"access_crm_send_mail_attachment","crm.send.mail.attachment","model_crm_send_mail_attachment","crm.group_crm_user",1,1,1,1
"access_crm_partner2opportunity","crm.partner2opportunity","model_crm_partner2opportunity","crm.group_crm_user",1,1,1,1
"access_crm_lead2opportunity_partner","crm.lead2opportunity.partner","model_crm_lead2opportunity_partner","crm.group_crm_user",1,1,1,1
+"access_crm_installer","crm.installer.rule","model_crm_installer","crm.group_crm_user",1,1,1,1
+"access_crm_lead_forward_to_partner","crm.lead.forward.to.partner","model_crm_lead_forward_to_partner","crm.group_crm_user",2,2,2,2
diff --git a/addons/crm/test/test_crm_lead.yml b/addons/crm/test/test_crm_lead.yml
index 437059f371c..bfdbe912fed 100644
--- a/addons/crm/test/test_crm_lead.yml
+++ b/addons/crm/test/test_crm_lead.yml
@@ -92,7 +92,7 @@
-
!python {model: crm.lead}: |
lead = self.browse(cr, uid, ref("crm_lead_newcustomer0"))
- obj_opportunity = self.pool.get('crm.opportunity')
+ obj_opportunity = self.pool.get('crm.lead')
ids = obj_opportunity.search(cr, uid, [('name', '=', lead.partner_name)])
opportunity = obj_opportunity.browse(cr, uid, ids)[0]
assert lead.partner_name == opportunity.partner_id.name
@@ -113,4 +113,4 @@
- |
I Reply to last Email to lead with some document attached.and check that communication history generated or not.
-
\ No newline at end of file
+
diff --git a/addons/crm/test/test_crm_opportunity.yml b/addons/crm/test/test_crm_opportunity.yml
index d7118dd0c90..e3a4f28cb5e 100644
--- a/addons/crm/test/test_crm_opportunity.yml
+++ b/addons/crm/test/test_crm_opportunity.yml
@@ -2,7 +2,7 @@
I start by creating a new Opportunity. And I select partner for opportunity.
I can see that after selecting partner his contact and email is automatically filled.
-
- !record {model: crm.opportunity, id: crm_opportunity_abcfuelcounits0}:
+ !record {model: crm.lead, id: crm_opportunity_abcfuelcounits0}:
email_from: info@balmerinc.be
name: 'ABC FUEL CO 829264 - 10002 units '
partner_address_id: base.res_partner_address_1
@@ -15,19 +15,19 @@
- |
I check that the opportunity is in 'New' state.
-
- !assert {model: crm.opportunity, id: crm_opportunity_abcfuelcounits0}:
+ !assert {model: crm.lead, id: crm_opportunity_abcfuelcounits0}:
- state == "draft"
- |
I open opportunity by click on "Open" button,
-
- !python {model: crm.opportunity}: |
+ !python {model: crm.lead}: |
self.case_open(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
I schedule Meeting on this current opportunity by clicking on "schedule
Meeting".
-
- !python {model: crm.opportunity}: |
+ !python {model: crm.lead}: |
self.action_makeMeeting(cr, uid, [ref("crm_opportunity_abcfuelcounits0")])
- |
diff --git a/addons/crm/test/test_crm_phonecall.yml b/addons/crm/test/test_crm_phonecall.yml
index 47021843348..f2020659aed 100644
--- a/addons/crm/test/test_crm_phonecall.yml
+++ b/addons/crm/test/test_crm_phonecall.yml
@@ -49,8 +49,8 @@
!python {model: crm.phonecall}: |
xid= ref('crm_phonecall_interviewcall0')
obj_phonecall = self.browse(cr, uid, xid)
- ids = self.pool.get('crm.opportunity').search(cr, uid, [('name', '=', obj_phonecall.opportunity_id.name)])
- obj_opp = self.pool.get('crm.opportunity').browse(cr, uid, ids)[0]
+ ids = self.pool.get('crm.lead').search(cr, uid, [('name', '=', obj_phonecall.opportunity_id.name)])
+ obj_opp = self.pool.get('crm.lead').browse(cr, uid, ids)[0]
assert obj_phonecall.partner_id == obj_opp.partner_id
- |
diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py
index a9067d9fa0b..e4eb233a9b8 100644
--- a/addons/crm/wizard/__init__.py
+++ b/addons/crm/wizard/__init__.py
@@ -21,7 +21,6 @@
import crm_send_email
import crm_forward_to_partner
-import crm_email_add_cc
import crm_lead_to_partner
import crm_lead_to_opportunity
diff --git a/addons/crm/wizard/crm_email_add_cc.py b/addons/crm/wizard/crm_email_add_cc.py
deleted file mode 100644
index 9af1b5eda5d..00000000000
--- a/addons/crm/wizard/crm_email_add_cc.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from osv import osv, fields
-from tools.translate import _
-import tools
-
-class crm_email_add_cc_wizard(osv.osv_memory):
- """ Email Add CC"""
-
- _name = "crm.email.add.cc"
- _description = "Email Add CC"
-
- _columns = {
- 'name': fields.selection([('user', 'User'), ('partner', 'Partner'), \
- ('email', 'Email Address')], 'Send to', required=True),
- 'user_id': fields.many2one('res.users', "User"),
- 'partner_id': fields.many2one('res.partner', "Partner"),
- 'email': fields.char('Email', size=32),
- 'subject': fields.char('Subject', size=32),
- }
-
- def on_change_email(self, cr, uid, ids, user, partner):
-
- """ @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of Mail’s IDs
- """
-
- if (not partner and not user):
- return {'value': {'email': False}}
- email = False
- if partner:
- addr = self.pool.get('res.partner').address_get(cr, uid, [partner], ['contact'])
- if addr:
- email = self.pool.get('res.partner.address').read(cr, uid, addr['contact'] , ['email'])['email']
- elif user:
- addr = self.pool.get('res.users').read(cr, uid, user, ['address_id'])['address_id']
- if addr:
- email = self.pool.get('res.partner.address').read(cr, uid, addr[0] , ['email'])['email']
- return {'value': {'email': email}}
-
-
- def add_cc(self, cr, uid, ids, context={}):
- """
- Adds CC value in case
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of create menu’s IDs
- @param context: A standard dictionary for contextual values
- """
-
- data = self.read(cr, uid, ids[0])
- email = data['email']
- subject = data['subject']
-
- if not context:
- return {}
- history_line = self.pool.get('crm.case.history').browse(cr, uid, context['active_id'])
- model = history_line.log_id.model_id.model
- model_pool = self.pool.get(model)
- case = model_pool.browse(cr, uid, history_line.log_id.res_id)
- body = history_line.description.replace('\n', '\n> ')
- if not case.email_from:
- raise osv.except_osv(_('Warning!'), ('No email Address defined for this case..'))
- if not email:
- raise osv.except_osv(_('Warning!'), ('Please Specify email address..'))
- flag = tools.email_send(
- case.user_id.address_id.email,
- [case.email_from],
- subject or '[' + str(case.id) + '] ' + case.name,
- model_pool.format_body(body),
- email_cc = [email],
- openobject_id = str(case.id),
- subtype = "html"
- )
-
- if flag:
- model_pool.write(cr, uid, case.id, {'email_cc' : case.email_cc and case.email_cc + ',' + email or email})
- else:
- raise osv.except_osv(_('Email Fail!'), ("Lastest Email is not sent successfully"))
- return {}
-
-crm_email_add_cc_wizard()
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_email_add_cc_view.xml b/addons/crm/wizard/crm_email_add_cc_view.xml
deleted file mode 100644
index 7c6ea96df23..00000000000
--- a/addons/crm/wizard/crm_email_add_cc_view.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
-
- Add CC
- crm.email.add.cc
- form
-
-
-
-
-
-
-
-
- Add CC
- ir.actions.act_window
- crm.email.add.cc
- form
- form
- new
-
-
-
-
diff --git a/addons/crm/wizard/crm_forward_to_partner.py b/addons/crm/wizard/crm_forward_to_partner.py
index 413747739c2..98d5392faa3 100644
--- a/addons/crm/wizard/crm_forward_to_partner.py
+++ b/addons/crm/wizard/crm_forward_to_partner.py
@@ -26,34 +26,137 @@ from tools.translate import _
import tools
class crm_lead_forward_to_partner(osv.osv_memory):
+ """Forwards lead history"""
_name = 'crm.lead.forward.to.partner'
_columns = {
- 'partner_id' : fields.many2one('res.partner', 'Partner'),
- 'address_id' : fields.many2one('res.partner.address', 'Address'),
- 'email_from' : fields.char('From', required=True, size=128),
- 'email_to' : fields.char('To', required=True, size=128),
- 'subject' : fields.char('Subject', required=True, size=128),
- 'message' : fields.text('Message', required=True),
+ 'name': fields.selection([('user', 'User'), ('partner', 'Partner'), \
+ ('email', 'Email Address')], 'Send to', required=True),
+ 'user_id': fields.many2one('res.users', "User"),
+ 'partner_id' : fields.many2one('res.partner', 'Partner'),
+ 'address_id' : fields.many2one('res.partner.address', 'Address'),
+ 'email_from' : fields.char('From', required=True, size=128),
+ 'email_to' : fields.char('To', required=True, size=128),
+ 'subject' : fields.char('Subject', required=True, size=128),
+ 'message' : fields.text('Message', required=True),
+ 'history': fields.selection([('latest', 'Latest email'), ('whole', 'Whole Story'), ('info', 'Case Information')], 'Send history', required=True),
+ 'add_cc': fields.boolean('Add as CC', required=False, help="Selcect if you want this user to add as cc for this case.This user will receive all future conversations"),
}
+
+ def get_whole_history(self, cr, uid, ids, context=None):
+ """This function gets whole communication history and returns as top posting style
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param ids: List of history IDs
+ @param context: A standard dictionary for contextual values
+ """
+ whole = []
+ for hist_id in ids:
+ whole.append(self.get_latest_history(cr, uid, hist_id, context=context))
+ whole = '\n\n'.join(whole)
+ return whole or ''
+
+ def get_latest_history(self, cr, uid, hist_id, context=None):
+ """This function gets latest communication and returns as top posting style
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param hist_id: Id of latest history
+ @param context: A standard dictionary for contextual values
+ """
+ log_pool = self.pool.get('mailgate.message')
+ hist = log_pool.browse(cr, uid, hist_id, context=context)
+ header = '-------- Original Message --------'
+ sender = 'From: %s' %(hist.email_from or '')
+ to = 'To: %s' % (hist.email_to or '')
+ sentdate = 'Date: %s' % (hist.date or '')
+ desc = '\n%s'%(hist.description)
+ original = [header, sender, to, sentdate, desc]
+ original = '\n'.join(original)
+ return original
+
+ def on_change_email(self, cr, uid, ids, user):
+ """This function fills email information based on user selected
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param ids: List of Mail’s IDs
+ @param user: Changed User id
+ @param partner: Changed Partner id
+ """
+ if not user:
+ return {'value': {'email_to': False}}
+ email = False
+ addr = self.pool.get('res.users').read(cr, uid, user, ['address_id'])['address_id']
+ if addr:
+ email = self.pool.get('res.partner.address').read(cr, uid, addr[0] , ['email'])['email']
+ return {'value': {'email_to': email}}
+
+ def on_change_history(self, cr, uid, ids, history, context=None):
+ """Gives message body according to type of history selected
+ * info: Forward the case information
+ * whole: Send the whole history
+ * latest: Send the latest histoy
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param ids: List of history IDs
+ @param context: A standard dictionary for contextual values
+ """
+ #TODO: ids and context are not comming
+ res = False
+ msg_val = ''
+ res_id = False # Comes from context
+ model = None # Comes from context
+ model_pool = self.pool.get(model)
+ if not res_id or not model:
+ return res
+ if history == 'info':
+ msg_val = self.get_lead_details(cr, uid, res_id, context=context)
+
+ if history == 'whole':
+ log_ids = model_pool.browse(cr, uid, res_id, context=context).message_ids
+ log_ids = map(lambda x: x.id, log_ids)
+ if not log_ids:
+ raise osv.except_osv('Warning!', 'There is no history to send')
+ msg_val = self.get_whole_history(cr, uid, log_ids, context=context)
+
+ if history == 'latest':
+ log_ids = model_pool.browse(cr, uid, res_id, context=context).message_ids
+ if not log_ids:
+ raise osv.except_osv('Warning!', 'There is no history to send')
+ msg_val = self.get_latest_history(cr, uid, log_ids[0].id, context=context)
+
+ if msg_val:
+ res = {'value': {'message' : '\n\n' + msg_val}}
+ return res
def on_change_partner(self, cr, uid, ids, partner_id):
+ """This function fills address information based on partner/user selected
+ @param self: The object pointer
+ @param cr: the current row, from the database cursor,
+ @param uid: the current user’s ID for security checks,
+ @param ids: List of Mail’s IDs
+ @param user: Changed User id
+ @param partner: Changed Partner id
+ """
+ if not partner_id:
+ return {'value' : {'email_to' : False, 'address_id': False}}
+
+ addr = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['contact'])
+ data = {'address_id': addr['contact']}
+ data.update(self.on_change_address(cr, uid, ids, addr['contact'])['value'])
return {
- 'domain' : {
- 'address_id' : partner_id and "[('partner_id', '=', partner_id)]" or "[]",
+ 'value' : data,
+ 'domain' : {'address_id' : partner_id and "[('partner_id', '=', partner_id)]" or "[]"}
}
- }
def on_change_address(self, cr, uid, ids, address_id):
email = ''
if address_id:
email = self.pool.get('res.partner.address').browse(cr, uid, address_id).email
-
- return {
- 'value' : {
- 'email_to' : email,
- }
- }
+ return {'value': {'email_to' : email}}
def action_cancel(self, cr, uid, ids, context=None):
return {'type' : 'ir.actions.act_window_close'}
@@ -66,15 +169,16 @@ class crm_lead_forward_to_partner(osv.osv_memory):
context = {}
res_id = context.get('active_id', False)
-
- if not res_id:
+
+ model = context.get('active_model', False)
+ if not res_id or not model:
return {}
this = self.browse(cr, uid, ids[0], context=context)
hist_obj = self.pool.get('crm.case.history')
smtp_pool = self.pool.get('email.smtpclient')
- case_pool = self.pool.get('crm.lead')
+ case_pool = self.pool.get(model)
case = case_pool.browse(cr, uid, res_id, context=context)
emails = [this.email_to]
@@ -85,23 +189,72 @@ class crm_lead_forward_to_partner(osv.osv_memory):
flag = False
if case.section_id and case.section_id.server_id:
flag = smtp_pool.send_email(
- cr=cr,
- uid=uid,
- server_id=case.section_id.server_id.id,
- emailto=emails,
- subject=this.subject,
- body="%s
" % body,
+ cr=cr,
+ uid=uid,
+ server_id=case.section_id.server_id.id,
+ emailto=emails,
+ subject=this.subject,
+ body="%s
" % body,
)
else:
flag = tools.email_send(
- email_from,
- emails,
- this.subject,
- body,
+ email_from,
+ emails,
+ this.subject,
+ body,
)
-
+ if this.add_cc:
+ case_pool.write(cr, uid, case.id, {'email_cc' : case.email_cc and case.email_cc + ', ' + this.email_to or this.email_to})
return {}
+ def get_lead_details(self, cr, uid, lead_id, context=None):
+ message = []
+ lead_proxy = self.pool.get('crm.lead')
+ lead = lead_proxy.browse(cr, uid, lead_id, context=context)
+ if lead.type == 'lead':
+ field_names = [
+ 'partner_name', 'title', 'function_name', 'street', 'street2',
+ 'zip', 'city', 'country_id', 'state_id', 'email_from',
+ 'phone', 'fax', 'mobile'
+ ]
+
+ for field_name in field_names:
+ field_definition = lead_proxy._columns[field_name]
+ value = None
+
+ if field_definition._type == 'selection':
+ if hasattr(field_definition.selection, '__call__'):
+ key = field_definition.selection(lead_proxy, cr, uid, context=context)
+ else:
+ key = field.definition.selection
+ value = dict(key).get(lead[field_name], lead[field_name])
+ elif field_definition._type == 'many2one':
+ if lead[field_name]:
+ value = lead[field_name].name_get()[0][1]
+ else:
+ value = lead[field_name]
+
+ message.append("%s: %s" % (field_definition.string, value or ''))
+ elif lead.type == 'opportunity':
+ pa = lead.partner_address_id
+ message = [
+ "Partner: %s" % (lead.partner_id.name_get()[0][1]),
+ "Contact: %s" % (pa.name or ''),
+ "Title: %s" % (pa.title or ''),
+ "Function: %s" % (pa.function and pa.function.name_get()[0][1] or ''),
+ "Street: %s" % (pa.street or ''),
+ "Street2: %s" % (pa.street2 or ''),
+ "Zip: %s" % (pa.zip or ''),
+ "City: %s" % (pa.city or ''),
+ "Country: %s" % (pa.country_id and pa.country_id.name_get()[0][1] or ''),
+ "State: %s" % (pa.state_id and pa.state_id.name_get()[0][1] or ''),
+ "Email: %s" % (pa.email or ''),
+ "Phone: %s" % (pa.phone or ''),
+ "Fax: %s" % (pa.fax or ''),
+ "Mobile: %s" % (pa.mobile or ''),
+ ]
+ return "\n".join(message + ['---'])
+
def default_get(self, cr, uid, fields, context=None):
"""
This function gets default values
@@ -114,172 +267,25 @@ class crm_lead_forward_to_partner(osv.osv_memory):
return {}
lead_proxy = self.pool.get('crm.lead')
-
lead = lead_proxy.browse(cr, uid, active_ids[0], context=context)
-
- field_names = [
- 'partner_name', 'title', 'function_name', 'street', 'street2',
- 'zip', 'city', 'country_id', 'state_id', 'email_from',
- 'phone', 'fax', 'mobile'
- ]
-
- message = []
-
- for field_name in field_names:
- field_definition = lead_proxy._columns[field_name]
- value = None
-
- if field_definition._type == 'selection':
- if hasattr(field_definition.selection, '__call__'):
- key = field_definition.selection(lead_proxy, cr, uid, context=context)
- else:
- key = field.definition.selection
- value = dict(key).get(lead[field_name], lead[field_name])
- elif field_definition._type == 'many2one':
- if lead[field_name]:
- value = lead[field_name].name_get()[0][1]
- else:
- value = lead[field_name]
-
- message.append("%s: %s" % (field_definition.string, value or ''))
-
+ message = False
+
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
email_from = ''
if user.address_id and user.address_id.email:
email_from = "%s <%s>" % (user.name, user.address_id.email)
+
+ message = self.get_lead_details(cr, uid, lead.id, context=context)
res = {
- 'email_from' : email_from,
- 'subject' : '[Lead-Forward:%06d] %s' % (lead.id, lead.name),
- 'message' : "\n".join(message + ['---']),
+ 'email_from' : email_from,
+ 'subject' : '[%s-Forward:%06d] %s' % (lead.type.title(), lead.id, lead.name),
+ 'message' : message,
}
-
+ if 'history' in fields:
+ res.update({'history': 'info'})
return res
crm_lead_forward_to_partner()
-class crm_opportunity_forward_to_partner(osv.osv_memory):
- _name = 'crm.opportunity.forward.to.partner'
-
- _columns = {
- 'partner_id' : fields.many2one('res.partner', 'Partner'),
- 'address_id' : fields.many2one('res.partner.address', 'Address'),
- 'email_from' : fields.char('From', required=True, size=128),
- 'email_to' : fields.char('To', required=True, size=128),
- 'subject' : fields.char('Subject', required=True, size=128),
- 'message' : fields.text('Message', required=True),
- }
-
- def on_change_partner(self, cr, uid, ids, partner_id):
- return {
- 'domain' : {
- 'address_id' : partner_id and "[('partner_id', '=', partner_id)]" or "[]",
- }
- }
-
- def on_change_address(self, cr, uid, ids, address_id):
- email = ''
- if address_id:
- email = self.pool.get('res.partner.address').browse(cr, uid, address_id).email
-
- return {
- 'value' : {
- 'email_to' : email,
- }
- }
-
- def action_cancel(self, cr, uid, ids, context=None):
- return {'type' : 'ir.actions.act_window_close'}
-
- def action_forward(self, cr, uid, ids, context=None):
- """
- Forward the opportunity to a partner
- """
- if context is None:
- context = {}
-
- res_id = context.get('active_id', False)
-
- if not res_id:
- return {}
-
- this = self.browse(cr, uid, ids[0], context=context)
-
- hist_obj = self.pool.get('crm.case.history')
- smtp_pool = self.pool.get('email.smtpclient')
- case_pool = self.pool.get('crm.opportunity')
- case = case_pool.browse(cr, uid, res_id, context=context)
-
- emails = [this.email_to]
- body = case_pool.format_body(this.message)
- email_from = this.email_from or False
- case_pool._history(cr, uid, [case], _('Forward'), history=True, email=this.email_to, details=body, email_from=email_from)
-
- flag = False
- if case.section_id and case.section_id.server_id:
- flag = smtp_pool.send_email(
- cr=cr,
- uid=uid,
- server_id=case.section_id.server_id.id,
- emailto=emails,
- subject=this.subject,
- body="%s
" % body,
- )
- else:
- flag = tools.email_send(
- email_from,
- emails,
- this.subject,
- body,
- )
-
- return {}
-
- def default_get(self, cr, uid, fields, context=None):
- """
- This function gets default values
- """
- if context is None:
- context = {}
-
- active_ids = context.get('active_ids')
- if not active_ids:
- return {}
-
- opportunity_proxy = self.pool.get('crm.opportunity')
-
- opportunity = opportunity_proxy.browse(cr, uid, active_ids[0], context=context)
-
- pa = opportunity.partner_address_id
- message = [
- "Partner: %s" % (opportunity.partner_id.name_get()[0][1]),
- "Contact: %s" % (pa.name),
- "Title: %s" % (pa.title),
- "Function: %s" % (pa.function and pa.function.name_get()[0][1] or ''),
- "Street: %s" % (pa.street),
- "Street2: %s" % (pa.street2),
- "Zip: %s" % (pa.zip),
- "City: %s" % (pa.city),
- "Country: %s" % (pa.country_id and pa.country_id.name_get()[0][1] or ''),
- "State: %s" % (pa.state_id and pa.state_id.name_get()[0][1] or ''),
- "Email: %s" % (pa.email),
- "Phone: %s" % (pa.phone),
- "Fax: %s" % (pa.fax),
- "Mobile: %s" % (pa.mobile),
- ]
-
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- email_from = ''
- if user.address_id and user.address_id.email:
- email_from = "%s <%s>" % (user.name, user.address_id.email)
-
- res = {
- 'email_from' : email_from,
- 'subject' : '[Opportunity-Forward:%06d] %s' % (opportunity.id, opportunity.name),
- 'message' : "\n".join(message + ['---']),
- }
-
- return res
-
-crm_opportunity_forward_to_partner()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm/wizard/crm_forward_to_partner_view.xml b/addons/crm/wizard/crm_forward_to_partner_view.xml
index 8f238333a29..1e8c6fc8583 100644
--- a/addons/crm/wizard/crm_forward_to_partner_view.xml
+++ b/addons/crm/wizard/crm_forward_to_partner_view.xml
@@ -8,16 +8,28 @@