1 <?php
3 class ppdManager
4 {
5 var $path= "";
6 var $cachedList= array();
7 var $timestamps = array();
9 var $useGzip = true;
11 function ppdManager($path)
12 {
13 global $config;
14 if($config->boolValueIsTrue("core","ppdGzip")){
15 $this->useGzip = false;
16 }
18 if(is_dir($path)){
19 $this->path= $path;
20 }elseif(is_link($path) && (is_dir(readlink($path)))) {
21 $this->path= $path;
22 } else {
23 msg_dialog::display(_("PPD manager error"), sprintf(_("The specified path '%s' does not exist."),$path), ERROR_DIALOG);
24 return (false);
25 }
26 }
29 function findPPD($path)
30 {
31 $list= array();
32 $currentDir= getcwd();
34 $dh = opendir($path);
35 while(false !== ($file = readdir($dh))){
37 /* Skip well known files */
38 if( $file == '.' || $file == '..'){
39 continue;
40 }
42 /* Recurse through all "common" directories */
43 $check_path= $path.'/'.$file;
44 if(is_dir($check_path) || (is_link($check_path) && (is_dir(readlink($check_path))))){
45 $list= array_merge($list, $this->findPPD($check_path));
46 continue;
47 }
49 /* Check for PPD extension */
50 if (preg_match('/\.ppd(.gz)?$/i', $file)){
51 $list[]= $path.'/'.$file;
52 }
53 }
55 closedir($dh);
56 chdir ($currentDir);
57 return ($list);
58 }
61 function updateAttribute($file, $section, $attribute, $value)
62 {
63 $fsection= false;
64 $fattribute= false;
65 $section= preg_replace('/^\*/', '', $section);
66 $attribute= preg_replace('/^\*/', '', $attribute);
68 if($this->useGzip){
69 $rp= @gzopen($file, "r");
70 $wp= @gzopen("$file.tmp", "w");
71 }else{
72 $rp= @gzopen($file, "r");
73 $wp= @fopen("$file.tmp", "w");
74 }
76 if($this->useGzip){
77 while (!gzeof($rp)){
78 $lines[]= gzgets($rp, 1024);
79 }
80 }else{
81 while (!feof($rp)){
82 $lines[]= fgets($rp, 1024);
83 }
84 }
86 $ret = "";
87 $done =false;
88 foreach($lines as $nr => $line){
90 if (preg_match("/\*OpenGroup:*\s+\**$section\/*/", $line)){
91 $fsection= true;
92 $ret .=$line;
93 continue;
94 }
96 /* Change model name .. */
97 if ((preg_match("/^\*".$attribute.":*\s+/",$line)) && ($attribute == "ModelName")){
98 $line= "*$attribute: \"$value\"\n";
99 $done =true;
100 }
102 if (($fsection) && ($section != "NO_SECTION")){
103 if (preg_match("/^\*CloseGroup:*\s+\**$section\/*/", $line)){
104 $fsection= false;
105 $ret .=$line;
106 continue;
107 }
110 if (preg_match("/^\*OpenUI:*\s+\**$attribute\/*/", $line)){
111 $fattribute= true;
112 $ret .= $line;
113 continue;
114 }
116 if ($fattribute){
117 if (preg_match("/^\*CloseUI:*\s+\**$attribute\/*/", $line)){
118 $fattribute= false;
119 $ret .= $line;
120 continue;
121 }
123 if (preg_match("/^\*Default$attribute:*\s+/", $line)){
124 $line= "*Default$attribute: $value\n";
125 $done =true;
126 }
127 }
128 }else{
129 if (preg_match("/^\*OpenUI:*\s+\**$attribute\/*/", $line)){
130 $fattribute= true;
131 $ret .= $line;
132 continue;
133 }
135 if ($fattribute){
136 if (preg_match("/^\*CloseUI:*\s+\**$attribute\/*/", $line)){
137 $fattribute= false;
138 $ret .= $line;
139 continue;
140 }
142 if (preg_match("/^\*Default$attribute:*\s+/", $line)){
143 $line= "*Default$attribute: $value\n";
144 $done =true;
145 }
146 }
147 }
148 $ret .=$line;
149 }
151 if($this->useGzip){
152 gzwrite($wp,$ret);
153 gzclose($wp);
154 gzclose($rp);
155 }else{
156 fwrite($wp,$ret);
157 fclose($wp);
158 gzclose($rp);
159 }
161 copy("$file.tmp", "$file");
162 unlink("$file.tmp");
163 }
166 function saveProperties($ppdFile, $properties)
167 {
168 if(!is_readable($ppdFile)){
169 msg_dialog::display(_("PPD manager error"), sprintf(_("Specified PPD file '%s' cannot be opened for reading."),$ppdFile), ERROR_DIALOG);
170 }elseif(!is_writeable(preg_replace("#(^.*/).*$#","\\1",$ppdFile.".tmp"))){
171 msg_dialog::display(_("PPD manager error"), sprintf(_("The temporary file '%s' cannot be opened for writing."),$ppdFile.".tmp"), ERROR_DIALOG);
172 }else{
173 if(is_array($properties)){
174 foreach ($properties as $name => $section){
175 foreach ($section as $attribute => $value){
176 if (is_array($value)){
177 $this->updateAttribute($ppdFile, $name, $attribute, $value['_default']);
178 }
179 }
180 }
181 }
182 }
183 }
185 function loadProperties($ppdFile)
186 {
187 $group= "";
188 $option= "";
189 $properties= array();
191 // Check for empty files
192 if(!filesize($ppdFile)) {
193 trigger_error(_('Parsing PPD file failed - file is empty!'));
194 return;
195 }
197 $fh= gzopen ($ppdFile, 'r');
198 while (!gzeof($fh) && $fh){
200 /* Read line */
201 $line= gzgets($fh, 256);
202 if (strlen($line) >= 256){
203 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored!'), E_USER_WARNING);
204 }
206 /* Trigger for option groups */
207 if (preg_match('/^\*OpenGroup:/i', $line)){
209 /* Sanity checks */
210 if ($group != ""){
211 trigger_error(_('Nested groups are not supported!'), E_USER_WARNING);
212 continue;
213 }
214 if (in_array($group, $properties)){
215 trigger_error(_('Group name not unique!'), E_USER_WARNING);
216 continue;
217 }
219 // TODO: Symbol values are not supported yet!
220 if (preg_match('/\^/', $line)){
221 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
222 }
223 $complete= preg_replace('@^\*OpenGroup:\s+(.*)$@i', '\1', $line);
224 $complete= trim($complete, '"');
225 if (preg_match('@/@', $complete)){
226 $group= trim(preg_replace('@^\*OpenGroup:\s+"?([^/]+)/.*$@i', '\1', $line));
227 $name = preg_replace('@^\*OpenGroup:\s+"?[^/]+/([^/]+).*$@i', '\1', $line);
228 } else {
229 $group= $complete;
230 $name = $complete;
231 }
232 $properties[$group]= array('_name' => $name);
233 continue;
234 }
235 if (preg_match("/^\*CloseGroup:\s+\"?$group\"?/i", $line)){
236 $group= "";
237 continue;
238 }
240 /* Trigger for options */
241 if (preg_match('/^\*OpenUI\s+/i', $line)){
243 /* Sanity check */
244 if ($option != ""){
245 trigger_error(_('Nested options are not supported!'), E_USER_WARNING);
246 continue;
247 }
249 // TODO: Symbol values are not supported yet!
250 if (preg_match('/\^/', $line)){
251 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
252 }
253 $complete= preg_replace('@^\*OpenUI\s+(.*)$@i', '\1', $line);
254 $complete= trim($complete, '"');
255 if (preg_match('@/@', $complete)){
256 $option= trim(preg_replace('@^\*OpenUI\s+([^/]+)/.*$@i', '\1', $line));
257 $name = trim(preg_replace('@^\*OpenUI\s+[^/]+/([^/]+).*$@i', '\1', $line));
258 } else {
259 $option= trim($complete);
260 $name = trim($complete);
261 }
263 /* Extract option type */
264 $type= trim(preg_replace('/^[^:]+:\s+/', '', $line));
265 $name= preg_replace('/:.*$/', '', $name);
266 $option= preg_replace('/:.*$/', '', $option);
268 // TODO: PickMany is not supported yet!
269 if (preg_match('/PickMany/i', $type)){
270 trigger_error(_('PickMany is not supported yet!'), E_USER_WARNING);
271 }
272 if(empty($group)){
273 $properties["NO_SECTION"][$option]= array('_name' => $name, '_type' => $type);
274 }else{
275 $properties[$group][$option]= array('_name' => $name, '_type' => $type);
276 }
277 continue;
278 }
279 /* Show interest for option parsing */
280 if ($option != ""){
282 $eoption= preg_replace('@\*@', '', $option);
284 /* Close section? */
285 if (preg_match("@^\*CloseUI:\s+\*$eoption@i", $line)){
286 $option= "";
287 continue;
288 }
290 /* Default value? */
291 if (preg_match("@^\*Default$eoption:@", $line)){
292 $c= preg_replace("@^\*Default$eoption:\s+@", "", $line);
293 if(empty($group)){
294 $properties["NO_SECTION"][$option]['_default']= trim(trim($c, '"'));
295 }else{
296 $properties[$group][$option]['_default']= trim(trim($c, '"'));
297 }
298 continue;
299 }
301 /* Possible value? */
302 if (preg_match("@^\*$eoption\s+@", $line)){
304 // Detect what comes first, the '/' or the ':'.
305 // We may have entries like this:
306 // *Resolution 300dpi: "<</HWResolution [300 300] >> setpagedevice"
307 // and
308 // *Resolution 300dpi/300: "<</HWResolution [300 300] >> setpagedevice"
309 $name = $value = preg_replace("@^\*$eoption\s+([^:]+).*$@", "$1", $line);
310 if(preg_match("/\//", $name)){
311 list($name, $value) = preg_split("/\//", $name);
312 }
313 if(empty($group)){
314 $properties["NO_SECTION"][$option][$name]= $value;
315 }else{
316 $properties[$group][$option][$name]= $value;
317 }
318 continue;
319 }
320 }
321 }
323 gzclose ($fh);
324 return ($properties);
325 }
327 function loadDescription($ppdFile)
328 {
329 // Check for empty files
330 if(!filesize($ppdFile)) {
331 trigger_error(_('Parsing PPD file failed - file is empty!'));
332 return;
333 }
335 $ppdDesc = array();
337 /* Only parse complete PPD file again, if it was changed */
338 $modified = filemtime($ppdFile);
339 if(isset($this->cachedList[$ppdFile]) && isset($this->timestamps[$ppdFile]) && $modified == $this->timestamps[$ppdFile]){
340 return($this->cachedList[$ppdFile]);
341 }
343 /* Remember modified timestamp, to speed up next request */
344 $this->timestamps[$ppdFile] = filemtime($ppdFile);
345 $fh= gzopen ($ppdFile, 'r');
347 while (!gzeof($fh) && $fh){
349 $line= gzgets($fh, 256);
350 if (strlen($line) >= 256){
351 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored'), E_USER_WARNING);
352 }
354 /* Extract interesting informations */
355 if (preg_match('/^\*Manufacturer:/i', $line)){
356 $ppdDesc['manufacturer'] = trim(preg_replace('/^\*Manufacturer:\s+"?([^"]+)"?.*$/i', '\1', $line));
357 }
358 if (preg_match('/^\*ModelName:/i', $line)){
359 $ppdDesc['model'] = trim(preg_replace('/^\*ModelName:\s+"?([^"]+)"?.*$/i', '\1', $line));
360 }
361 if (preg_match('/^\*ShortNickName:/i', $line)) {
362 $ppdDesc['nickname'] = trim(preg_replace('/^\*ShortNickName:\s+"?([^"]+)"?.*$/i', '\1', $line));
363 }
365 /* Got everything we need? Skip rest for speed reasons... */
366 if (isset($ppdDesc['manufacturer']) && isset($ppdDesc['model']) && isset($ppdDesc['nickname'])){
367 break;
368 }
369 }
371 gzclose ($fh);
373 /* If model contains manufacturer strip it */
374 $ppdDesc['model'] = str_replace($ppdDesc['manufacturer']." ", '', $ppdDesc['model']);
376 /* Write out a notice that the PPD file seems to be broken if we can't
377 extract any usefull informations */
378 if ($ppdDesc['manufacturer'] == '' || $ppdDesc['model'] == ''){
379 trigger_error(sprintf(_('Parsing PPD file %s failed - no information found.'), $ppdFile), E_USER_WARNING);
380 }
382 $ppdDesc['name'] = $ppdDesc['manufacturer'] . " - " . $ppdDesc['model'];
383 return $ppdDesc;
384 }
387 function getPrinterList($reload= false)
388 {
389 /* Load list of PPD files */
390 if (count($this->cachedList) == 0 || $reload){
391 $list= $this->findPPD($this->path);
393 /* Load descriptive informations to build final printer list */
394 $new = array();
395 foreach ($list as $ppdFile){
396 $new[$ppdFile] = $this->loadDescription($ppdFile);
397 }
398 $this->cachedList= $new ;
400 }
402 return ($this->cachedList);
403 }
405 }
406 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
407 ?>