1 <?php
  2 /**
  3  * WooCommerce API
  4  *
  5  * Handles parsing XML request bodies and generating XML responses
  6  *
  7  * @author      WooThemes
  8  * @category    API
  9  * @package     WooCommerce/API
 10  * @since       2.1
 11  */
 12 
 13 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 14 
 15 class WC_API_XML_Handler implements WC_API_Handler {
 16 
 17     /** @var XMLWriter instance */
 18     private $xml;
 19 
 20     /**
 21      * Add some response filters
 22      *
 23      * @since 2.1
 24      */
 25     public function __construct() {
 26 
 27         // tweak sales report response data
 28         add_filter( 'woocommerce_api_report_response', array( $this, 'format_sales_report_data' ), 100 );
 29 
 30         // tweak product response data
 31         add_filter( 'woocommerce_api_product_response', array( $this, 'format_product_data' ), 100 );
 32     }
 33 
 34     /**
 35      * Get the content type for the response
 36      *
 37      * @since 2.1
 38      * @return string
 39      */
 40     public function get_content_type() {
 41 
 42         return 'application/xml; charset=' . get_option( 'blog_charset' );
 43     }
 44 
 45     /**
 46      * Parse the raw request body entity
 47      *
 48      * @since 2.1
 49      * @param string $data the raw request body
 50      * @return array
 51      */
 52     public function parse_body( $data ) {
 53 
 54         // TODO: implement simpleXML parsing
 55     }
 56 
 57     /**
 58      * Generate an XML response given an array of data
 59      *
 60      * @since 2.1
 61      * @param array $data the response data
 62      * @return string
 63      */
 64     public function generate_response( $data ) {
 65 
 66         $this->xml = new XMLWriter();
 67 
 68         $this->xml->openMemory();
 69 
 70         $this->xml->setIndent(true);
 71 
 72         $this->xml->startDocument( '1.0', 'UTF-8' );
 73 
 74         $root_element = key( $data );
 75 
 76         $data = $data[ $root_element ];
 77 
 78         switch ( $root_element ) {
 79 
 80             case 'orders':
 81                 $data = array( 'order' => $data );
 82                 break;
 83 
 84             case 'order_notes':
 85                 $data = array( 'order_note' => $data );
 86                 break;
 87 
 88             case 'customers':
 89                 $data = array( 'customer' => $data );
 90                 break;
 91 
 92             case 'coupons':
 93                 $data = array( 'coupon' => $data );
 94                 break;
 95 
 96             case 'products':
 97                 $data = array( 'product' => $data );
 98                 break;
 99 
100             case 'product_reviews':
101                 $data = array( 'product_review' => $data );
102                 break;
103 
104             default:
105                 $data = apply_filters( 'woocommerce_api_xml_data', $data, $root_element );
106                 break;
107         }
108 
109         // generate xml starting with the root element and recursively generating child elements
110         $this->array_to_xml( $root_element, $data );
111 
112         $this->xml->endDocument();
113 
114         return $this->xml->outputMemory();
115     }
116 
117     /**
118      * Convert array into XML by recursively generating child elements
119      *
120      * @since 2.1
121      * @param string|array $element_key - name for element, e.g. <OrderID>
122      * @param string|array $element_value - value for element, e.g. 1234
123      * @return string - generated XML
124      */
125     private function array_to_xml( $element_key, $element_value = array() ) {
126 
127         if ( is_array( $element_value ) ) {
128 
129             // handle attributes
130             if ( '@attributes' === $element_key ) {
131                 foreach ( $element_value as $attribute_key => $attribute_value ) {
132 
133                     $this->xml->startAttribute( $attribute_key );
134                     $this->xml->text( $attribute_value );
135                     $this->xml->endAttribute();
136                 }
137                 return;
138             }
139 
140             // handle multi-elements (e.g. multiple <Order> elements)
141             if ( is_numeric( key( $element_value ) ) ) {
142 
143                 // recursively generate child elements
144                 foreach ( $element_value as $child_element_key => $child_element_value ) {
145 
146                     $this->xml->startElement( $element_key );
147 
148                     foreach ( $child_element_value as $sibling_element_key => $sibling_element_value ) {
149                         $this->array_to_xml( $sibling_element_key, $sibling_element_value );
150                     }
151 
152                     $this->xml->endElement();
153                 }
154 
155             } else {
156 
157                 // start root element
158                 $this->xml->startElement( $element_key );
159 
160                 // recursively generate child elements
161                 foreach ( $element_value as $child_element_key => $child_element_value ) {
162                     $this->array_to_xml( $child_element_key, $child_element_value );
163                 }
164 
165                 // end root element
166                 $this->xml->endElement();
167             }
168 
169         } else {
170 
171             // handle single elements
172             if ( '@value' == $element_key ) {
173 
174                 $this->xml->text( $element_value );
175 
176             } else {
177 
178                 // wrap element in CDATA tags if it contains illegal characters
179                 if ( false !== strpos( $element_value, '<' ) || false !== strpos( $element_value, '>' ) ) {
180 
181                     $this->xml->startElement( $element_key );
182                     $this->xml->writeCdata( $element_value );
183                     $this->xml->endElement();
184 
185                 } else {
186 
187                     $this->xml->writeElement( $element_key, $element_value );
188                 }
189 
190             }
191 
192             return;
193         }
194     }
195 
196     /**
197      * Adjust the sales report array format to change totals keyed with the sales date to become an
198      * attribute for the totals element instead
199      *
200      * @since 2.1
201      * @param array $data
202      * @return array
203      */
204     public function format_sales_report_data( $data ) {
205 
206         if ( ! empty( $data['totals'] ) ) {
207 
208             foreach ( $data['totals'] as $date => $totals ) {
209 
210                 unset( $data['totals'][ $date ] );
211 
212                 $data['totals'][] = array_merge( array( '@attributes' => array( 'date' => $date ) ), $totals );
213             }
214         }
215 
216         return $data;
217     }
218 
219     /**
220      * Adjust the product data to handle options for attributes without a named child element and other
221      * fields that have no named child elements (e.g. categories = array( 'cat1', 'cat2' ) )
222      *
223      * Note that the parent product data for variations is also adjusted in the same manner as needed
224      *
225      * @since 2.1
226      * @param array $data
227      * @return array
228      */
229     public function format_product_data( $data ) {
230 
231         // handle attribute values
232         if ( ! empty( $data['attributes'] ) ) {
233 
234             foreach ( $data['attributes'] as $attribute_key => $attribute ) {
235 
236                 if ( ! empty( $attribute['options'] ) && is_array( $attribute['options'] ) ) {
237 
238                     foreach ( $attribute['options'] as $option_key => $option ) {
239 
240                         unset( $data['attributes'][ $attribute_key ]['options'][ $option_key ] );
241 
242                         $data['attributes'][ $attribute_key ]['options']['option'][] = array( $option );
243                     }
244                 }
245             }
246         }
247 
248         // simple arrays are fine for JSON, but XML requires a child element name, so this adjusts the data
249         // array to define a child element name for each field
250         $fields_to_fix = array(
251             'related_ids'    => 'related_id',
252             'upsell_ids'     => 'upsell_id',
253             'cross_sell_ids' => 'cross_sell_id',
254             'categories'     => 'category',
255             'tags'           => 'tag'
256         );
257 
258         foreach ( $fields_to_fix as $parent_field_name => $child_field_name ) {
259 
260             if ( ! empty( $data[ $parent_field_name ] ) ) {
261 
262                 foreach ( $data[ $parent_field_name ] as $field_key => $field ) {
263 
264                     unset( $data[ $parent_field_name ][ $field_key ] );
265 
266                     $data[ $parent_field_name ][ $child_field_name ][] = array( $field );
267                 }
268             }
269         }
270 
271         // handle adjusting the parent product for variations
272         if ( ! empty( $data['parent'] ) ) {
273 
274             // attributes
275             if ( ! empty( $data['parent']['attributes'] ) ) {
276 
277                 foreach ( $data['parent']['attributes'] as $attribute_key => $attribute ) {
278 
279                     if ( ! empty( $attribute['options'] ) && is_array( $attribute['options'] ) ) {
280 
281                         foreach ( $attribute['options'] as $option_key => $option ) {
282 
283                             unset( $data['parent']['attributes'][ $attribute_key ]['options'][ $option_key ] );
284 
285                             $data['parent']['attributes'][ $attribute_key ]['options']['option'][] = array( $option );
286                         }
287                     }
288                 }
289             }
290 
291             // fields
292             foreach ( $fields_to_fix as $parent_field_name => $child_field_name ) {
293 
294                 if ( ! empty( $data['parent'][ $parent_field_name ] ) ) {
295 
296                     foreach ( $data['parent'][ $parent_field_name ] as $field_key => $field ) {
297 
298                         unset( $data['parent'][ $parent_field_name ][ $field_key ] );
299 
300                         $data['parent'][ $parent_field_name ][ $child_field_name ][] = array( $field );
301                     }
302                 }
303             }
304         }
305 
306         return $data;
307     }
308 
309 }
310 
WooCommerce API documentation generated by ApiGen 2.8.0