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 $value =$config->get_cfg_value("ppdGzip","true");
15 if(preg_match('/false/i',$value)){
16 $this->useGzip = false;
17 }
19 if(is_dir($path)){
20 $this->path= $path;
21 }elseif(is_link($path) && (is_dir(readlink($path)))) {
22 $this->path= $path;
23 } else {
24 msg_dialog::display(_("PPD manager error"), sprintf(_("The specified path '%s' does not exist."),$path), ERROR_DIALOG);
25 return (false);
26 }
29 }
32 function findPPD($path)
33 {
34 $list= array();
35 $currentDir= getcwd();
37 $dh = opendir($path);
38 while(false !== ($file = readdir($dh))){
40 /* Skip well known files */
41 if( $file == '.' || $file == '..'){
42 continue;
43 }
45 /* Recurse through all "common" directories */
46 $check_path= $path.'/'.$file;
47 if(is_dir($check_path) || (is_link($check_path) && (is_dir(readlink($check_path))))){
48 $list= array_merge($list, $this->findPPD($check_path));
49 continue;
50 }
52 /* Check for PPD extension */
53 if (preg_match('/\.ppd(.gz)?$/i', $file)){
54 $list[]= $path.'/'.$file;
55 }
56 }
58 closedir($dh);
59 chdir ($currentDir);
60 return ($list);
61 }
64 function updateAttribute($file, $section, $attribute, $value)
65 {
66 $fsection= false;
67 $fattribute= false;
68 $section= preg_replace('/^\*/', '', $section);
69 $attribute= preg_replace('/^\*/', '', $attribute);
71 if($this->useGzip){
72 $rp= @gzopen($file, "r");
73 $wp= @gzopen("$file.tmp", "w");
74 }else{
75 $rp= @gzopen($file, "r");
76 $wp= @fopen("$file.tmp", "w");
77 }
79 if($this->useGzip){
80 while (!gzeof($rp)){
81 $lines[]= gzgets($rp, 1024);
82 }
83 }else{
84 while (!feof($rp)){
85 $lines[]= fgets($rp, 1024);
86 }
87 }
89 $ret = "";
90 $done =false;
91 foreach($lines as $nr => $line){
93 if (preg_match("/\*OpenGroup:*\s+\**$section\/*/", $line)){
94 $fsection= true;
95 $ret .=$line;
96 continue;
97 }
99 /* Change model name .. */
100 if ((preg_match("/^\*".$attribute.":*\s+/",$line)) && ($attribute == "ModelName")){
101 $line= "*$attribute: \"$value\"\n";
102 $done =true;
103 }
105 if (($fsection) && ($section != "NO_SECTION")){
106 if (preg_match("/^\*CloseGroup:*\s+\**$section\/*/", $line)){
107 $fsection= false;
108 $ret .=$line;
109 continue;
110 }
113 if (preg_match("/^\*OpenUI:*\s+\**$attribute\/*/", $line)){
114 $fattribute= true;
115 $ret .= $line;
116 continue;
117 }
119 if ($fattribute){
120 if (preg_match("/^\*CloseUI:*\s+\**$attribute\/*/", $line)){
121 $fattribute= false;
122 $ret .= $line;
123 continue;
124 }
126 if (preg_match("/^\*Default$attribute:*\s+/", $line)){
127 $line= "*Default$attribute: $value\n";
128 $done =true;
129 }
130 }
131 }else{
132 if (preg_match("/^\*OpenUI:*\s+\**$attribute\/*/", $line)){
133 $fattribute= true;
134 $ret .= $line;
135 continue;
136 }
138 if ($fattribute){
139 if (preg_match("/^\*CloseUI:*\s+\**$attribute\/*/", $line)){
140 $fattribute= false;
141 $ret .= $line;
142 continue;
143 }
145 if (preg_match("/^\*Default$attribute:*\s+/", $line)){
146 $line= "*Default$attribute: $value\n";
147 $done =true;
148 }
149 }
150 }
151 $ret .=$line;
152 }
154 if($this->useGzip){
155 gzwrite($wp,$ret);
156 gzclose($wp);
157 gzclose($rp);
158 }else{
159 fwrite($wp,$ret);
160 fclose($wp);
161 gzclose($rp);
162 }
164 copy("$file.tmp", "$file");
165 unlink("$file.tmp");
166 }
169 function saveProperties($ppdFile, $properties)
170 {
171 if(!is_readable($ppdFile)){
172 msg_dialog::display(_("PPD manager error"), sprintf(_("Specified PPD file '%s' cannot be opened for reading."),$ppdFile), ERROR_DIALOG);
173 }elseif(!is_writeable(preg_replace("#(^.*/).*$#","\\1",$ppdFile.".tmp"))){
174 msg_dialog::display(_("PPD manager error"), sprintf(_("The temporary file '%s' cannot be opened for writing."),$ppdFile.".tmp"), ERROR_DIALOG);
175 }else{
176 if(is_array($properties)){
177 foreach ($properties as $name => $section){
178 foreach ($section as $attribute => $value){
179 if (is_array($value)){
180 $this->updateAttribute($ppdFile, $name, $attribute, $value['_default']);
181 }
182 }
183 }
184 }
185 }
186 }
188 function loadProperties($ppdFile)
189 {
190 $group= "";
191 $option= "";
192 $properties= array();
194 // Check for empty files
195 if(!filesize($ppdFile)) {
196 trigger_error(_('Parsing PPD file failed - file is empty!'));
197 return;
198 }
200 $fh= gzopen ($ppdFile, 'r');
201 while (!gzeof($fh) && $fh){
203 /* Read line */
204 $line= gzgets($fh, 256);
205 if (strlen($line) >= 256){
206 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored!'), E_USER_WARNING);
207 }
209 /* Trigger for option groups */
210 if (preg_match('/^\*OpenGroup:/i', $line)){
212 /* Sanity checks */
213 if ($group != ""){
214 trigger_error(_('Nested groups are not supported!'), E_USER_WARNING);
215 continue;
216 }
217 if (in_array($group, $properties)){
218 trigger_error(_('Group name not unique!'), E_USER_WARNING);
219 continue;
220 }
222 // TODO: Symbol values are not supported yet!
223 if (preg_match('/\^/', $line)){
224 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
225 }
226 $complete= preg_replace('@^\*OpenGroup:\s+(.*)$@i', '\1', $line);
227 $complete= trim($complete, '"');
228 if (preg_match('@/@', $complete)){
229 $group= trim(preg_replace('@^\*OpenGroup:\s+"?([^/]+)/.*$@i', '\1', $line));
230 $name = preg_replace('@^\*OpenGroup:\s+"?[^/]+/([^/]+).*$@i', '\1', $line);
231 } else {
232 $group= $complete;
233 $name = $complete;
234 }
235 $properties[$group]= array('_name' => $name);
236 continue;
237 }
238 if (preg_match("/^\*CloseGroup:\s+\"?$group\"?/i", $line)){
239 $group= "";
240 continue;
241 }
243 /* Trigger for options */
244 if (preg_match('/^\*OpenUI\s+/i', $line)){
246 /* Sanity check */
247 if ($option != ""){
248 trigger_error(_('Nested options are not supported!'), E_USER_WARNING);
249 continue;
250 }
252 // TODO: Symbol values are not supported yet!
253 if (preg_match('/\^/', $line)){
254 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
255 }
256 $complete= preg_replace('@^\*OpenUI\s+(.*)$@i', '\1', $line);
257 $complete= trim($complete, '"');
258 if (preg_match('@/@', $complete)){
259 $option= trim(preg_replace('@^\*OpenUI\s+([^/]+)/.*$@i', '\1', $line));
260 $name = trim(preg_replace('@^\*OpenUI\s+[^/]+/([^/]+).*$@i', '\1', $line));
261 } else {
262 $option= trim($complete);
263 $name = trim($complete);
264 }
266 /* Extract option type */
267 $type= trim(preg_replace('/^[^:]+:\s+/', '', $line));
268 $name= preg_replace('/:.*$/', '', $name);
269 $option= preg_replace('/:.*$/', '', $option);
271 // TODO: PickMany is not supported yet!
272 if (preg_match('/PickMany/i', $type)){
273 trigger_error(_('PickMany is not supported yet!'), E_USER_WARNING);
274 }
275 if(empty($group)){
276 $properties["NO_SECTION"][$option]= array('_name' => $name, '_type' => $type);
277 }else{
278 $properties[$group][$option]= array('_name' => $name, '_type' => $type);
279 }
280 continue;
281 }
282 /* Show interest for option parsing */
283 if ($option != ""){
285 $eoption= preg_replace('@\*@', '', $option);
287 /* Close section? */
288 if (preg_match("@^\*CloseUI:\s+\*$eoption@i", $line)){
289 $option= "";
290 continue;
291 }
293 /* Default value? */
294 if (preg_match("@^\*Default$eoption:@", $line)){
295 $c= preg_replace("@^\*Default$eoption:\s+@", "", $line);
296 if(empty($group)){
297 $properties["NO_SECTION"][$option]['_default']= trim(trim($c, '"'));
298 }else{
299 $properties[$group][$option]['_default']= trim(trim($c, '"'));
300 }
301 continue;
302 }
304 /* Possible value? */
305 if (preg_match("@^\*$eoption\s+@", $line)){
306 #*PageSize Letter/US Letter: "<>setpagedevice"
307 $c= preg_replace("@^\*$eoption\s+([^/]+).*$@", "$1", $line);
308 $d= preg_replace("@^\*$eoption\s+[^/]+/([^:]+).*$@", "$1", $line);
309 if(empty($group)){
310 $properties["NO_SECTION"][$option][trim($c)]= trim($d);
311 }else{
312 $properties[$group][$option][trim($c)]= trim($d);
313 }
314 continue;
315 }
316 }
317 }
318 gzclose ($fh);
319 return ($properties);
320 }
322 function loadDescription($ppdFile)
323 {
324 // Check for empty files
325 if(!filesize($ppdFile)) {
326 trigger_error(_('Parsing PPD file failed - file is empty!'));
327 return;
328 }
330 $ppdDesc = array();
332 /* Only parse complete PPD file again, if it was changed */
333 $modified = filemtime($ppdFile);
334 if(isset($this->cachedList[$ppdFile]) && isset($this->timestamps[$ppdFile]) && $modified == $this->timestamps[$ppdFile]){
335 return($this->cachedList[$ppdFile]);
336 }
338 /* Remember modified timestamp, to speed up next request */
339 $this->timestamps[$ppdFile] = filemtime($ppdFile);
340 $fh= gzopen ($ppdFile, 'r');
342 while (!gzeof($fh) && $fh){
344 $line= gzgets($fh, 256);
345 if (strlen($line) >= 256){
346 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored'), E_USER_WARNING);
347 }
349 /* Extract interesting informations */
350 if (preg_match('/^\*Manufacturer:/i', $line)){
351 $ppdDesc['manufacturer'] = trim(preg_replace('/^\*Manufacturer:\s+"?([^"]+)"?.*$/i', '\1', $line));
352 }
353 if (preg_match('/^\*ModelName:/i', $line)){
354 $ppdDesc['model'] = trim(preg_replace('/^\*ModelName:\s+"?([^"]+)"?.*$/i', '\1', $line));
355 }
357 /* Got everything we need? Skip rest for speed reasons... */
358 if (isset($ppdDesc['manufacturer']) && isset($ppdDesc['model'])){
359 break;
360 }
361 }
363 gzclose ($fh);
365 /* If model contains manufacturer strip it */
366 $ppdDesc['model'] = str_replace($ppdDesc['manufacturer']." ", '', $ppdDesc['model']);
368 /* Write out a notice that the PPD file seems to be broken if we can't
369 extract any usefull informations */
370 if ($ppdDesc['manufacturer'] == '' || $ppdDesc['model'] == ''){
371 trigger_error(sprintf(_('Parsing PPD file %s failed - no information found.'), $ppdFile), E_USER_WARNING);
372 }
374 $ppdDesc['name'] = $ppdDesc['manufacturer'] . " - " . $ppdDesc['model'];
375 return $ppdDesc;
376 }
379 function getPrinterList($reload= false)
380 {
381 /* Load list of PPD files */
382 if (count($this->cachedList) == 0 || $reload){
383 $list= $this->findPPD($this->path);
385 /* Load descriptive informations to build final printer list */
386 $new = array();
387 foreach ($list as $ppdFile){
388 $new[$ppdFile] = $this->loadDescription($ppdFile);
389 }
390 $this->cachedList= $new ;
392 }
394 return ($this->cachedList);
395 }
397 }
398 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
399 ?>