1<?php
2
3/* @(#) $Header: /sources/phpprintipp/phpprintipp/php_classes/BasicIPP.php,v 1.7 2012/03/01 17:21:04 harding Exp $
4 *
5 * Class BasicIPP - Send Basic IPP requests, Get and parses IPP Responses.
6 *
7 *   Copyright (C) 2005-2009  Thomas HARDING
8 *   Parts Copyright (C) 2005-2006 Manuel Lemos
9 *
10 *   This library is free software; you can redistribute it and/or
11 *   modify it under the terms of the GNU Library General Public
12 *   License as published by the Free Software Foundation; either
13 *   version 2 of the License, or (at your option) any later version.
14 *
15 *   This library is distributed in the hope that it will be useful,
16 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 *   Library General Public License for more details.
19 *
20 *   You should have received a copy of the GNU Library General Public
21 *   License along with this library; if not, write to the Free Software
22 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 *
24 *   mailto:thomas.harding@laposte.net
25 *   Thomas Harding, 56 rue de la bourie rouge, 45 000 ORLEANS -- FRANCE
26 *
27 */
28/*
29
30   This class is intended to implement Internet Printing Protocol on client side.
31
32   References needed to debug / add functionnalities:
33   - RFC 2910
34   - RFC 2911
35   - RFC 3380
36   - RFC 3382
37 */
38
39require_once ("http_class.php");
40
41class ippException extends \Exception
42{
43    protected $errno;
44
45    public function __construct($msg, $errno = null)
46    {
47        parent::__construct($msg);
48        $this->errno = $errno;
49    }
50
51    public function getErrorFormatted()
52    {
53        $return = sprintf("[ipp]: %s -- " . _(" file %s, line %s"),
54            $this->getMessage() , $this->getFile() , $this->getLine());
55        return $return;
56    }
57
58    public function getErrno()
59    {
60        return $this->errno;
61    }
62}
63
64class BasicIPP
65{
66    public $paths = array(
67        "root" => "/",
68        "admin" => "/admin/",
69        "printers" => "/printers/",
70        "jobs" => "/jobs/"
71    );
72    public $http_timeout = 30; // timeout at http connection (seconds) 0 => default => 30.
73    public $http_data_timeout = 30; // data reading timeout (milliseconds) 0 => default => 30.
74    public $ssl = false;
75    public $debug_level = 3; // max 3: almost silent
76    public $alert_on_end_tag; // debugging purpose: echo "END tag OK" if (1 and  reads while end tag)
77    public $with_exceptions = 1; // compatibility mode for old scripts		// DOL_LDR_CHANGE set this to 1
78    public $handle_http_exceptions = 1;
79
80    // readables variables
81    public $jobs = array();
82    public $jobs_uri = array();
83    public $status = array();
84    public $response_completed = array();
85    public $last_job = "";
86    public $attributes; // object you can read: attributes after validateJob()
87    public $printer_attributes; // object you can read: printer's attributes after getPrinterAttributes()
88    public $job_attributes; // object you can read: last job attributes
89    public $jobs_attributes; // object you can read: jobs attributes after getJobs()
90    public $available_printers = array();
91    public $printer_map = array();
92    public $printers_uri = array();
93    public $debug = array();
94    public $response;
95    public $meta;
96
97    // protected variables;
98    protected $log_level = 2; // max 3: very verbose
99    protected $log_type = 3; // 3: file | 1: e-mail | 0: logger
100    protected $log_destination; // e-mail or file
101    protected $serveroutput;
102    protected $setup;
103    protected $stringjob;
104    protected $data;
105    protected $debug_count = 0;
106    protected $username;
107    protected $charset;
108    protected $password;
109    protected $requesring_user;
110    protected $client_hostname = "localhost";
111    protected $stream;
112    protected $host = "localhost";
113    protected $port = "631";
114    protected $requesting_user = '';
115    protected $printer_uri;
116    protected $timeout = "20"; //20 secs
117    protected $errNo;
118    protected $errStr;
119    protected $datatype;
120    protected $datahead;
121    protected $datatail;
122    protected $operation_id;
123    protected $delay;
124    protected $error_generation; //devel feature
125    protected $debug_http = 0;
126    protected $no_disconnect;
127    protected $job_tags;
128    protected $operation_tags;
129    protected $index;
130    protected $collection; //RFC3382
131    protected $collection_index; //RFC3382
132    protected $collection_key = array(); //RFC3382
133    protected $collection_depth = - 1; //RFC3382
134    protected $end_collection = false; //RFC3382
135    protected $collection_nbr = array(); //RFC3382
136    protected $unix = false; // true -> use unix sockets instead of http
137    protected $output;
138
139    public function __construct()
140    {
141        $tz = getenv("date.timezone");
142        if (!$tz)
143        {
144            $tz = @date_default_timezone_get();
145        }
146
147        date_default_timezone_set($tz);
148        $this->meta = new \stdClass();
149        $this->setup = new \stdClass();
150        $this->values = new \stdClass();
151        $this->serveroutput = new \stdClass();
152        $this->error_generation = new \stdClass();
153        $this->_parsing = new \stdClass();
154        self::_initTags();
155    }
156
157    public function setPort($port = '631')
158    {
159        $this->port = $port;
160        self::_putDebug("Port is " . $this->port, 2);
161    }
162
163    public function setUnix($socket = '/var/run/cups/cups.sock')
164    {
165        $this->host = $socket;
166        $this->unix = true;
167        self::_putDebug("Host is " . $this->host, 2);
168    }
169
170    public function setHost($host = 'localhost')
171    {
172        $this->host = $host;
173        $this->unix = false;
174        self::_putDebug("Host is " . $this->host, 2);
175    }
176
177    public function setTimeout($timeout)
178    {
179        $this->timeout = $timeout;
180    }
181
182    public function setPrinterURI($uri)
183    {
184        $length = strlen($uri);
185        $length = chr($length);
186        while (strlen($length) < 2) $length = chr(0x00) . $length;
187        $this->meta->printer_uri = chr(0x45) // uri type | value-tag
188            . chr(0x00) . chr(0x0B) // name-length
189            . "printer-uri" // printer-uri | name
190            . $length . $uri;
191        $this->printer_uri = $uri;
192        self::_putDebug(sprintf(_("Printer URI: %s") , $uri) , 2);
193        $this->setup->uri = 1;
194    }
195
196    public function setData($data)
197    {
198        $this->data = $data;
199        self::_putDebug("Data set", 2);
200    }
201
202    public function setRawText()
203    {
204        $this->setup->datatype = 'TEXT';
205        $this->meta->mime_media_type = "";
206        $this->setup->mime_media_type = 1;
207        $this->datahead = chr(0x16);
208        if (is_readable($this->data))
209        {
210            //It's a filename.  Open and stream.
211            $data = fopen($this->data, "rb");
212            while (!feof($data)) $output = fread($data, 8192);
213        }
214        else
215        {
216            $output = $this->data;
217        }
218        if (substr($output, -1, 1) != chr(0x0c)) {
219            if (!isset($this->setup->noFormFeed))
220            {
221                $this->datatail = chr(0x0c);
222            }
223        }
224        self::_putDebug(_("Forcing data to be interpreted as RAW TEXT") , 2);
225    }
226
227    public function unsetRawText()
228    {
229        $this->setup->datatype = 'BINARY';
230        $this->datahead = '';
231        $this->datatail = '';
232        self::_putDebug(_("Unset forcing data to be interpreted as RAW TEXT") , 2);
233    }
234
235    public function setBinary()
236    {
237        self::unsetRawText();
238    }
239
240    public function setFormFeed()
241    {
242        $this->datatail = "\r\n" . chr(0x0c);
243        unset($this->setup->noFormFeed);
244    }
245
246    public function unsetFormFeed()
247    {
248        $this->datatail = '';
249        $this->setup->noFormFeed = 1;
250    }
251
252    public function setCharset($charset = 'utf-8')
253    {
254        $charset = strtolower($charset);
255        $this->charset = $charset;
256        $this->meta->charset = chr(0x47) // charset type | value-tag
257            . chr(0x00) . chr(0x12) // name-length
258            . "attributes-charset" // attributes-charset | name
259            . self::_giveMeStringLength($charset) // value-length
260            . $charset; // value
261        self::_putDebug(sprintf(_("Charset: %s") , $charset) , 2);
262        $this->setup->charset = 1;
263    }
264
265    public function setLanguage($language = 'en_us')
266    {
267        $language = strtolower($language);
268        $this->meta->language = chr(0x48) // natural-language type | value-tag
269            . chr(0x00) . chr(0x1B) //  name-length
270            . "attributes-natural-language" //attributes-natural-language
271            . self::_giveMeStringLength($language) // value-length
272            . $language; // value
273        self::_putDebug(sprintf(_("Language: %s") , $language) , 2);
274        $this->setup->language = 1;
275    }
276
277    public function setDocumentFormat($mime_media_type = 'application/octet-stream')
278    {
279        self::setBinary();
280        $length = chr(strlen($mime_media_type));
281        while (strlen($length) < 2) $length = chr(0x00) . $length;
282        self::_putDebug(sprintf(_("mime type: %s") , $mime_media_type) , 2);
283        $this->meta->mime_media_type = chr(0x49) // document-format tag
284            . self::_giveMeStringLength('document-format') . 'document-format' //
285            . self::_giveMeStringLength($mime_media_type) . $mime_media_type; // value
286        $this->setup->mime_media_type = 1;
287    }
288
289    // setDocumentFormat alias for backward compatibility
290    public function setMimeMediaType($mime_media_type = "application/octet-stream")
291    {
292        self::setDocumentFormat($mime_media_type);
293    }
294
295    public function setCopies($nbrcopies = 1)
296    {
297        $this->meta->copies = "";
298
299        if ($nbrcopies == 1 || !$nbrcopies)
300        {
301            return true;
302        }
303
304        $copies = self::_integerBuild($nbrcopies);
305        $this->meta->copies = chr(0x21) // integer type | value-tag
306            . chr(0x00) . chr(0x06) //             name-length
307            . "copies" // copies    |             name
308            . self::_giveMeStringLength($copies) // value-length
309            . $copies;
310        self::_putDebug(sprintf(_("Copies: %s") , $nbrcopies) , 2);
311        $this->setup->copies = 1;
312    }
313
314    public function setDocumentName($document_name = "")
315    {
316        $this->meta->document_name = "";
317        if (!$document_name) {
318            return true;
319        }
320        $document_name = substr($document_name, 0, 1023);
321        $length = strlen($document_name);
322        $length = chr($length);
323        while (strlen($length) < 2) $length = chr(0x00) . $length;
324        self::_putDebug(sprintf(_("document name: %s") , $document_name) , 2);
325        $this->meta->document_name = chr(0x41) // textWithoutLanguage tag
326            . chr(0x00) . chr(0x0d) // name-length
327            . "document-name" // mimeMediaType
328            . self::_giveMeStringLength($document_name) . $document_name; // value
329
330    }
331
332    public function setJobName($jobname = '', $absolute = false)
333    {
334        $this->meta->jobname = '';
335        if ($jobname == '')
336        {
337            $this->meta->jobname = '';
338            return true;
339        }
340        $postpend = date('-H:i:s-') . $this->_setJobId();
341        if ($absolute) {
342            $postpend = '';
343        }
344        if (isset($this->values->jobname) && $jobname == '(PHP)')
345        {
346            $jobname = $this->values->jobname;
347        }
348        $this->values->jobname = $jobname;
349        $jobname.= $postpend;
350        $this->meta->jobname = chr(0x42) // nameWithoutLanguage type || value-tag
351            . chr(0x00) . chr(0x08) //  name-length
352            . "job-name" //  job-name || name
353            . self::_giveMeStringLength($jobname) // value-length
354            . $jobname; // value
355        self::_putDebug(sprintf(_("Job name: %s") , $jobname) , 2);
356        $this->setup->jobname = 1;
357    }
358
359    public function setUserName($username = 'PHP-SERVER')
360    {
361        $this->requesting_user = $username;
362        $this->meta->username = '';
363        if (!$username) {
364            return true;
365        }
366        if ($username == 'PHP-SERVER' && isset($this->meta->username)) {
367            return TRUE;
368        }
369        /*
370        $value_length = 0x00;
371        for ($i = 0; $i < strlen($username); $i++)
372        {
373            $value_length+= 0x01;
374        }
375        $value_length = chr($value_length);
376        while (strlen($value_length) < 2) $value_length = chr(0x00) . $value_length;
377        */
378        $this->meta->username = chr(0x42) // keyword type || value-tag
379            . chr(0x00) . chr(0x14) // name-length
380            . "requesting-user-name"
381            . self::_giveMeStringLength($username) // value-length
382            . $username;
383        self::_putDebug(sprintf(_("Username: %s") , $username) , 2);
384        $this->setup->username = 1;
385    }
386
387    public function setAuthentification($username, $password)
388    {
389        self::setAuthentication($username, $password);
390    }
391
392    public function setAuthentication($username, $password)
393    {
394        $this->password = $password;
395        $this->username = $username;
396        self::_putDebug(_("Setting password") , 2);
397        $this->setup->password = 1;
398    }
399
400    public function setSides($sides = 2)
401    {
402        $this->meta->sides = '';
403        if (!$sides)
404        {
405            return true;
406        }
407
408        switch ($sides)
409        {
410            case 1:
411                $sides = "one-sided";
412                break;
413
414            case 2:
415                $sides = "two-sided-long-edge";
416                break;
417
418            case "2CE":
419                $sides = "two-sided-short-edge";
420                break;
421        }
422
423        $this->meta->sides = chr(0x44) // keyword type | value-tag
424            . chr(0x00) . chr(0x05) //        name-length
425            . "sides" // sides |             name
426            . self::_giveMeStringLength($sides) //               value-length
427            . $sides; // one-sided |          value
428        self::_putDebug(sprintf(_("Sides value set to %s") , $sides) , 2);
429    }
430
431    public function setFidelity()
432    {
433        // whether the server can't replace any attributes
434        // (eg, 2 sided print is not possible,
435        // so print one sided) and DO NOT THE JOB.
436        $this->meta->fidelity = chr(0x22) // boolean type  |  value-tag
437            . chr(0x00) . chr(0x16) //                  name-length
438            . "ipp-attribute-fidelity" // ipp-attribute-fidelity | name
439            . chr(0x00) . chr(0x01) //  value-length
440            . chr(0x01); //  true | value
441        self::_putDebug(_("Fidelity attribute is set (paranoid mode)") , 3);
442    }
443
444    public function unsetFidelity()
445    {
446        // whether the server can replace any attributes
447        // (eg, 2 sided print is not possible,
448        // so print one sided) and DO THE JOB.
449        $this->meta->fidelity = chr(0x22) //  boolean type | value-tag
450            . chr(0x00) . chr(0x16) //        name-length
451            . "ipp-attribute-fidelity" // ipp-attribute-fidelity | name
452            . chr(0x00) . chr(0x01) //               value-length
453            . chr(0x00); // false |                   value
454        self::_putDebug(_("Fidelity attribute is unset") , 2);
455    }
456
457    public function setMessage($message = '')
458    {
459        $this->meta->message = '';
460        if (!$message) {
461            return true;
462        }
463        $this->meta->message =
464            chr(0x41) // attribute type = textWithoutLanguage
465                . chr(0x00)
466                . chr(0x07)
467                . "message"
468                . self::_giveMeStringLength(substr($message, 0, 127))
469                . substr($message, 0, 127);
470        self::_putDebug(sprintf(_('Setting message to "%s"') , $message) , 2);
471    }
472
473    public function setPageRanges($page_ranges)
474    {
475        // $pages_ranges = string:  "1:5 10:25 40:52 ..."
476        // to unset, specify an empty string.
477        $this->meta->page_range = '';
478        if (!$page_ranges) {
479            return true;
480        }
481        $page_ranges = trim(str_replace("-", ":", $page_ranges));
482        $first = true;
483        #$page_ranges = split(' ', $page_ranges);
484        $page_ranges = preg_split('# #', $page_ranges);
485        foreach($page_ranges as $page_range)
486        {
487            $value = self::_rangeOfIntegerBuild($page_range);
488            if ($first)
489            {
490                $this->meta->page_ranges .=
491                $this->tags_types['rangeOfInteger']['tag']
492                    . self::_giveMeStringLength('page-ranges')
493                    . 'page-ranges'
494                    . self::_giveMeStringLength($value)
495                    . $value;
496            }
497            else
498            {
499                $this->meta->page_ranges .=
500                $this->tags_types['rangeOfInteger']['tag']
501                    . self::_giveMeStringLength('')
502                    . self::_giveMeStringLength($value)
503                    . $value;
504                $first = false;
505            }
506        }
507    }
508
509    public function setAttribute($attribute, $values)
510    {
511        $operation_attributes_tags = array_keys($this->operation_tags);
512        $job_attributes_tags = array_keys($this->job_tags);
513        $printer_attributes_tags = array_keys($this->printer_tags);
514        self::unsetAttribute($attribute);
515        if (in_array($attribute, $operation_attributes_tags))
516        {
517            if (!is_array($values))
518            {
519                self::_setOperationAttribute($attribute, $values);
520            }
521            else
522            {
523                foreach($values as $value)
524                {
525                    self::_setOperationAttribute($attribute, $value);
526                }
527            }
528        }
529        elseif (in_array($attribute, $job_attributes_tags))
530        {
531            if (!is_array($values))
532            {
533                self::_setJobAttribute($attribute, $values);
534            }
535            else
536            {
537                foreach($values as $value)
538                {
539                    self::_setJobAttribute($attribute, $value);
540                }
541            }
542        }
543        elseif (in_array($attribute, $printer_attributes_tags))
544        {
545            if (!is_array($values))
546            {
547                self::_setPrinterAttribute($attribute, $values);
548            }
549            else
550            {
551                foreach($values as $value)
552                {
553                    self::_setPrinterAttribute($attribute, $value);
554                }
555            }
556        }
557        else
558        {
559            trigger_error(
560                sprintf(_('SetAttribute: Tag "%s" is not a printer or a job attribute'),
561                    $attribute) , E_USER_NOTICE);
562            self::_putDebug(
563                sprintf(_('SetAttribute: Tag "%s" is not a printer or a job attribute'),
564                    $attribute) , 3);
565            self::_errorLog(
566                sprintf(_('SetAttribute: Tag "%s" is not a printer or a job attribute'),
567                    $attribute) , 2);
568            return FALSE;
569        }
570    }
571
572    public function unsetAttribute($attribute)
573    {
574        $operation_attributes_tags = array_keys($this->operation_tags);
575        $job_attributes_tags = array_keys($this->job_tags);
576        $printer_attributes_tags = array_keys($this->printer_tags);
577        if (in_array($attribute, $operation_attributes_tags))
578        {
579            unset(
580                $this->operation_tags[$attribute]['value'],
581                $this->operation_tags[$attribute]['systag']
582            );
583        }
584        elseif (in_array($attribute, $job_attributes_tags))
585        {
586            unset(
587                $this->job_tags[$attribute]['value'],
588                $this->job_tags[$attribute]['systag']
589            );
590        }
591        elseif (in_array($attribute, $printer_attributes_tags))
592        {
593            unset(
594                $this->printer_tags[$attribute]['value'],
595                $this->printer_tags[$attribute]['systag']
596            );
597        }
598        else
599        {
600            trigger_error(
601                sprintf(_('unsetAttribute: Tag "%s" is not a printer or a job attribute'),
602                    $attribute) , E_USER_NOTICE);
603            self::_putDebug(
604                sprintf(_('unsetAttribute: Tag "%s" is not a printer or a job attribute'),
605                    $attribute) , 3);
606            self::_errorLog(
607                sprintf(_('unsetAttribute: Tag "%s" is not a printer or a job attribute'),
608                    $attribute) , 2);
609            return FALSE;
610        }
611        return true;
612    }
613
614    //
615    // LOGGING / DEBUGGING
616    //
617    /**
618     * Sets log file destination. Creates the file if has permission.
619     *
620     * @param string $log_destination
621     * @param string $destination_type
622     * @param int $level
623     *
624     * @throws ippException
625     */
626    public function setLog($log_destination, $destination_type = 'file', $level = 2)
627    {
628        if (!file_exists($log_destination) && is_writable(dirname($log_destination)))
629        {
630            touch($log_destination);
631            chmod($log_destination, 0777);
632        }
633
634        switch ($destination_type)
635        {
636            case 'file':
637            case 3:
638                $this->log_destination = $log_destination;
639                $this->log_type = 3;
640                break;
641
642            case 'logger':
643            case 0:
644                $this->log_destination = '';
645                $this->log_type = 0;
646                break;
647
648            case 'e-mail':
649            case 1:
650                $this->log_destination = $log_destination;
651                $this->log_type = 1;
652                break;
653        }
654        $this->log_level = $level;
655    }
656
657    public function printDebug()
658    {
659        for ($i = 0; $i < $this->debug_count; $i++)
660        {
661            echo $this->debug[$i], "\n";
662        }
663        $this->debug = array();
664        $this->debug_count = 0;
665    }
666
667    public function getDebug()
668    {
669        $debug = '';
670        for ($i = 0; $i < $this->debug_count; $i++)
671        {
672            $debug.= $this->debug[$i];
673        }
674        $this->debug = array();
675        $this->debug_count = 0;
676        return $debug;
677    }
678
679    //
680    // OPERATIONS
681    //
682    public function printJob()
683    {
684        // this BASIC version of printJob do not parse server
685        // output for job's attributes
686        self::_putDebug(
687            sprintf(
688                "************** Date: %s ***********",
689                date('Y-m-d H:i:s')
690            )
691        );
692        if (!$this->_stringJob()) {
693            return FALSE;
694        }
695        if (is_readable($this->data))
696        {
697            self::_putDebug(_("Printing a FILE"));
698            $this->output = $this->stringjob;
699            if ($this->setup->datatype == "TEXT")
700            {
701                $this->output.= chr(0x16);
702            }
703            $post_values = array(
704                "Content-Type" => "application/ipp",
705                "Data" => $this->output,
706                "File" => $this->data
707            );
708            if ($this->setup->datatype == "TEXT" && !isset($this->setup->noFormFeed))
709            {
710                $post_values = array_merge(
711                    $post_values,
712                    array(
713                        "Filetype" => "TEXT"
714                    )
715                );
716            }
717        }
718        else
719        {
720            self::_putDebug(_("Printing DATA"));
721            $this->output =
722                $this->stringjob
723                    . $this->datahead
724                    . $this->data
725                    . $this->datatail;
726            $post_values = array(
727                "Content-Type" => "application/ipp",
728                "Data" => $this->output
729            );
730        }
731        if (self::_sendHttp($post_values, $this->paths["printers"]))
732        {
733            self::_parseServerOutput();
734        }
735        if (isset($this->serveroutput) && isset($this->serveroutput->status))
736        {
737            $this->status = array_merge($this->status, array(
738                $this->serveroutput->status
739            ));
740            if ($this->serveroutput->status == "successfull-ok")
741            {
742                self::_errorLog(
743                    sprintf("printing job %s: ", $this->last_job)
744                        . $this->serveroutput->status,
745                    3);
746            }
747            else
748            {
749                self::_errorLog(
750                    sprintf("printing job: ", $this->last_job)
751                        . $this->serveroutput->status,
752                    1);
753            }
754                return $this->serveroutput->status;
755        }
756
757    $this->status =
758        array_merge($this->status, array("OPERATION FAILED"));
759        $this->jobs =
760            array_merge($this->jobs, array(""));
761        $this->jobs_uri =
762            array_merge($this->jobs_uri, array(""));
763
764        self::_errorLog("printing job : OPERATION FAILED", 1);
765        return false;
766    }
767
768    //
769    // HTTP OUTPUT
770    //
771    protected function _sendHttp($post_values, $uri)
772    {
773        /*
774            This function Copyright (C) 2005-2006 Thomas Harding, Manuel Lemos
775        */
776        $this->response_completed[] = "no";
777        unset($this->serverouptut);
778        self::_putDebug(_("Processing HTTP request") , 2);
779        $this->serveroutput->headers = array();
780        $this->serveroutput->body = "";
781        $http = new http_class;
782        if (!$this->unix) {
783        	// DOL_LDR_CHANGE
784        	if (empty($this->host)) $this->host='127.0.0.1';
785            $http->host = $this->host;
786        }
787        else {
788            $http->host = "localhost";
789        }
790        $http->with_exceptions = $this->with_exceptions;
791        if ($this->debug_http)
792        {
793            $http->debug = 1;
794            $http->html_debug = 0;
795        }
796        else
797        {
798            $http->debug = 0;
799            $http->html_debug = 0;
800        }
801        $url = "http://" . $this->host;
802        if ($this->ssl) {
803            $url = "https://" . $this->host;
804        }
805        if ($this->unix) {
806            $url = "unix://" . $this->host;
807        }
808        $http->port = $this->port;
809        $http->timeout = $this->http_timeout;
810        $http->data_timeout = $this->http_data_timeout;
811        $http->force_multipart_form_post = false;
812        $http->user = $this->username;
813        $http->password = $this->password;
814        $error = $http->GetRequestArguments($url, $arguments);
815        $arguments["RequestMethod"] = "POST";
816        $arguments["Headers"] = array(
817            "Content-Type" => "application/ipp"
818        );
819        $arguments["BodyStream"] = array(
820            array(
821                "Data" => $post_values["Data"]
822            )
823        );
824        if (isset($post_values["File"])) {
825            $arguments["BodyStream"][] = array(
826                "File" => $post_values["File"]
827            );
828        }
829        if (isset($post_values["FileType"])
830            && !strcmp($post_values["FileType"], "TEXT")
831        )
832        {
833            $arguments["BodyStream"][] = array("Data" => Chr(12));
834        }
835        $arguments["RequestURI"] = $uri;
836        if ($this->with_exceptions && $this->handle_http_exceptions)
837        {
838            try
839            {
840                $success = $http->Open($arguments);
841            }
842            catch(httpException $e)
843            {
844                throw new ippException(
845                    sprintf("http error: %s", $e->getMessage()),
846                        $e->getErrno());
847            }
848        }
849        else
850        {
851        	$success = $http->Open($arguments);
852        }
853        if ($success[0] == true)
854        {
855            $success = $http->SendRequest($arguments);
856            if ($success[0] == true)
857            {
858                self::_putDebug("H T T P    R E Q U E S T :");
859                self::_putDebug("Request headers:");
860                for (Reset($http->request_headers) , $header = 0; $header < count($http->request_headers); Next($http->request_headers) , $header++)
861                {
862                    $header_name = Key($http->request_headers);
863                    if (GetType($http->request_headers[$header_name]) == "array")
864                    {
865                        for ($header_value = 0; $header_value < count($http->request_headers[$header_name]); $header_value++)
866                        {
867                            self::_putDebug($header_name . ": " . $http->request_headers[$header_name][$header_value]);
868                        }
869                    }
870                    else
871                    {
872                        self::_putDebug($header_name . ": " . $http->request_headers[$header_name]);
873                    }
874                }
875                self::_putDebug("Request body:");
876                self::_putDebug(
877                    htmlspecialchars($http->request_body)
878                        . "*********** END REQUEST BODY *********"
879                );
880                $i = 0;
881                $headers = array();
882                unset($this->serveroutput->headers);
883                $http->ReadReplyHeaders($headers);
884                self::_putDebug("H T T P    R E S P O N S E :");
885                self::_putDebug("Response headers:");
886                for (Reset($headers) , $header = 0; $header < count($headers); Next($headers) , $header++)
887                {
888                    $header_name = Key($headers);
889                    if (GetType($headers[$header_name]) == "array")
890                    {
891                        for ($header_value = 0; $header_value < count($headers[$header_name]); $header_value++)
892                        {
893                            self::_putDebug($header_name . ": " . $headers[$header_name][$header_value]);
894                            $this->serveroutput->headers[$i] =
895                                $header_name . ": "
896                                    . $headers[$header_name][$header_value];
897                            $i++;
898                        }
899                    }
900                    else
901                    {
902                        self::_putDebug($header_name . ": " . $headers[$header_name]);
903                        $this->serveroutput->headers[$i] =
904                            $header_name
905                                . ": "
906                                . $headers[$header_name];
907                        $i++;
908                    }
909                }
910                self::_putDebug("\n\nResponse body:\n");
911                $this->serveroutput->body = "";
912                for (;;)
913                {
914                    $http->ReadReplyBody($body, 1024);
915                    if (strlen($body) == 0) {
916                        break;
917                    }
918
919                    self::_putDebug(htmlentities($body));
920                    $this->serveroutput->body.= $body;
921                }
922                self::_putDebug("********* END RESPONSE BODY ********");
923            }
924        }
925        $http->Close();
926        return true;
927    }
928
929    //
930    // INIT
931    //
932    protected function _initTags()
933    {
934        $this->tags_types = array(
935            "unsupported" => array(
936                "tag" => chr(0x10) ,
937                "build" => ""
938            ) ,
939            "reserved" => array(
940                "tag" => chr(0x11) ,
941                "build" => ""
942            ) ,
943            "unknown" => array(
944                "tag" => chr(0x12) ,
945                "build" => ""
946            ) ,
947            "no-value" => array(
948                "tag" => chr(0x13) ,
949                "build" => "no_value"
950            ) ,
951            "integer" => array(
952                "tag" => chr(0x21) ,
953                "build" => "integer"
954            ) ,
955            "boolean" => array(
956                "tag" => chr(0x22) ,
957                "build" => "boolean"
958            ) ,
959            "enum" => array(
960                "tag" => chr(0x23) ,
961                "build" => "enum"
962            ) ,
963            "octetString" => array(
964                "tag" => chr(0x30) ,
965                "build" => "octet_string"
966            ) ,
967            "datetime" => array(
968                "tag" => chr(0x31) ,
969                "build" => "datetime"
970            ) ,
971            "resolution" => array(
972                "tag" => chr(0x32) ,
973                "build" => "resolution"
974            ) ,
975            "rangeOfInteger" => array(
976                "tag" => chr(0x33) ,
977                "build" => "range_of_integers"
978            ) ,
979            "textWithLanguage" => array(
980                "tag" => chr(0x35) ,
981                "build" => "string"
982            ) ,
983            "nameWithLanguage" => array(
984                "tag" => chr(0x36) ,
985                "build" => "string"
986            ) ,
987            /*
988            "text" => array ("tag" => chr(0x40),
989            "build" => "string"),
990            "text string" => array ("tag" => chr(0x40),
991            "build" => "string"),
992            */
993            "textWithoutLanguage" => array(
994                "tag" => chr(0x41) ,
995                "build" => "string"
996            ) ,
997            "nameWithoutLanguage" => array(
998                "tag" => chr(0x42) ,
999                "buid" => "string"
1000            ) ,
1001            "keyword" => array(
1002                "tag" => chr(0x44) ,
1003                "build" => "string"
1004            ) ,
1005            "uri" => array(
1006                "tag" => chr(0x45) ,
1007                "build" => "string"
1008            ) ,
1009            "uriScheme" => array(
1010                "tag" => chr(0x46) ,
1011                "build" => "string"
1012            ) ,
1013            "charset" => array(
1014                "tag" => chr(0x47) ,
1015                "build" => "string"
1016            ) ,
1017            "naturalLanguage" => array(
1018                "tag" => chr(0x48) ,
1019                "build" => "string"
1020            ) ,
1021            "mimeMediaType" => array(
1022                "tag" => chr(0x49) ,
1023                "build" => "string"
1024            ) ,
1025            "extendedAttributes" => array(
1026                "tag" => chr(0x7F) ,
1027                "build" => "extended"
1028            ) ,
1029        );
1030        $this->operation_tags = array(
1031            "compression" => array(
1032                "tag" => "keyword"
1033            ) ,
1034            "document-natural-language" => array(
1035                "tag" => "naturalLanguage"
1036            ) ,
1037            "job-k-octets" => array(
1038                "tag" => "integer"
1039            ) ,
1040            "job-impressions" => array(
1041                "tag" => "integer"
1042            ) ,
1043            "job-media-sheets" => array(
1044                "tag" => "integer"
1045            ) ,
1046        );
1047        $this->job_tags = array(
1048            "job-priority" => array(
1049                "tag" => "integer"
1050            ) ,
1051            "job-hold-until" => array(
1052                "tag" => "keyword"
1053            ) ,
1054            "job-sheets" => array(
1055                "tag" => "keyword"
1056            ) , //banner page
1057            "multiple-document-handling" => array(
1058                "tag" => "keyword"
1059            ) ,
1060            //"copies" => array("tag" => "integer"),
1061            "finishings" => array(
1062                "tag" => "enum"
1063            ) ,
1064            //"page-ranges" => array("tag" => "rangeOfInteger"), // has its own function
1065            //"sides" => array("tag" => "keyword"), // has its own function
1066            "number-up" => array(
1067                "tag" => "integer"
1068            ) ,
1069            "orientation-requested" => array(
1070                "tag" => "enum"
1071            ) ,
1072            "media" => array(
1073                "tag" => "keyword"
1074            ) ,
1075            "printer-resolution" => array(
1076                "tag" => "resolution"
1077            ) ,
1078            "print-quality" => array(
1079                "tag" => "enum"
1080            ) ,
1081            "job-message-from-operator" => array(
1082                "tag" => "textWithoutLanguage"
1083            ) ,
1084        );
1085        $this->printer_tags = array(
1086            "requested-attributes" => array(
1087                "tag" => "keyword"
1088            )
1089        );
1090    }
1091
1092    //
1093    // SETUP
1094    //
1095    protected function _setOperationId()
1096    {
1097        $prepend = '';
1098        $this->operation_id+= 1;
1099        $this->meta->operation_id = self::_integerBuild($this->operation_id);
1100        self::_putDebug("operation id is: " . $this->operation_id, 2);
1101    }
1102
1103    protected function _setJobId()
1104    {
1105        $this->meta->jobid+= 1;
1106        $prepend = '';
1107        $prepend_length = 4 - strlen($this->meta->jobid);
1108        for ($i = 0; $i < $prepend_length; $i++) {
1109            $prepend.= '0';
1110        }
1111        return $prepend . $this->meta->jobid;
1112    }
1113
1114    protected function _setJobUri($job_uri)
1115    {
1116        $this->meta->job_uri = chr(0x45) // type uri
1117            . chr(0x00) . chr(0x07) // name-length
1118            . "job-uri"
1119            //. chr(0x00).chr(strlen($job_uri))
1120            . self::_giveMeStringLength($job_uri) . $job_uri;
1121        self::_putDebug("job-uri is: " . $job_uri, 2);
1122    }
1123
1124    //
1125    // RESPONSE PARSING
1126    //
1127    protected function _parseServerOutput()
1128    {
1129        $this->serveroutput->response = array();
1130        if (!self::_parseHttpHeaders()) {
1131            return FALSE;
1132        }
1133        $this->_parsing->offset = 0;
1134        self::_parseIppVersion();
1135        self::_parseStatusCode();
1136        self::_parseRequestID();
1137        $this->_parseResponse();
1138        //devel
1139        self::_putDebug(
1140            sprintf("***** IPP STATUS: %s ******", $this->serveroutput->status),
1141            4);
1142        self::_putDebug("****** END OF OPERATION ****");
1143        return true;
1144    }
1145
1146    protected function _parseHttpHeaders()
1147    {
1148        $response = "";
1149        switch ($this->serveroutput->headers[0])
1150        {
1151            case "http/1.1 200 ok: ":
1152                $this->serveroutput->httpstatus = "HTTP/1.1 200 OK";
1153                $response = "OK";
1154                break;
1155
1156            // primitive http/1.0 for Lexmark printers (from Rick Baril)
1157            case "http/1.0 200 ok: ":
1158                $this->serveroutput->httpstatus = "HTTP/1.0 200 OK";
1159                $response = "OK";
1160                break;
1161
1162            case "http/1.1 100 continue: ":
1163                $this->serveroutput->httpstatus = "HTTP/1.1 100 CONTINUE";
1164                $response = "OK";
1165                break;
1166
1167            case "":
1168                $this->serveroutput->httpstatus = "HTTP/1.1 000 No Response From Server";
1169                $this->serveroutput->status = "HTTP-ERROR-000_NO_RESPONSE_FROM_SERVER";
1170                trigger_error("No Response From Server", E_USER_WARNING);
1171                self::_errorLog("No Response From Server", 1);
1172                $this->disconnected = 1;
1173                return FALSE;
1174                break;
1175
1176            default:
1177                $server_response = preg_replace("/: $/", '', $this->serveroutput->headers[0]);
1178                #$strings = split(' ', $server_response, 3);
1179                $strings = preg_split('# #', $server_response, 3);
1180                $errno = $strings[1];
1181                $string = strtoupper(str_replace(' ', '_', $strings[2]));
1182                trigger_error(
1183                    sprintf(_("server responds %s") , $server_response),
1184                    E_USER_WARNING);
1185                self::_errorLog("server responds " . $server_response, 1);
1186                $this->serveroutput->httpstatus =
1187                    strtoupper($strings[0])
1188                        . " "
1189                        . $errno
1190                        . " "
1191                        . ucfirst($strings[2]);
1192
1193                $this->serveroutput->status =
1194                    "HTTP-ERROR-"
1195                        . $errno
1196                        . "-"
1197                        . $string;
1198                $this->disconnected = 1;
1199                return FALSE;
1200                break;
1201        }
1202        unset($this->serveroutput->headers);
1203        return TRUE;
1204    }
1205
1206    protected function _parseIppVersion()
1207    {
1208        $ippversion =
1209            (ord($this->serveroutput->body[$this->_parsing->offset]) * 256)
1210                + ord($this->serveroutput->body[$this->_parsing->offset + 1]);
1211        switch ($ippversion)
1212        {
1213            case 0x0101:
1214                $this->serveroutput->ipp_version = "1.1";
1215                break;
1216
1217            default:
1218                $this->serveroutput->ipp_version =
1219                    sprintf("%u.%u (Unknown)",
1220                        ord($this->serveroutput->body[$this->_parsing->offset]) * 256,
1221                        ord($this->serveroutput->body[$this->_parsing->offset + 1]));
1222                break;
1223        }
1224        self::_putDebug("I P P    R E S P O N S E :\n\n");
1225        self::_putDebug(
1226            sprintf("IPP version %s%s: %s",
1227                ord($this->serveroutput->body[$this->_parsing->offset]),
1228                ord($this->serveroutput->body[$this->_parsing->offset + 1]),
1229                $this->serveroutput->ipp_version));
1230        $this->_parsing->offset+= 2;
1231        return;
1232    }
1233
1234    protected function _parseStatusCode()
1235    {
1236        $status_code =
1237            (ord($this->serveroutput->body[$this->_parsing->offset]) * 256)
1238            + ord($this->serveroutput->body[$this->_parsing->offset + 1]);
1239        $this->serveroutput->status = "NOT PARSED";
1240        $this->_parsing->offset+= 2;
1241        if (strlen($this->serveroutput->body) < $this->_parsing->offset)
1242        {
1243            return false;
1244        }
1245        if ($status_code < 0x00FF)
1246        {
1247            $this->serveroutput->status = "successfull";
1248        }
1249        elseif ($status_code < 0x01FF)
1250        {
1251            $this->serveroutput->status = "informational";
1252        }
1253        elseif ($status_code < 0x02FF)
1254        {
1255            $this->serveroutput->status = "redirection";
1256        }
1257        elseif ($status_code < 0x04FF)
1258        {
1259            $this->serveroutput->status = "client-error";
1260        }
1261        elseif ($status_code < 0x05FF)
1262        {
1263            $this->serveroutput->status = "server-error";
1264        }
1265        switch ($status_code)
1266        {
1267            case 0x0000:
1268                $this->serveroutput->status = "successfull-ok";
1269                break;
1270
1271            case 0x0001:
1272                $this->serveroutput->status = "successful-ok-ignored-or-substituted-attributes";
1273                break;
1274
1275            case 0x002:
1276                $this->serveroutput->status = "successful-ok-conflicting-attributes";
1277                break;
1278
1279            case 0x0400:
1280                $this->serveroutput->status = "client-error-bad-request";
1281                break;
1282
1283            case 0x0401:
1284                $this->serveroutput->status = "client-error-forbidden";
1285                break;
1286
1287            case 0x0402:
1288                $this->serveroutput->status = "client-error-not-authenticated";
1289                break;
1290
1291            case 0x0403:
1292                $this->serveroutput->status = "client-error-not-authorized";
1293                break;
1294
1295            case 0x0404:
1296                $this->serveroutput->status = "client-error-not-possible";
1297                break;
1298
1299            case 0x0405:
1300                $this->serveroutput->status = "client-error-timeout";
1301                break;
1302
1303            case 0x0406:
1304                $this->serveroutput->status = "client-error-not-found";
1305                break;
1306
1307            case 0x0407:
1308                $this->serveroutput->status = "client-error-gone";
1309                break;
1310
1311            case 0x0408:
1312                $this->serveroutput->status = "client-error-request-entity-too-large";
1313                break;
1314
1315            case 0x0409:
1316                $this->serveroutput->status = "client-error-request-value-too-long";
1317                break;
1318
1319            case 0x040A:
1320                $this->serveroutput->status = "client-error-document-format-not-supported";
1321                break;
1322
1323            case 0x040B:
1324                $this->serveroutput->status = "client-error-attributes-or-values-not-supported";
1325                break;
1326
1327            case 0x040C:
1328                $this->serveroutput->status = "client-error-uri-scheme-not-supported";
1329                break;
1330
1331            case 0x040D:
1332                $this->serveroutput->status = "client-error-charset-not-supported";
1333                break;
1334
1335            case 0x040E:
1336                $this->serveroutput->status = "client-error-conflicting-attributes";
1337                break;
1338
1339            case 0x040F:
1340                $this->serveroutput->status = "client-error-compression-not-supported";
1341                break;
1342
1343            case 0x0410:
1344                $this->serveroutput->status = "client-error-compression-error";
1345                break;
1346
1347            case 0x0411:
1348                $this->serveroutput->status = "client-error-document-format-error";
1349                break;
1350
1351            case 0x0412:
1352                $this->serveroutput->status = "client-error-document-access-error";
1353                break;
1354
1355            case 0x0413: // RFC3380
1356                $this->serveroutput->status = "client-error-attributes-not-settable";
1357                break;
1358
1359            case 0x0500:
1360                $this->serveroutput->status = "server-error-internal-error";
1361                break;
1362
1363            case 0x0501:
1364                $this->serveroutput->status = "server-error-operation-not-supported";
1365                break;
1366
1367            case 0x0502:
1368                $this->serveroutput->status = "server-error-service-unavailable";
1369                break;
1370
1371            case 0x0503:
1372                $this->serveroutput->status = "server-error-version-not-supported";
1373                break;
1374
1375            case 0x0504:
1376                $this->serveroutput->status = "server-error-device-error";
1377                break;
1378
1379            case 0x0505:
1380                $this->serveroutput->status = "server-error-temporary-error";
1381                break;
1382
1383            case 0x0506:
1384                $this->serveroutput->status = "server-error-not-accepting-jobs";
1385                break;
1386
1387            case 0x0507:
1388                $this->serveroutput->status = "server-error-busy";
1389                break;
1390
1391            case 0x0508:
1392                $this->serveroutput->status = "server-error-job-canceled";
1393                break;
1394
1395            case 0x0509:
1396                $this->serveroutput->status = "server-error-multiple-document-jobs-not-supported";
1397                break;
1398
1399            default:
1400                break;
1401        }
1402        self::_putDebug(
1403            sprintf(
1404                "status-code: %s%s: %s ",
1405                $this->serveroutput->body[$this->_parsing->offset],
1406                $this->serveroutput->body[$this->_parsing->offset + 1],
1407                $this->serveroutput->status),
1408            4);
1409        return;
1410    }
1411
1412    protected function _parseRequestID()
1413    {
1414        $this->serveroutput->request_id =
1415            self::_interpretInteger(
1416                substr($this->serveroutput->body, $this->_parsing->offset, 4)
1417            );
1418        self::_putDebug("request-id " . $this->serveroutput->request_id, 2);
1419        $this->_parsing->offset+= 4;
1420        return;
1421    }
1422
1423    protected function _interpretInteger($value)
1424    {
1425        // they are _signed_ integers
1426        $value_parsed = 0;
1427        for ($i = strlen($value); $i > 0; $i --)
1428        {
1429            $value_parsed +=
1430                (
1431                    (1 << (($i - 1) * 8))
1432                        *
1433                        ord($value[strlen($value) - $i])
1434                );
1435        }
1436        if ($value_parsed >= 2147483648)
1437        {
1438            $value_parsed -= 4294967296;
1439        }
1440        return $value_parsed;
1441    }
1442
1443    protected function _parseResponse()
1444    {
1445    }
1446
1447    //
1448    // REQUEST BUILDING
1449    //
1450    protected function _stringJob()
1451    {
1452        if (!isset($this->setup->charset)) {
1453            self::setCharset();
1454        }
1455        if (!isset($this->setup->datatype)) {
1456            self::setBinary();
1457        }
1458        if (!isset($this->setup->uri))
1459        {
1460            $this->getPrinters();
1461            unset($this->jobs[count($this->jobs) - 1]);
1462            unset($this->jobs_uri[count($this->jobs_uri) - 1]);
1463            unset($this->status[count($this->status) - 1]);
1464            if (array_key_exists(0, $this->available_printers))
1465            {
1466                self::setPrinterURI($this->available_printers[0]);
1467            }
1468            else
1469            {
1470                trigger_error(
1471                    _("_stringJob: Printer URI is not set: die"),
1472                    E_USER_WARNING);
1473                self::_putDebug(_("_stringJob: Printer URI is not set: die") , 4);
1474                self::_errorLog(" Printer URI is not set, die", 2);
1475                return FALSE;
1476            }
1477        }
1478        if (!isset($this->setup->copies)) {
1479            self::setCopies(1);
1480        }
1481        if (!isset($this->setup->language)) {
1482            self::setLanguage('en_us');
1483        }
1484        if (!isset($this->setup->mime_media_type)) {
1485            self::setMimeMediaType();
1486        }
1487        if (!isset($this->setup->jobname)) {
1488            self::setJobName();
1489        }
1490        unset($this->setup->jobname);
1491        if (!isset($this->meta->username)) {
1492            self::setUserName();
1493        }
1494        if (!isset($this->meta->fidelity)) {
1495            $this->meta->fidelity = '';
1496        }
1497        if (!isset($this->meta->document_name)) {
1498            $this->meta->document_name = '';
1499        }
1500        if (!isset($this->meta->sides)) {
1501            $this->meta->sides = '';
1502        }
1503        if (!isset($this->meta->page_ranges)) {
1504            $this->meta->page_ranges = '';
1505        }
1506        $jobattributes = '';
1507        $operationattributes = '';
1508        $printerattributes = '';
1509        $this->_buildValues($operationattributes, $jobattributes, $printerattributes);
1510        self::_setOperationId();
1511        if (!isset($this->error_generation->request_body_malformed))
1512        {
1513            $this->error_generation->request_body_malformed = "";
1514        }
1515        $this->stringjob = chr(0x01) . chr(0x01) // 1.1  | version-number
1516            . chr(0x00) . chr(0x02) // Print-Job | operation-id
1517            . $this->meta->operation_id //           request-id
1518            . chr(0x01) // start operation-attributes | operation-attributes-tag
1519            . $this->meta->charset
1520            . $this->meta->language
1521            . $this->meta->printer_uri
1522            . $this->meta->username
1523            . $this->meta->jobname
1524            . $this->meta->fidelity
1525            . $this->meta->document_name
1526            . $this->meta->mime_media_type
1527            . $operationattributes;
1528        if ($this->meta->copies || $this->meta->sides || $this->meta->page_ranges || !empty($jobattributes))
1529        {
1530            $this->stringjob .=
1531                chr(0x02) // start job-attributes | job-attributes-tag
1532                    . $this->meta->copies
1533                    . $this->meta->sides
1534                    . $this->meta->page_ranges
1535                    . $jobattributes;
1536        }
1537        $this->stringjob.= chr(0x03); // end-of-attributes | end-of-attributes-tag
1538        self::_putDebug(
1539            sprintf(_("String sent to the server is: %s"),
1540                $this->stringjob)
1541            );
1542        return TRUE;
1543    }
1544
1545    protected function _buildValues(&$operationattributes, &$jobattributes, &$printerattributes)
1546    {
1547        $operationattributes = '';
1548        foreach($this->operation_tags as $key => $values)
1549        {
1550            $item = 0;
1551            if (array_key_exists('value', $values))
1552            {
1553                foreach($values['value'] as $item_value)
1554                {
1555                    if ($item == 0)
1556                    {
1557                        $operationattributes .=
1558                            $values['systag']
1559                                . self::_giveMeStringLength($key)
1560                                . $key
1561                                . self::_giveMeStringLength($item_value)
1562                                . $item_value;
1563                    }
1564                    else
1565                    {
1566                        $operationattributes .=
1567                            $values['systag']
1568                                . self::_giveMeStringLength('')
1569                                . self::_giveMeStringLength($item_value)
1570                                . $item_value;
1571                    }
1572                    $item++;
1573                }
1574            }
1575        }
1576        $jobattributes = '';
1577        foreach($this->job_tags as $key => $values)
1578        {
1579            $item = 0;
1580            if (array_key_exists('value', $values))
1581            {
1582                foreach($values['value'] as $item_value)
1583                {
1584                    if ($item == 0)
1585                    {
1586                        $jobattributes .=
1587                            $values['systag']
1588                                . self::_giveMeStringLength($key)
1589                                . $key
1590                                . self::_giveMeStringLength($item_value)
1591                                . $item_value;
1592                    }
1593                    else
1594                    {
1595                        $jobattributes .=
1596                            $values['systag']
1597                                . self::_giveMeStringLength('')
1598                                . self::_giveMeStringLength($item_value)
1599                                . $item_value;
1600                    }
1601                    $item++;
1602                }
1603            }
1604        }
1605        $printerattributes = '';
1606        foreach($this->printer_tags as $key => $values)
1607        {
1608            $item = 0;
1609            if (array_key_exists('value', $values))
1610            {
1611                foreach($values['value'] as $item_value)
1612                {
1613                    if ($item == 0)
1614                    {
1615                        $printerattributes .=
1616                            $values['systag']
1617                                . self::_giveMeStringLength($key)
1618                                . $key
1619                                . self::_giveMeStringLength($item_value)
1620                                . $item_value;
1621                    }
1622                    else
1623                    {
1624                        $printerattributes .=
1625                            $values['systag']
1626                                . self::_giveMeStringLength('')
1627                                . self::_giveMeStringLength($item_value)
1628                                . $item_value;
1629                    }
1630                    $item++;
1631                }
1632            }
1633        }
1634        reset($this->job_tags);
1635        reset($this->operation_tags);
1636        reset($this->printer_tags);
1637        return true;
1638    }
1639
1640    protected function _giveMeStringLength($string)
1641    {
1642        $length = strlen($string);
1643        if ($length > ((0xFF << 8) + 0xFF)  )
1644        {
1645            $errmsg = sprintf (
1646                _('max string length for an ipp meta-information = %d, while here %d'),
1647                ((0xFF << 8) + 0xFF), $length);
1648
1649            if ($this->with_exceptions)
1650            {
1651                throw new ippException($errmsg);
1652            }
1653            else
1654            {
1655                trigger_error ($errmsg, E_USER_ERROR);
1656            }
1657        }
1658        $int1 = $length & 0xFF;
1659        $length -= $int1;
1660        $length = $length >> 8;
1661        $int2 = $length & 0xFF;
1662        return chr($int2) . chr($int1);
1663    }
1664
1665    protected function _enumBuild($tag, $value)
1666    {
1667        switch ($tag)
1668        {
1669            case "orientation-requested":
1670                switch ($value)
1671                {
1672                    case 'portrait':
1673                        $value = chr(3);
1674                        break;
1675
1676                    case 'landscape':
1677                        $value = chr(4);
1678                        break;
1679
1680                    case 'reverse-landscape':
1681                        $value = chr(5);
1682                        break;
1683
1684                    case 'reverse-portrait':
1685                        $value = chr(6);
1686                        break;
1687                }
1688                break;
1689
1690            case "print-quality":
1691                switch ($value)
1692                {
1693                    case 'draft':
1694                        $value = chr(3);
1695                        break;
1696
1697                    case 'normal':
1698                        $value = chr(4);
1699                        break;
1700
1701                    case 'high':
1702                        $value = chr(5);
1703                        break;
1704                }
1705                break;
1706
1707            case "finishing":
1708                switch ($value)
1709                {
1710                    case 'none':
1711                        $value = chr(3);
1712                        break;
1713
1714                    case 'staple':
1715                        $value = chr(4);
1716                        break;
1717
1718                    case 'punch':
1719                        $value = chr(5);
1720                        break;
1721
1722                    case 'cover':
1723                        $value = chr(6);
1724                        break;
1725
1726                    case 'bind':
1727                        $value = chr(7);
1728                        break;
1729
1730                    case 'saddle-stitch':
1731                        $value = chr(8);
1732                        break;
1733
1734                    case 'edge-stitch':
1735                        $value = chr(9);
1736                        break;
1737
1738                    case 'staple-top-left':
1739                        $value = chr(20);
1740                        break;
1741
1742                    case 'staple-bottom-left':
1743                        $value = chr(21);
1744                        break;
1745
1746                    case 'staple-top-right':
1747                        $value = chr(22);
1748                        break;
1749
1750                    case 'staple-bottom-right':
1751                        $value = chr(23);
1752                        break;
1753
1754                    case 'edge-stitch-left':
1755                        $value = chr(24);
1756                        break;
1757
1758                    case 'edge-stitch-top':
1759                        $value = chr(25);
1760                        break;
1761
1762                    case 'edge-stitch-right':
1763                        $value = chr(26);
1764                        break;
1765
1766                    case 'edge-stitch-bottom':
1767                        $value = chr(27);
1768                        break;
1769
1770                    case 'staple-dual-left':
1771                        $value = chr(28);
1772                        break;
1773
1774                    case 'staple-dual-top':
1775                        $value = chr(29);
1776                        break;
1777
1778                    case 'staple-dual-right':
1779                        $value = chr(30);
1780                        break;
1781
1782                    case 'staple-dual-bottom':
1783                        $value = chr(31);
1784                        break;
1785                }
1786                break;
1787        }
1788        $prepend = '';
1789        while ((strlen($value) + strlen($prepend)) < 4)
1790        {
1791            $prepend .= chr(0);
1792        }
1793        return $prepend . $value;
1794    }
1795
1796    protected function _integerBuild($value)
1797    {
1798        if ($value >= 2147483647 || $value < - 2147483648)
1799        {
1800            trigger_error(
1801                _("Values must be between -2147483648 and 2147483647: assuming '0'") , E_USER_WARNING);
1802            return chr(0x00) . chr(0x00) . chr(0x00) . chr(0x00);
1803        }
1804        $initial_value = $value;
1805        $int1 = $value & 0xFF;
1806        $value -= $int1;
1807        $value = $value >> 8;
1808        $int2 = $value & 0xFF;
1809        $value-= $int2;
1810        $value = $value >> 8;
1811        $int3 = $value & 0xFF;
1812        $value-= $int3;
1813        $value = $value >> 8;
1814        $int4 = $value & 0xFF; //64bits
1815        if ($initial_value < 0) {
1816            $int4 = chr($int4) | chr(0x80);
1817        }
1818        else {
1819            $int4 = chr($int4);
1820        }
1821        $value = $int4 . chr($int3) . chr($int2) . chr($int1);
1822        return $value;
1823    }
1824
1825    protected function _rangeOfIntegerBuild($integers)
1826    {
1827        #$integers = split(":", $integers);
1828        $integers = preg_split("#:#", $integers);
1829        for ($i = 0; $i < 2; $i++) {
1830            $outvalue[$i] = self::_integerBuild($integers[$i]);
1831        }
1832        return $outvalue[0] . $outvalue[1];
1833    }
1834
1835    protected function _setJobAttribute($attribute, $value)
1836    {
1837        //used by setAttribute
1838        $tag_type = $this->job_tags[$attribute]['tag'];
1839        switch ($tag_type)
1840        {
1841            case 'integer':
1842                $this->job_tags[$attribute]['value'][] = self::_integerBuild($value);
1843                break;
1844
1845            case 'boolean':
1846            case 'nameWithoutLanguage':
1847            case 'nameWithLanguage':
1848            case 'textWithoutLanguage':
1849            case 'textWithLanguage':
1850            case 'keyword':
1851            case 'naturalLanguage':
1852                $this->job_tags[$attribute]['value'][] = $value;
1853                break;
1854
1855            case 'enum':
1856                $value = $this->_enumBuild($attribute, $value); // may be overwritten by children
1857                $this->job_tags[$attribute]['value'][] = $value;
1858                break;
1859
1860            case 'rangeOfInteger':
1861                // $value have to be: INT1:INT2 , eg 100:1000
1862                $this->job_tags[$attribute]['value'][] = self::_rangeOfIntegerBuild($value);
1863                break;
1864
1865            case 'resolution':
1866                if (preg_match("#dpi#", $value)) {
1867                    $unit = chr(0x3);
1868                }
1869                if (preg_match("#dpc#", $value)) {
1870                    $unit = chr(0x4);
1871                }
1872                $search = array(
1873                    "#(dpi|dpc)#",
1874                    '#(x|-)#'
1875                );
1876                $replace = array(
1877                    "",
1878                    ":"
1879                );
1880                $value = self::_rangeOfIntegerBuild(preg_replace($search, $replace, $value)) . $unit;
1881                $this->job_tags[$attribute]['value'][] = $value;
1882                break;
1883
1884            default:
1885                trigger_error(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , E_USER_NOTICE);
1886                self::_putDebug(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1887                self::_errorLog(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1888                return FALSE;
1889                break;
1890        }
1891        $this->job_tags[$attribute]['systag'] = $this->tags_types[$tag_type]['tag'];
1892    }
1893
1894    protected function _setOperationAttribute($attribute, $value)
1895    {
1896        //used by setAttribute
1897        $tag_type = $this->operation_tags[$attribute]['tag'];
1898        switch ($tag_type)
1899        {
1900            case 'integer':
1901                $this->operation_tags[$attribute]['value'][] = self::_integerBuild($value);
1902                break;
1903
1904            case 'keyword':
1905            case 'naturalLanguage':
1906                $this->operation_tags[$attribute]['value'][] = $value;
1907                break;
1908
1909            default:
1910                trigger_error(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , E_USER_NOTICE);
1911                self::_putDebug(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1912                self::_errorLog(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1913                return FALSE;
1914                break;
1915        }
1916        $this->operation_tags[$attribute]['systag'] = $this->tags_types[$tag_type]['tag'];
1917    }
1918
1919    protected function _setPrinterAttribute($attribute, $value)
1920    {
1921        //used by setAttribute
1922        $tag_type = $this->printer_tags[$attribute]['tag'];
1923        switch ($tag_type)
1924        {
1925            case 'integer':
1926                $this->printer_tags[$attribute]['value'][] = self::_integerBuild($value);
1927                break;
1928
1929            case 'keyword':
1930            case 'naturalLanguage':
1931                $this->printer_tags[$attribute]['value'][] = $value;
1932                break;
1933
1934            default:
1935                trigger_error(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , E_USER_NOTICE);
1936                self::_putDebug(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1937                self::_errorLog(sprintf(_('SetAttribute: Tag "%s": cannot set attribute') , $attribute) , 2);
1938                return FALSE;
1939                break;
1940        }
1941        $this->printer_tags[$attribute]['systag'] = $this->tags_types[$tag_type]['tag'];
1942    }
1943
1944    //
1945    // DEBUGGING
1946    //
1947    protected function _putDebug($string, $level = 1)
1948    {
1949        if ($level === false) {
1950            return;
1951        }
1952
1953        if ($level < $this->debug_level) {
1954            return;
1955        }
1956
1957        $this->debug[$this->debug_count] = substr($string, 0, 1024);
1958        $this->debug_count++;
1959        //$this->debug .= substr($string,0,1024);
1960
1961    }
1962
1963    //
1964    // LOGGING
1965    //
1966    protected function _errorLog($string_to_log, $level)
1967    {
1968        if ($level > $this->log_level) {
1969            return;
1970        }
1971
1972        $string = sprintf('%s : %s:%s user %s : %s', basename($_SERVER['PHP_SELF']) , $this->host, $this->port, $this->requesting_user, $string_to_log);
1973
1974        if ($this->log_type == 0)
1975        {
1976            error_log($string);
1977            return;
1978        }
1979
1980        $string = sprintf("%s %s Host %s:%s user %s : %s\n", date('M d H:i:s') , basename($_SERVER['PHP_SELF']) , $this->host, $this->port, $this->requesting_user, $string_to_log);
1981        error_log($string, $this->log_type, $this->log_destination);
1982        return;
1983    }
1984}
1985