1<?php
2/**
3 * Copyright (c) 2007, Laurent Laville <pear@laurent-laville.org>
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 *     * Redistributions of source code must retain the above copyright
12 *       notice, this list of conditions and the following disclaimer.
13 *     * Redistributions in binary form must reproduce the above copyright
14 *       notice, this list of conditions and the following disclaimer in the
15 *       documentation and/or other materials provided with the distribution.
16 *     * Neither the name of the authors nor the names of its contributors
17 *       may be used to endorse or promote products derived from this software
18 *       without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *
32 * PHP version 5
33 *
34 * @category Web_Services
35 * @package  Services_W3C_CSSValidator
36 * @author   Laurent Laville <pear@laurent-laville.org>
37 * @license  http://www.opensource.org/licenses/bsd-license.php BSD
38 * @version  CVS: $Id$
39 * @link     http://pear.php.net/package/Services_W3C_CSSValidator
40 * @since    File available since Release 0.1.0
41 */
42
43require_once 'HTTP/Request2.php';
44
45require_once 'Services/W3C/CSSValidator/Response.php';
46require_once 'Services/W3C/CSSValidator/Error.php';
47require_once 'Services/W3C/CSSValidator/Warning.php';
48
49/**
50 * Base class for utilizing the W3C CSS Validator service.
51 *
52 * @category Web_Services
53 * @package  Services_W3C_CSSValidator
54 * @author   Laurent Laville <pear@laurent-laville.org>
55 * @license  http://www.opensource.org/licenses/bsd-license.php BSD
56 * @link     http://pear.php.net/package/Services_W3C_CSSValidator
57 * @since    Class available since Release 0.1.0
58 */
59class Services_W3C_CSSValidator
60{
61    /**
62     * URI to the W3C validator.
63     *
64     * @var    string
65     */
66    const VALIDATOR_URI = 'http://jigsaw.w3.org/css-validator/validator';
67
68    /**
69     * The URL of the document to validate
70     *
71     * @var    string
72     */
73    protected $uri;
74
75    /**
76     * Internally used filename of a file to upload to the validator
77     * POSTed as multipart/form-data
78     *
79     * @var    string
80     */
81    protected $uploaded_file;
82
83    /**
84     * CSS fragment to validate.
85     *
86     * Full documents only. At the moment, will only work if data is sent with the
87     * UTF-8 encoding.
88     *
89     * @var    string
90     */
91    protected $fragment;
92
93    /**
94     * Options list (available with default values) :
95     *
96     * output - Output format
97     *          Triggers the various outputs formats of the validator. If unset,
98     *          the usual Web html format will be sent. If set to soap12,
99     *          the SOAP1.2 interface will be triggered.
100     *
101     * warning - Warning level
102     *           Default value is '1', and value could one of these :
103     *           <ul>
104     *             <li>2</li> all warning messages
105     *             <li>1</li> normal report
106     *             <li>0</li> most important warning messages
107     *             <li>no</li> none messages
108     *           </ul>
109     *
110     * profile - Profile
111     *           Default value is 'css21', and value could one of these :
112     *           <ul>
113     *             <li>none</li> none profile
114     *             <li>css1</li> CSS level 1
115     *             <li>css2</li> CSS level 2
116     *             <li>css21</li> CSS level 2.1
117     *             <li>css3</li> CSS level 3
118     *             <li>svg</li> SVG
119     *             <li>svgbasic</li> SVG Basic
120     *             <li>svgtiny</li> SVG Tiny
121     *             <li>mobile</li> Mobile
122     *             <li>atsc-tv</li> ATSC TV
123     *             <li>tv</li> TV
124     *           </ul>
125     *
126     * usermedium - User medium
127     *              Default value is 'all', and value could one of these :
128     *              <ul>
129     *                <li>all</li>
130     *                <li>aural</li>
131     *                <li>braille</li>
132     *                <li>embossed</li>
133     *                <li>handheld</li>
134     *                <li>print</li>
135     *                <li>projection</li>
136     *                <li>screen</li>
137     *                <li>tty</li>
138     *                <li>tv</li>
139     *                <li>presentation</li>
140     *              </ul>
141     *
142     * lang - Language used for response messages
143     *        Default value is 'en', and value could one of these :
144     *        en, fr, ja, es, zh-cn, nl, de
145     *
146     * @var    array
147     */
148    protected $options;
149
150    /**
151     * HTTP_Request2 object.
152     *
153     * @var    object
154     */
155    protected $request;
156
157    /**
158     * Constructor for the class.
159     *
160     * @return void
161     */
162    public function __construct(HTTP_Request2 $request = null)
163    {
164        $this->options = array('output' => 'soap12', 'warning' => '1',
165            'profile' => 'css21', 'usermedium' => 'all', 'lang' => 'en');
166
167        if (empty($request)) {
168            $request = new HTTP_Request2();
169        }
170
171        $this->request = $request;
172    }
173
174    /**
175     * Sets options for the class.
176     *
177     * @param string $option Name of option to set
178     * @param string $val    Value of option to set
179     *
180     * @return void
181     */
182    public function __set($option, $val)
183    {
184        // properties that can be set directly
185        $setting_allowed = array('uri');
186
187        if (isset($this->options[$option])) {
188            $this->options[$option] = $val;
189        } elseif (property_exists($this, $option)) {
190            if (in_array($option, $setting_allowed)) {
191                $this->$option = $val;
192            }
193        }
194    }
195
196    /**
197     * Gets options for the class.
198     *
199     * @param string $option Name of option to set
200     *
201     * @return mixed
202     */
203    public function __get($option)
204    {
205        // properties that can be get directly
206        $getting_allowed = array('uri');
207
208        $r = null;
209        if (isset($this->options[$option])) {
210            $r = $this->options[$option];
211        } elseif (property_exists($this, $option)) {
212            if (in_array($option, $getting_allowed)) {
213                $r = $this->$option;
214            }
215        }
216        return $r;
217    }
218
219    /**
220     * Validates a given URI
221     *
222     * Executes the validator using the current parameters and returns a Response
223     * object on success.
224     *
225     * @param string $uri The address to the page to validate ex: http://example.com/
226     *
227     * @return mixed object Services_W3C_CSSValidator_Response
228     *                      if web service call successfull,
229     *               boolean FALSE otherwise
230     */
231    public function validateUri($uri)
232    {
233        $this->uri = $uri;
234        $this->buildRequest('uri');
235        if ($response = $this->sendRequest()) {
236            return $this->parseSOAP12Response($response->getBody());
237        } else {
238            return false;
239        }
240    }
241
242    /**
243     * Validates the local file
244     *
245     * Requests validation on the local file, from an instance of the W3C validator.
246     * The file is posted to the W3C validator using multipart/form-data.
247     *
248     * @param string $file file to be validated.
249     *
250     * @return mixed object Services_W3C_CSSValidator_Response
251     *                      if web service call successfull,
252     *               boolean FALSE otherwise
253     */
254    public function validateFile($file)
255    {
256        if (file_exists($file)) {
257            $this->uploaded_file = $file;
258            $this->buildRequest('file'); //return $this->request;
259            if ($response = $this->sendRequest()) {
260                return $this->parseSOAP12Response($response->getBody());
261            } else {
262                return false;
263            }
264        } else {
265            return false;
266        }
267    }
268
269    /**
270     * Validate an html string
271     *
272     * @param string $css Full css document fragment
273     *
274     * @return mixed object Services_W3C_CSSValidator_Response
275     *                      if web service call successfull,
276     *               boolean FALSE otherwise
277     */
278    public function validateFragment($css)
279    {
280        $this->fragment = $css;
281        $this->buildRequest('fragment');
282        if ($response = $this->sendRequest()) {
283            return $this->parseSOAP12Response($response->getBody());
284        } else {
285            return false;
286        }
287    }
288
289    /**
290     * Prepares a request object to send to the validator.
291     *
292     * @param string $type uri, file, or fragment
293     *
294     * @return void
295     */
296    protected function buildRequest($type = 'uri')
297    {
298        $this->request->setURL(self::VALIDATOR_URI);
299        switch ($type) {
300        case 'uri':
301        default:
302            $this->request->setMethod(HTTP_Request2::METHOD_GET);
303            $this->setQueryVariable('uri', $this->uri);
304            $method = 'setQueryVariable';
305            break;
306        case 'file':
307            $this->request->setMethod(HTTP_Request2::METHOD_POST);
308            $this->request->addUpload('file',
309                                     $this->uploaded_file,
310                                     null,
311                                     'text/css');
312            $method = 'addPostParameter';
313            break;
314        case 'fragment':
315            $this->request->setMethod(HTTP_Request2::METHOD_GET);
316            $this->setQueryVariable('text', $this->fragment);
317            $method = 'setQueryVariable';
318            break;
319        }
320
321        $options = array('output', 'warning', 'profile', 'usermedium', 'lang');
322        foreach ($options as $option) {
323            if (isset($this->options[$option])) {
324                if (is_bool($this->options[$option])) {
325                    $this->request->$method($option,
326                        intval($this->options[$option]));
327                } else {
328                    $this->$method($option, $this->options[$option]);
329                }
330            }
331        }
332    }
333
334    /**
335     * Set a querystring variable for the request
336     *
337     * @param string $name  Name of the querystring parameter
338     * @param mixed  $value Value of the parameter
339     *
340     * @return void
341     */
342    protected function setQueryVariable($name, $value = '')
343    {
344        $url = $this->request->getURL();
345        $url->setQueryVariable($name, $value);
346        $this->request->setURL($url);
347    }
348
349    /**
350     * Add post data to the request
351     *
352     * @param string $name  Name of the post field
353     * @param mixed  $value Value of the field
354     *
355     * @return void
356     */
357    protected function addPostParameter($name, $value = '')
358    {
359        $this->request->addPostParameter($name, $value);
360    }
361
362
363    /**
364     * Actually sends the request to the CSS Validator service
365     *
366     * @return bool TRUE if request was sent successfully, FALSE otherwise
367     * @todo   Raise dependency to 5.3.0 all together? (#18082)
368     */
369    protected function sendRequest()
370    {
371        try {
372            return $this->request->send();
373        } catch (Exception $e) {
374            if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
375                throw new Exception('Error sending request', null, $e);
376            }
377            throw new Exception($e->getMessage());
378        }
379    }
380
381    /**
382     * Parse an XML response from the validator
383     *
384     * This function parses a SOAP 1.2 response xml string from the validator.
385     *
386     * @param string $xml The raw soap12 XML response from the validator.
387     *
388     * @return mixed object Services_W3C_CSSValidator_Response
389     *                      if parsing soap12 response successfully,
390     *               boolean FALSE otherwise
391     */
392    protected function parseSOAP12Response($xml)
393    {
394        $doc = new DOMDocument();
395        // try to load soap 1.2 xml response, and suppress warning reports if any
396        if (@$doc->loadXML($xml)) {
397            $response = new Services_W3C_CSSValidator_Response();
398
399            // Get the standard CDATA elements
400            $cdata = array('uri', 'checkedby', 'csslevel', 'date');
401            foreach ($cdata as $var) {
402                $element = $doc->getElementsByTagName($var);
403                if ($element->length) {
404                    $response->$var = $element->item(0)->nodeValue;
405                }
406            }
407            // Handle the bool element validity
408            $element = $doc->getElementsByTagName('validity');
409            if ($element->length &&
410                $element->item(0)->nodeValue == 'true') {
411                $response->validity = true;
412            } else {
413                $response->validity = false;
414            }
415            if (!$response->validity) {
416                $errors = $doc->getElementsByTagName('error');
417                foreach ($errors as $error) {
418                    $response->addError(new
419                        Services_W3C_CSSValidator_Error($error));
420                }
421            }
422            $warnings = $doc->getElementsByTagName('warning');
423            foreach ($warnings as $warning) {
424                    $response->addWarning(new
425                        Services_W3C_CSSValidator_Warning($warning));
426            }
427            return $response;
428        } else {
429            // Could not load the XML document
430            return false;
431        }
432    }
433}
434?>
435