1 <?php
3 class ppdManager
4 {
5 var $path= "";
6 var $cachedList= array();
7 var $timestamps = array();
9 function ppdManager($path)
10 {
11 if(is_dir($path)){
12 $this->path= $path;
13 }else{
14 print_red(sprintf(_("PPD manager : The specified path '%s' doesn't exists."),$path));
15 return (false);
16 }
17 }
20 function findPPD($path)
21 {
22 $list= array();
23 $currentDir= getcwd();
25 $dh = opendir($path);
26 while(false !== ($file = readdir($dh))){
28 /* Skip well known files */
29 if( $file == '.' || $file == '..'){
30 continue;
31 }
33 /* Recurse through all "common" directories */
34 if(is_dir($path.'/'.$file)){
35 $list= array_merge($list, $this->findPPD($path.'/'.$file));
36 continue;
37 }
39 /* Check for PPD extension */
40 if (preg_match('/\.ppd$/i', $file)){
41 $list[]= $path.'/'.$file;
42 }
43 }
45 closedir($dh);
46 chdir ($currentDir);
47 return ($list);
48 }
51 function updateAttribute($file, $section, $attribute, $value)
52 {
53 $fsection= false;
54 $fattribute= false;
55 $section= preg_replace('/^\*/', '', $section);
56 $attribute= preg_replace('/^\*/', '', $attribute);
58 $rp= @fopen($file, "r");
59 $wp= @fopen("$file.tmp", "w");
63 while (!feof($rp)){
64 $lines[]= fgets($rp, 1024);
65 }
67 $ret = "";
68 $done =false;
69 foreach($lines as $nr => $line){
71 if (preg_match("/\*OpenGroup:*\s+\**$section\/*/", $line)){
72 $fsection= true;
73 $ret .=$line;
74 continue;
75 }
77 /* Change model name .. */
78 if ((preg_match("/^\*".$attribute.":*\s+/",$line)) && ($attribute == "ModelName")){
79 $line= "*$attribute: \"$value\"\n";
80 $done =true;
81 }
83 if (($fsection) && ($section != "NO_SECTION")){
84 if (preg_match("/^\*CloseGroup:*\s+\**$section\/*/", $line)){
85 $fsection= false;
86 $ret .=$line;
87 continue;
88 }
91 if (preg_match("/^\*OpenUI:*\s+\**$attribute\/*/", $line)){
92 $fattribute= true;
93 $ret .= $line;
94 continue;
95 }
97 if ($fattribute){
98 if (preg_match("/^\*CloseUI:*\s+\**$attribute\/*/", $line)){
99 $fattribute= false;
100 $ret .= $line;
101 continue;
102 }
104 if (preg_match("/^\*Default$attribute:*\s+/", $line)){
105 $line= "*Default$attribute: $value\n";
106 $done =true;
107 }
108 }
109 }else{
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 }
129 $ret .=$line;
130 }
132 fwrite($wp,$ret);
134 fclose($wp);
135 fclose($rp);
137 copy("$file.tmp", "$file");
138 unlink("$file.tmp");
139 }
142 function saveProperties($ppdFile, $properties)
143 {
144 if(!is_readable($ppdFile)){
145 print_red(sprintf(_("Specified ppd file '%s' can't be opened for reading."),$ppdFile));
146 }elseif(!is_writeable(preg_replace("#(^.*/).*$#","\\1",$ppdFile.".tmp"))){
147 print_red(sprintf(_("The required tmp file file '%s' can't be opened for writing."),$ppdFile.".tmp"));
148 }else{
149 foreach ($properties as $name => $section){
150 foreach ($section as $attribute => $value){
151 if (is_array($value)){
152 $this->updateAttribute($ppdFile, $name, $attribute, $value['_default']);
153 }
154 }
155 }
156 }
157 }
159 function loadProperties($ppdFile)
160 {
161 $group= "";
162 $option= "";
163 $properties= array();
165 $fh= fopen ($ppdFile, 'r');
166 while (!feof($fh)) {
168 /* Read line */
169 $line= fgets($fh, 256);
170 if (strlen($line) >= 256){
171 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored'), E_USER_WARNING);
172 }
174 /* Trigger for option groups */
175 if (preg_match('/^\*OpenGroup:/i', $line)){
177 /* Sanity checks */
178 if ($group != ""){
179 trigger_error(_('Nested groups are not supported!'), E_USER_WARNING);
180 continue;
181 }
182 if (in_array($group, $properties)){
183 trigger_error(_('Group name not unique!'), E_USER_WARNING);
184 continue;
185 }
187 // TODO: Symbol values are not supported yet!
188 if (preg_match('/\^/', $line)){
189 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
190 }
191 $complete= preg_replace('@^\*OpenGroup:\s+(.*)$@i', '\1', $line);
192 $complete= trim($complete, '"');
193 if (preg_match('@/@', $complete)){
194 $group= trim(preg_replace('@^\*OpenGroup:\s+"?([^/]+)/.*$@i', '\1', $line));
195 $name = preg_replace('@^\*OpenGroup:\s+"?[^/]+/([^/]+).*$@i', '\1', $line);
196 } else {
197 $group= $complete;
198 $name = $complete;
199 }
200 $properties[$group]= array('_name' => $name);
201 continue;
202 }
203 if (preg_match("/^\*CloseGroup:\s+\"?$group\"?/i", $line)){
204 $group= "";
205 continue;
206 }
208 /* Trigger for options */
209 if (preg_match('/^\*OpenUI\s+/i', $line)){
211 /* Sanity check */
212 if ($option != ""){
213 trigger_error(_('Nested options are not supported!'), E_USER_WARNING);
214 continue;
215 }
217 // TODO: Symbol values are not supported yet!
218 if (preg_match('/\^/', $line)){
219 trigger_error(_('Symbol values are not supported yet!'), E_USER_WARNING);
220 }
221 $complete= preg_replace('@^\*OpenUI\s+(.*)$@i', '\1', $line);
222 $complete= trim($complete, '"');
223 if (preg_match('@/@', $complete)){
224 $option= trim(preg_replace('@^\*OpenUI\s+([^/]+)/.*$@i', '\1', $line));
225 $name = trim(preg_replace('@^\*OpenUI\s+[^/]+/([^/]+).*$@i', '\1', $line));
226 } else {
227 $option= trim($complete);
228 $name = trim($complete);
229 }
231 /* Extract option type */
232 $type= trim(preg_replace('/^[^:]+:\s+/', '', $line));
233 $name= preg_replace('/:.*$/', '', $name);
234 $option= preg_replace('/:.*$/', '', $option);
236 // TODO: PickMany is not supported yet!
237 if (preg_match('/PickMany/i', $type)){
238 trigger_error(_('PickMany is not supported yet!'), E_USER_WARNING);
239 }
240 if(empty($group)){
241 $properties["NO_SECTION"][$option]= array('_name' => $name, '_type' => $type);
242 }else{
243 $properties[$group][$option]= array('_name' => $name, '_type' => $type);
244 }
245 continue;
246 }
247 /* Show interest for option parsing */
248 if ($option != ""){
250 $eoption= preg_replace('@\*@', '', $option);
252 /* Close section? */
253 if (preg_match("@^\*CloseUI:\s+\*$eoption@i", $line)){
254 $option= "";
255 continue;
256 }
258 /* Default value? */
259 if (preg_match("@^\*Default$eoption:@", $line)){
260 $c= preg_replace("@^\*Default$eoption:\s+@", "", $line);
261 if(empty($group)){
262 $properties["NO_SECTION"][$option]['_default']= trim(trim($c, '"'));
263 }else{
264 $properties[$group][$option]['_default']= trim(trim($c, '"'));
265 }
266 continue;
267 }
269 /* Possible value? */
270 if (preg_match("@^\*$eoption\s+@", $line)){
271 #*PageSize Letter/US Letter: "<>setpagedevice"
272 $c= preg_replace("@^\*$eoption\s+([^/]+).*$@", "$1", $line);
273 $d= preg_replace("@^\*$eoption\s+[^/]+/([^:]+).*$@", "$1", $line);
274 if(empty($group)){
275 $properties["NO_SECTION"][$option][trim($c)]= trim($d);
276 }else{
277 $properties[$group][$option][trim($c)]= trim($d);
278 }
279 continue;
280 }
281 }
282 }
283 fclose ($fh);
284 return ($properties);
285 }
287 function loadDescription($ppdFile)
288 {
289 $model= "";
290 $manufacturer= "";
293 /* Only parse complete PPD file again, if it was changed */
294 $modified = filemtime($ppdFile);
295 if(isset($this->cachedList[$ppdFile]) && isset($this->timestamps[$ppdFile]) && $modified == $this->timestamps[$ppdFile]){
296 return($this->cachedList[$ppdFile]);
297 }
299 /* Remember modified timestamp, to speed up next request */
300 $this->timestamps[$ppdFile] = filemtime($ppdFile);
302 $fh= fopen ($ppdFile, 'r');
303 while ((!feof($fh))&&($fh)) {
305 /* Read line */
306 $line= fgets($fh, 256);
307 if (strlen($line) >= 256){
308 trigger_error(_('Parsing PPD file %s failed - line too long. Trailing characters have been ignored'), E_USER_WARNING);
309 }
311 /* Extract interesting informations */
312 if (preg_match('/^\*Manufacturer:/i', $line)){
313 $manufacturer= preg_replace('/^\*Manufacturer:\s+"?([^"]+)"?.*$/i', '\1', $line);
314 }
315 if (preg_match('/^\*ModelName:/i', $line)){
316 $model= preg_replace('/^\*ModelName:\s+"?([^"]+)"?.*$/i', '\1', $line);
317 }
319 /* Got everything we need? Skip rest for speed reasons... */
320 if ($model != '' && $manufacturer != ''){
321 break;
322 }
323 }
324 fclose ($fh);
326 /* Write out a notice that the PPD file seems to be broken if we can't
327 extract any usefull informations */
328 if ($model == '' || $manufacturer == ''){
329 trigger_error(sprintf(_('Parsing PPD file %s failed - no information found.'), $ppdFile), E_USER_WARNING);
330 }
332 return ($manufacturer.' - '.$model);
333 }
336 function getPrinterList($reload= false)
337 {
338 /* Load list of PPD files */
339 if (count($this->cachedList) == 0 || $reload){
340 $list= $this->findPPD($this->path);
342 /* Load descriptive informations to build final printer list */
343 $new = array();
344 foreach ($list as $ppdFile){
345 $new[$ppdFile] = $this->loadDescription($ppdFile);
346 }
347 $this->cachedList= $new ;
349 }
351 return ($this->cachedList);
352 }
354 }
356 ?>