Merge lp:~magentoerpconnect-community/magentoerpconnect/magento-module-with-crm_claim-integration into lp:magentoerpconnect/magento-module-oerp6.x-stable

Status: Needs review
Proposed branch: lp:~magentoerpconnect-community/magentoerpconnect/magento-module-with-crm_claim-integration
Merge into: lp:magentoerpconnect/magento-module-oerp6.x-stable
Diff against target: 7304 lines (+7182/-0)
18 files modified
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Block/Form.php (+160/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Helper/Data.php (+74/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config/Backend/Links.php (+48/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/controllers/IndexController.php (+360/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/adminhtml.xml (+22/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/config.xml (+117/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/modules/Savoirfairelinux_Claim.xml (+9/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/system.xml (+114/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc.inc (+3776/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc_wrappers.inc (+955/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpcs.inc (+1246/-0)
Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/savoirfairelinux_claim.xml (+70/-0)
Savoirfairelinux_Claim/app/design/frontend/default/default/layout/savoirfairelinux_claim.xml (+63/-0)
Savoirfairelinux_Claim/app/design/frontend/default/default/template/claim/form.phtml (+121/-0)
Savoirfairelinux_Claim/app/etc/modules/Savoirfairelinux_Claim.xml (+9/-0)
Savoirfairelinux_Claim/app/locale/en_US/Savoirfairelinux_Claim.csv (+10/-0)
Savoirfairelinux_Claim/app/locale/fr_FR/Savoirfairelinux_Claim.csv (+10/-0)
Savoirfairelinux_Claim/package.xml (+18/-0)
To merge this branch: bzr merge lp:~magentoerpconnect-community/magentoerpconnect/magento-module-with-crm_claim-integration
Reviewer Review Type Date Requested Status
MagentoERPConnect core editors Pending
Review via email: mp+129685@code.launchpad.net

Description of the change

This Magento module enables Magento users to create a claim (crm_claim) in OpenERP from Magento using OpenERP API.

To post a comment you must log in.
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

I'm surprised that you had to include xmlrpc.inc in the patch. There's no such thing available in magento ?

NB: I'm not a Magento or even PHP programmer. Just being curious.

Unmerged revisions

25. By Joao Alfredo Gama Batista

[ADD] Module to allow Magento users to create a claim in OpenERP from Magento

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'Savoirfairelinux_Claim'
2=== added directory 'Savoirfairelinux_Claim/app'
3=== added directory 'Savoirfairelinux_Claim/app/code'
4=== added directory 'Savoirfairelinux_Claim/app/code/local'
5=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux'
6=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim'
7=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Block'
8=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Block/Form.php'
9--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Block/Form.php 1970-01-01 00:00:00 +0000
10+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Block/Form.php 2012-10-15 14:51:21 +0000
11@@ -0,0 +1,160 @@
12+<?php
13+/**
14+ * Magento
15+ *
16+ * NOTICE OF LICENSE
17+ *
18+ * Savoirfairelinux_Claim
19+ * Copyright (C) 2012 Savoir-faire Linux
20+ *
21+ * This program is free software: you can redistribute it and/or modify
22+ * it under the terms of the GNU General Public License as published by
23+ * the Free Software Foundation, either version 3 of the License, or
24+ * (at your option) any later version.
25+ *
26+ * This program is distributed in the hope that it will be useful,
27+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29+ * GNU General Public License for more details.
30+ *
31+ * You should have received a copy of the GNU General Public License
32+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
33+ *
34+ * DISCLAIMER
35+ *
36+ * Do not edit or add to this file if you wish to upgrade Magento to newer
37+ * versions in the future. If you wish to customize Magento for your
38+ * needs please refer to http://www.magentocommerce.com for more information.
39+ *
40+ * @category Savoirfairelinux
41+ * @package Savoirfairelinux_Claim
42+ * @copyright Copyright (c) 2012 Savoir-faire Linux. (http://www.savoirfairelinux.com)
43+ * @license http://www.gnu.org/licenses/gpl.html General Public License (GPLv3)
44+ */
45+
46+/**
47+ * Customer Complains block
48+ *
49+ * @category Savoirfairelinux
50+ * @package Savoirfairelinux_Claim
51+ * @author Joao Alfredo Gama Batista <joao.gama@savoirfairelinux.com>
52+ */
53+class Savoirfairelinux_Claim_Block_Form extends Mage_Customer_Block_Account_Dashboard
54+{
55+
56+ protected $_collection;
57+
58+ const XML_PATH_ENABLED = 'claim/claim/enabled';
59+ const XML_PATH_OPENERP_URL = 'claim/settings/openerp_url';
60+ const XML_PATH_OPENERP_LOGIN = 'claim/settings/openerp_login';
61+ const XML_PATH_OPENERP_PASSWORD = 'claim/settings/openerp_password';
62+ const XML_PATH_OPENERP_DATABASE = 'claim/settings/openerp_database';
63+ const XML_PATH_OPENERP_CLAIM_REF = 'claim/settings/openerp_claim_ref';
64+ const XML_PATH_OPENERP_CLAIM_REF_PREFIX = 'claim/settings/openerp_claim_ref_prefix';
65+
66+ /* protected function _construct() */
67+ /* { */
68+ /* /\* $this->_collection = Mage::getModel('review/review')->getProductCollection(); *\/ */
69+ /* /\* $this->_collection *\/ */
70+ /* /\* ->addStoreFilter(Mage::app()->getStore()->getId()) *\/ */
71+ /* /\* ->addCustomerFilter(Mage::getSingleton('customer/session')->getCustomerId()) *\/ */
72+ /* /\* ->setDateOrder(); *\/ */
73+
74+ /* $this->partner_name = Mage::helper('contacts')->getUserName(); */
75+ /* $this->partner_email = Mage::helper('contacts')->getUserEmail(); */
76+
77+ /* $this->openerp = new Savoirfairelinux_Claim_Openerpclient */
78+ /* (Mage::getStoreConfig(self::XML_PATH_OPENERP_LOGIN), */
79+ /* Mage::helper('core')->decrypt(Mage::getStoreConfig(self::XML_PATH_OPENERP_PASSWORD)), */
80+ /* Mage::getStoreConfig(self::XML_PATH_OPENERP_DATABASE), */
81+ /* Mage::getStoreConfig(self::XML_PATH_OPENERP_URL), */
82+ /* Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF), */
83+ /* Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF_PREFIX) */
84+ /* ); */
85+ /* parent::__construct(); */
86+
87+ /* } */
88+
89+ public function isPartner()
90+ {
91+ $this->partner_name = Mage::helper('contacts')->getUserName();
92+ $this->partner_email = Mage::helper('contacts')->getUserEmail();
93+
94+ if( $this->openerp->isPartner($this->partner_name, $this->partner_email) )
95+ return True;
96+ else
97+ return False;
98+
99+ }
100+
101+ public function isConnected()
102+ {
103+ try {
104+ $this->openerp = new Savoirfairelinux_Claim_Openerpclient
105+ (Mage::getStoreConfig(self::XML_PATH_OPENERP_LOGIN),
106+ Mage::helper('core')->decrypt(Mage::getStoreConfig(self::XML_PATH_OPENERP_PASSWORD)),
107+ Mage::getStoreConfig(self::XML_PATH_OPENERP_DATABASE),
108+ Mage::getStoreConfig(self::XML_PATH_OPENERP_URL),
109+ Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF),
110+ Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF_PREFIX));
111+
112+ } catch (Exception $e) {
113+ return False;
114+ }
115+
116+ return True;
117+ }
118+
119+ public function count()
120+ {
121+ return $this->_collection->getSize();
122+ }
123+
124+ public function getToolbarHtml()
125+ {
126+ return $this->getChildHtml('toolbar');
127+ }
128+
129+ /* protected function _prepareLayout() */
130+ /* { */
131+ /* $toolbar = $this->getLayout()->createBlock('page/html_pager', 'customer_review_list.toolbar') */
132+ /* ->setCollection($this->_getCollection()); */
133+
134+ /* $this->setChild('toolbar', $toolbar); */
135+ /* return parent::_prepareLayout(); */
136+ /* } */
137+
138+ protected function _getCollection()
139+ {
140+ return $this->_collection;
141+ }
142+
143+ public function getCollection()
144+ {
145+ return $this->_getCollection();
146+ }
147+
148+ public function getReviewLink()
149+ {
150+ return Mage::getUrl('review/customer/view/');
151+ }
152+
153+ public function getProductLink()
154+ {
155+ return Mage::getUrl('catalog/product/view/');
156+ }
157+
158+ public function dateFormat($date)
159+ {
160+ return $this->formatDate($date, Mage_Core_Model_Locale::FORMAT_TYPE_SHORT);
161+ }
162+
163+ /* protected function _beforeToHtml() */
164+ /* { */
165+ /* $this->_getCollection() */
166+ /* ->load() */
167+ /* ->addReviewSummary(); */
168+ /* return parent::_beforeToHtml(); */
169+ /* } */
170+
171+}
172
173=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Helper'
174=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Helper/Data.php'
175--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Helper/Data.php 1970-01-01 00:00:00 +0000
176+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Helper/Data.php 2012-10-15 14:51:21 +0000
177@@ -0,0 +1,74 @@
178+<?php
179+/**
180+ * Magento
181+ *
182+ * NOTICE OF LICENSE
183+ *
184+ * Savoirfairelinux_Claim
185+ * Copyright (C) 2012 Savoir-faire Linux
186+ *
187+ * This program is free software: you can redistribute it and/or modify
188+ * it under the terms of the GNU General Public License as published by
189+ * the Free Software Foundation, either version 3 of the License, or
190+ * (at your option) any later version.
191+ *
192+ * This program is distributed in the hope that it will be useful,
193+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
194+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
195+ * GNU General Public License for more details.
196+ *
197+ * You should have received a copy of the GNU General Public License
198+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
199+ *
200+ * DISCLAIMER
201+ *
202+ * Do not edit or add to this file if you wish to upgrade Magento to newer
203+ * versions in the future. If you wish to customize Magento for your
204+ * needs please refer to http://www.magentocommerce.com for more information.
205+ *
206+ * @category Savoirfairelinux
207+ * @package Savoirfairelinux_Claim
208+ * @copyright Copyright (c) 2012 Savoir-faire Linux. (http://www.savoirfairelinux.com)
209+ * @license http://www.gnu.org/licenses/gpl.html General Public License (GPLv3)
210+ */
211+
212+/**
213+ * Default claim helper
214+ *
215+ * @author Joao Alfredo Gama Batista <joao.gama@savoirfairelinux.com>
216+ */
217+class Savoirfairelinux_Claim_Helper_Data extends Mage_Core_Helper_Abstract
218+{
219+
220+ const XML_PATH_ENABLED = 'claim/claim/enabled';
221+
222+ public function getOrdersHtmlSelect()
223+ {
224+ if (Mage::getSingleton('customer/session')->isLoggedIn()) {
225+ $options = array();
226+
227+ $orders = Mage::getResourceModel('sales/order_collection')
228+ ->addFieldToSelect('*')
229+ ->addFieldToFilter('customer_id', Mage::getSingleton('customer/session')->getCustomer()->getId())
230+ ->addFieldToFilter('state', array('in' => Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates()))
231+ ->setOrder('created_at', 'desc')
232+ ;
233+
234+ foreach ($orders as $order) {
235+ $options[] = array('value' => $order->getRealOrderId(),
236+ 'label' => $order->getRealOrderId());
237+ }
238+ $select = $this->getLayout()->createBlock('core/html_select')
239+ ->setName('order')
240+ ->setId('order')
241+ ->setTitle(Mage::helper('claim')->__('Order'))
242+ ->setClass('validate-select')
243+ ->setValue()
244+ ->setOptions($options);
245+
246+ return $select->getHtml();
247+ }
248+ return '';
249+ }
250+
251+}
252
253=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model'
254=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System'
255=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config'
256=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config/Backend'
257=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config/Backend/Links.php'
258--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config/Backend/Links.php 1970-01-01 00:00:00 +0000
259+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/Model/System/Config/Backend/Links.php 2012-10-15 14:51:21 +0000
260@@ -0,0 +1,48 @@
261+<?php
262+/**
263+ * Magento
264+ *
265+ * NOTICE OF LICENSE
266+ *
267+ * Savoirfairelinux_Claim
268+ * Copyright (C) 2012 Savoir-faire Linux
269+ *
270+ * This program is free software: you can redistribute it and/or modify
271+ * it under the terms of the GNU General Public License as published by
272+ * the Free Software Foundation, either version 3 of the License, or
273+ * (at your option) any later version.
274+ *
275+ * This program is distributed in the hope that it will be useful,
276+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
277+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
278+ * GNU General Public License for more details.
279+ *
280+ * You should have received a copy of the GNU General Public License
281+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
282+ *
283+ * DISCLAIMER
284+ *
285+ * Do not edit or add to this file if you wish to upgrade Magento to newer
286+ * versions in the future. If you wish to customize Magento for your
287+ * needs please refer to http://www.magentocommerce.com for more information.
288+ *
289+ * @category Savoirfairelinux
290+ * @package Savoirfairelinux_Claim
291+ * @copyright Copyright (c) 2012 Savoir-faire Linux. (http://www.savoirfairelinux.com)
292+ * @license http://www.gnu.org/licenses/gpl.html General Public License (GPLv3)
293+ */
294+
295+/**
296+ * Cache cleaner backend model
297+ *
298+ */
299+class Savoirfairelinux_Claim_Model_System_Config_Backend_Links extends Mage_Adminhtml_Model_System_Config_Backend_Cache
300+{
301+ /**
302+ * Cache tags to clean
303+ *
304+ * @var array
305+ */
306+ protected $_cacheTags = array(Mage_Core_Model_Store::CACHE_TAG, Mage_Cms_Model_Block::CACHE_TAG);
307+
308+}
309
310=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/controllers'
311=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/controllers/IndexController.php'
312--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/controllers/IndexController.php 1970-01-01 00:00:00 +0000
313+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/controllers/IndexController.php 2012-10-15 14:51:21 +0000
314@@ -0,0 +1,360 @@
315+<?php
316+/**
317+ * Magento
318+ *
319+ * NOTICE OF LICENSE
320+ *
321+ * Savoirfairelinux_Claim
322+ * Copyright (C) 2012 Savoir-faire Linux
323+ *
324+ * This program is free software: you can redistribute it and/or modify
325+ * it under the terms of the GNU General Public License as published by
326+ * the Free Software Foundation, either version 3 of the License, or
327+ * (at your option) any later version.
328+ *
329+ * This program is distributed in the hope that it will be useful,
330+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
331+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
332+ * GNU General Public License for more details.
333+ *
334+ * You should have received a copy of the GNU General Public License
335+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
336+ *
337+ * DISCLAIMER
338+ *
339+ * Do not edit or add to this file if you wish to upgrade Magento to newer
340+ * versions in the future. If you wish to customize Magento for your
341+ * needs please refer to http://www.magentocommerce.com for more information.
342+ *
343+ * @category Savoirfairelinux
344+ * @package Savoirfairelinux_Claim
345+ * @copyright Copyright (c) 2012 Savoir-faire Linux. (http://www.savoirfairelinux.com)
346+ * @license http://www.gnu.org/licenses/gpl.html General Public License (GPLv3)
347+ */
348+
349+/**
350+ * Claim index controller
351+ *
352+ * @category Savoirfairelinux
353+ * @package Savoirfairelinux_Claim
354+ * @author Joao Alfredo Gama Batista <joao.gama@savoirfairelinux.com>
355+ */
356+
357+class Savoirfairelinux_Claim_IndexController extends Mage_Core_Controller_Front_Action
358+{
359+
360+ const XML_PATH_ENABLED = 'claim/claim/enabled';
361+ const XML_PATH_OPENERP_URL = 'claim/settings/openerp_url';
362+ const XML_PATH_OPENERP_LOGIN = 'claim/settings/openerp_login';
363+ const XML_PATH_OPENERP_PASSWORD = 'claim/settings/openerp_password';
364+ const XML_PATH_OPENERP_DATABASE = 'claim/settings/openerp_database';
365+ const XML_PATH_OPENERP_CLAIM_REF = 'claim/settings/openerp_claim_ref';
366+ const XML_PATH_OPENERP_CLAIM_REF_PREFIX = 'claim/settings/openerp_claim_ref_prefix';
367+
368+ public function preDispatch()
369+ {
370+ parent::preDispatch();
371+ $translate = Mage::getSingleton('core/translate');
372+
373+ if( !Mage::getStoreConfigFlag(self::XML_PATH_ENABLED) ) {
374+ $this->norouteAction();
375+ }
376+
377+ if( !Mage::getSingleton('customer/session')->authenticate($this) ) {
378+ $this->setFlag('', self::FLAG_NO_DISPATCH, true);
379+ }
380+
381+ $this->partner_name = Mage::helper('contacts')->getUserName();
382+
383+ $this->partner_email = Mage::helper('contacts')->getUserEmail();
384+
385+ return;
386+ }
387+
388+ public function indexAction()
389+ {
390+ $this->loadLayout();
391+
392+ $this->getLayout()->getBlock('claimForm')
393+ ->setFormAction( Mage::getUrl('*/*/post') )
394+ ->setRefererUrl($this->_getRefererUrl());
395+
396+ if ($navigationBlock = $this->getLayout()->getBlock('customer_account_navigation')) {
397+ $navigationBlock->setActive('claim');
398+ }
399+
400+ $this->_initLayoutMessages('customer/session');
401+ $this->_initLayoutMessages('catalog/session');
402+ $this->renderLayout();
403+ }
404+
405+ public function postAction()
406+ {
407+
408+ $post = $this->getRequest()->getPost();
409+ if ( $post ) {
410+ $translate = Mage::getSingleton('core/translate');
411+ /* @var $translate Mage_Core_Model_Translate */
412+ $translate->setTranslateInline(false);
413+ try {
414+ $postObject = new Varien_Object();
415+ $postObject->setData($post);
416+
417+ $error = false;
418+
419+ If (!Zend_Validate::is(trim($post['subject']) , 'NotEmpty')) {
420+ $error = true;
421+ }
422+
423+ if ($error) {
424+ throw new Exception();
425+ }
426+
427+ $this->openerp = new Savoirfairelinux_Claim_Openerpclient
428+ (Mage::getStoreConfig(self::XML_PATH_OPENERP_LOGIN),
429+ Mage::helper('core')->decrypt(Mage::getStoreConfig(self::XML_PATH_OPENERP_PASSWORD)),
430+ Mage::getStoreConfig(self::XML_PATH_OPENERP_DATABASE),
431+ Mage::getStoreConfig(self::XML_PATH_OPENERP_URL),
432+ Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF),
433+ Mage::getStoreConfig(self::XML_PATH_OPENERP_CLAIM_REF_PREFIX)
434+ );
435+
436+ /* $openerp->connect(); */
437+ $partner_id = $this->openerp->getPartnerId($this->partner_name,
438+ $this->partner_email);
439+ $partner_contact_id = $this->openerp->getPartnerContactId($partner_id);
440+ $order_id = $this->openerp->getOrderId(trim($post['order']));
441+ $this->openerp->createClaim($partner_id,
442+ $partner_contact_id,
443+ $order_id,
444+ trim($post['subject']),
445+ trim($post['claim']));
446+
447+
448+ $translate->setTranslateInline(true);
449+
450+ Mage::getSingleton('customer/session')->addSuccess(Mage::helper('claim')->__('Your complaint was submitted and will be responded to as soon as possible. Thank you for contacting us.'));
451+ $this->_redirect('*/*/');
452+
453+ return;
454+ } catch (Exception $e) {
455+ $translate->setTranslateInline(true);
456+
457+ Mage::getSingleton('customer/session')->addError(Mage::helper('claim')->__('Unable to submit your complaint. Please, try again later.'));
458+ $this->_redirect('*/*/');
459+ return;
460+ }
461+
462+ } else {
463+ $this->_redirect('*/*/');
464+ }
465+ }
466+
467+}
468+
469+class Savoirfairelinux_Claim_Openerpclient
470+{
471+
472+ function __construct($usr, $pass, $db, $server, $claim_ref, $claim_ref_prefix)
473+ {
474+
475+ $ExternalLibPath=Mage::getModuleDir('', 'Savoirfairelinux_Claim').DS.'lib'.DS.'xmlrpc.inc';
476+ require_once ($ExternalLibPath);
477+
478+ $this->user = $usr;
479+ $this->password = $pass;
480+ $this->database = $db;
481+ $this->services = $server;
482+ $this->claim_ref = $claim_ref;
483+ $this->claim_ref_prefix = $claim_ref_prefix;
484+
485+ $this->client = new xmlrpc_client($this->services.'common');
486+
487+ $this->msg = new xmlrpcmsg('login');
488+ $this->msg->addParam(new xmlrpcval($this->database, "string"));
489+ $this->msg->addParam(new xmlrpcval($this->user, "string"));
490+ $this->msg->addParam(new xmlrpcval($this->password, "string"));
491+
492+ $this->res = &$this->client->send($this->msg);
493+
494+ if(!$this->res->faultCode()){
495+ $this->userid = $this->res->value()->scalarval();
496+ }
497+ else {
498+ Mage::log("Unable to login ".$this->res->faultString());
499+ throw new Exception();
500+ exit;
501+ }
502+ }
503+
504+ public function getPartnerId($partner, $email)
505+ {
506+ Mage::log('getPartnerId: '.$partner);
507+
508+ $key = array(new xmlrpcval(array(new xmlrpcval("name" , "string"),
509+ new xmlrpcval("=","string"),
510+ new xmlrpcval($partner, "string")),"array"),
511+ );
512+
513+ $ids = $this->search('res.partner', $key);
514+ if(sizeof($ids) == 0) {
515+ $id = -1;
516+ } elseif(sizeof($ids) == 1) {
517+ $id = $ids[0]->scalarval();
518+ } elseif( sizeof($ids) > 1) {
519+ foreach( $ids as $v ) {
520+ $m = $this->getPartnerEmail($v);
521+ if( $m == $email )
522+ $id = $v->scalarval();
523+ }
524+ }
525+ /* return $ids; */
526+ Mage::log('getPartnerId: '.$id);
527+ return $id;
528+ }
529+
530+ public function isPartner($name, $email)
531+ {
532+ $partner_id = $this->getPartnerId($name, $email);
533+ if( $partner_id == -1 )
534+ $ret = False;
535+ else
536+ $ret = True;
537+
538+ return $ret;
539+ }
540+
541+ public function getPartnerContactId($partner_id)
542+ {
543+ Mage::log('getPartnerContactId: '.$partner_id);
544+
545+ $key = array(new xmlrpcval(array(new xmlrpcval("partner_id" , "string"),
546+ new xmlrpcval("=","string"),
547+ new xmlrpcval($partner_id, "int")),"array"));
548+
549+ $ids = $this->search('res.partner.address', $key);
550+ /* return $ids; */
551+ Mage::log('getpartnerContactId: '.$ids[0]->scalarval());
552+ return $ids[0]->scalarval();
553+ }
554+
555+ public function getOrderId($order)
556+ {
557+
558+ $key = array(new xmlrpcval(array(new xmlrpcval("name" , "string"),
559+ new xmlrpcval("=","string"),
560+ new xmlrpcval($this->claim_ref_prefix.$order, "string")),"array"),
561+ );
562+
563+ $ids = $this->search($this->claim_ref, $key);
564+ if( sizeof($ids) > 1 ) {
565+ Mage::log('Duplicated orders error!!');
566+ throw new Exception();
567+ }
568+
569+ /* return $ids; */
570+ Mage::log('Order_id: '.$ids[0]->scalarval());
571+ return $ids[0]->scalarval();
572+ }
573+
574+ public function createClaim($partner,
575+ $partner_address,
576+ $order,
577+ $subject,
578+ $description)
579+ {
580+ $arrayVal = array(
581+ 'name'=>new xmlrpcval($subject, "string") ,
582+ 'description'=>new xmlrpcval($description, "string"),
583+ 'partner_id'=>new xmlrpcval($partner, "int"),
584+ 'partner_address_id'=>new xmlrpcval($partner_address, "int"),
585+ 'ref'=>new xmlrpcval('sale.order,'.$order, 'string')
586+ );
587+
588+ $this->client = new xmlrpc_client($this->services."object");
589+
590+ $this->msg = new xmlrpcmsg('execute');
591+ $this->msg->addParam(new xmlrpcval($this->database, "string"));
592+ $this->msg->addParam(new xmlrpcval($this->userid, "int"));
593+ $this->msg->addParam(new xmlrpcval($this->password, "string"));
594+ $this->msg->addParam(new xmlrpcval("crm.claim", "string"));
595+ $this->msg->addParam(new xmlrpcval("create", "string"));
596+ $this->msg->addParam(new xmlrpcval($arrayVal, "struct"));
597+
598+ $this->resp = $this->client->send($this->msg);
599+
600+ if ($this->resp->faultCode())
601+ Mage::log('Error: '.$this->resp->faultString());
602+ else
603+ Mage::log('Claim '.$this->resp->value()->scalarval().' created !');
604+
605+ }
606+
607+ public function getPartnerEmail($partner_id)
608+ {
609+ Mage::log('getPartnerEmail: '.$partner_id->scalarval());
610+
611+ $ids = array($partner_id);
612+ $fields = array(new xmlrpcval('email', 'string'));
613+
614+ $ids = $this->read('res.partner', $ids, $fields);
615+
616+ $id = $ids[0]->scalarval();
617+ Mage::log('getPartnerEmail: '.$id['email']->scalarval());
618+ return $id['email']->scalarval();
619+ }
620+
621+ public function search($relation, $key)
622+ {
623+ Mage::log('search: '.$relation);
624+ $this->client = new xmlrpc_client($this->services.'object');
625+
626+ $this->msg = new xmlrpcmsg('execute');
627+ $this->msg->addParam(new xmlrpcval($this->database, "string"));
628+ $this->msg->addParam(new xmlrpcval($this->userid, "int"));
629+ $this->msg->addParam(new xmlrpcval($this->password, "string"));
630+ $this->msg->addParam(new xmlrpcval($relation, "string"));
631+ $this->msg->addParam(new xmlrpcval("search", "string"));
632+ $this->msg->addParam(new xmlrpcval($key, "array"));
633+
634+ $this->resp = $this->client->send($this->msg);
635+
636+ if ($this->resp->faultCode()) {
637+ Mage::log('Search error: '.$this->resp->faultString());
638+ $id = '';
639+ } else {
640+ Mage::log('Search '.$relation.' succeded !');
641+ $id = $this->resp->value()->scalarval();
642+ }
643+
644+ return $id;
645+ }
646+
647+ public function read($relation, $ids, $fields)
648+ {
649+ Mage::log('read: '.$relation);
650+ $this->client = new xmlrpc_client($this->services.'object');
651+
652+ $this->msg = new xmlrpcmsg('execute');
653+ $this->msg->addParam(new xmlrpcval($this->database, "string"));
654+ $this->msg->addParam(new xmlrpcval($this->userid, "int"));
655+ $this->msg->addParam(new xmlrpcval($this->password, "string"));
656+ $this->msg->addParam(new xmlrpcval($relation, "string"));
657+ $this->msg->addParam(new xmlrpcval("read", "string"));
658+ $this->msg->addParam(new xmlrpcval($ids, "array"));
659+ $this->msg->addParam(new xmlrpcval($fields, "array"));
660+
661+ $this->resp = $this->client->send($this->msg);
662+
663+ if ($this->resp->faultCode()) {
664+ Mage::log('Read error: '.$this->resp->faultString());
665+ $id = '';
666+ } else {
667+ Mage::log('Read '.$relation.' succeded !');
668+ $id = $this->resp->value()->scalarval();
669+ }
670+
671+ return $id;
672+ }
673+
674+}
675
676=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc'
677=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/adminhtml.xml'
678--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/adminhtml.xml 1970-01-01 00:00:00 +0000
679+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/adminhtml.xml 2012-10-15 14:51:21 +0000
680@@ -0,0 +1,22 @@
681+<?xml version="1.0" encoding="utf-8"?>
682+<config>
683+ <acl>
684+ <resources>
685+ <admin>
686+ <children>
687+ <system>
688+ <children>
689+ <config>
690+ <children>
691+ <claim translate="title" module="claim">
692+ <title>Complaints Settings</title>
693+ </claim>
694+ </children>
695+ </config>
696+ </children>
697+ </system>
698+ </children>
699+ </admin>
700+ </resources>
701+ </acl>
702+</config>
703\ No newline at end of file
704
705=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/config.xml'
706--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/config.xml 1970-01-01 00:00:00 +0000
707+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/config.xml 2012-10-15 14:51:21 +0000
708@@ -0,0 +1,117 @@
709+<?xml version="1.0"?>
710+<!--
711+/**
712+ * Magento
713+ *
714+ * NOTICE OF LICENSE
715+ *
716+ * Savoirfairelinux_Claim
717+ * Copyright (C) 2012 Savoir-faire Linux
718+ *
719+ * This program is free software: you can redistribute it and/or modify
720+ * it under the terms of the GNU General Public License as published by
721+ * the Free Software Foundation, either version 3 of the License, or
722+ * (at your option) any later version.
723+ *
724+ * This program is distributed in the hope that it will be useful,
725+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
726+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
727+ * GNU General Public License for more details.
728+ *
729+ * You should have received a copy of the GNU General Public License
730+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
731+ *
732+ * DISCLAIMER
733+ *
734+ * Do not edit or add to this file if you wish to upgrade Magento to newer
735+ * versions in the future. If you wish to customize Magento for your
736+ * needs please refer to http://www.magentocommerce.com for more information.
737+ *
738+ * @category Savoirfairelinux
739+ * @package Savoirfairelinux_Claim
740+ * @copyright Copyright (c) 2012 Savoir-faire Linux (http://www.savoirfairelinux.com)
741+ * @license http://www.gnu.org/licenses/gpl.html General Public License (GPLv3)
742+ */
743+-->
744+<config>
745+ <modules>
746+ <Savoirfairelinux_Claim>
747+ <version>0.1.1</version>
748+ </Savoirfairelinux_Claim>
749+ </modules>
750+ <global>
751+ <!-- <resources> -->
752+ <!-- <claim_setup> -->
753+ <!-- <setup> -->
754+ <!-- <module>Savoirfairelinux_Claim</module> -->
755+ <!-- </setup> -->
756+ <!-- </claim_setup> -->
757+ <!-- </resources> -->
758+ <helpers>
759+ <claim>
760+ <class>Savoirfairelinux_Claim_Helper</class>
761+ </claim>
762+ </helpers>
763+
764+ <blocks>
765+ <claim>
766+ <class>Savoirfairelinux_Claim_Block</class>
767+ </claim>
768+ </blocks>
769+
770+ </global>
771+
772+ <adminhtml>
773+ <translate>
774+ <modules>
775+ <Savoirfairelinux_Claim>
776+ <files>
777+ <default>Savoirfairelinux_Claim.csv</default>
778+ </files>
779+ </Savoirfairelinux_Claim>
780+ </modules>
781+ </translate>
782+ </adminhtml>
783+ <frontend>
784+ <routers>
785+ <claim>
786+ <use>standard</use>
787+ <args>
788+ <module>Savoirfairelinux_Claim</module>
789+ <frontName>claim</frontName>
790+ </args>
791+ </claim>
792+ </routers>
793+ <translate>
794+ <modules>
795+ <Savoirfairelinux_Claim>
796+ <files>
797+ <default>Savoirfairelinux_Claim.csv</default>
798+ </files>
799+ </Savoirfairelinux_Claim>
800+ </modules>
801+ </translate>
802+ <layout>
803+ <updates>
804+ <claim>
805+ <file>savoirfairelinux_claim.xml</file>
806+ </claim>
807+ </updates>
808+ </layout>
809+ </frontend>
810+ <default>
811+ <claim>
812+ <claim>
813+ <enabled>1</enabled>
814+ </claim>
815+ <settings>
816+ <openerp_url>http://localhost:8069/xmlrpc/</openerp_url>
817+ <openerp_database>test</openerp_database>
818+ <openerp_login>admin</openerp_login>
819+ <openerp_password>admin123</openerp_password>
820+ <openerp_claim_ref>sale.order</openerp_claim_ref>
821+ <openerp_claim_ref_prefix>mag_</openerp_claim_ref_prefix>
822+ </settings>
823+ </claim>
824+ </default>
825+</config>
826
827=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/modules'
828=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/modules/Savoirfairelinux_Claim.xml'
829--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/modules/Savoirfairelinux_Claim.xml 1970-01-01 00:00:00 +0000
830+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/modules/Savoirfairelinux_Claim.xml 2012-10-15 14:51:21 +0000
831@@ -0,0 +1,9 @@
832+<?xml version="1.0" encoding="utf-8"?>
833+<config>
834+ <modules>
835+ <Savoirfairelinux_Claim>
836+ <active>true</active>
837+ <codePool>local</codePool>
838+ </Savoirfairelinux_Claim>
839+ </modules>
840+</config>
841
842=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/system.xml'
843--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/system.xml 1970-01-01 00:00:00 +0000
844+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/etc/system.xml 2012-10-15 14:51:21 +0000
845@@ -0,0 +1,114 @@
846+<?xml version="1.0" encoding="utf-8"?>
847+<config>
848+ <sections>
849+ <claim translate="label" module="claim">
850+ <label>Complaints</label>
851+ <tab>customer</tab>
852+ <frontend_type>text</frontend_type>
853+ <sort_order>990</sort_order>
854+ <show_in_default>1</show_in_default>
855+ <show_in_website>1</show_in_website>
856+ <show_in_store>1</show_in_store>
857+ <groups>
858+ <claim translate="label">
859+ <label>Claim</label>
860+ <frontend_type>text</frontend_type>
861+ <sort_order>20</sort_order>
862+ <show_in_default>1</show_in_default>
863+ <show_in_website>1</show_in_website>
864+ <show_in_store>1</show_in_store>
865+ <fields>
866+ <enabled translate="label">
867+ <label>Enabled</label>
868+ <frontend_type>select</frontend_type>
869+ <source_model>adminhtml/system_config_source_yesno</source_model>
870+ <!-- <backend_model>claim/system_config_backend_links</backend_model> -->
871+ <sort_order>4</sort_order>
872+ <show_in_default>1</show_in_default>
873+ <show_in_website>1</show_in_website>
874+ <show_in_store>1</show_in_store>
875+ </enabled>
876+
877+ <!-- <enabled translate="label"> -->
878+ <!-- <label>Enabled</label> -->
879+ <!-- <frontend_type>select</frontend_type> -->
880+ <!-- <source_model>adminhtml/system_config_source_yesno</source_model> -->
881+ <!-- <backend_model>savoirfairelinux_claim/system_config_backend_links</backend_model> -->
882+ <!-- <sort_order>1</sort_order> -->
883+ <!-- <show_in_default>1</show_in_default> -->
884+ <!-- <show_in_website>1</show_in_website> -->
885+ <!-- <show_in_store>1</show_in_store> -->
886+ <!-- </enabled> -->
887+ </fields>
888+ </claim>
889+ <settings translate="label">
890+ <label>OpenERP Settings</label>
891+ <frontend_type>text</frontend_type>
892+ <sort_order>20</sort_order>
893+ <show_in_default>1</show_in_default>
894+ <show_in_website>1</show_in_website>
895+ <show_in_store>1</show_in_store>
896+ <fields>
897+ <openerp_url translate="label,comment">
898+ <label>OpenERP server URL</label>
899+ <comment><![CDATA[]]></comment>
900+ <frontend_type>text</frontend_type>
901+ <sort_order>1</sort_order>
902+ <show_in_default>1</show_in_default>
903+ <show_in_website>1</show_in_website>
904+ <show_in_store>1</show_in_store>
905+ </openerp_url>
906+ <openerp_database translate="label,comment">
907+ <label>OpenERP database name</label>
908+ <comment><![CDATA[]]></comment>
909+ <frontend_type>text</frontend_type>
910+ <sort_order>1</sort_order>
911+ <show_in_default>1</show_in_default>
912+ <show_in_website>1</show_in_website>
913+ <show_in_store>1</show_in_store>
914+ </openerp_database>
915+
916+ <openerp_login translate="label">
917+ <label>OpenERP login</label>
918+ <frontend_type>text</frontend_type>
919+ <sort_order>2</sort_order>
920+ <show_in_default>1</show_in_default>
921+ <show_in_website>1</show_in_website>
922+ <show_in_store>1</show_in_store>
923+ </openerp_login>
924+ <openerp_password translate="label,comment">
925+ <label>OpenERP password</label>
926+ <comment></comment>
927+ <backend_model>adminhtml/system_config_backend_encrypted</backend_model>
928+ <frontend_type>obscure</frontend_type>
929+ <sort_order>3</sort_order>
930+ <show_in_default>1</show_in_default>
931+ <show_in_website>1</show_in_website>
932+ <show_in_store>1</show_in_store>
933+ </openerp_password>
934+ <openerp_claim_ref translate="label,comment">
935+ <label>OpenERP Reference field entity</label>
936+ <comment></comment>
937+ <frontend_type>text</frontend_type>
938+ <sort_order>4</sort_order>
939+ <show_in_default>1</show_in_default>
940+ <show_in_website>1</show_in_website>
941+ <show_in_store>1</show_in_store>
942+ </openerp_claim_ref>
943+ <openerp_claim_ref_prefix translate="label,comment">
944+ <label>OpenERP Reference field prefix</label>
945+ <comment></comment>
946+ <frontend_type>text</frontend_type>
947+ <sort_order>5</sort_order>
948+ <show_in_default>1</show_in_default>
949+ <show_in_website>1</show_in_website>
950+ <show_in_store>1</show_in_store>
951+ </openerp_claim_ref_prefix>
952+ </fields>
953+ </settings>
954+ </groups>
955+ </claim>
956+ </sections>
957+</config>
958+
959+
960\ No newline at end of file
961
962=== added directory 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib'
963=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc.inc'
964--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc.inc 1970-01-01 00:00:00 +0000
965+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc.inc 2012-10-15 14:51:21 +0000
966@@ -0,0 +1,3776 @@
967+<?php
968+// by Edd Dumbill (C) 1999-2002
969+// <edd@usefulinc.com>
970+// $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $
971+
972+// Copyright (c) 1999,2000,2002 Edd Dumbill.
973+// All rights reserved.
974+//
975+// Redistribution and use in source and binary forms, with or without
976+// modification, are permitted provided that the following conditions
977+// are met:
978+//
979+// * Redistributions of source code must retain the above copyright
980+// notice, this list of conditions and the following disclaimer.
981+//
982+// * Redistributions in binary form must reproduce the above
983+// copyright notice, this list of conditions and the following
984+// disclaimer in the documentation and/or other materials provided
985+// with the distribution.
986+//
987+// * Neither the name of the "XML-RPC for PHP" nor the names of its
988+// contributors may be used to endorse or promote products derived
989+// from this software without specific prior written permission.
990+//
991+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
992+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
993+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
994+// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
995+// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
996+// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
997+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
998+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
999+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
1000+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1001+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
1002+// OF THE POSSIBILITY OF SUCH DAMAGE.
1003+
1004+ if(!function_exists('xml_parser_create'))
1005+ {
1006+ // For PHP 4 onward, XML functionality is always compiled-in on windows:
1007+ // no more need to dl-open it. It might have been compiled out on *nix...
1008+ if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN'))
1009+ {
1010+ dl('xml.so');
1011+ }
1012+ }
1013+
1014+ // G. Giunta 2005/01/29: declare global these variables,
1015+ // so that xmlrpc.inc will work even if included from within a function
1016+ // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used.
1017+ $GLOBALS['xmlrpcI4']='i4';
1018+ $GLOBALS['xmlrpcInt']='int';
1019+ $GLOBALS['xmlrpcBoolean']='boolean';
1020+ $GLOBALS['xmlrpcDouble']='double';
1021+ $GLOBALS['xmlrpcString']='string';
1022+ $GLOBALS['xmlrpcDateTime']='dateTime.iso8601';
1023+ $GLOBALS['xmlrpcBase64']='base64';
1024+ $GLOBALS['xmlrpcArray']='array';
1025+ $GLOBALS['xmlrpcStruct']='struct';
1026+ $GLOBALS['xmlrpcValue']='undefined';
1027+
1028+ $GLOBALS['xmlrpcTypes']=array(
1029+ $GLOBALS['xmlrpcI4'] => 1,
1030+ $GLOBALS['xmlrpcInt'] => 1,
1031+ $GLOBALS['xmlrpcBoolean'] => 1,
1032+ $GLOBALS['xmlrpcString'] => 1,
1033+ $GLOBALS['xmlrpcDouble'] => 1,
1034+ $GLOBALS['xmlrpcDateTime'] => 1,
1035+ $GLOBALS['xmlrpcBase64'] => 1,
1036+ $GLOBALS['xmlrpcArray'] => 2,
1037+ $GLOBALS['xmlrpcStruct'] => 3
1038+ );
1039+
1040+ $GLOBALS['xmlrpc_valid_parents'] = array(
1041+ 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'),
1042+ 'BOOLEAN' => array('VALUE'),
1043+ 'I4' => array('VALUE'),
1044+ 'INT' => array('VALUE'),
1045+ 'STRING' => array('VALUE'),
1046+ 'DOUBLE' => array('VALUE'),
1047+ 'DATETIME.ISO8601' => array('VALUE'),
1048+ 'BASE64' => array('VALUE'),
1049+ 'MEMBER' => array('STRUCT'),
1050+ 'NAME' => array('MEMBER'),
1051+ 'DATA' => array('ARRAY'),
1052+ 'ARRAY' => array('VALUE'),
1053+ 'STRUCT' => array('VALUE'),
1054+ 'PARAM' => array('PARAMS'),
1055+ 'METHODNAME' => array('METHODCALL'),
1056+ 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'),
1057+ 'FAULT' => array('METHODRESPONSE'),
1058+ 'NIL' => array('VALUE'), // only used when extension activated
1059+ 'EX:NIL' => array('VALUE') // only used when extension activated
1060+ );
1061+
1062+ // define extra types for supporting NULL (useful for json or <NIL/>)
1063+ $GLOBALS['xmlrpcNull']='null';
1064+ $GLOBALS['xmlrpcTypes']['null']=1;
1065+
1066+ // Not in use anymore since 2.0. Shall we remove it?
1067+ /// @deprecated
1068+ $GLOBALS['xmlEntities']=array(
1069+ 'amp' => '&',
1070+ 'quot' => '"',
1071+ 'lt' => '<',
1072+ 'gt' => '>',
1073+ 'apos' => "'"
1074+ );
1075+
1076+ // tables used for transcoding different charsets into us-ascii xml
1077+
1078+ $GLOBALS['xml_iso88591_Entities']=array();
1079+ $GLOBALS['xml_iso88591_Entities']['in'] = array();
1080+ $GLOBALS['xml_iso88591_Entities']['out'] = array();
1081+ for ($i = 0; $i < 32; $i++)
1082+ {
1083+ $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
1084+ $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
1085+ }
1086+ for ($i = 160; $i < 256; $i++)
1087+ {
1088+ $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i);
1089+ $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';';
1090+ }
1091+
1092+ /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159?
1093+ /// These will NOT be present in true ISO-8859-1, but will save the unwary
1094+ /// windows user from sending junk (though no luck when reciving them...)
1095+ /*
1096+ $GLOBALS['xml_cp1252_Entities']=array();
1097+ for ($i = 128; $i < 160; $i++)
1098+ {
1099+ $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i);
1100+ }
1101+ $GLOBALS['xml_cp1252_Entities']['out'] = array(
1102+ '&#x20AC;', '?', '&#x201A;', '&#x0192;',
1103+ '&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
1104+ '&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
1105+ '&#x0152;', '?', '&#x017D;', '?',
1106+ '?', '&#x2018;', '&#x2019;', '&#x201C;',
1107+ '&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
1108+ '&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
1109+ '&#x0153;', '?', '&#x017E;', '&#x0178;'
1110+ );
1111+ */
1112+
1113+ $GLOBALS['xmlrpcerr'] = array(
1114+ 'unknown_method'=>1,
1115+ 'invalid_return'=>2,
1116+ 'incorrect_params'=>3,
1117+ 'introspect_unknown'=>4,
1118+ 'http_error'=>5,
1119+ 'no_data'=>6,
1120+ 'no_ssl'=>7,
1121+ 'curl_fail'=>8,
1122+ 'invalid_request'=>15,
1123+ 'no_curl'=>16,
1124+ 'server_error'=>17,
1125+ 'multicall_error'=>18,
1126+ 'multicall_notstruct'=>9,
1127+ 'multicall_nomethod'=>10,
1128+ 'multicall_notstring'=>11,
1129+ 'multicall_recursion'=>12,
1130+ 'multicall_noparams'=>13,
1131+ 'multicall_notarray'=>14,
1132+
1133+ 'cannot_decompress'=>103,
1134+ 'decompress_fail'=>104,
1135+ 'dechunk_fail'=>105,
1136+ 'server_cannot_decompress'=>106,
1137+ 'server_decompress_fail'=>107
1138+ );
1139+
1140+ $GLOBALS['xmlrpcstr'] = array(
1141+ 'unknown_method'=>'Unknown method',
1142+ 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload',
1143+ 'incorrect_params'=>'Incorrect parameters passed to method',
1144+ 'introspect_unknown'=>"Can't introspect: method unknown",
1145+ 'http_error'=>"Didn't receive 200 OK from remote server.",
1146+ 'no_data'=>'No data received from server.',
1147+ 'no_ssl'=>'No SSL support compiled in.',
1148+ 'curl_fail'=>'CURL error',
1149+ 'invalid_request'=>'Invalid request payload',
1150+ 'no_curl'=>'No CURL support compiled in.',
1151+ 'server_error'=>'Internal server error',
1152+ 'multicall_error'=>'Received from server invalid multicall response',
1153+ 'multicall_notstruct'=>'system.multicall expected struct',
1154+ 'multicall_nomethod'=>'missing methodName',
1155+ 'multicall_notstring'=>'methodName is not a string',
1156+ 'multicall_recursion'=>'recursive system.multicall forbidden',
1157+ 'multicall_noparams'=>'missing params',
1158+ 'multicall_notarray'=>'params is not an array',
1159+
1160+ 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress',
1161+ 'decompress_fail'=>'Received from server invalid compressed HTTP',
1162+ 'dechunk_fail'=>'Received from server invalid chunked HTTP',
1163+ 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress',
1164+ 'server_decompress_fail'=>'Received from client invalid compressed HTTP request'
1165+ );
1166+
1167+ // The charset encoding used by the server for received messages and
1168+ // by the client for received responses when received charset cannot be determined
1169+ // or is not supported
1170+ $GLOBALS['xmlrpc_defencoding']='UTF-8';
1171+
1172+ // The encoding used internally by PHP.
1173+ // String values received as xml will be converted to this, and php strings will be converted to xml
1174+ // as if having been coded with this
1175+ $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1';
1176+
1177+ $GLOBALS['xmlrpcName']='XML-RPC for PHP';
1178+ $GLOBALS['xmlrpcVersion']='3.0.0.beta';
1179+
1180+ // let user errors start at 800
1181+ $GLOBALS['xmlrpcerruser']=800;
1182+ // let XML parse errors start at 100
1183+ $GLOBALS['xmlrpcerrxml']=100;
1184+
1185+ // formulate backslashes for escaping regexp
1186+ // Not in use anymore since 2.0. Shall we remove it?
1187+ /// @deprecated
1188+ $GLOBALS['xmlrpc_backslash']=chr(92).chr(92);
1189+
1190+ // set to TRUE to enable correct decoding of <NIL/> and <EX:NIL/> values
1191+ $GLOBALS['xmlrpc_null_extension']=false;
1192+
1193+ // set to TRUE to enable encoding of php NULL values to <EX:NIL/> instead of <NIL/>
1194+ $GLOBALS['xmlrpc_null_apache_encoding']=false;
1195+
1196+ // used to store state during parsing
1197+ // quick explanation of components:
1198+ // ac - used to accumulate values
1199+ // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1)
1200+ // isf_reason - used for storing xmlrpcresp fault string
1201+ // lv - used to indicate "looking for a value": implements
1202+ // the logic to allow values with no types to be strings
1203+ // params - used to store parameters in method calls
1204+ // method - used to store method name
1205+ // stack - array with genealogy of xml elements names:
1206+ // used to validate nesting of xmlrpc elements
1207+ $GLOBALS['_xh']=null;
1208+
1209+ /**
1210+ * Convert a string to the correct XML representation in a target charset
1211+ * To help correct communication of non-ascii chars inside strings, regardless
1212+ * of the charset used when sending requests, parsing them, sending responses
1213+ * and parsing responses, an option is to convert all non-ascii chars present in the message
1214+ * into their equivalent 'charset entity'. Charset entities enumerated this way
1215+ * are independent of the charset encoding used to transmit them, and all XML
1216+ * parsers are bound to understand them.
1217+ * Note that in the std case we are not sending a charset encoding mime type
1218+ * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii.
1219+ *
1220+ * @todo do a bit of basic benchmarking (strtr vs. str_replace)
1221+ * @todo make usage of iconv() or recode_string() or mb_string() where available
1222+ */
1223+ function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='')
1224+ {
1225+ if ($src_encoding == '')
1226+ {
1227+ // lame, but we know no better...
1228+ $src_encoding = $GLOBALS['xmlrpc_internalencoding'];
1229+ }
1230+
1231+ switch(strtoupper($src_encoding.'_'.$dest_encoding))
1232+ {
1233+ case 'ISO-8859-1_':
1234+ case 'ISO-8859-1_US-ASCII':
1235+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1236+ $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
1237+ break;
1238+ case 'ISO-8859-1_UTF-8':
1239+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1240+ $escaped_data = utf8_encode($escaped_data);
1241+ break;
1242+ case 'ISO-8859-1_ISO-8859-1':
1243+ case 'US-ASCII_US-ASCII':
1244+ case 'US-ASCII_UTF-8':
1245+ case 'US-ASCII_':
1246+ case 'US-ASCII_ISO-8859-1':
1247+ case 'UTF-8_UTF-8':
1248+ //case 'CP1252_CP1252':
1249+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1250+ break;
1251+ case 'UTF-8_':
1252+ case 'UTF-8_US-ASCII':
1253+ case 'UTF-8_ISO-8859-1':
1254+ // NB: this will choke on invalid UTF-8, going most likely beyond EOF
1255+ $escaped_data = '';
1256+ // be kind to users creating string xmlrpcvals out of different php types
1257+ $data = (string) $data;
1258+ $ns = strlen ($data);
1259+ for ($nn = 0; $nn < $ns; $nn++)
1260+ {
1261+ $ch = $data[$nn];
1262+ $ii = ord($ch);
1263+ //1 7 0bbbbbbb (127)
1264+ if ($ii < 128)
1265+ {
1266+ /// @todo shall we replace this with a (supposedly) faster str_replace?
1267+ switch($ii){
1268+ case 34:
1269+ $escaped_data .= '&quot;';
1270+ break;
1271+ case 38:
1272+ $escaped_data .= '&amp;';
1273+ break;
1274+ case 39:
1275+ $escaped_data .= '&apos;';
1276+ break;
1277+ case 60:
1278+ $escaped_data .= '&lt;';
1279+ break;
1280+ case 62:
1281+ $escaped_data .= '&gt;';
1282+ break;
1283+ default:
1284+ $escaped_data .= $ch;
1285+ } // switch
1286+ }
1287+ //2 11 110bbbbb 10bbbbbb (2047)
1288+ else if ($ii>>5 == 6)
1289+ {
1290+ $b1 = ($ii & 31);
1291+ $ii = ord($data[$nn+1]);
1292+ $b2 = ($ii & 63);
1293+ $ii = ($b1 * 64) + $b2;
1294+ $ent = sprintf ('&#%d;', $ii);
1295+ $escaped_data .= $ent;
1296+ $nn += 1;
1297+ }
1298+ //3 16 1110bbbb 10bbbbbb 10bbbbbb
1299+ else if ($ii>>4 == 14)
1300+ {
1301+ $b1 = ($ii & 15);
1302+ $ii = ord($data[$nn+1]);
1303+ $b2 = ($ii & 63);
1304+ $ii = ord($data[$nn+2]);
1305+ $b3 = ($ii & 63);
1306+ $ii = ((($b1 * 64) + $b2) * 64) + $b3;
1307+ $ent = sprintf ('&#%d;', $ii);
1308+ $escaped_data .= $ent;
1309+ $nn += 2;
1310+ }
1311+ //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
1312+ else if ($ii>>3 == 30)
1313+ {
1314+ $b1 = ($ii & 7);
1315+ $ii = ord($data[$nn+1]);
1316+ $b2 = ($ii & 63);
1317+ $ii = ord($data[$nn+2]);
1318+ $b3 = ($ii & 63);
1319+ $ii = ord($data[$nn+3]);
1320+ $b4 = ($ii & 63);
1321+ $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
1322+ $ent = sprintf ('&#%d;', $ii);
1323+ $escaped_data .= $ent;
1324+ $nn += 3;
1325+ }
1326+ }
1327+ break;
1328+/*
1329+ case 'CP1252_':
1330+ case 'CP1252_US-ASCII':
1331+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1332+ $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data);
1333+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
1334+ break;
1335+ case 'CP1252_UTF-8':
1336+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1337+ /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them)
1338+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
1339+ $escaped_data = utf8_encode($escaped_data);
1340+ break;
1341+ case 'CP1252_ISO-8859-1':
1342+ $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
1343+ // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
1344+ $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data);
1345+ break;
1346+*/
1347+ default:
1348+ $escaped_data = '';
1349+ error_log("Converting from $src_encoding to $dest_encoding: not supported...");
1350+ }
1351+ return $escaped_data;
1352+ }
1353+
1354+ /// xml parser handler function for opening element tags
1355+ function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false)
1356+ {
1357+ // if invalid xmlrpc already detected, skip all processing
1358+ if ($GLOBALS['_xh']['isf'] < 2)
1359+ {
1360+ // check for correct element nesting
1361+ // top level element can only be of 2 types
1362+ /// @todo optimization creep: save this check into a bool variable, instead of using count() every time:
1363+ /// there is only a single top level element in xml anyway
1364+ if (count($GLOBALS['_xh']['stack']) == 0)
1365+ {
1366+ if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && (
1367+ $name != 'VALUE' && !$accept_single_vals))
1368+ {
1369+ $GLOBALS['_xh']['isf'] = 2;
1370+ $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element';
1371+ return;
1372+ }
1373+ else
1374+ {
1375+ $GLOBALS['_xh']['rt'] = strtolower($name);
1376+ $GLOBALS['_xh']['rt'] = strtolower($name);
1377+ }
1378+ }
1379+ else
1380+ {
1381+ // not top level element: see if parent is OK
1382+ $parent = end($GLOBALS['_xh']['stack']);
1383+ if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name]))
1384+ {
1385+ $GLOBALS['_xh']['isf'] = 2;
1386+ $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent";
1387+ return;
1388+ }
1389+ }
1390+
1391+ switch($name)
1392+ {
1393+ // optimize for speed switch cases: most common cases first
1394+ case 'VALUE':
1395+ /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element
1396+ $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet
1397+ $GLOBALS['_xh']['ac']='';
1398+ $GLOBALS['_xh']['lv']=1;
1399+ $GLOBALS['_xh']['php_class']=null;
1400+ break;
1401+ case 'I4':
1402+ case 'INT':
1403+ case 'STRING':
1404+ case 'BOOLEAN':
1405+ case 'DOUBLE':
1406+ case 'DATETIME.ISO8601':
1407+ case 'BASE64':
1408+ if ($GLOBALS['_xh']['vt']!='value')
1409+ {
1410+ //two data elements inside a value: an error occurred!
1411+ $GLOBALS['_xh']['isf'] = 2;
1412+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
1413+ return;
1414+ }
1415+ $GLOBALS['_xh']['ac']=''; // reset the accumulator
1416+ break;
1417+ case 'STRUCT':
1418+ case 'ARRAY':
1419+ if ($GLOBALS['_xh']['vt']!='value')
1420+ {
1421+ //two data elements inside a value: an error occurred!
1422+ $GLOBALS['_xh']['isf'] = 2;
1423+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
1424+ return;
1425+ }
1426+ // create an empty array to hold child values, and push it onto appropriate stack
1427+ $cur_val = array();
1428+ $cur_val['values'] = array();
1429+ $cur_val['type'] = $name;
1430+ // check for out-of-band information to rebuild php objs
1431+ // and in case it is found, save it
1432+ if (@isset($attrs['PHP_CLASS']))
1433+ {
1434+ $cur_val['php_class'] = $attrs['PHP_CLASS'];
1435+ }
1436+ $GLOBALS['_xh']['valuestack'][] = $cur_val;
1437+ $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next
1438+ break;
1439+ case 'DATA':
1440+ if ($GLOBALS['_xh']['vt']!='data')
1441+ {
1442+ //two data elements inside a value: an error occurred!
1443+ $GLOBALS['_xh']['isf'] = 2;
1444+ $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element";
1445+ return;
1446+ }
1447+ case 'METHODCALL':
1448+ case 'METHODRESPONSE':
1449+ case 'PARAMS':
1450+ // valid elements that add little to processing
1451+ break;
1452+ case 'METHODNAME':
1453+ case 'NAME':
1454+ /// @todo we could check for 2 NAME elements inside a MEMBER element
1455+ $GLOBALS['_xh']['ac']='';
1456+ break;
1457+ case 'FAULT':
1458+ $GLOBALS['_xh']['isf']=1;
1459+ break;
1460+ case 'MEMBER':
1461+ $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on
1462+ //$GLOBALS['_xh']['ac']='';
1463+ // Drop trough intentionally
1464+ case 'PARAM':
1465+ // clear value type, so we can check later if no value has been passed for this param/member
1466+ $GLOBALS['_xh']['vt']=null;
1467+ break;
1468+ case 'NIL':
1469+ case 'EX:NIL':
1470+ if ($GLOBALS['xmlrpc_null_extension'])
1471+ {
1472+ if ($GLOBALS['_xh']['vt']!='value')
1473+ {
1474+ //two data elements inside a value: an error occurred!
1475+ $GLOBALS['_xh']['isf'] = 2;
1476+ $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value";
1477+ return;
1478+ }
1479+ $GLOBALS['_xh']['ac']=''; // reset the accumulator
1480+ break;
1481+ }
1482+ // we do not support the <NIL/> extension, so
1483+ // drop through intentionally
1484+ default:
1485+ /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!!
1486+ $GLOBALS['_xh']['isf'] = 2;
1487+ $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name";
1488+ break;
1489+ }
1490+
1491+ // Save current element name to stack, to validate nesting
1492+ $GLOBALS['_xh']['stack'][] = $name;
1493+
1494+ /// @todo optimization creep: move this inside the big switch() above
1495+ if($name!='VALUE')
1496+ {
1497+ $GLOBALS['_xh']['lv']=0;
1498+ }
1499+ }
1500+ }
1501+
1502+ /// Used in decoding xml chunks that might represent single xmlrpc values
1503+ function xmlrpc_se_any($parser, $name, $attrs)
1504+ {
1505+ xmlrpc_se($parser, $name, $attrs, true);
1506+ }
1507+
1508+ /// xml parser handler function for close element tags
1509+ function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true)
1510+ {
1511+ if ($GLOBALS['_xh']['isf'] < 2)
1512+ {
1513+ // push this element name from stack
1514+ // NB: if XML validates, correct opening/closing is guaranteed and
1515+ // we do not have to check for $name == $curr_elem.
1516+ // we also checked for proper nesting at start of elements...
1517+ $curr_elem = array_pop($GLOBALS['_xh']['stack']);
1518+
1519+ switch($name)
1520+ {
1521+ case 'VALUE':
1522+ // This if() detects if no scalar was inside <VALUE></VALUE>
1523+ if ($GLOBALS['_xh']['vt']=='value')
1524+ {
1525+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
1526+ $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString'];
1527+ }
1528+
1529+ if ($rebuild_xmlrpcvals)
1530+ {
1531+ // build the xmlrpc val out of the data received, and substitute it
1532+ $temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']);
1533+ // in case we got info about underlying php class, save it
1534+ // in the object we're rebuilding
1535+ if (isset($GLOBALS['_xh']['php_class']))
1536+ $temp->_php_class = $GLOBALS['_xh']['php_class'];
1537+ // check if we are inside an array or struct:
1538+ // if value just built is inside an array, let's move it into array on the stack
1539+ $vscount = count($GLOBALS['_xh']['valuestack']);
1540+ if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
1541+ {
1542+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp;
1543+ }
1544+ else
1545+ {
1546+ $GLOBALS['_xh']['value'] = $temp;
1547+ }
1548+ }
1549+ else
1550+ {
1551+ /// @todo this needs to treat correctly php-serialized objects,
1552+ /// since std deserializing is done by php_xmlrpc_decode,
1553+ /// which we will not be calling...
1554+ if (isset($GLOBALS['_xh']['php_class']))
1555+ {
1556+ }
1557+
1558+ // check if we are inside an array or struct:
1559+ // if value just built is inside an array, let's move it into array on the stack
1560+ $vscount = count($GLOBALS['_xh']['valuestack']);
1561+ if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY')
1562+ {
1563+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value'];
1564+ }
1565+ }
1566+ break;
1567+ case 'BOOLEAN':
1568+ case 'I4':
1569+ case 'INT':
1570+ case 'STRING':
1571+ case 'DOUBLE':
1572+ case 'DATETIME.ISO8601':
1573+ case 'BASE64':
1574+ $GLOBALS['_xh']['vt']=strtolower($name);
1575+ /// @todo: optimization creep - remove the if/elseif cycle below
1576+ /// since the case() in which we are already did that
1577+ if ($name=='STRING')
1578+ {
1579+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
1580+ }
1581+ elseif ($name=='DATETIME.ISO8601')
1582+ {
1583+ if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac']))
1584+ {
1585+ error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']);
1586+ }
1587+ $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime'];
1588+ $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac'];
1589+ }
1590+ elseif ($name=='BASE64')
1591+ {
1592+ /// @todo check for failure of base64 decoding / catch warnings
1593+ $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']);
1594+ }
1595+ elseif ($name=='BOOLEAN')
1596+ {
1597+ // special case here: we translate boolean 1 or 0 into PHP
1598+ // constants true or false.
1599+ // Strings 'true' and 'false' are accepted, even though the
1600+ // spec never mentions them (see eg. Blogger api docs)
1601+ // NB: this simple checks helps a lot sanitizing input, ie no
1602+ // security problems around here
1603+ if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0)
1604+ {
1605+ $GLOBALS['_xh']['value']=true;
1606+ }
1607+ else
1608+ {
1609+ // log if receiveing something strange, even though we set the value to false anyway
1610+ if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0)
1611+ error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']);
1612+ $GLOBALS['_xh']['value']=false;
1613+ }
1614+ }
1615+ elseif ($name=='DOUBLE')
1616+ {
1617+ // we have a DOUBLE
1618+ // we must check that only 0123456789-.<space> are characters here
1619+ // NOTE: regexp could be much stricter than this...
1620+ if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac']))
1621+ {
1622+ /// @todo: find a better way of throwing an error than this!
1623+ error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']);
1624+ $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
1625+ }
1626+ else
1627+ {
1628+ // it's ok, add it on
1629+ $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac'];
1630+ }
1631+ }
1632+ else
1633+ {
1634+ // we have an I4/INT
1635+ // we must check that only 0123456789-<space> are characters here
1636+ if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac']))
1637+ {
1638+ /// @todo find a better way of throwing an error than this!
1639+ error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']);
1640+ $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND';
1641+ }
1642+ else
1643+ {
1644+ // it's ok, add it on
1645+ $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac'];
1646+ }
1647+ }
1648+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
1649+ $GLOBALS['_xh']['lv']=3; // indicate we've found a value
1650+ break;
1651+ case 'NAME':
1652+ $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac'];
1653+ break;
1654+ case 'MEMBER':
1655+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
1656+ // add to array in the stack the last element built,
1657+ // unless no VALUE was found
1658+ if ($GLOBALS['_xh']['vt'])
1659+ {
1660+ $vscount = count($GLOBALS['_xh']['valuestack']);
1661+ $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value'];
1662+ } else
1663+ error_log('XML-RPC: missing VALUE inside STRUCT in received xml');
1664+ break;
1665+ case 'DATA':
1666+ //$GLOBALS['_xh']['ac']=''; // is this necessary?
1667+ $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty
1668+ break;
1669+ case 'STRUCT':
1670+ case 'ARRAY':
1671+ // fetch out of stack array of values, and promote it to current value
1672+ $curr_val = array_pop($GLOBALS['_xh']['valuestack']);
1673+ $GLOBALS['_xh']['value'] = $curr_val['values'];
1674+ $GLOBALS['_xh']['vt']=strtolower($name);
1675+ if (isset($curr_val['php_class']))
1676+ {
1677+ $GLOBALS['_xh']['php_class'] = $curr_val['php_class'];
1678+ }
1679+ break;
1680+ case 'PARAM':
1681+ // add to array of params the current value,
1682+ // unless no VALUE was found
1683+ if ($GLOBALS['_xh']['vt'])
1684+ {
1685+ $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value'];
1686+ $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt'];
1687+ }
1688+ else
1689+ error_log('XML-RPC: missing VALUE inside PARAM in received xml');
1690+ break;
1691+ case 'METHODNAME':
1692+ $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']);
1693+ break;
1694+ case 'NIL':
1695+ case 'EX:NIL':
1696+ if ($GLOBALS['xmlrpc_null_extension'])
1697+ {
1698+ $GLOBALS['_xh']['vt']='null';
1699+ $GLOBALS['_xh']['value']=null;
1700+ $GLOBALS['_xh']['lv']=3;
1701+ break;
1702+ }
1703+ // drop through intentionally if nil extension not enabled
1704+ case 'PARAMS':
1705+ case 'FAULT':
1706+ case 'METHODCALL':
1707+ case 'METHORESPONSE':
1708+ break;
1709+ default:
1710+ // End of INVALID ELEMENT!
1711+ // shall we add an assert here for unreachable code???
1712+ break;
1713+ }
1714+ }
1715+ }
1716+
1717+ /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values
1718+ function xmlrpc_ee_fast($parser, $name)
1719+ {
1720+ xmlrpc_ee($parser, $name, false);
1721+ }
1722+
1723+ /// xml parser handler function for character data
1724+ function xmlrpc_cd($parser, $data)
1725+ {
1726+ // skip processing if xml fault already detected
1727+ if ($GLOBALS['_xh']['isf'] < 2)
1728+ {
1729+ // "lookforvalue==3" means that we've found an entire value
1730+ // and should discard any further character data
1731+ if($GLOBALS['_xh']['lv']!=3)
1732+ {
1733+ // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2
1734+ //if($GLOBALS['_xh']['lv']==1)
1735+ //{
1736+ // if we've found text and we're just in a <value> then
1737+ // say we've found a value
1738+ //$GLOBALS['_xh']['lv']=2;
1739+ //}
1740+ // we always initialize the accumulator before starting parsing, anyway...
1741+ //if(!@isset($GLOBALS['_xh']['ac']))
1742+ //{
1743+ // $GLOBALS['_xh']['ac'] = '';
1744+ //}
1745+ $GLOBALS['_xh']['ac'].=$data;
1746+ }
1747+ }
1748+ }
1749+
1750+ /// xml parser handler function for 'other stuff', ie. not char data or
1751+ /// element start/end tag. In fact it only gets called on unknown entities...
1752+ function xmlrpc_dh($parser, $data)
1753+ {
1754+ // skip processing if xml fault already detected
1755+ if ($GLOBALS['_xh']['isf'] < 2)
1756+ {
1757+ if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
1758+ {
1759+ // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2
1760+ //if($GLOBALS['_xh']['lv']==1)
1761+ //{
1762+ // $GLOBALS['_xh']['lv']=2;
1763+ //}
1764+ $GLOBALS['_xh']['ac'].=$data;
1765+ }
1766+ }
1767+ return true;
1768+ }
1769+
1770+ class xmlrpc_client
1771+ {
1772+ var $path;
1773+ var $server;
1774+ var $port=0;
1775+ var $method='http';
1776+ var $errno;
1777+ var $errstr;
1778+ var $debug=0;
1779+ var $username='';
1780+ var $password='';
1781+ var $authtype=1;
1782+ var $cert='';
1783+ var $certpass='';
1784+ var $cacert='';
1785+ var $cacertdir='';
1786+ var $key='';
1787+ var $keypass='';
1788+ var $verifypeer=true;
1789+ var $verifyhost=1;
1790+ var $no_multicall=false;
1791+ var $proxy='';
1792+ var $proxyport=0;
1793+ var $proxy_user='';
1794+ var $proxy_pass='';
1795+ var $proxy_authtype=1;
1796+ var $cookies=array();
1797+ var $extracurlopts=array();
1798+
1799+ /**
1800+ * List of http compression methods accepted by the client for responses.
1801+ * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
1802+ *
1803+ * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
1804+ * in those cases it will be up to CURL to decide the compression methods
1805+ * it supports. You might check for the presence of 'zlib' in the output of
1806+ * curl_version() to determine wheter compression is supported or not
1807+ */
1808+ var $accepted_compression = array();
1809+ /**
1810+ * Name of compression scheme to be used for sending requests.
1811+ * Either null, gzip or deflate
1812+ */
1813+ var $request_compression = '';
1814+ /**
1815+ * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
1816+ * http://curl.haxx.se/docs/faq.html#7.3)
1817+ */
1818+ var $xmlrpc_curl_handle = null;
1819+ /// Wheter to use persistent connections for http 1.1 and https
1820+ var $keepalive = false;
1821+ /// Charset encodings that can be decoded without problems by the client
1822+ var $accepted_charset_encodings = array();
1823+ /// Charset encoding to be used in serializing request. NULL = use ASCII
1824+ var $request_charset_encoding = '';
1825+ /**
1826+ * Decides the content of xmlrpcresp objects returned by calls to send()
1827+ * valid strings are 'xmlrpcvals', 'phpvals' or 'xml'
1828+ */
1829+ var $return_type = 'xmlrpcvals';
1830+ /**
1831+ * Sent to servers in http headers
1832+ */
1833+ var $user_agent;
1834+
1835+ /**
1836+ * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php
1837+ * @param string $server the server name / ip address
1838+ * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used
1839+ * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed
1840+ */
1841+ function xmlrpc_client($path, $server='', $port='', $method='')
1842+ {
1843+ // allow user to specify all params in $path
1844+ if($server == '' and $port == '' and $method == '')
1845+ {
1846+ $parts = parse_url($path);
1847+ $server = $parts['host'];
1848+ $path = isset($parts['path']) ? $parts['path'] : '';
1849+ if(isset($parts['query']))
1850+ {
1851+ $path .= '?'.$parts['query'];
1852+ }
1853+ if(isset($parts['fragment']))
1854+ {
1855+ $path .= '#'.$parts['fragment'];
1856+ }
1857+ if(isset($parts['port']))
1858+ {
1859+ $port = $parts['port'];
1860+ }
1861+ if(isset($parts['scheme']))
1862+ {
1863+ $method = $parts['scheme'];
1864+ }
1865+ if(isset($parts['user']))
1866+ {
1867+ $this->username = $parts['user'];
1868+ }
1869+ if(isset($parts['pass']))
1870+ {
1871+ $this->password = $parts['pass'];
1872+ }
1873+ }
1874+ if($path == '' || $path[0] != '/')
1875+ {
1876+ $this->path='/'.$path;
1877+ }
1878+ else
1879+ {
1880+ $this->path=$path;
1881+ }
1882+ $this->server=$server;
1883+ if($port != '')
1884+ {
1885+ $this->port=$port;
1886+ }
1887+ if($method != '')
1888+ {
1889+ $this->method=$method;
1890+ }
1891+
1892+ // if ZLIB is enabled, let the client by default accept compressed responses
1893+ if(function_exists('gzinflate') || (
1894+ function_exists('curl_init') && (($info = curl_version()) &&
1895+ ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
1896+ ))
1897+ {
1898+ $this->accepted_compression = array('gzip', 'deflate');
1899+ }
1900+
1901+ // keepalives: enabled by default
1902+ $this->keepalive = true;
1903+
1904+ // by default the xml parser can support these 3 charset encodings
1905+ $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
1906+
1907+ // initialize user_agent string
1908+ $this->user_agent = $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'];
1909+ }
1910+
1911+ /**
1912+ * Enables/disables the echoing to screen of the xmlrpc responses received
1913+ * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
1914+ * @access public
1915+ */
1916+ function setDebug($in)
1917+ {
1918+ $this->debug=$in;
1919+ }
1920+
1921+ /**
1922+ * Add some http BASIC AUTH credentials, used by the client to authenticate
1923+ * @param string $u username
1924+ * @param string $p password
1925+ * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth)
1926+ * @access public
1927+ */
1928+ function setCredentials($u, $p, $t=1)
1929+ {
1930+ $this->username=$u;
1931+ $this->password=$p;
1932+ $this->authtype=$t;
1933+ }
1934+
1935+ /**
1936+ * Add a client-side https certificate
1937+ * @param string $cert
1938+ * @param string $certpass
1939+ * @access public
1940+ */
1941+ function setCertificate($cert, $certpass)
1942+ {
1943+ $this->cert = $cert;
1944+ $this->certpass = $certpass;
1945+ }
1946+
1947+ /**
1948+ * Add a CA certificate to verify server with (see man page about
1949+ * CURLOPT_CAINFO for more details
1950+ * @param string $cacert certificate file name (or dir holding certificates)
1951+ * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false
1952+ * @access public
1953+ */
1954+ function setCaCertificate($cacert, $is_dir=false)
1955+ {
1956+ if ($is_dir)
1957+ {
1958+ $this->cacertdir = $cacert;
1959+ }
1960+ else
1961+ {
1962+ $this->cacert = $cacert;
1963+ }
1964+ }
1965+
1966+ /**
1967+ * Set attributes for SSL communication: private SSL key
1968+ * NB: does not work in older php/curl installs
1969+ * Thanks to Daniel Convissor
1970+ * @param string $key The name of a file containing a private SSL key
1971+ * @param string $keypass The secret password needed to use the private SSL key
1972+ * @access public
1973+ */
1974+ function setKey($key, $keypass)
1975+ {
1976+ $this->key = $key;
1977+ $this->keypass = $keypass;
1978+ }
1979+
1980+ /**
1981+ * Set attributes for SSL communication: verify server certificate
1982+ * @param bool $i enable/disable verification of peer certificate
1983+ * @access public
1984+ */
1985+ function setSSLVerifyPeer($i)
1986+ {
1987+ $this->verifypeer = $i;
1988+ }
1989+
1990+ /**
1991+ * Set attributes for SSL communication: verify match of server cert w. hostname
1992+ * @param int $i
1993+ * @access public
1994+ */
1995+ function setSSLVerifyHost($i)
1996+ {
1997+ $this->verifyhost = $i;
1998+ }
1999+
2000+ /**
2001+ * Set proxy info
2002+ * @param string $proxyhost
2003+ * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS
2004+ * @param string $proxyusername Leave blank if proxy has public access
2005+ * @param string $proxypassword Leave blank if proxy has public access
2006+ * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy
2007+ * @access public
2008+ */
2009+ function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1)
2010+ {
2011+ $this->proxy = $proxyhost;
2012+ $this->proxyport = $proxyport;
2013+ $this->proxy_user = $proxyusername;
2014+ $this->proxy_pass = $proxypassword;
2015+ $this->proxy_authtype = $proxyauthtype;
2016+ }
2017+
2018+ /**
2019+ * Enables/disables reception of compressed xmlrpc responses.
2020+ * Note that enabling reception of compressed responses merely adds some standard
2021+ * http headers to xmlrpc requests. It is up to the xmlrpc server to return
2022+ * compressed responses when receiving such requests.
2023+ * @param string $compmethod either 'gzip', 'deflate', 'any' or ''
2024+ * @access public
2025+ */
2026+ function setAcceptedCompression($compmethod)
2027+ {
2028+ if ($compmethod == 'any')
2029+ $this->accepted_compression = array('gzip', 'deflate');
2030+ else
2031+ $this->accepted_compression = array($compmethod);
2032+ }
2033+
2034+ /**
2035+ * Enables/disables http compression of xmlrpc request.
2036+ * Take care when sending compressed requests: servers might not support them
2037+ * (and automatic fallback to uncompressed requests is not yet implemented)
2038+ * @param string $compmethod either 'gzip', 'deflate' or ''
2039+ * @access public
2040+ */
2041+ function setRequestCompression($compmethod)
2042+ {
2043+ $this->request_compression = $compmethod;
2044+ }
2045+
2046+ /**
2047+ * Adds a cookie to list of cookies that will be sent to server.
2048+ * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie:
2049+ * do not do it unless you know what you are doing
2050+ * @param string $name
2051+ * @param string $value
2052+ * @param string $path
2053+ * @param string $domain
2054+ * @param int $port
2055+ * @access public
2056+ *
2057+ * @todo check correctness of urlencoding cookie value (copied from php way of doing it...)
2058+ */
2059+ function setCookie($name, $value='', $path='', $domain='', $port=null)
2060+ {
2061+ $this->cookies[$name]['value'] = urlencode($value);
2062+ if ($path || $domain || $port)
2063+ {
2064+ $this->cookies[$name]['path'] = $path;
2065+ $this->cookies[$name]['domain'] = $domain;
2066+ $this->cookies[$name]['port'] = $port;
2067+ $this->cookies[$name]['version'] = 1;
2068+ }
2069+ else
2070+ {
2071+ $this->cookies[$name]['version'] = 0;
2072+ }
2073+ }
2074+
2075+ /**
2076+ * Directly set cURL options, for extra flexibility
2077+ * It allows eg. to bind client to a specific IP interface / address
2078+ * @param $options array
2079+ */
2080+ function SetCurlOptions( $options )
2081+ {
2082+ $this->extracurlopts = $options;
2083+ }
2084+
2085+ /**
2086+ * Set user-agent string that will be used by this client instance
2087+ * in http headers sent to the server
2088+ */
2089+ function SetUserAgent( $agentstring )
2090+ {
2091+ $this->user_agent = $agentstring;
2092+ }
2093+
2094+ /**
2095+ * Send an xmlrpc request
2096+ * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request
2097+ * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply
2098+ * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used
2099+ * @return xmlrpcresp
2100+ * @access public
2101+ */
2102+ function& send($msg, $timeout=0, $method='')
2103+ {
2104+ // if user deos not specify http protocol, use native method of this client
2105+ // (i.e. method set during call to constructor)
2106+ if($method == '')
2107+ {
2108+ $method = $this->method;
2109+ }
2110+
2111+ if(is_array($msg))
2112+ {
2113+ // $msg is an array of xmlrpcmsg's
2114+ $r = $this->multicall($msg, $timeout, $method);
2115+ return $r;
2116+ }
2117+ elseif(is_string($msg))
2118+ {
2119+ $n = new xmlrpcmsg('');
2120+ $n->payload = $msg;
2121+ $msg = $n;
2122+ }
2123+
2124+ // where msg is an xmlrpcmsg
2125+ $msg->debug=$this->debug;
2126+
2127+ if($method == 'https')
2128+ {
2129+ $r =& $this->sendPayloadHTTPS(
2130+ $msg,
2131+ $this->server,
2132+ $this->port,
2133+ $timeout,
2134+ $this->username,
2135+ $this->password,
2136+ $this->authtype,
2137+ $this->cert,
2138+ $this->certpass,
2139+ $this->cacert,
2140+ $this->cacertdir,
2141+ $this->proxy,
2142+ $this->proxyport,
2143+ $this->proxy_user,
2144+ $this->proxy_pass,
2145+ $this->proxy_authtype,
2146+ $this->keepalive,
2147+ $this->key,
2148+ $this->keypass
2149+ );
2150+ }
2151+ elseif($method == 'http11')
2152+ {
2153+ $r =& $this->sendPayloadCURL(
2154+ $msg,
2155+ $this->server,
2156+ $this->port,
2157+ $timeout,
2158+ $this->username,
2159+ $this->password,
2160+ $this->authtype,
2161+ null,
2162+ null,
2163+ null,
2164+ null,
2165+ $this->proxy,
2166+ $this->proxyport,
2167+ $this->proxy_user,
2168+ $this->proxy_pass,
2169+ $this->proxy_authtype,
2170+ 'http',
2171+ $this->keepalive
2172+ );
2173+ }
2174+ else
2175+ {
2176+ $r =& $this->sendPayloadHTTP10(
2177+ $msg,
2178+ $this->server,
2179+ $this->port,
2180+ $timeout,
2181+ $this->username,
2182+ $this->password,
2183+ $this->authtype,
2184+ $this->proxy,
2185+ $this->proxyport,
2186+ $this->proxy_user,
2187+ $this->proxy_pass,
2188+ $this->proxy_authtype
2189+ );
2190+ }
2191+
2192+ return $r;
2193+ }
2194+
2195+ /**
2196+ * @access private
2197+ */
2198+ function &sendPayloadHTTP10($msg, $server, $port, $timeout=0,
2199+ $username='', $password='', $authtype=1, $proxyhost='',
2200+ $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1)
2201+ {
2202+ if($port==0)
2203+ {
2204+ $port=80;
2205+ }
2206+
2207+ // Only create the payload if it was not created previously
2208+ if(empty($msg->payload))
2209+ {
2210+ $msg->createPayload($this->request_charset_encoding);
2211+ }
2212+
2213+ $payload = $msg->payload;
2214+ // Deflate request body and set appropriate request headers
2215+ if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
2216+ {
2217+ if($this->request_compression == 'gzip')
2218+ {
2219+ $a = @gzencode($payload);
2220+ if($a)
2221+ {
2222+ $payload = $a;
2223+ $encoding_hdr = "Content-Encoding: gzip\r\n";
2224+ }
2225+ }
2226+ else
2227+ {
2228+ $a = @gzcompress($payload);
2229+ if($a)
2230+ {
2231+ $payload = $a;
2232+ $encoding_hdr = "Content-Encoding: deflate\r\n";
2233+ }
2234+ }
2235+ }
2236+ else
2237+ {
2238+ $encoding_hdr = '';
2239+ }
2240+
2241+ // thanks to Grant Rauscher <grant7@firstworld.net> for this
2242+ $credentials='';
2243+ if($username!='')
2244+ {
2245+ $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
2246+ if ($authtype != 1)
2247+ {
2248+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported with HTTP 1.0');
2249+ }
2250+ }
2251+
2252+ $accepted_encoding = '';
2253+ if(is_array($this->accepted_compression) && count($this->accepted_compression))
2254+ {
2255+ $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
2256+ }
2257+
2258+ $proxy_credentials = '';
2259+ if($proxyhost)
2260+ {
2261+ if($proxyport == 0)
2262+ {
2263+ $proxyport = 8080;
2264+ }
2265+ $connectserver = $proxyhost;
2266+ $connectport = $proxyport;
2267+ $uri = 'http://'.$server.':'.$port.$this->path;
2268+ if($proxyusername != '')
2269+ {
2270+ if ($proxyauthtype != 1)
2271+ {
2272+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported with HTTP 1.0');
2273+ }
2274+ $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n";
2275+ }
2276+ }
2277+ else
2278+ {
2279+ $connectserver = $server;
2280+ $connectport = $port;
2281+ $uri = $this->path;
2282+ }
2283+
2284+ // Cookie generation, as per rfc2965 (version 1 cookies) or
2285+ // netscape's rules (version 0 cookies)
2286+ $cookieheader='';
2287+ if (count($this->cookies))
2288+ {
2289+ $version = '';
2290+ foreach ($this->cookies as $name => $cookie)
2291+ {
2292+ if ($cookie['version'])
2293+ {
2294+ $version = ' $Version="' . $cookie['version'] . '";';
2295+ $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";';
2296+ if ($cookie['path'])
2297+ $cookieheader .= ' $Path="' . $cookie['path'] . '";';
2298+ if ($cookie['domain'])
2299+ $cookieheader .= ' $Domain="' . $cookie['domain'] . '";';
2300+ if ($cookie['port'])
2301+ $cookieheader .= ' $Port="' . $cookie['port'] . '";';
2302+ }
2303+ else
2304+ {
2305+ $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";";
2306+ }
2307+ }
2308+ $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n";
2309+ }
2310+
2311+ $op= 'POST ' . $uri. " HTTP/1.0\r\n" .
2312+ 'User-Agent: ' . $this->user_agent . "\r\n" .
2313+ 'Host: '. $server . ':' . $port . "\r\n" .
2314+ $credentials .
2315+ $proxy_credentials .
2316+ $accepted_encoding .
2317+ $encoding_hdr .
2318+ 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
2319+ $cookieheader .
2320+ 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " .
2321+ strlen($payload) . "\r\n\r\n" .
2322+ $payload;
2323+
2324+ if($this->debug > 1)
2325+ {
2326+ print "<PRE>\n---SENDING---\n" . htmlentities($op) . "\n---END---\n</PRE>";
2327+ // let the client see this now in case http times out...
2328+ flush();
2329+ }
2330+
2331+ if($timeout>0)
2332+ {
2333+ $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout);
2334+ }
2335+ else
2336+ {
2337+ $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr);
2338+ }
2339+ if($fp)
2340+ {
2341+ if($timeout>0 && function_exists('stream_set_timeout'))
2342+ {
2343+ stream_set_timeout($fp, $timeout);
2344+ }
2345+ }
2346+ else
2347+ {
2348+ $this->errstr='Connect error: '.$this->errstr;
2349+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')');
2350+ return $r;
2351+ }
2352+
2353+ if(!fputs($fp, $op, strlen($op)))
2354+ {
2355+ fclose($fp);
2356+ $this->errstr='Write error';
2357+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr);
2358+ return $r;
2359+ }
2360+ else
2361+ {
2362+ // reset errno and errstr on succesful socket connection
2363+ $this->errstr = '';
2364+ }
2365+ // G. Giunta 2005/10/24: close socket before parsing.
2366+ // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
2367+ $ipd='';
2368+ do
2369+ {
2370+ // shall we check for $data === FALSE?
2371+ // as per the manual, it signals an error
2372+ $ipd.=fread($fp, 32768);
2373+ } while(!feof($fp));
2374+ fclose($fp);
2375+ $r =& $msg->parseResponse($ipd, false, $this->return_type);
2376+ return $r;
2377+
2378+ }
2379+
2380+ /**
2381+ * @access private
2382+ */
2383+ function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='',
2384+ $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='',
2385+ $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1,
2386+ $keepalive=false, $key='', $keypass='')
2387+ {
2388+ $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username,
2389+ $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport,
2390+ $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass);
2391+ return $r;
2392+ }
2393+
2394+ /**
2395+ * Contributed by Justin Miller <justin@voxel.net>
2396+ * Requires curl to be built into PHP
2397+ * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
2398+ * @access private
2399+ */
2400+ function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='',
2401+ $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='',
2402+ $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https',
2403+ $keepalive=false, $key='', $keypass='')
2404+ {
2405+ if(!function_exists('curl_init'))
2406+ {
2407+ $this->errstr='CURL unavailable on this install';
2408+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']);
2409+ return $r;
2410+ }
2411+ if($method == 'https')
2412+ {
2413+ if(($info = curl_version()) &&
2414+ ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version']))))
2415+ {
2416+ $this->errstr='SSL unavailable on this install';
2417+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']);
2418+ return $r;
2419+ }
2420+ }
2421+
2422+ if($port == 0)
2423+ {
2424+ if($method == 'http')
2425+ {
2426+ $port = 80;
2427+ }
2428+ else
2429+ {
2430+ $port = 443;
2431+ }
2432+ }
2433+
2434+ // Only create the payload if it was not created previously
2435+ if(empty($msg->payload))
2436+ {
2437+ $msg->createPayload($this->request_charset_encoding);
2438+ }
2439+
2440+ // Deflate request body and set appropriate request headers
2441+ $payload = $msg->payload;
2442+ if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate'))
2443+ {
2444+ if($this->request_compression == 'gzip')
2445+ {
2446+ $a = @gzencode($payload);
2447+ if($a)
2448+ {
2449+ $payload = $a;
2450+ $encoding_hdr = 'Content-Encoding: gzip';
2451+ }
2452+ }
2453+ else
2454+ {
2455+ $a = @gzcompress($payload);
2456+ if($a)
2457+ {
2458+ $payload = $a;
2459+ $encoding_hdr = 'Content-Encoding: deflate';
2460+ }
2461+ }
2462+ }
2463+ else
2464+ {
2465+ $encoding_hdr = '';
2466+ }
2467+
2468+ if($this->debug > 1)
2469+ {
2470+ print "<PRE>\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n</PRE>";
2471+ // let the client see this now in case http times out...
2472+ flush();
2473+ }
2474+
2475+ if(!$keepalive || !$this->xmlrpc_curl_handle)
2476+ {
2477+ $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
2478+ if($keepalive)
2479+ {
2480+ $this->xmlrpc_curl_handle = $curl;
2481+ }
2482+ }
2483+ else
2484+ {
2485+ $curl = $this->xmlrpc_curl_handle;
2486+ }
2487+
2488+ // results into variable
2489+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
2490+
2491+ if($this->debug)
2492+ {
2493+ curl_setopt($curl, CURLOPT_VERBOSE, 1);
2494+ }
2495+ curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
2496+ // required for XMLRPC: post the data
2497+ curl_setopt($curl, CURLOPT_POST, 1);
2498+ // the data
2499+ curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
2500+
2501+ // return the header too
2502+ curl_setopt($curl, CURLOPT_HEADER, 1);
2503+
2504+ // will only work with PHP >= 5.0
2505+ // NB: if we set an empty string, CURL will add http header indicating
2506+ // ALL methods it is supporting. This is possibly a better option than
2507+ // letting the user tell what curl can / cannot do...
2508+ if(is_array($this->accepted_compression) && count($this->accepted_compression))
2509+ {
2510+ //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
2511+ // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
2512+ if (count($this->accepted_compression) == 1)
2513+ {
2514+ curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
2515+ }
2516+ else
2517+ curl_setopt($curl, CURLOPT_ENCODING, '');
2518+ }
2519+ // extra headers
2520+ $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
2521+ // if no keepalive is wanted, let the server know it in advance
2522+ if(!$keepalive)
2523+ {
2524+ $headers[] = 'Connection: close';
2525+ }
2526+ // request compression header
2527+ if($encoding_hdr)
2528+ {
2529+ $headers[] = $encoding_hdr;
2530+ }
2531+
2532+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
2533+ // timeout is borked
2534+ if($timeout)
2535+ {
2536+ curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
2537+ }
2538+
2539+ if($username && $password)
2540+ {
2541+ curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password);
2542+ if (defined('CURLOPT_HTTPAUTH'))
2543+ {
2544+ curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype);
2545+ }
2546+ else if ($authtype != 1)
2547+ {
2548+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth is supported by the current PHP/curl install');
2549+ }
2550+ }
2551+
2552+ if($method == 'https')
2553+ {
2554+ // set cert file
2555+ if($cert)
2556+ {
2557+ curl_setopt($curl, CURLOPT_SSLCERT, $cert);
2558+ }
2559+ // set cert password
2560+ if($certpass)
2561+ {
2562+ curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass);
2563+ }
2564+ // whether to verify remote host's cert
2565+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
2566+ // set ca certificates file/dir
2567+ if($cacert)
2568+ {
2569+ curl_setopt($curl, CURLOPT_CAINFO, $cacert);
2570+ }
2571+ if($cacertdir)
2572+ {
2573+ curl_setopt($curl, CURLOPT_CAPATH, $cacertdir);
2574+ }
2575+ // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
2576+ if($key)
2577+ {
2578+ curl_setopt($curl, CURLOPT_SSLKEY, $key);
2579+ }
2580+ // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
2581+ if($keypass)
2582+ {
2583+ curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass);
2584+ }
2585+ // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
2586+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
2587+ }
2588+
2589+ // proxy info
2590+ if($proxyhost)
2591+ {
2592+ if($proxyport == 0)
2593+ {
2594+ $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080
2595+ }
2596+ curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport);
2597+ //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport);
2598+ if($proxyusername)
2599+ {
2600+ curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword);
2601+ if (defined('CURLOPT_PROXYAUTH'))
2602+ {
2603+ curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype);
2604+ }
2605+ else if ($proxyauthtype != 1)
2606+ {
2607+ error_log('XML-RPC: '.__METHOD__.': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
2608+ }
2609+ }
2610+ }
2611+
2612+ // NB: should we build cookie http headers by hand rather than let CURL do it?
2613+ // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
2614+ // set to client obj the the user...
2615+ if (count($this->cookies))
2616+ {
2617+ $cookieheader = '';
2618+ foreach ($this->cookies as $name => $cookie)
2619+ {
2620+ $cookieheader .= $name . '=' . $cookie['value'] . '; ';
2621+ }
2622+ curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2));
2623+ }
2624+
2625+ foreach ($this->extracurlopts as $opt => $val)
2626+ {
2627+ curl_setopt($curl, $opt, $val);
2628+ }
2629+
2630+ $result = curl_exec($curl);
2631+
2632+ if ($this->debug > 1)
2633+ {
2634+ print "<PRE>\n---CURL INFO---\n";
2635+ foreach(curl_getinfo($curl) as $name => $val)
2636+ print $name . ': ' . htmlentities($val). "\n";
2637+ print "---END---\n</PRE>";
2638+ }
2639+
2640+ if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'?
2641+ {
2642+ $this->errstr='no response';
2643+ $resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl));
2644+ curl_close($curl);
2645+ if($keepalive)
2646+ {
2647+ $this->xmlrpc_curl_handle = null;
2648+ }
2649+ }
2650+ else
2651+ {
2652+ if(!$keepalive)
2653+ {
2654+ curl_close($curl);
2655+ }
2656+ $resp =& $msg->parseResponse($result, true, $this->return_type);
2657+ }
2658+ return $resp;
2659+ }
2660+
2661+ /**
2662+ * Send an array of request messages and return an array of responses.
2663+ * Unless $this->no_multicall has been set to true, it will try first
2664+ * to use one single xmlrpc call to server method system.multicall, and
2665+ * revert to sending many successive calls in case of failure.
2666+ * This failure is also stored in $this->no_multicall for subsequent calls.
2667+ * Unfortunately, there is no server error code universally used to denote
2668+ * the fact that multicall is unsupported, so there is no way to reliably
2669+ * distinguish between that and a temporary failure.
2670+ * If you are sure that server supports multicall and do not want to
2671+ * fallback to using many single calls, set the fourth parameter to FALSE.
2672+ *
2673+ * NB: trying to shoehorn extra functionality into existing syntax has resulted
2674+ * in pretty much convoluted code...
2675+ *
2676+ * @param array $msgs an array of xmlrpcmsg objects
2677+ * @param integer $timeout connection timeout (in seconds)
2678+ * @param string $method the http protocol variant to be used
2679+ * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted
2680+ * @return array
2681+ * @access public
2682+ */
2683+ function multicall($msgs, $timeout=0, $method='', $fallback=true)
2684+ {
2685+ if ($method == '')
2686+ {
2687+ $method = $this->method;
2688+ }
2689+ if(!$this->no_multicall)
2690+ {
2691+ $results = $this->_try_multicall($msgs, $timeout, $method);
2692+ if(is_array($results))
2693+ {
2694+ // System.multicall succeeded
2695+ return $results;
2696+ }
2697+ else
2698+ {
2699+ // either system.multicall is unsupported by server,
2700+ // or call failed for some other reason.
2701+ if ($fallback)
2702+ {
2703+ // Don't try it next time...
2704+ $this->no_multicall = true;
2705+ }
2706+ else
2707+ {
2708+ if (is_a($results, 'xmlrpcresp'))
2709+ {
2710+ $result = $results;
2711+ }
2712+ else
2713+ {
2714+ $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']);
2715+ }
2716+ }
2717+ }
2718+ }
2719+ else
2720+ {
2721+ // override fallback, in case careless user tries to do two
2722+ // opposite things at the same time
2723+ $fallback = true;
2724+ }
2725+
2726+ $results = array();
2727+ if ($fallback)
2728+ {
2729+ // system.multicall is (probably) unsupported by server:
2730+ // emulate multicall via multiple requests
2731+ foreach($msgs as $msg)
2732+ {
2733+ $results[] =& $this->send($msg, $timeout, $method);
2734+ }
2735+ }
2736+ else
2737+ {
2738+ // user does NOT want to fallback on many single calls:
2739+ // since we should always return an array of responses,
2740+ // return an array with the same error repeated n times
2741+ foreach($msgs as $msg)
2742+ {
2743+ $results[] = $result;
2744+ }
2745+ }
2746+ return $results;
2747+ }
2748+
2749+ /**
2750+ * Attempt to boxcar $msgs via system.multicall.
2751+ * Returns either an array of xmlrpcreponses, an xmlrpc error response
2752+ * or false (when received response does not respect valid multicall syntax)
2753+ * @access private
2754+ */
2755+ function _try_multicall($msgs, $timeout, $method)
2756+ {
2757+ // Construct multicall message
2758+ $calls = array();
2759+ foreach($msgs as $msg)
2760+ {
2761+ $call['methodName'] = new xmlrpcval($msg->method(),'string');
2762+ $numParams = $msg->getNumParams();
2763+ $params = array();
2764+ for($i = 0; $i < $numParams; $i++)
2765+ {
2766+ $params[$i] = $msg->getParam($i);
2767+ }
2768+ $call['params'] = new xmlrpcval($params, 'array');
2769+ $calls[] = new xmlrpcval($call, 'struct');
2770+ }
2771+ $multicall = new xmlrpcmsg('system.multicall');
2772+ $multicall->addParam(new xmlrpcval($calls, 'array'));
2773+
2774+ // Attempt RPC call
2775+ $result =& $this->send($multicall, $timeout, $method);
2776+
2777+ if($result->faultCode() != 0)
2778+ {
2779+ // call to system.multicall failed
2780+ return $result;
2781+ }
2782+
2783+ // Unpack responses.
2784+ $rets = $result->value();
2785+
2786+ if ($this->return_type == 'xml')
2787+ {
2788+ return $rets;
2789+ }
2790+ else if ($this->return_type == 'phpvals')
2791+ {
2792+ ///@todo test this code branch...
2793+ $rets = $result->value();
2794+ if(!is_array($rets))
2795+ {
2796+ return false; // bad return type from system.multicall
2797+ }
2798+ $numRets = count($rets);
2799+ if($numRets != count($msgs))
2800+ {
2801+ return false; // wrong number of return values.
2802+ }
2803+
2804+ $response = array();
2805+ for($i = 0; $i < $numRets; $i++)
2806+ {
2807+ $val = $rets[$i];
2808+ if (!is_array($val)) {
2809+ return false;
2810+ }
2811+ switch(count($val))
2812+ {
2813+ case 1:
2814+ if(!isset($val[0]))
2815+ {
2816+ return false; // Bad value
2817+ }
2818+ // Normal return value
2819+ $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals');
2820+ break;
2821+ case 2:
2822+ /// @todo remove usage of @: it is apparently quite slow
2823+ $code = @$val['faultCode'];
2824+ if(!is_int($code))
2825+ {
2826+ return false;
2827+ }
2828+ $str = @$val['faultString'];
2829+ if(!is_string($str))
2830+ {
2831+ return false;
2832+ }
2833+ $response[$i] = new xmlrpcresp(0, $code, $str);
2834+ break;
2835+ default:
2836+ return false;
2837+ }
2838+ }
2839+ return $response;
2840+ }
2841+ else // return type == 'xmlrpcvals'
2842+ {
2843+ $rets = $result->value();
2844+ if($rets->kindOf() != 'array')
2845+ {
2846+ return false; // bad return type from system.multicall
2847+ }
2848+ $numRets = $rets->arraysize();
2849+ if($numRets != count($msgs))
2850+ {
2851+ return false; // wrong number of return values.
2852+ }
2853+
2854+ $response = array();
2855+ for($i = 0; $i < $numRets; $i++)
2856+ {
2857+ $val = $rets->arraymem($i);
2858+ switch($val->kindOf())
2859+ {
2860+ case 'array':
2861+ if($val->arraysize() != 1)
2862+ {
2863+ return false; // Bad value
2864+ }
2865+ // Normal return value
2866+ $response[$i] = new xmlrpcresp($val->arraymem(0));
2867+ break;
2868+ case 'struct':
2869+ $code = $val->structmem('faultCode');
2870+ if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
2871+ {
2872+ return false;
2873+ }
2874+ $str = $val->structmem('faultString');
2875+ if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
2876+ {
2877+ return false;
2878+ }
2879+ $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
2880+ break;
2881+ default:
2882+ return false;
2883+ }
2884+ }
2885+ return $response;
2886+ }
2887+ }
2888+ } // end class xmlrpc_client
2889+
2890+ class xmlrpcresp
2891+ {
2892+ var $val = 0;
2893+ var $valtyp;
2894+ var $errno = 0;
2895+ var $errstr = '';
2896+ var $payload;
2897+ var $hdrs = array();
2898+ var $_cookies = array();
2899+ var $content_type = 'text/xml';
2900+ var $raw_data = '';
2901+
2902+ /**
2903+ * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string)
2904+ * @param integer $fcode set it to anything but 0 to create an error response
2905+ * @param string $fstr the error string, in case of an error response
2906+ * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml'
2907+ *
2908+ * @todo add check that $val / $fcode / $fstr is of correct type???
2909+ * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain
2910+ * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called...
2911+ */
2912+ function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='')
2913+ {
2914+ if($fcode != 0)
2915+ {
2916+ // error response
2917+ $this->errno = $fcode;
2918+ $this->errstr = $fstr;
2919+ //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
2920+ }
2921+ else
2922+ {
2923+ // successful response
2924+ $this->val = $val;
2925+ if ($valtyp == '')
2926+ {
2927+ // user did not declare type of response value: try to guess it
2928+ if (is_object($this->val) && is_a($this->val, 'xmlrpcval'))
2929+ {
2930+ $this->valtyp = 'xmlrpcvals';
2931+ }
2932+ else if (is_string($this->val))
2933+ {
2934+ $this->valtyp = 'xml';
2935+
2936+ }
2937+ else
2938+ {
2939+ $this->valtyp = 'phpvals';
2940+ }
2941+ }
2942+ else
2943+ {
2944+ // user declares type of resp value: believe him
2945+ $this->valtyp = $valtyp;
2946+ }
2947+ }
2948+ }
2949+
2950+ /**
2951+ * Returns the error code of the response.
2952+ * @return integer the error code of this response (0 for not-error responses)
2953+ * @access public
2954+ */
2955+ function faultCode()
2956+ {
2957+ return $this->errno;
2958+ }
2959+
2960+ /**
2961+ * Returns the error code of the response.
2962+ * @return string the error string of this response ('' for not-error responses)
2963+ * @access public
2964+ */
2965+ function faultString()
2966+ {
2967+ return $this->errstr;
2968+ }
2969+
2970+ /**
2971+ * Returns the value received by the server.
2972+ * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects
2973+ * @access public
2974+ */
2975+ function value()
2976+ {
2977+ return $this->val;
2978+ }
2979+
2980+ /**
2981+ * Returns an array with the cookies received from the server.
2982+ * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...)
2983+ * with attributes being e.g. 'expires', 'path', domain'.
2984+ * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past)
2985+ * are still present in the array. It is up to the user-defined code to decide
2986+ * how to use the received cookies, and wheter they have to be sent back with the next
2987+ * request to the server (using xmlrpc_client::setCookie) or not
2988+ * @return array array of cookies received from the server
2989+ * @access public
2990+ */
2991+ function cookies()
2992+ {
2993+ return $this->_cookies;
2994+ }
2995+
2996+ /**
2997+ * Returns xml representation of the response. XML prologue not included
2998+ * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
2999+ * @return string the xml representation of the response
3000+ * @access public
3001+ */
3002+ function serialize($charset_encoding='')
3003+ {
3004+ if ($charset_encoding != '')
3005+ $this->content_type = 'text/xml; charset=' . $charset_encoding;
3006+ else
3007+ $this->content_type = 'text/xml';
3008+ $result = "<methodResponse>\n";
3009+ if($this->errno)
3010+ {
3011+ // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
3012+ // by xml-encoding non ascii chars
3013+ $result .= "<fault>\n" .
3014+"<value>\n<struct><member><name>faultCode</name>\n<value><int>" . $this->errno .
3015+"</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string>" .
3016+xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "</string></value>\n</member>\n" .
3017+"</struct>\n</value>\n</fault>";
3018+ }
3019+ else
3020+ {
3021+ if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval'))
3022+ {
3023+ if (is_string($this->val) && $this->valtyp == 'xml')
3024+ {
3025+ $result .= "<params>\n<param>\n" .
3026+ $this->val .
3027+ "</param>\n</params>";
3028+ }
3029+ else
3030+ {
3031+ /// @todo try to build something serializable?
3032+ die('cannot serialize xmlrpcresp objects whose content is native php values');
3033+ }
3034+ }
3035+ else
3036+ {
3037+ $result .= "<params>\n<param>\n" .
3038+ $this->val->serialize($charset_encoding) .
3039+ "</param>\n</params>";
3040+ }
3041+ }
3042+ $result .= "\n</methodResponse>";
3043+ $this->payload = $result;
3044+ return $result;
3045+ }
3046+ }
3047+
3048+ class xmlrpcmsg
3049+ {
3050+ var $payload;
3051+ var $methodname;
3052+ var $params=array();
3053+ var $debug=0;
3054+ var $content_type = 'text/xml';
3055+
3056+ /**
3057+ * @param string $meth the name of the method to invoke
3058+ * @param array $pars array of parameters to be paased to the method (xmlrpcval objects)
3059+ */
3060+ function xmlrpcmsg($meth, $pars=0)
3061+ {
3062+ $this->methodname=$meth;
3063+ if(is_array($pars) && count($pars)>0)
3064+ {
3065+ for($i=0; $i<count($pars); $i++)
3066+ {
3067+ $this->addParam($pars[$i]);
3068+ }
3069+ }
3070+ }
3071+
3072+ /**
3073+ * @access private
3074+ */
3075+ function xml_header($charset_encoding='')
3076+ {
3077+ if ($charset_encoding != '')
3078+ {
3079+ return "<?xml version=\"1.0\" encoding=\"$charset_encoding\" ?" . ">\n<methodCall>\n";
3080+ }
3081+ else
3082+ {
3083+ return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
3084+ }
3085+ }
3086+
3087+ /**
3088+ * @access private
3089+ */
3090+ function xml_footer()
3091+ {
3092+ return '</methodCall>';
3093+ }
3094+
3095+ /**
3096+ * @access private
3097+ */
3098+ function kindOf()
3099+ {
3100+ return 'msg';
3101+ }
3102+
3103+ /**
3104+ * @access private
3105+ */
3106+ function createPayload($charset_encoding='')
3107+ {
3108+ if ($charset_encoding != '')
3109+ $this->content_type = 'text/xml; charset=' . $charset_encoding;
3110+ else
3111+ $this->content_type = 'text/xml';
3112+ $this->payload=$this->xml_header($charset_encoding);
3113+ $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
3114+ $this->payload.="<params>\n";
3115+ for($i=0; $i<count($this->params); $i++)
3116+ {
3117+ $p=$this->params[$i];
3118+ $this->payload.="<param>\n" . $p->serialize($charset_encoding) .
3119+ "</param>\n";
3120+ }
3121+ $this->payload.="</params>\n";
3122+ $this->payload.=$this->xml_footer();
3123+ }
3124+
3125+ /**
3126+ * Gets/sets the xmlrpc method to be invoked
3127+ * @param string $meth the method to be set (leave empty not to set it)
3128+ * @return string the method that will be invoked
3129+ * @access public
3130+ */
3131+ function method($meth='')
3132+ {
3133+ if($meth!='')
3134+ {
3135+ $this->methodname=$meth;
3136+ }
3137+ return $this->methodname;
3138+ }
3139+
3140+ /**
3141+ * Returns xml representation of the message. XML prologue included
3142+ * @return string the xml representation of the message, xml prologue included
3143+ * @access public
3144+ */
3145+ function serialize($charset_encoding='')
3146+ {
3147+ $this->createPayload($charset_encoding);
3148+ return $this->payload;
3149+ }
3150+
3151+ /**
3152+ * Add a parameter to the list of parameters to be used upon method invocation
3153+ * @param xmlrpcval $par
3154+ * @return boolean false on failure
3155+ * @access public
3156+ */
3157+ function addParam($par)
3158+ {
3159+ // add check: do not add to self params which are not xmlrpcvals
3160+ if(is_object($par) && is_a($par, 'xmlrpcval'))
3161+ {
3162+ $this->params[]=$par;
3163+ return true;
3164+ }
3165+ else
3166+ {
3167+ return false;
3168+ }
3169+ }
3170+
3171+ /**
3172+ * Returns the nth parameter in the message. The index zero-based.
3173+ * @param integer $i the index of the parameter to fetch (zero based)
3174+ * @return xmlrpcval the i-th parameter
3175+ * @access public
3176+ */
3177+ function getParam($i) { return $this->params[$i]; }
3178+
3179+ /**
3180+ * Returns the number of parameters in the messge.
3181+ * @return integer the number of parameters currently set
3182+ * @access public
3183+ */
3184+ function getNumParams() { return count($this->params); }
3185+
3186+ /**
3187+ * Given an open file handle, read all data available and parse it as axmlrpc response.
3188+ * NB: the file handle is not closed by this function.
3189+ * NNB: might have trouble in rare cases to work on network streams, as we
3190+ * check for a read of 0 bytes instead of feof($fp).
3191+ * But since checking for feof(null) returns false, we would risk an
3192+ * infinite loop in that case, because we cannot trust the caller
3193+ * to give us a valid pointer to an open file...
3194+ * @access public
3195+ * @return xmlrpcresp
3196+ * @todo add 2nd & 3rd param to be passed to ParseResponse() ???
3197+ */
3198+ function &parseResponseFile($fp)
3199+ {
3200+ $ipd='';
3201+ while($data=fread($fp, 32768))
3202+ {
3203+ $ipd.=$data;
3204+ }
3205+ //fclose($fp);
3206+ $r =& $this->parseResponse($ipd);
3207+ return $r;
3208+ }
3209+
3210+ /**
3211+ * Parses HTTP headers and separates them from data.
3212+ * @access private
3213+ */
3214+ function &parseResponseHeaders(&$data, $headers_processed=false)
3215+ {
3216+ // Support "web-proxy-tunelling" connections for https through proxies
3217+ if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data))
3218+ {
3219+ // Look for CR/LF or simple LF as line separator,
3220+ // (even though it is not valid http)
3221+ $pos = strpos($data,"\r\n\r\n");
3222+ if($pos || is_int($pos))
3223+ {
3224+ $bd = $pos+4;
3225+ }
3226+ else
3227+ {
3228+ $pos = strpos($data,"\n\n");
3229+ if($pos || is_int($pos))
3230+ {
3231+ $bd = $pos+2;
3232+ }
3233+ else
3234+ {
3235+ // No separation between response headers and body: fault?
3236+ $bd = 0;
3237+ }
3238+ }
3239+ if ($bd)
3240+ {
3241+ // this filters out all http headers from proxy.
3242+ // maybe we could take them into account, too?
3243+ $data = substr($data, $bd);
3244+ }
3245+ else
3246+ {
3247+ error_log('XML-RPC: '.__METHOD__.': HTTPS via proxy error, tunnel connection possibly failed');
3248+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)');
3249+ return $r;
3250+ }
3251+ }
3252+
3253+ // Strip HTTP 1.1 100 Continue header if present
3254+ while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data))
3255+ {
3256+ $pos = strpos($data, 'HTTP', 12);
3257+ // server sent a Continue header without any (valid) content following...
3258+ // give the client a chance to know it
3259+ if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
3260+ {
3261+ break;
3262+ }
3263+ $data = substr($data, $pos);
3264+ }
3265+ if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data))
3266+ {
3267+ $errstr= substr($data, 0, strpos($data, "\n")-1);
3268+ error_log('XML-RPC: '.__METHOD__.': HTTP error, got response: ' .$errstr);
3269+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
3270+ return $r;
3271+ }
3272+
3273+ $GLOBALS['_xh']['headers'] = array();
3274+ $GLOBALS['_xh']['cookies'] = array();
3275+
3276+ // be tolerant to usage of \n instead of \r\n to separate headers and data
3277+ // (even though it is not valid http)
3278+ $pos = strpos($data,"\r\n\r\n");
3279+ if($pos || is_int($pos))
3280+ {
3281+ $bd = $pos+4;
3282+ }
3283+ else
3284+ {
3285+ $pos = strpos($data,"\n\n");
3286+ if($pos || is_int($pos))
3287+ {
3288+ $bd = $pos+2;
3289+ }
3290+ else
3291+ {
3292+ // No separation between response headers and body: fault?
3293+ // we could take some action here instead of going on...
3294+ $bd = 0;
3295+ }
3296+ }
3297+ // be tolerant to line endings, and extra empty lines
3298+ $ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
3299+ while(list(,$line) = @each($ar))
3300+ {
3301+ // take care of multi-line headers and cookies
3302+ $arr = explode(':',$line,2);
3303+ if(count($arr) > 1)
3304+ {
3305+ $header_name = strtolower(trim($arr[0]));
3306+ /// @todo some other headers (the ones that allow a CSV list of values)
3307+ /// do allow many values to be passed using multiple header lines.
3308+ /// We should add content to $GLOBALS['_xh']['headers'][$header_name]
3309+ /// instead of replacing it for those...
3310+ if ($header_name == 'set-cookie' || $header_name == 'set-cookie2')
3311+ {
3312+ if ($header_name == 'set-cookie2')
3313+ {
3314+ // version 2 cookies:
3315+ // there could be many cookies on one line, comma separated
3316+ $cookies = explode(',', $arr[1]);
3317+ }
3318+ else
3319+ {
3320+ $cookies = array($arr[1]);
3321+ }
3322+ foreach ($cookies as $cookie)
3323+ {
3324+ // glue together all received cookies, using a comma to separate them
3325+ // (same as php does with getallheaders())
3326+ if (isset($GLOBALS['_xh']['headers'][$header_name]))
3327+ $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie);
3328+ else
3329+ $GLOBALS['_xh']['headers'][$header_name] = trim($cookie);
3330+ // parse cookie attributes, in case user wants to correctly honour them
3331+ // feature creep: only allow rfc-compliant cookie attributes?
3332+ // @todo support for server sending multiple time cookie with same name, but using different PATHs
3333+ $cookie = explode(';', $cookie);
3334+ foreach ($cookie as $pos => $val)
3335+ {
3336+ $val = explode('=', $val, 2);
3337+ $tag = trim($val[0]);
3338+ $val = trim(@$val[1]);
3339+ /// @todo with version 1 cookies, we should strip leading and trailing " chars
3340+ if ($pos == 0)
3341+ {
3342+ $cookiename = $tag;
3343+ $GLOBALS['_xh']['cookies'][$tag] = array();
3344+ $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val);
3345+ }
3346+ else
3347+ {
3348+ if ($tag != 'value')
3349+ {
3350+ $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val;
3351+ }
3352+ }
3353+ }
3354+ }
3355+ }
3356+ else
3357+ {
3358+ $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]);
3359+ }
3360+ }
3361+ elseif(isset($header_name))
3362+ {
3363+ /// @todo version1 cookies might span multiple lines, thus breaking the parsing above
3364+ $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line);
3365+ }
3366+ }
3367+
3368+ $data = substr($data, $bd);
3369+
3370+ if($this->debug && count($GLOBALS['_xh']['headers']))
3371+ {
3372+ print '<PRE>';
3373+ foreach($GLOBALS['_xh']['headers'] as $header => $value)
3374+ {
3375+ print htmlentities("HEADER: $header: $value\n");
3376+ }
3377+ foreach($GLOBALS['_xh']['cookies'] as $header => $value)
3378+ {
3379+ print htmlentities("COOKIE: $header={$value['value']}\n");
3380+ }
3381+ print "</PRE>\n";
3382+ }
3383+
3384+ // if CURL was used for the call, http headers have been processed,
3385+ // and dechunking + reinflating have been carried out
3386+ if(!$headers_processed)
3387+ {
3388+ // Decode chunked encoding sent by http 1.1 servers
3389+ if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked')
3390+ {
3391+ if(!$data = decode_chunked($data))
3392+ {
3393+ error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to rebuild the chunked data received from server');
3394+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
3395+ return $r;
3396+ }
3397+ }
3398+
3399+ // Decode gzip-compressed stuff
3400+ // code shamelessly inspired from nusoap library by Dietrich Ayala
3401+ if(isset($GLOBALS['_xh']['headers']['content-encoding']))
3402+ {
3403+ $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']);
3404+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip')
3405+ {
3406+ // if decoding works, use it. else assume data wasn't gzencoded
3407+ if(function_exists('gzinflate'))
3408+ {
3409+ if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data))
3410+ {
3411+ $data = $degzdata;
3412+ if($this->debug)
3413+ print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
3414+ }
3415+ elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
3416+ {
3417+ $data = $degzdata;
3418+ if($this->debug)
3419+ print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
3420+ }
3421+ else
3422+ {
3423+ error_log('XML-RPC: '.__METHOD__.': errors occurred when trying to decode the deflated data received from server');
3424+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
3425+ return $r;
3426+ }
3427+ }
3428+ else
3429+ {
3430+ error_log('XML-RPC: '.__METHOD__.': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
3431+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
3432+ return $r;
3433+ }
3434+ }
3435+ }
3436+ } // end of 'if needed, de-chunk, re-inflate response'
3437+
3438+ // real stupid hack to avoid PHP complaining about returning NULL by ref
3439+ $r = null;
3440+ $r =& $r;
3441+ return $r;
3442+ }
3443+
3444+ /**
3445+ * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object.
3446+ * @param string $data the xmlrpc response, eventually including http headers
3447+ * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding
3448+ * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals'
3449+ * @return xmlrpcresp
3450+ * @access public
3451+ */
3452+ function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals')
3453+ {
3454+ if($this->debug)
3455+ {
3456+ //by maHo, replaced htmlspecialchars with htmlentities
3457+ print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
3458+ }
3459+
3460+ if($data == '')
3461+ {
3462+ error_log('XML-RPC: '.__METHOD__.': no response received from server.');
3463+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
3464+ return $r;
3465+ }
3466+
3467+ $GLOBALS['_xh']=array();
3468+
3469+ $raw_data = $data;
3470+ // parse the HTTP headers of the response, if present, and separate them from data
3471+ if(substr($data, 0, 4) == 'HTTP')
3472+ {
3473+ $r =& $this->parseResponseHeaders($data, $headers_processed);
3474+ if ($r)
3475+ {
3476+ // failed processing of HTTP response headers
3477+ // save into response obj the full payload received, for debugging
3478+ $r->raw_data = $data;
3479+ return $r;
3480+ }
3481+ }
3482+ else
3483+ {
3484+ $GLOBALS['_xh']['headers'] = array();
3485+ $GLOBALS['_xh']['cookies'] = array();
3486+ }
3487+
3488+ if($this->debug)
3489+ {
3490+ $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
3491+ if ($start)
3492+ {
3493+ $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):');
3494+ $end = strpos($data, '-->', $start);
3495+ $comments = substr($data, $start, $end-$start);
3496+ print "<PRE>---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n</PRE>";
3497+ }
3498+ }
3499+
3500+ // be tolerant of extra whitespace in response body
3501+ $data = trim($data);
3502+
3503+ /// @todo return an error msg if $data=='' ?
3504+
3505+ // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts)
3506+ // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
3507+ $pos = strrpos($data, '</methodResponse>');
3508+ if($pos !== false)
3509+ {
3510+ $data = substr($data, 0, $pos+17);
3511+ }
3512+
3513+ // if user wants back raw xml, give it to him
3514+ if ($return_type == 'xml')
3515+ {
3516+ $r = new xmlrpcresp($data, 0, '', 'xml');
3517+ $r->hdrs = $GLOBALS['_xh']['headers'];
3518+ $r->_cookies = $GLOBALS['_xh']['cookies'];
3519+ $r->raw_data = $raw_data;
3520+ return $r;
3521+ }
3522+
3523+ // try to 'guestimate' the character encoding of the received response
3524+ $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data);
3525+
3526+ $GLOBALS['_xh']['ac']='';
3527+ //$GLOBALS['_xh']['qt']=''; //unused...
3528+ $GLOBALS['_xh']['stack'] = array();
3529+ $GLOBALS['_xh']['valuestack'] = array();
3530+ $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc
3531+ $GLOBALS['_xh']['isf_reason']='';
3532+ $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse'
3533+
3534+ // if response charset encoding is not known / supported, try to use
3535+ // the default encoding and parse the xml anyway, but log a warning...
3536+ if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3537+ // the following code might be better for mb_string enabled installs, but
3538+ // makes the lib about 200% slower...
3539+ //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3540+ {
3541+ error_log('XML-RPC: '.__METHOD__.': invalid charset encoding of received response: '.$resp_encoding);
3542+ $resp_encoding = $GLOBALS['xmlrpc_defencoding'];
3543+ }
3544+ $parser = xml_parser_create($resp_encoding);
3545+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
3546+ // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
3547+ // the xml parser to give us back data in the expected charset.
3548+ // What if internal encoding is not in one of the 3 allowed?
3549+ // we use the broadest one, ie. utf8
3550+ // This allows to send data which is native in various charset,
3551+ // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding
3552+ if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
3553+ {
3554+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
3555+ }
3556+ else
3557+ {
3558+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
3559+ }
3560+
3561+ if ($return_type == 'phpvals')
3562+ {
3563+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
3564+ }
3565+ else
3566+ {
3567+ xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
3568+ }
3569+
3570+ xml_set_character_data_handler($parser, 'xmlrpc_cd');
3571+ xml_set_default_handler($parser, 'xmlrpc_dh');
3572+
3573+ // first error check: xml not well formed
3574+ if(!xml_parse($parser, $data, count($data)))
3575+ {
3576+ // thanks to Peter Kocks <peter.kocks@baygate.com>
3577+ if((xml_get_current_line_number($parser)) == 1)
3578+ {
3579+ $errstr = 'XML error at line 1, check URL';
3580+ }
3581+ else
3582+ {
3583+ $errstr = sprintf('XML error: %s at line %d, column %d',
3584+ xml_error_string(xml_get_error_code($parser)),
3585+ xml_get_current_line_number($parser), xml_get_current_column_number($parser));
3586+ }
3587+ error_log($errstr);
3588+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
3589+ xml_parser_free($parser);
3590+ if($this->debug)
3591+ {
3592+ print $errstr;
3593+ }
3594+ $r->hdrs = $GLOBALS['_xh']['headers'];
3595+ $r->_cookies = $GLOBALS['_xh']['cookies'];
3596+ $r->raw_data = $raw_data;
3597+ return $r;
3598+ }
3599+ xml_parser_free($parser);
3600+ // second error check: xml well formed but not xml-rpc compliant
3601+ if ($GLOBALS['_xh']['isf'] > 1)
3602+ {
3603+ if ($this->debug)
3604+ {
3605+ /// @todo echo something for user?
3606+ }
3607+
3608+ $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
3609+ $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']);
3610+ }
3611+ // third error check: parsing of the response has somehow gone boink.
3612+ // NB: shall we omit this check, since we trust the parsing code?
3613+ elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value']))
3614+ {
3615+ // something odd has happened
3616+ // and it's time to generate a client side error
3617+ // indicating something odd went on
3618+ $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'],
3619+ $GLOBALS['xmlrpcstr']['invalid_return']);
3620+ }
3621+ else
3622+ {
3623+ if ($this->debug)
3624+ {
3625+ print "<PRE>---PARSED---\n";
3626+ // somehow htmlentities chokes on var_export, and some full html string...
3627+ //print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
3628+ print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
3629+ print "\n---END---</PRE>";
3630+ }
3631+
3632+ // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object.
3633+ $v =& $GLOBALS['_xh']['value'];
3634+
3635+ if($GLOBALS['_xh']['isf'])
3636+ {
3637+ /// @todo we should test here if server sent an int and a string,
3638+ /// and/or coerce them into such...
3639+ if ($return_type == 'xmlrpcvals')
3640+ {
3641+ $errno_v = $v->structmem('faultCode');
3642+ $errstr_v = $v->structmem('faultString');
3643+ $errno = $errno_v->scalarval();
3644+ $errstr = $errstr_v->scalarval();
3645+ }
3646+ else
3647+ {
3648+ $errno = $v['faultCode'];
3649+ $errstr = $v['faultString'];
3650+ }
3651+
3652+ if($errno == 0)
3653+ {
3654+ // FAULT returned, errno needs to reflect that
3655+ $errno = -1;
3656+ }
3657+
3658+ $r = new xmlrpcresp(0, $errno, $errstr);
3659+ }
3660+ else
3661+ {
3662+ $r=new xmlrpcresp($v, 0, '', $return_type);
3663+ }
3664+ }
3665+
3666+ $r->hdrs = $GLOBALS['_xh']['headers'];
3667+ $r->_cookies = $GLOBALS['_xh']['cookies'];
3668+ $r->raw_data = $raw_data;
3669+ return $r;
3670+ }
3671+ }
3672+
3673+ class xmlrpcval
3674+ {
3675+ var $me=array();
3676+ var $mytype=0;
3677+ var $_php_class=null;
3678+
3679+ /**
3680+ * @param mixed $val
3681+ * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed
3682+ */
3683+ function xmlrpcval($val=-1, $type='')
3684+ {
3685+ /// @todo: optimization creep - do not call addXX, do it all inline.
3686+ /// downside: booleans will not be coerced anymore
3687+ if($val!==-1 || $type!='')
3688+ {
3689+ // optimization creep: inlined all work done by constructor
3690+ switch($type)
3691+ {
3692+ case '':
3693+ $this->mytype=1;
3694+ $this->me['string']=$val;
3695+ break;
3696+ case 'i4':
3697+ case 'int':
3698+ case 'double':
3699+ case 'string':
3700+ case 'boolean':
3701+ case 'dateTime.iso8601':
3702+ case 'base64':
3703+ case 'null':
3704+ $this->mytype=1;
3705+ $this->me[$type]=$val;
3706+ break;
3707+ case 'array':
3708+ $this->mytype=2;
3709+ $this->me['array']=$val;
3710+ break;
3711+ case 'struct':
3712+ $this->mytype=3;
3713+ $this->me['struct']=$val;
3714+ break;
3715+ default:
3716+ error_log("XML-RPC: ".__METHOD__.": not a known type ($type)");
3717+ }
3718+ /*if($type=='')
3719+ {
3720+ $type='string';
3721+ }
3722+ if($GLOBALS['xmlrpcTypes'][$type]==1)
3723+ {
3724+ $this->addScalar($val,$type);
3725+ }
3726+ elseif($GLOBALS['xmlrpcTypes'][$type]==2)
3727+ {
3728+ $this->addArray($val);
3729+ }
3730+ elseif($GLOBALS['xmlrpcTypes'][$type]==3)
3731+ {
3732+ $this->addStruct($val);
3733+ }*/
3734+ }
3735+ }
3736+
3737+ /**
3738+ * Add a single php value to an (unitialized) xmlrpcval
3739+ * @param mixed $val
3740+ * @param string $type
3741+ * @return int 1 or 0 on failure
3742+ */
3743+ function addScalar($val, $type='string')
3744+ {
3745+ $typeof=@$GLOBALS['xmlrpcTypes'][$type];
3746+ if($typeof!=1)
3747+ {
3748+ error_log("XML-RPC: ".__METHOD__.": not a scalar type ($type)");
3749+ return 0;
3750+ }
3751+
3752+ // coerce booleans into correct values
3753+ // NB: we should either do it for datetimes, integers and doubles, too,
3754+ // or just plain remove this check, implemented on booleans only...
3755+ if($type==$GLOBALS['xmlrpcBoolean'])
3756+ {
3757+ if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
3758+ {
3759+ $val=true;
3760+ }
3761+ else
3762+ {
3763+ $val=false;
3764+ }
3765+ }
3766+
3767+ switch($this->mytype)
3768+ {
3769+ case 1:
3770+ error_log('XML-RPC: '.__METHOD__.': scalar xmlrpcval can have only one value');
3771+ return 0;
3772+ case 3:
3773+ error_log('XML-RPC: '.__METHOD__.': cannot add anonymous scalar to struct xmlrpcval');
3774+ return 0;
3775+ case 2:
3776+ // we're adding a scalar value to an array here
3777+ //$ar=$this->me['array'];
3778+ //$ar[]=new xmlrpcval($val, $type);
3779+ //$this->me['array']=$ar;
3780+ // Faster (?) avoid all the costly array-copy-by-val done here...
3781+ $this->me['array'][]=new xmlrpcval($val, $type);
3782+ return 1;
3783+ default:
3784+ // a scalar, so set the value and remember we're scalar
3785+ $this->me[$type]=$val;
3786+ $this->mytype=$typeof;
3787+ return 1;
3788+ }
3789+ }
3790+
3791+ /**
3792+ * Add an array of xmlrpcval objects to an xmlrpcval
3793+ * @param array $vals
3794+ * @return int 1 or 0 on failure
3795+ * @access public
3796+ *
3797+ * @todo add some checking for $vals to be an array of xmlrpcvals?
3798+ */
3799+ function addArray($vals)
3800+ {
3801+ if($this->mytype==0)
3802+ {
3803+ $this->mytype=$GLOBALS['xmlrpcTypes']['array'];
3804+ $this->me['array']=$vals;
3805+ return 1;
3806+ }
3807+ elseif($this->mytype==2)
3808+ {
3809+ // we're adding to an array here
3810+ $this->me['array'] = array_merge($this->me['array'], $vals);
3811+ return 1;
3812+ }
3813+ else
3814+ {
3815+ error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
3816+ return 0;
3817+ }
3818+ }
3819+
3820+ /**
3821+ * Add an array of named xmlrpcval objects to an xmlrpcval
3822+ * @param array $vals
3823+ * @return int 1 or 0 on failure
3824+ * @access public
3825+ *
3826+ * @todo add some checking for $vals to be an array?
3827+ */
3828+ function addStruct($vals)
3829+ {
3830+ if($this->mytype==0)
3831+ {
3832+ $this->mytype=$GLOBALS['xmlrpcTypes']['struct'];
3833+ $this->me['struct']=$vals;
3834+ return 1;
3835+ }
3836+ elseif($this->mytype==3)
3837+ {
3838+ // we're adding to a struct here
3839+ $this->me['struct'] = array_merge($this->me['struct'], $vals);
3840+ return 1;
3841+ }
3842+ else
3843+ {
3844+ error_log('XML-RPC: '.__METHOD__.': already initialized as a [' . $this->kindOf() . ']');
3845+ return 0;
3846+ }
3847+ }
3848+
3849+ // poor man's version of print_r ???
3850+ // DEPRECATED!
3851+ function dump($ar)
3852+ {
3853+ foreach($ar as $key => $val)
3854+ {
3855+ echo "$key => $val<br />";
3856+ if($key == 'array')
3857+ {
3858+ while(list($key2, $val2) = each($val))
3859+ {
3860+ echo "-- $key2 => $val2<br />";
3861+ }
3862+ }
3863+ }
3864+ }
3865+
3866+ /**
3867+ * Returns a string containing "struct", "array" or "scalar" describing the base type of the value
3868+ * @return string
3869+ * @access public
3870+ */
3871+ function kindOf()
3872+ {
3873+ switch($this->mytype)
3874+ {
3875+ case 3:
3876+ return 'struct';
3877+ break;
3878+ case 2:
3879+ return 'array';
3880+ break;
3881+ case 1:
3882+ return 'scalar';
3883+ break;
3884+ default:
3885+ return 'undef';
3886+ }
3887+ }
3888+
3889+ /**
3890+ * @access private
3891+ */
3892+ function serializedata($typ, $val, $charset_encoding='')
3893+ {
3894+ $rs='';
3895+ switch(@$GLOBALS['xmlrpcTypes'][$typ])
3896+ {
3897+ case 1:
3898+ switch($typ)
3899+ {
3900+ case $GLOBALS['xmlrpcBase64']:
3901+ $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
3902+ break;
3903+ case $GLOBALS['xmlrpcBoolean']:
3904+ $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
3905+ break;
3906+ case $GLOBALS['xmlrpcString']:
3907+ // G. Giunta 2005/2/13: do NOT use htmlentities, since
3908+ // it will produce named html entities, which are invalid xml
3909+ $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). "</${typ}>";
3910+ break;
3911+ case $GLOBALS['xmlrpcInt']:
3912+ case $GLOBALS['xmlrpcI4']:
3913+ $rs.="<${typ}>".(int)$val."</${typ}>";
3914+ break;
3915+ case $GLOBALS['xmlrpcDouble']:
3916+ // avoid using standard conversion of float to string because it is locale-dependent,
3917+ // and also because the xmlrpc spec forbids exponential notation.
3918+ // sprintf('%F') could be most likely ok but it fails eg. on 2e-14.
3919+ // The code below tries its best at keeping max precision while avoiding exp notation,
3920+ // but there is of course no limit in the number of decimal places to be used...
3921+ $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', ''))."</${typ}>";
3922+ break;
3923+ case $GLOBALS['xmlrpcDateTime']:
3924+ if (is_string($val))
3925+ {
3926+ $rs.="<${typ}>${val}</${typ}>";
3927+ }
3928+ else if(is_a($val, 'DateTime'))
3929+ {
3930+ $rs.="<${typ}>".$val->format('Ymd\TH:i:s')."</${typ}>";
3931+ }
3932+ else if(is_int($val))
3933+ {
3934+ $rs.="<${typ}>".strftime("%Y%m%dT%H:%M:%S", $val)."</${typ}>";
3935+ }
3936+ else
3937+ {
3938+ // not really a good idea here: but what shall we output anyway? left for backward compat...
3939+ $rs.="<${typ}>${val}</${typ}>";
3940+ }
3941+ break;
3942+ case $GLOBALS['xmlrpcNull']:
3943+ if ($GLOBALS['xmlrpc_null_apache_encoding'])
3944+ {
3945+ $rs.="<ex:nil/>";
3946+ }
3947+ else
3948+ {
3949+ $rs.="<nil/>";
3950+ }
3951+ break;
3952+ default:
3953+ // no standard type value should arrive here, but provide a possibility
3954+ // for xmlrpcvals of unknown type...
3955+ $rs.="<${typ}>${val}</${typ}>";
3956+ }
3957+ break;
3958+ case 3:
3959+ // struct
3960+ if ($this->_php_class)
3961+ {
3962+ $rs.='<struct php_class="' . $this->_php_class . "\">\n";
3963+ }
3964+ else
3965+ {
3966+ $rs.="<struct>\n";
3967+ }
3968+ foreach($val as $key2 => $val2)
3969+ {
3970+ $rs.='<member><name>'.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."</name>\n";
3971+ //$rs.=$this->serializeval($val2);
3972+ $rs.=$val2->serialize($charset_encoding);
3973+ $rs.="</member>\n";
3974+ }
3975+ $rs.='</struct>';
3976+ break;
3977+ case 2:
3978+ // array
3979+ $rs.="<array>\n<data>\n";
3980+ for($i=0; $i<count($val); $i++)
3981+ {
3982+ //$rs.=$this->serializeval($val[$i]);
3983+ $rs.=$val[$i]->serialize($charset_encoding);
3984+ }
3985+ $rs.="</data>\n</array>";
3986+ break;
3987+ default:
3988+ break;
3989+ }
3990+ return $rs;
3991+ }
3992+
3993+ /**
3994+ * Returns xml representation of the value. XML prologue not included
3995+ * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed
3996+ * @return string
3997+ * @access public
3998+ */
3999+ function serialize($charset_encoding='')
4000+ {
4001+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
4002+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
4003+ //{
4004+ reset($this->me);
4005+ list($typ, $val) = each($this->me);
4006+ return '<value>' . $this->serializedata($typ, $val, $charset_encoding) . "</value>\n";
4007+ //}
4008+ }
4009+
4010+ // DEPRECATED
4011+ function serializeval($o)
4012+ {
4013+ // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals...
4014+ //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval')))
4015+ //{
4016+ $ar=$o->me;
4017+ reset($ar);
4018+ list($typ, $val) = each($ar);
4019+ return '<value>' . $this->serializedata($typ, $val) . "</value>\n";
4020+ //}
4021+ }
4022+
4023+ /**
4024+ * Checks wheter a struct member with a given name is present.
4025+ * Works only on xmlrpcvals of type struct.
4026+ * @param string $m the name of the struct member to be looked up
4027+ * @return boolean
4028+ * @access public
4029+ */
4030+ function structmemexists($m)
4031+ {
4032+ return array_key_exists($m, $this->me['struct']);
4033+ }
4034+
4035+ /**
4036+ * Returns the value of a given struct member (an xmlrpcval object in itself).
4037+ * Will raise a php warning if struct member of given name does not exist
4038+ * @param string $m the name of the struct member to be looked up
4039+ * @return xmlrpcval
4040+ * @access public
4041+ */
4042+ function structmem($m)
4043+ {
4044+ return $this->me['struct'][$m];
4045+ }
4046+
4047+ /**
4048+ * Reset internal pointer for xmlrpcvals of type struct.
4049+ * @access public
4050+ */
4051+ function structreset()
4052+ {
4053+ reset($this->me['struct']);
4054+ }
4055+
4056+ /**
4057+ * Return next member element for xmlrpcvals of type struct.
4058+ * @return xmlrpcval
4059+ * @access public
4060+ */
4061+ function structeach()
4062+ {
4063+ return each($this->me['struct']);
4064+ }
4065+
4066+ // DEPRECATED! this code looks like it is very fragile and has not been fixed
4067+ // for a long long time. Shall we remove it for 2.0?
4068+ function getval()
4069+ {
4070+ // UNSTABLE
4071+ reset($this->me);
4072+ list($a,$b)=each($this->me);
4073+ // contributed by I Sofer, 2001-03-24
4074+ // add support for nested arrays to scalarval
4075+ // i've created a new method here, so as to
4076+ // preserve back compatibility
4077+
4078+ if(is_array($b))
4079+ {
4080+ @reset($b);
4081+ while(list($id,$cont) = @each($b))
4082+ {
4083+ $b[$id] = $cont->scalarval();
4084+ }
4085+ }
4086+
4087+ // add support for structures directly encoding php objects
4088+ if(is_object($b))
4089+ {
4090+ $t = get_object_vars($b);
4091+ @reset($t);
4092+ while(list($id,$cont) = @each($t))
4093+ {
4094+ $t[$id] = $cont->scalarval();
4095+ }
4096+ @reset($t);
4097+ while(list($id,$cont) = @each($t))
4098+ {
4099+ @$b->$id = $cont;
4100+ }
4101+ }
4102+ // end contrib
4103+ return $b;
4104+ }
4105+
4106+ /**
4107+ * Returns the value of a scalar xmlrpcval
4108+ * @return mixed
4109+ * @access public
4110+ */
4111+ function scalarval()
4112+ {
4113+ reset($this->me);
4114+ list(,$b)=each($this->me);
4115+ return $b;
4116+ }
4117+
4118+ /**
4119+ * Returns the type of the xmlrpcval.
4120+ * For integers, 'int' is always returned in place of 'i4'
4121+ * @return string
4122+ * @access public
4123+ */
4124+ function scalartyp()
4125+ {
4126+ reset($this->me);
4127+ list($a,)=each($this->me);
4128+ if($a==$GLOBALS['xmlrpcI4'])
4129+ {
4130+ $a=$GLOBALS['xmlrpcInt'];
4131+ }
4132+ return $a;
4133+ }
4134+
4135+ /**
4136+ * Returns the m-th member of an xmlrpcval of struct type
4137+ * @param integer $m the index of the value to be retrieved (zero based)
4138+ * @return xmlrpcval
4139+ * @access public
4140+ */
4141+ function arraymem($m)
4142+ {
4143+ return $this->me['array'][$m];
4144+ }
4145+
4146+ /**
4147+ * Returns the number of members in an xmlrpcval of array type
4148+ * @return integer
4149+ * @access public
4150+ */
4151+ function arraysize()
4152+ {
4153+ return count($this->me['array']);
4154+ }
4155+
4156+ /**
4157+ * Returns the number of members in an xmlrpcval of struct type
4158+ * @return integer
4159+ * @access public
4160+ */
4161+ function structsize()
4162+ {
4163+ return count($this->me['struct']);
4164+ }
4165+ }
4166+
4167+
4168+ // date helpers
4169+
4170+ /**
4171+ * Given a timestamp, return the corresponding ISO8601 encoded string.
4172+ *
4173+ * Really, timezones ought to be supported
4174+ * but the XML-RPC spec says:
4175+ *
4176+ * "Don't assume a timezone. It should be specified by the server in its
4177+ * documentation what assumptions it makes about timezones."
4178+ *
4179+ * These routines always assume localtime unless
4180+ * $utc is set to 1, in which case UTC is assumed
4181+ * and an adjustment for locale is made when encoding
4182+ *
4183+ * @param int $timet (timestamp)
4184+ * @param int $utc (0 or 1)
4185+ * @return string
4186+ */
4187+ function iso8601_encode($timet, $utc=0)
4188+ {
4189+ if(!$utc)
4190+ {
4191+ $t=strftime("%Y%m%dT%H:%M:%S", $timet);
4192+ }
4193+ else
4194+ {
4195+ if(function_exists('gmstrftime'))
4196+ {
4197+ // gmstrftime doesn't exist in some versions
4198+ // of PHP
4199+ $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
4200+ }
4201+ else
4202+ {
4203+ $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
4204+ }
4205+ }
4206+ return $t;
4207+ }
4208+
4209+ /**
4210+ * Given an ISO8601 date string, return a timet in the localtime, or UTC
4211+ * @param string $idate
4212+ * @param int $utc either 0 or 1
4213+ * @return int (datetime)
4214+ */
4215+ function iso8601_decode($idate, $utc=0)
4216+ {
4217+ $t=0;
4218+ if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs))
4219+ {
4220+ if($utc)
4221+ {
4222+ $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
4223+ }
4224+ else
4225+ {
4226+ $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
4227+ }
4228+ }
4229+ return $t;
4230+ }
4231+
4232+ /**
4233+ * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types.
4234+ *
4235+ * Works with xmlrpc message objects as input, too.
4236+ *
4237+ * Given proper options parameter, can rebuild generic php object instances
4238+ * (provided those have been encoded to xmlrpc format using a corresponding
4239+ * option in php_xmlrpc_encode())
4240+ * PLEASE NOTE that rebuilding php objects involves calling their constructor function.
4241+ * This means that the remote communication end can decide which php code will
4242+ * get executed on your server, leaving the door possibly open to 'php-injection'
4243+ * style of attacks (provided you have some classes defined on your server that
4244+ * might wreak havoc if instances are built outside an appropriate context).
4245+ * Make sure you trust the remote server/client before eanbling this!
4246+ *
4247+ * @author Dan Libby (dan@libby.com)
4248+ *
4249+ * @param xmlrpcval $xmlrpc_val
4250+ * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects; if 'dates_as_objects' is set xmlrpc datetimes are decoded as php DateTime objects (standard is
4251+ * @return mixed
4252+ */
4253+ function php_xmlrpc_decode($xmlrpc_val, $options=array())
4254+ {
4255+ switch($xmlrpc_val->kindOf())
4256+ {
4257+ case 'scalar':
4258+ if (in_array('extension_api', $options))
4259+ {
4260+ reset($xmlrpc_val->me);
4261+ list($typ,$val) = each($xmlrpc_val->me);
4262+ switch ($typ)
4263+ {
4264+ case 'dateTime.iso8601':
4265+ $xmlrpc_val->scalar = $val;
4266+ $xmlrpc_val->xmlrpc_type = 'datetime';
4267+ $xmlrpc_val->timestamp = iso8601_decode($val);
4268+ return $xmlrpc_val;
4269+ case 'base64':
4270+ $xmlrpc_val->scalar = $val;
4271+ $xmlrpc_val->type = $typ;
4272+ return $xmlrpc_val;
4273+ default:
4274+ return $xmlrpc_val->scalarval();
4275+ }
4276+ }
4277+ if (in_array('dates_as_objects', $options) && $xmlrpc_val->scalartyp() == 'dateTime.iso8601')
4278+ {
4279+ // we return a Datetime object instead of a string
4280+ // since now the constructor of xmlrpcval accepts safely strings, ints and datetimes,
4281+ // we cater to all 3 cases here
4282+ $out = $xmlrpc_val->scalarval();
4283+ if (is_string($out))
4284+ {
4285+ $out = strtotime($out);
4286+ }
4287+ if (is_int($out))
4288+ {
4289+ $result = new Datetime();
4290+ $result->setTimestamp($out);
4291+ return $result;
4292+ }
4293+ elseif (is_a($out, 'Datetime'))
4294+ {
4295+ return $out;
4296+ }
4297+ }
4298+ return $xmlrpc_val->scalarval();
4299+ case 'array':
4300+ $size = $xmlrpc_val->arraysize();
4301+ $arr = array();
4302+ for($i = 0; $i < $size; $i++)
4303+ {
4304+ $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options);
4305+ }
4306+ return $arr;
4307+ case 'struct':
4308+ $xmlrpc_val->structreset();
4309+ // If user said so, try to rebuild php objects for specific struct vals.
4310+ /// @todo should we raise a warning for class not found?
4311+ // shall we check for proper subclass of xmlrpcval instead of
4312+ // presence of _php_class to detect what we can do?
4313+ if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != ''
4314+ && class_exists($xmlrpc_val->_php_class))
4315+ {
4316+ $obj = @new $xmlrpc_val->_php_class;
4317+ while(list($key,$value)=$xmlrpc_val->structeach())
4318+ {
4319+ $obj->$key = php_xmlrpc_decode($value, $options);
4320+ }
4321+ return $obj;
4322+ }
4323+ else
4324+ {
4325+ $arr = array();
4326+ while(list($key,$value)=$xmlrpc_val->structeach())
4327+ {
4328+ $arr[$key] = php_xmlrpc_decode($value, $options);
4329+ }
4330+ return $arr;
4331+ }
4332+ case 'msg':
4333+ $paramcount = $xmlrpc_val->getNumParams();
4334+ $arr = array();
4335+ for($i = 0; $i < $paramcount; $i++)
4336+ {
4337+ $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i));
4338+ }
4339+ return $arr;
4340+ }
4341+ }
4342+
4343+ // This constant left here only for historical reasons...
4344+ // it was used to decide if we have to define xmlrpc_encode on our own, but
4345+ // we do not do it anymore
4346+ if(function_exists('xmlrpc_decode'))
4347+ {
4348+ define('XMLRPC_EPI_ENABLED','1');
4349+ }
4350+ else
4351+ {
4352+ define('XMLRPC_EPI_ENABLED','0');
4353+ }
4354+
4355+ /**
4356+ * Takes native php types and encodes them into xmlrpc PHP object format.
4357+ * It will not re-encode xmlrpcval objects.
4358+ *
4359+ * Feature creep -- could support more types via optional type argument
4360+ * (string => datetime support has been added, ??? => base64 not yet)
4361+ *
4362+ * If given a proper options parameter, php object instances will be encoded
4363+ * into 'special' xmlrpc values, that can later be decoded into php objects
4364+ * by calling php_xmlrpc_decode() with a corresponding option
4365+ *
4366+ * @author Dan Libby (dan@libby.com)
4367+ *
4368+ * @param mixed $php_val the value to be converted into an xmlrpcval object
4369+ * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api'
4370+ * @return xmlrpcval
4371+ */
4372+ function php_xmlrpc_encode($php_val, $options=array())
4373+ {
4374+ $type = gettype($php_val);
4375+ switch($type)
4376+ {
4377+ case 'string':
4378+ if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val))
4379+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']);
4380+ else
4381+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']);
4382+ break;
4383+ case 'integer':
4384+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']);
4385+ break;
4386+ case 'double':
4387+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']);
4388+ break;
4389+ // <G_Giunta_2001-02-29>
4390+ // Add support for encoding/decoding of booleans, since they are supported in PHP
4391+ case 'boolean':
4392+ $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']);
4393+ break;
4394+ // </G_Giunta_2001-02-29>
4395+ case 'array':
4396+ // PHP arrays can be encoded to either xmlrpc structs or arrays,
4397+ // depending on wheter they are hashes or plain 0..n integer indexed
4398+ // A shorter one-liner would be
4399+ // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1));
4400+ // but execution time skyrockets!
4401+ $j = 0;
4402+ $arr = array();
4403+ $ko = false;
4404+ foreach($php_val as $key => $val)
4405+ {
4406+ $arr[$key] = php_xmlrpc_encode($val, $options);
4407+ if(!$ko && $key !== $j)
4408+ {
4409+ $ko = true;
4410+ }
4411+ $j++;
4412+ }
4413+ if($ko)
4414+ {
4415+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
4416+ }
4417+ else
4418+ {
4419+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']);
4420+ }
4421+ break;
4422+ case 'object':
4423+ if(is_a($php_val, 'xmlrpcval'))
4424+ {
4425+ $xmlrpc_val = $php_val;
4426+ }
4427+ else if(is_a($php_val, 'DateTime'))
4428+ {
4429+ $xmlrpc_val = new xmlrpcval($php_val->format('Ymd\TH:i:s'), $GLOBALS['xmlrpcStruct']);
4430+ }
4431+ else
4432+ {
4433+ $arr = array();
4434+ reset($php_val);
4435+ while(list($k,$v) = each($php_val))
4436+ {
4437+ $arr[$k] = php_xmlrpc_encode($v, $options);
4438+ }
4439+ $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']);
4440+ if (in_array('encode_php_objs', $options))
4441+ {
4442+ // let's save original class name into xmlrpcval:
4443+ // might be useful later on...
4444+ $xmlrpc_val->_php_class = get_class($php_val);
4445+ }
4446+ }
4447+ break;
4448+ case 'NULL':
4449+ if (in_array('extension_api', $options))
4450+ {
4451+ $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']);
4452+ }
4453+ else if (in_array('null_extension', $options))
4454+ {
4455+ $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']);
4456+ }
4457+ else
4458+ {
4459+ $xmlrpc_val = new xmlrpcval();
4460+ }
4461+ break;
4462+ case 'resource':
4463+ if (in_array('extension_api', $options))
4464+ {
4465+ $xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']);
4466+ }
4467+ else
4468+ {
4469+ $xmlrpc_val = new xmlrpcval();
4470+ }
4471+ // catch "user function", "unknown type"
4472+ default:
4473+ // giancarlo pinerolo <ping@alt.it>
4474+ // it has to return
4475+ // an empty object in case, not a boolean.
4476+ $xmlrpc_val = new xmlrpcval();
4477+ break;
4478+ }
4479+ return $xmlrpc_val;
4480+ }
4481+
4482+ /**
4483+ * Convert the xml representation of a method response, method request or single
4484+ * xmlrpc value into the appropriate object (a.k.a. deserialize)
4485+ * @param string $xml_val
4486+ * @param array $options
4487+ * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp
4488+ */
4489+ function php_xmlrpc_decode_xml($xml_val, $options=array())
4490+ {
4491+ $GLOBALS['_xh'] = array();
4492+ $GLOBALS['_xh']['ac'] = '';
4493+ $GLOBALS['_xh']['stack'] = array();
4494+ $GLOBALS['_xh']['valuestack'] = array();
4495+ $GLOBALS['_xh']['params'] = array();
4496+ $GLOBALS['_xh']['pt'] = array();
4497+ $GLOBALS['_xh']['isf'] = 0;
4498+ $GLOBALS['_xh']['isf_reason'] = '';
4499+ $GLOBALS['_xh']['method'] = false;
4500+ $GLOBALS['_xh']['rt'] = '';
4501+ /// @todo 'guestimate' encoding
4502+ $parser = xml_parser_create();
4503+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
4504+ // What if internal encoding is not in one of the 3 allowed?
4505+ // we use the broadest one, ie. utf8!
4506+ if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
4507+ {
4508+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
4509+ }
4510+ else
4511+ {
4512+ xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
4513+ }
4514+ xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee');
4515+ xml_set_character_data_handler($parser, 'xmlrpc_cd');
4516+ xml_set_default_handler($parser, 'xmlrpc_dh');
4517+ if(!xml_parse($parser, $xml_val, 1))
4518+ {
4519+ $errstr = sprintf('XML error: %s at line %d, column %d',
4520+ xml_error_string(xml_get_error_code($parser)),
4521+ xml_get_current_line_number($parser), xml_get_current_column_number($parser));
4522+ error_log($errstr);
4523+ xml_parser_free($parser);
4524+ return false;
4525+ }
4526+ xml_parser_free($parser);
4527+ if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too???
4528+ {
4529+ error_log($GLOBALS['_xh']['isf_reason']);
4530+ return false;
4531+ }
4532+ switch ($GLOBALS['_xh']['rt'])
4533+ {
4534+ case 'methodresponse':
4535+ $v =& $GLOBALS['_xh']['value'];
4536+ if ($GLOBALS['_xh']['isf'] == 1)
4537+ {
4538+ $vc = $v->structmem('faultCode');
4539+ $vs = $v->structmem('faultString');
4540+ $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval());
4541+ }
4542+ else
4543+ {
4544+ $r = new xmlrpcresp($v);
4545+ }
4546+ return $r;
4547+ case 'methodcall':
4548+ $m = new xmlrpcmsg($GLOBALS['_xh']['method']);
4549+ for($i=0; $i < count($GLOBALS['_xh']['params']); $i++)
4550+ {
4551+ $m->addParam($GLOBALS['_xh']['params'][$i]);
4552+ }
4553+ return $m;
4554+ case 'value':
4555+ return $GLOBALS['_xh']['value'];
4556+ default:
4557+ return false;
4558+ }
4559+ }
4560+
4561+ /**
4562+ * decode a string that is encoded w/ "chunked" transfer encoding
4563+ * as defined in rfc2068 par. 19.4.6
4564+ * code shamelessly stolen from nusoap library by Dietrich Ayala
4565+ *
4566+ * @param string $buffer the string to be decoded
4567+ * @return string
4568+ */
4569+ function decode_chunked($buffer)
4570+ {
4571+ // length := 0
4572+ $length = 0;
4573+ $new = '';
4574+
4575+ // read chunk-size, chunk-extension (if any) and crlf
4576+ // get the position of the linebreak
4577+ $chunkend = strpos($buffer,"\r\n") + 2;
4578+ $temp = substr($buffer,0,$chunkend);
4579+ $chunk_size = hexdec( trim($temp) );
4580+ $chunkstart = $chunkend;
4581+ while($chunk_size > 0)
4582+ {
4583+ $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size);
4584+
4585+ // just in case we got a broken connection
4586+ if($chunkend == false)
4587+ {
4588+ $chunk = substr($buffer,$chunkstart);
4589+ // append chunk-data to entity-body
4590+ $new .= $chunk;
4591+ $length += strlen($chunk);
4592+ break;
4593+ }
4594+
4595+ // read chunk-data and crlf
4596+ $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart);
4597+ // append chunk-data to entity-body
4598+ $new .= $chunk;
4599+ // length := length + chunk-size
4600+ $length += strlen($chunk);
4601+ // read chunk-size and crlf
4602+ $chunkstart = $chunkend + 2;
4603+
4604+ $chunkend = strpos($buffer,"\r\n",$chunkstart)+2;
4605+ if($chunkend == false)
4606+ {
4607+ break; //just in case we got a broken connection
4608+ }
4609+ $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart);
4610+ $chunk_size = hexdec( trim($temp) );
4611+ $chunkstart = $chunkend;
4612+ }
4613+ return $new;
4614+ }
4615+
4616+ /**
4617+ * xml charset encoding guessing helper function.
4618+ * Tries to determine the charset encoding of an XML chunk received over HTTP.
4619+ * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type,
4620+ * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers,
4621+ * which will be most probably using UTF-8 anyway...
4622+ *
4623+ * @param string $httpheaders the http Content-type header
4624+ * @param string $xmlchunk xml content buffer
4625+ * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled)
4626+ *
4627+ * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!!
4628+ */
4629+ function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null)
4630+ {
4631+ // discussion: see http://www.yale.edu/pclt/encoding/
4632+ // 1 - test if encoding is specified in HTTP HEADERS
4633+
4634+ //Details:
4635+ // LWS: (\13\10)?( |\t)+
4636+ // token: (any char but excluded stuff)+
4637+ // quoted string: " (any char but double quotes and cointrol chars)* "
4638+ // header: Content-type = ...; charset=value(; ...)*
4639+ // where value is of type token, no LWS allowed between 'charset' and value
4640+ // Note: we do not check for invalid chars in VALUE:
4641+ // this had better be done using pure ereg as below
4642+ // Note 2: we might be removing whitespace/tabs that ought to be left in if
4643+ // the received charset is a quoted string. But nobody uses such charset names...
4644+
4645+ /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it?
4646+ $matches = array();
4647+ if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches))
4648+ {
4649+ return strtoupper(trim($matches[1], " \t\""));
4650+ }
4651+
4652+ // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern
4653+ // (source: http://www.w3.org/TR/2000/REC-xml-20001006)
4654+ // NOTE: actually, according to the spec, even if we find the BOM and determine
4655+ // an encoding, we should check if there is an encoding specified
4656+ // in the xml declaration, and verify if they match.
4657+ /// @todo implement check as described above?
4658+ /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM)
4659+ if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk))
4660+ {
4661+ return 'UCS-4';
4662+ }
4663+ elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk))
4664+ {
4665+ return 'UTF-16';
4666+ }
4667+ elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk))
4668+ {
4669+ return 'UTF-8';
4670+ }
4671+
4672+ // 3 - test if encoding is specified in the xml declaration
4673+ // Details:
4674+ // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+
4675+ // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]*
4676+ if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))".
4677+ '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/",
4678+ $xmlchunk, $matches))
4679+ {
4680+ return strtoupper(substr($matches[2], 1, -1));
4681+ }
4682+
4683+ // 4 - if mbstring is available, let it do the guesswork
4684+ // NB: we favour finding an encoding that is compatible with what we can process
4685+ if(extension_loaded('mbstring'))
4686+ {
4687+ if($encoding_prefs)
4688+ {
4689+ $enc = mb_detect_encoding($xmlchunk, $encoding_prefs);
4690+ }
4691+ else
4692+ {
4693+ $enc = mb_detect_encoding($xmlchunk);
4694+ }
4695+ // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII...
4696+ // IANA also likes better US-ASCII, so go with it
4697+ if($enc == 'ASCII')
4698+ {
4699+ $enc = 'US-'.$enc;
4700+ }
4701+ return $enc;
4702+ }
4703+ else
4704+ {
4705+ // no encoding specified: as per HTTP1.1 assume it is iso-8859-1?
4706+ // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types
4707+ // this should be the standard. And we should be getting text/xml as request and response.
4708+ // BUT we have to be backward compatible with the lib, which always used UTF-8 as default...
4709+ return $GLOBALS['xmlrpc_defencoding'];
4710+ }
4711+ }
4712+
4713+ /**
4714+ * Checks if a given charset encoding is present in a list of encodings or
4715+ * if it is a valid subset of any encoding in the list
4716+ * @param string $encoding charset to be tested
4717+ * @param mixed $validlist comma separated list of valid charsets (or array of charsets)
4718+ */
4719+ function is_valid_charset($encoding, $validlist)
4720+ {
4721+ $charset_supersets = array(
4722+ 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
4723+ 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
4724+ 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
4725+ 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
4726+ 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN')
4727+ );
4728+ if (is_string($validlist))
4729+ $validlist = explode(',', $validlist);
4730+ if (@in_array(strtoupper($encoding), $validlist))
4731+ return true;
4732+ else
4733+ {
4734+ if (array_key_exists($encoding, $charset_supersets))
4735+ foreach ($validlist as $allowed)
4736+ if (in_array($allowed, $charset_supersets[$encoding]))
4737+ return true;
4738+ return false;
4739+ }
4740+ }
4741+
4742+?>
4743\ No newline at end of file
4744
4745=== added file 'Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc_wrappers.inc'
4746--- Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc_wrappers.inc 1970-01-01 00:00:00 +0000
4747+++ Savoirfairelinux_Claim/app/code/local/Savoirfairelinux/Claim/lib/xmlrpc_wrappers.inc 2012-10-15 14:51:21 +0000
4748@@ -0,0 +1,955 @@
4749+<?php
4750+/**
4751+ * PHP-XMLRPC "wrapper" functions
4752+ * Generate stubs to transparently access xmlrpc methods as php functions and viceversa
4753+ *
4754+ * @version $Id: xmlrpc_wrappers.inc,v 1.13 2008/09/20 01:23:47 ggiunta Exp $
4755+ * @author Gaetano Giunta
4756+ * @copyright (C) 2006-2009 G. Giunta
4757+ * @license code licensed under the BSD License: http://phpxmlrpc.sourceforge.net/license.txt
4758+ *
4759+ * @todo separate introspection from code generation for func-2-method wrapping
4760+ * @todo use some better templating system for code generation?
4761+ * @todo implement method wrapping with preservation of php objs in calls
4762+ * @todo when wrapping methods without obj rebuilding, use return_type = 'phpvals' (faster)
4763+ * @todo implement self-parsing of php code for PHP <= 4
4764+ */
4765+
4766+ // requires: xmlrpc.inc
4767+
4768+ /**
4769+ * Given a string defining a php type or phpxmlrpc type (loosely defined: strings
4770+ * accepted come from javadoc blocks), return corresponding phpxmlrpc type.
4771+ * NB: for php 'resource' types returns empty string, since resources cannot be serialized;
4772+ * for php class names returns 'struct', since php objects can be serialized as xmlrpc structs
4773+ * for php arrays always return array, even though arrays sometiles serialize as json structs
4774+ * @param string $phptype
4775+ * @return string
4776+ */
4777+ function php_2_xmlrpc_type($phptype)
4778+ {
4779+ switch(strtolower($phptype))
4780+ {
4781+ case 'string':
4782+ return $GLOBALS['xmlrpcString'];
4783+ case 'integer':
4784+ case $GLOBALS['xmlrpcInt']: // 'int'
4785+ case $GLOBALS['xmlrpcI4']:
4786+ return $GLOBALS['xmlrpcInt'];
4787+ case 'double':
4788+ return $GLOBALS['xmlrpcDouble'];
4789+ case 'boolean':
4790+ return $GLOBALS['xmlrpcBoolean'];
4791+ case 'array':
4792+ return $GLOBALS['xmlrpcArray'];
4793+ case 'object':
4794+ return $GLOBALS['xmlrpcStruct'];
4795+ case $GLOBALS['xmlrpcBase64']:
4796+ case $GLOBALS['xmlrpcStruct']:
4797+ return strtolower($phptype);
4798+ case 'resource':
4799+ return '';
4800+ default:
4801+ if(class_exists($phptype))
4802+ {
4803+ return $GLOBALS['xmlrpcStruct'];
4804+ }
4805+ else
4806+ {
4807+ // unknown: might be any 'extended' xmlrpc type
4808+ return $GLOBALS['xmlrpcValue'];
4809+ }
4810+ }
4811+ }
4812+
4813+ /**
4814+ * Given a string defining a phpxmlrpc type return corresponding php type.
4815+ * @param string $xmlrpctype
4816+ * @return string
4817+ */
4818+ function xmlrpc_2_php_type($xmlrpctype)
4819+ {
4820+ switch(strtolower($xmlrpctype))
4821+ {
4822+ case 'base64':
4823+ case 'datetime.iso8601':
4824+ case 'string':
4825+ return $GLOBALS['xmlrpcString'];
4826+ case 'int':
4827+ case 'i4':
4828+ return 'integer';
4829+ case 'struct':
4830+ case 'array':
4831+ return 'array';
4832+ case 'double':
4833+ return 'float';
4834+ case 'undefined':
4835+ return 'mixed';
4836+ case 'boolean':
4837+ case 'null':
4838+ default:
4839+ // unknown: might be any xmlrpc type
4840+ return strtolower($xmlrpctype);
4841+ }
4842+ }
4843+
4844+ /**
4845+ * Given a user-defined PHP function, create a PHP 'wrapper' function that can
4846+ * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
4847+ * clients (as well as its corresponding signature info).
4848+ *
4849+ * Since php is a typeless language, to infer types of input and output parameters,
4850+ * it relies on parsing the javadoc-style comment block associated with the given
4851+ * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
4852+ * in the @param tag is also allowed, if you need the php function to receive/send
4853+ * data in that particular format (note that base64 encoding/decoding is transparently
4854+ * carried out by the lib, while datetime vals are passed around as strings)
4855+ *
4856+ * Known limitations:
4857+ * - requires PHP 5.0.3 +
4858+ * - only works for user-defined functions, not for PHP internal functions
4859+ * (reflection does not support retrieving number/type of params for those)
4860+ * - functions returning php objects will generate special xmlrpc responses:
4861+ * when the xmlrpc decoding of those responses is carried out by this same lib, using
4862+ * the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
4863+ * In short: php objects can be serialized, too (except for their resource members),
4864+ * using this function.
4865+ * Other libs might choke on the very same xml that will be generated in this case
4866+ * (i.e. it has a nonstandard attribute on struct element tags)
4867+ * - usage of javadoc @param tags using param names in a different order from the
4868+ * function prototype is not considered valid (to be fixed?)
4869+ *
4870+ * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
4871+ * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
4872+ * is by making use of the functions_parameters_type class member.
4873+ *
4874+ * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') and array('class', 'methodname') are ok too
4875+ * @param string $newfuncname (optional) name for function to be created
4876+ * @param array $extra_options (optional) array of options for conversion. valid values include:
4877+ * bool return_source when true, php code w. function definition will be returned, not evaluated
4878+ * bool encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
4879+ * bool decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
4880+ * bool suppress_warnings remove from produced xml any runtime warnings due to the php function being invoked
4881+ * @return false on error, or an array containing the name of the new php function,
4882+ * its signature and docs, to be used in the server dispatch map
4883+ *
4884+ * @todo decide how to deal with params passed by ref: bomb out or allow?
4885+ * @todo finish using javadoc info to build method sig if all params are named but out of order
4886+ * @todo add a check for params of 'resource' type
4887+ * @todo add some trigger_errors / error_log when returning false?
4888+ * @todo what to do when the PHP function returns NULL? we are currently returning an empty string value...
4889+ * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
4890+ * @todo if $newfuncname is empty, we could use create_user_func instead of eval, as it is possibly faster
4891+ * @todo add a verbatim_object_copy parameter to allow avoiding the same obj instance?
4892+ */
4893+ function wrap_php_function($funcname, $newfuncname='', $extra_options=array())
4894+ {
4895+ $buildit = isset($extra_options['return_source']) ? !($extra_options['return_source']) : true;
4896+ $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
4897+ $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool)$extra_options['encode_php_objs'] : false;
4898+ $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool)$extra_options['decode_php_objs'] : false;
4899+ $catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : '';
4900+
4901+ if(version_compare(phpversion(), '5.0.3') == -1)
4902+ {
4903+ // up to php 5.0.3 some useful reflection methods were missing
4904+ error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3');
4905+ return false;
4906+ }
4907+
4908+ $exists = false;
4909+ if (is_string($funcname) && strpos($funcname, '::') !== false)
4910+ {
4911+ $funcname = explode('::', $funcname);
4912+ }
4913+ if(is_array($funcname))
4914+ {
4915+ if(count($funcname) < 2 || (!is_string($funcname[0]) && !is_object($funcname[0])))
4916+ {
4917+ error_log('XML-RPC: syntax for function to be wrapped is wrong');
4918+ return false;
4919+ }
4920+ if(is_string($funcname[0]))
4921+ {
4922+ $plainfuncname = implode('::', $funcname);
4923+ }
4924+ elseif(is_object($funcname[0]))
4925+ {
4926+ $plainfuncname = get_class($funcname[0]) . '->' . $funcname[1];
4927+ }
4928+ $exists = method_exists($funcname[0], $funcname[1]);
4929+ if (!$exists && version_compare(phpversion(), '5.1') < 0)
4930+ {
4931+ // workaround for php 5.0: static class methods are not seen by method_exists
4932+ $exists = is_callable( $funcname );
4933+ }
4934+ }
4935+ else
4936+ {
4937+ $plainfuncname = $funcname;
4938+ $exists = function_exists($funcname);
4939+ }
4940+
4941+ if(!$exists)
4942+ {
4943+ error_log('XML-RPC: function to be wrapped is not defined: '.$plainfuncname);
4944+ return false;
4945+ }
4946+ else
4947+ {
4948+ // determine name of new php function
4949+ if($newfuncname == '')
4950+ {
4951+ if(is_array($funcname))
4952+ {
4953+ if(is_string($funcname[0]))
4954+ $xmlrpcfuncname = "{$prefix}_".implode('_', $funcname);
4955+ else
4956+ $xmlrpcfuncname = "{$prefix}_".get_class($funcname[0]) . '_' . $funcname[1];
4957+ }
4958+ else
4959+ {
4960+ $xmlrpcfuncname = "{$prefix}_$funcname";
4961+ }
4962+ }
4963+ else
4964+ {
4965+ $xmlrpcfuncname = $newfuncname;
4966+ }
4967+ while($buildit && function_exists($xmlrpcfuncname))
4968+ {
4969+ $xmlrpcfuncname .= 'x';
4970+ }
4971+
4972+ // start to introspect PHP code
4973+ if(is_array($funcname))
4974+ {
4975+ $func = new ReflectionMethod($funcname[0], $funcname[1]);
4976+ if($func->isPrivate())
4977+ {
4978+ error_log('XML-RPC: method to be wrapped is private: '.$plainfuncname);
4979+ return false;
4980+ }
4981+ if($func->isProtected())
4982+ {
4983+ error_log('XML-RPC: method to be wrapped is protected: '.$plainfuncname);
4984+ return false;
4985+ }
4986+ if($func->isConstructor())
4987+ {
4988+ error_log('XML-RPC: method to be wrapped is the constructor: '.$plainfuncname);
4989+ return false;
4990+ }
4991+ // php 503 always says isdestructor = true...
4992+ if( version_compare(phpversion(), '5.0.3') != 0 && $func->isDestructor())
4993+ {
4994+ error_log('XML-RPC: method to be wrapped is the destructor: '.$plainfuncname);
4995+ return false;
4996+ }
4997+ if($func->isAbstract())
4998+ {
4999+ error_log('XML-RPC: method to be wrapped is abstract: '.$plainfuncname);
5000+ return false;
The diff has been truncated for viewing.