Code

Added explorerps/2 mouse type
[gosa.git] / gosa-plugins / goto / admin / systems / goto / class_terminalService.inc
1 <?php
3 class termservice extends plugin
4 {
5   /* CLI vars */
6   var $cli_summary= "Manage terminal service aspects";
7   var $cli_description= "Some longer text\nfor help";
8   var $cli_parameters= array("eins" => "Eins ist toll", "zwei" => "Zwei ist noch besser");
10   /* Generic terminal attributes */
11   var $gotoLpdEnable= FALSE;
12   var $gotoXMonitor= "";
13   var $gotoXMethod= "default";
14   var $gotoXdmcpServer= "";
15   var $gotoXDriver= "";
16   var $gotoXResolution= "";
17   var $gotoXColordepth= "";
18   var $gotoXHsync= "";
19   var $gotoXVsync= "";
20   var $gotoXKbModel= "";
21   var $gotoXKbLayout= "";
22   var $gotoXKbVariant= "";
23   var $gotoXMouseType= "";
24   var $gotoXMouseport= "";
25   var $gotoLpdServer= "";
26   var $gotoScannerEnable= "";
27   var $gotoScannerModel= "";
28   var $gotoScannerClients= "";
29   var $gotoScannerBackend= "";
30   var $goFonHardware= "automatic";
32   var $AutoSync = false;
33   var $view_logged = FALSE;
35   /* Needed values and lists */
36   var $ignore_account= TRUE;
37   var $base= "";
38   var $cn= "";
39   var $orig_dn= "";
40   var $XMethods= array();
41   var $XDrivers= array();
42   var $XResolutions= array();
43   var $XColordepths= array();
44   var $XKbModels= array ();
45   var $XKbLayouts= array ();
46   var $XKbVariants= array ();
47   var $MouseTypes= array();
48   var $MousePorts= array();
49   var $hardware_list= array();
50   var $used_hardware= array();
53   /* attribute list for save action */
54   var $attributes= array("cn", "gotoLpdEnable", "gotoXMonitor", "gotoXMethod", "gotoXdmcpServer",
55       "gotoXDriver", "gotoXResolution", "gotoXColordepth",
56       "gotoXHsync", "gotoXVsync", "gotoLpdEnable", "gotoLpdServer",
57       "gotoScannerEnable", "gotoScannerModel", "gotoScannerClients",
58       "gotoScannerBackend", "gotoXKbModel", "gotoXKbLayout", "gotoXKbVariant",
59       "gotoXMouseType", "gotoXMouseport", "goFonHardware");
60   var $objectclasses= array("GOhard");
62   var $is_ogroup        = FALSE;
63   var $is_ogroup_member = FALSE;
65   var $selected_xdmcp_servers = array();
66   var $inherited_xdmcp_servers = array();
68   function termservice (&$config, $dn= NULL, $parent= NULL)
69   {
70     plugin::plugin ($config, $dn, $parent);
71     $this->orig_dn= $this->dn;
73     /* Check if we are a part of an ogroup.
74      * In this case, we have to hide all the inherit stuff.
75      */ 
76     if(isset($parent) && get_class($parent) == "ogroup"){
77       $this->is_ogroup = TRUE;
78     }
80     /* Check if we are member of an object group.
81      */
82     $ldap= $this->config->get_ldap_link();
83     $ldap->cd($this->config->current['BASE']);
84     $ldap->search("(&(objectClass=gotoWorkstationTemplate)(member=".LDAP::prepare4filter($this->dn)."))");
85     if($ldap->count()){
86       $this->is_ogroup_member = TRUE;
87     }
89     /* Get list of available xdrivers */
90     $this->XDrivers = $this->getListOfXDrivers();
92     /* Create a list of available resolutions.
93      */     
94     $this->XResolutions= array(
95           "640x480"   =>  "640x480",
96           "800x600"   =>  "800x600",
97           "1024x768"  =>  "1024x768",
98           "1152x864"  =>  "1152x864",
99           "1280x1024" =>  "1280x1024",
100           "1400x1050" =>  "1400x1050",
101           "1600x1200" =>  "1600x1200");
103     if(isset($this->config->data['MAIN']['RESOLUTION_HOOK'])){
104       $file = $this->config->data['MAIN']['RESOLUTION_HOOK'];
105       if(is_readable($file)){
106         $str = file_get_contents($file);
107         $lines = split("\n",$str);
108         foreach($lines as $line){
109           $line = trim($line);
110           if(!empty($line)){
111             $this->XResolutions[$line]=$line;
112           }
113         }
114       }else{
115         print_red(sprintf(_("You have specified an external resolution hook which can't be read, please check the permission of the file '%s'."),$file));
116       }
117     }
119     /* Create a set of selectable color depths
120      */
121     $this->XColordepths= array(
122         "8"        => "8 " ._("bit"),
123         "15"       => "15 "._("bit"),
124         "16"       => "16 "._("bit"),
125         "24"       => "24 "._("bit"));
127     
128     /* Create a set of selectable keyboard models
129      */
130     $this->XKbModels = array();
131     foreach(array ("btc9000", "chicony", "compaq", "dell", "dell101", "everex",
132           "flexpro", "geniuscomfy", "hp", "itouch", "jp106", "logicordless",
133           "logiinetnav", "logiinternet", "macintosh", "microsoft",
134           "microsoftpro", "omnikey101", "pc101", "pc102", "pc104",
135           "pc105", "rapidaccess", "rapidaccess2", "winbook") as $type){
136       $this->XKbModels[$type] = $type;
137     }
139     /* Additional values will be extracted from CONFIG_DIR.keyboardLayouts */
140     $this->XKbLayouts = array("de"=> "de","intl" =>"intl","us" =>"us");
141     $this->XKbVariants= array("nodeadkeys"=>"nodeadkeys", "basic"=>"basic");
142     $this->MouseTypes = array("ImPS/2"=>"ImPS/2","PS/2"=>"PS/2", "explorerps/2"=>"explorerps/2",
143                               "Microsoft"=>"Microsoft","Logitech"=>"Logitech");
144     $this->MousePorts = array("/dev/ttyS0"=>"/dev/ttyS0",
145                               "/dev/ttyS1"=>"/dev/ttyS1","/dev/psaux"=>"/dev/psaux", 
146                               "/dev/input/mice"=>"/dev/input/mice");
147  
149     /* Try to read additional keyboard layouts
150      */
151     if(file_exists(CONFIG_DIR."/keyboardLayouts")){
152       if(is_readable(CONFIG_DIR."/keyboardLayouts")){
153         $str = file_get_contents(CONFIG_DIR."/keyboardLayouts");
154         $tmp = split("\n",$str);
155         foreach($tmp as $entry){
156           if((!empty($entry)) && (!preg_match("/^#/",$entry))){
157             $entry = trim($entry);
158             $tmp2 = split ("\:",$entry);
159             $la =   trim($tmp2[0]);   // What would be saved to ldap
160             $da =   trim($tmp2[1]);   // This wis displayed in the listbox
161             $this->XKbLayouts [ $la] = $da;
162           }
163         }
164       }
165     }
167     /* Terminal server methods 
168      */
169     $this->XMethods["default"]= _("default");
170     $this->XMethods["xdmcp"]  = _("XDMCP");
171     $this->XMethods["ldm"]    = _("LDM");
172     $this->XMethods["rdp"]    = _("Windows RDP");
173     $this->XMethods["citrix"] = _("ICA client");
175     /* Get selected gotoXdmcpServer 
176      */
177     $this->selected_xdmcp_servers = array();
178     if(isset($this->attrs['gotoXdmcpServer'])){
179       for($i = 0 ; $i < $this->attrs['gotoXdmcpServer']['count'] ; $i++){
180         $this->selected_xdmcp_servers[] = $this->attrs['gotoXdmcpServer'][$i];
181       }
182     }
184     /* Load phone hardware list 
185      */
186     $ldap= $this->config->get_ldap_link();
187     $ldap->cd($this->config->current['BASE']);
188     $ldap->search("(objectClass=goFonHardware)", array('cn', 'description'));
189     while ($attrs= $ldap->fetch()){
190       $cn= $attrs['cn'][0];
191       if (isset($attrs['description'])){
192         $description= " - ".$attrs['description'][0];
193       } else {
194         $description= "";
195       }
196       $this->hardware_list[$cn]= "$cn$description";
197     }
199     /* Eventually colorize phones */
200     $ldap->cd($this->config->current['BASE']);
201     $ldap->search("(goFonHardware=*)",array('cn','dn','goFonHardware'));
202     while($attrs = $ldap->fetch()){
203         $cn = $attrs['goFonHardware'][0];
204         if(isset($this->hardware_list[$cn])){
205           $this->used_hardware[$cn]= $cn;
206         }
207     }
208     $this->hardware_list["automatic"]= _("automatic");
209     ksort($this->hardware_list);
211     /* Convert gotoLpdEnable 
212      */
213     $this->gotoLpdEnable= preg_match("/yes/i",$this->gotoLpdEnable);
216     /* Load hardware list */
217     $ldap= $this->config->get_ldap_link();
218     $ldap->cd($this->config->current['BASE']);
219     $map= array(
220         "gotoXResolution", 
221         "gotoXColordepth", 
222         "gotoXKbModel", 
223         "gotoXKbLayout",
224 #        "gotoXDriver",
225         "gotoXdmcpServer",
226         "gotoXKbVariant",
227         "gotoXMouseType", 
228         "gotoXMethod",
229         "gotoXMouseport");
230     $ldap->search("(&(objectClass=gotoWorkstationTemplate)(member=".LDAP::prepare4filter($this->dn)."))",$map);
231     if ($ldap->count() == 1){
232       $attrs= $ldap->fetch();
233       foreach ($map as $name){
234         if (!isset($attrs[$name][0])){
235           continue;
236         }
238         switch ($name){
239 #         case 'gotoXDriver': 
240 #           $this->XDrivers = array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XDrivers;
241 #           break;
242           case 'gotoXMethod': 
243             $this->XMethods = array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XMethods;
244             if(isset($attrs['gotoXdmcpServer'])){
245               for($i = 0 ; $i < $attrs['gotoXdmcpServer']['count'] ; $i++){
246                 $this->inherited_xdmcp_servers[] = $attrs['gotoXdmcpServer'][$i];
247               }
248             }
249             break;
250           case 'gotoXResolution':
251             $this->XResolutions= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XResolutions;
252             break;
253           case 'gotoXColordepth':
254             $this->XColordepths= array('default' => _("inherited").' ['.$attrs[$name][0].' '._('Bit').']') + $this->XColordepths;
255             break;
256           case 'gotoXKbModel':
257             $this->XKbModels= array('default' => _("inherited").' ['.$attrs[$name][0].']') + $this->XKbModels;
258             break;
259           case 'gotoXKbLayout':
260             $this->XKbLayouts= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XKbLayouts;
261             break;
262           case 'gotoXKbVariant':
263             $this->XKbVariants= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XKbVariants;
264             break;
265           case 'gotoXMouseType':
266             $this->MouseTypes= array('AUTO' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->MouseTypes;
267             break;
268           case 'gotoXMouseport':
269             $this->MousePorts= array('AUTO' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->MousePorts;
270             break;
271         }
272       }
273     }
275     if(preg_match("/\+/",$this->gotoXHsync)){
276       $this->AutoSync = true;
277       $this->gotoXHsync = preg_replace("/\+/","-",$this->gotoXHsync);
278       $this->gotoXVsync = preg_replace("/\+/","-",$this->gotoXVsync);
279     }
280   }
283   function execute()
284   {
285     /* Call parent execute */
286     plugin::execute();
288     if($this->is_account && !$this->view_logged){
289       $this->view_logged = TRUE;
290       new log("view","terminal/".get_class($this),$this->dn);
291     }
293     /* Do we need to flip is_account state? */
294     if (isset($_POST['modify_state'])){
295       $this->is_account= !$this->is_account;
296     }
298     /* Do we represent a valid terminal? */
299     if (!$this->is_account && $this->parent === NULL){
300       $display= "<img alt=\"\" src=\"images/stop.png\" align=\"middle\">&nbsp;<b>".
301         _("This 'dn' has no terminal features.")."</b>";
302       return ($display);
303     }
305     /* Show main page */
306     $smarty= get_smarty();
308     /* Assign acls */
309     $tmp= $this->plInfo();
310     foreach($tmp['plProvidedAcls'] as $name => $translation){
311       $smarty->assign($name."ACL",$this->getacl($name));
312     }
314     if(isset($_POST['gotoXdmcpServer_add']) && isset($_POST['XdmcpAddServer'])){
315       if(!in_array($_POST['gotoXdmcpServer_add'],$this->selected_xdmcp_servers)){
316         $this->selected_xdmcp_servers[] = $_POST['gotoXdmcpServer_add'];
317       }
318     }
320     if(isset($_POST['selected_xdmcp_servers']) && isset($_POST['XdmcpDelServer'])){
321       $tmp = array();
322       foreach($this->selected_xdmcp_servers as $name){
323         if(!in_array($name,$_POST['selected_xdmcp_servers'])){
324           $tmp[] =  $name;
325         }
326       }
327       $this->selected_xdmcp_servers = $tmp;
328     }
330     /* Arrays */ 
331     foreach(array("XMethods", "XDrivers", "XResolutions", "XColordepths",
332           "XKbModels","XKbVariants","MouseTypes", "MousePorts") as $val){
333       $smarty->assign("$val", $this->$val);
334     }
335     $smarty->assign("XKbLayouts",   $this->XKbLayouts);
336     $smarty->assign("XKbLayoutKeys",array_flip($this->XKbLayouts));
337     $smarty->assign("nfsservers", $this->config->data['SERVERS']['NFS']);
338   
339     
340     /* Create a list of useable servers for the currently selected 
341      *  connection type.
342      */ 
343     $xdmcp_types =  $this->config->data['SERVERS']['TERMINAL_SESSION_TYPES'];
344     $available_servers = array();
345     if(isset($xdmcp_types[strtoupper($this->gotoXMethod)])){
346       $available_servers = $xdmcp_types[strtoupper($this->gotoXMethod)];
347     }
349     /* Append additional information to invalid server selections.
350      */
351     $tmp = array();
352     foreach($this->selected_xdmcp_servers as $server){
353       if(in_array($server,$available_servers)){
354         $tmp[$server] = $server;
355       }else{
356         $tmp[$server] = $server."&nbsp;-&nbsp;"._("Unsupported");
357       }
358     }
360     /* Remove already selected servers from available list. 
361      */
362     foreach($available_servers as $key => $server){
363       if(isset($tmp[$server])){
364         unset($available_servers[$key]);
365       }
366     }
368     $smarty->assign("selected_xdmcp_servers", $tmp);
369     $smarty->assign("inherited_xdmcp_servers", $this->inherited_xdmcp_servers);
370     $smarty->assign("available_xdmcp_servers", $available_servers);
372     /* Variables - select */
373     foreach(array("gotoXMethod","gotoXDriver", "gotoXResolution", "gotoXColordepth", 
374           "gotoLpdServer", "gotoXKbModel", "gotoXKbLayout",
375           "gotoXKbVariant", "gotoXMouseType", "gotoXMouseport") as $val){
377       $smarty->assign($val."_select", $this->$val);
378     }
380     /* Variables */
381     foreach(array("gotoXHsync", "gotoXVsync") as $val){
382       $smarty->assign($val, $this->$val);
383     }
384     $smarty->assign("staticAddress", "");
386     /* Checkboxes */
387     foreach(array("gotoLpdEnable", "gotoScannerEnable") as $val){
388       if ($this->$val == TRUE) {
389         $smarty->assign("$val", "checked");
390       } else {
391         $smarty->assign("$val", "");
392       }
393     }
395     /* Phone stuff */
396     $smarty->assign ("goFonHardware", $this->goFonHardware);
397     $hl= "<select size=\"1\" name=\"goFonHardware\" title=\"".
398          _("Choose the phone located at the current terminal")."\" >\n";
399     foreach ($this->hardware_list as $cn => $description){
400       if ($cn == $this->goFonHardware){
401         $selected= "selected";
402       } else {
403         $selected= "";
404       }
405       if (isset($this->used_hardware[$cn])){
406         $color= "style=\"color:#A0A0A0\"";
407       } else {
408         $color= "";
409       }
410       $hl.= "  <option $color label=\"$cn\" value=\"$cn\" $selected>$description</option>\n";
411     }
412     $hl.= "</select>\n";
413     $smarty->assign ("hardware_list", $hl);
414     $smarty->assign ("gotoXMonitor", $this->gotoXMonitor);
416     $smarty->assign("AutoSyncACL",$this->getacl("AutoSync"));
418     $smarty->assign("AutoSyncCHK"," ");
419     if($this->AutoSync){
420       $smarty->assign("AutoSyncCHK"," checked ");
421       $smarty->assign("gotoXVsyncACL", preg_replace("/w/","",$this->getacl("gotoXVsync")));
422       $smarty->assign("gotoXHsyncACL", preg_replace("/w/","",$this->getacl("gotoXHsync")));
423     }
425     /* Show main page */
426     return($smarty->fetch (get_template_path('terminalService.tpl', TRUE,dirname(__FILE__))));
427   }
429   function remove_from_parent()
430   {
431     new log("remove","terminal/".get_class($this),$this->dn,array_keys($this->attrs));
432     $this->handle_post_events("remove");
433   }
436   /* Save data to object */
437   function save_object()
438   {
439     plugin::save_object();
441     /* Save checkbox state */
442     if (isset ($_POST['gotoXMethod'])){
443       foreach (array("gotoLpdEnable", "gotoScannerEnable") as $val){
445         if($this->acl_is_writeable($val)){
446           if (!isset ($_POST["$val"])){
447             $this->$val= FALSE;
448           } else {
449             $this->$val= TRUE;
450           }
451         }
452       }
453     } 
455     if(isset($_POST['gotoXDriver'])){
456       if(isset($_POST['AutoSync'])){
457         $this->AutoSync = true;
458       }else{
459         $this->AutoSync = false;
460       }
461     }
463     /* Default entries can use blank hsync/vsync entries */
464     if ($this->dn != "" && $this->cn != "default" && $this->cn != "default"){
466       /* But only if no auto sync is enabled... */
467       if (!$this->AutoSync){
469         /* Check vsync for correct usage */
470         $val= preg_replace ("/\s/", "", $this->gotoXVsync);
471         if (!preg_match ("/^\d+(\.\d+)?([-]\d+(\.\d+)?)?$/", $val) && $this->acl_is_writeable("gotoXVsync")){
473           $message[]= _("Please specify a valid VSync range.");
474         } elseif ($this->acl_is_writeable("gotoXVsync")){
475           list($v1,$v2)= preg_split ("/[-+]/", $val);
476           if ($v2 != ""){
477             if ($v1 > $v2){
478               $message[]= _("Please specify a valid VSync range.");
479             }
480           }
481         }
483         /* Check hsync for correct usage */
484         $val= preg_replace ("/\s/", "", $this->gotoXHsync);
485         if (!preg_match ("/^\d+(\.\d+)?([-]\d+(\.\d+)?)?$/", $val) && $this->acl_is_writeable("gotoXHsync")){
487           $message[]= _("Please specify a valid HSync range.");
488         } elseif ($this->acl_is_writeable("gotoXHsync")){
489           list($v1,$v2)= preg_split ("/[-+]/", $val);
490           if ($v2 != ""){
491             if ($v1 > $v2){
492               $message[]= _("Please specify a valid HSync range.");
493             }
494           }
495         }
496       }
497     }
498   }
501   /* Check supplied data */
502   function check()
503   {
504     /* Call common method to give check the hook */
505     $message= plugin::check();
507     if($this->gotoXMethod != "default"){
508       $xdmcp_types =  $this->config->data['SERVERS']['TERMINAL_SESSION_TYPES'];
509       $available_servers = array();
510       if(isset($xdmcp_types[strtoupper($this->gotoXMethod)])){
511         $available_servers = $xdmcp_types[strtoupper($this->gotoXMethod)];
512       }
513       foreach($this->selected_xdmcp_servers as $server){
514         if(!in_array($server,$available_servers)){
515           $message[] = _("Remote desktop settings contains servers that do not support the selected connection method.");
516           break;
517         }
518       }
519     }
521     return ($message);
522   }
525   /* Save to LDAP */
526   function save()
527   {
528     /* Convert to string */
529     $this->gotoLpdEnable= $this->gotoLpdEnable?"Yes":"No";
531     if($this->is_ogroup){
532       $this->objectclasses = array("gotoWorkstationTemplate");
533     }else{
534       $this->objectclasses = array("GOhard");
535     }
537     plugin::save();
539     /* Strip out 'default' values */
540     foreach(array("gotoXMethod","gotoXDriver", "gotoXResolution", "gotoXColordepth",
541           "gotoLpdServer", "gotoXKbModel", "gotoXKbLayout",
542           "gotoXKbVariant", "gotoXMouseType", "gotoXMouseport") as $val){
544       if ($this->attrs[$val] == "default"){
545         $this->attrs[$val]= array();
546       }
547     }
549     if($this->gotoXMethod == "default"){
550       $this->attrs['gotoXdmcpServer'] = array();
551       $this->attrs['gotoXMethod'] = array();
552     }else{
553       $this->attrs['gotoXdmcpServer'] = array_values($this->selected_xdmcp_servers);
554     }
557     if($this->AutoSync){
558       $this->attrs['gotoXHsync'] = "30+55";
559       $this->attrs['gotoXVsync'] = "50+70";
560     }
562     /* Write back to ldap */
563     $ldap= $this->config->get_ldap_link();
564     $ldap->cd($this->dn);
565     $this->cleanup();
566     $ldap->modify ($this->attrs); 
567     new log("modify","terminal/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
569     show_ldap_error($ldap->get_error(), sprintf(_("Saving of object system terminal/service with dn '%s' failed."),$this->dn));
570     $this->handle_post_events("modify");
571   }
574   function getListOfXDrivers()
575   {
576     $drivers = array("default" => "["._("unknown")."]");
578     /* Generate a list of xdrivers from CONFIG_DIR/xdrivers */
579     if (file_exists(CONFIG_DIR.'/xdrivers')){
580       $xdrivers = file (CONFIG_DIR.'/xdrivers');
581       foreach ($xdrivers as $line){
582         if (!preg_match ("/^#/", $line)){
583           $drivers[]= trim($line);
584         }
585       }
586     } else {
587       foreach( array(
588           "ati", "atimisc", "chips", "cirrus", "cyrix", "fbdev", "fglrx",
589           "i128", "i740", "i810", "imstt", "mga", "neomagic", "newport", "nsc",  "nv", "nvidia",
590           "r128", "radeon", "rendition", "s3", "s3virge", "savage", "siliconmotion",
591           "sis", "tdfx", "tga", "trident", "tseng", "vesa", "vga", "vmware") as $driver){
592         $drivers[] = $driver;
593       }
594     }
595     return($drivers);
596   }
599   /* Return plugin informations for acl handling */
600   static function plInfo()
601   {
602     return (array(
603           "plShortName"   => _("Service"),
604           "plDescription" => _("Terminal service"),
605           "plSelfModify"  => FALSE,
606           "plDepends"     => array(),
607           "plPriority"    => 3,
608           "plSection"     => array("administration"),
609           "plCategory"    => array("terminal"),
611           "plProvidedAcls"=> array(
612             "gotoXMonitor"            => _("Monitor"),
613             "gotoXMethod"             => _("Method"),
614             "gotoXdmcpServer"         => _("Remote desktop"),
615             "gotoXDriver"             => _("Gfx driver"),
616             "gotoXResolution"         => _("Gfx resolution"),
617             "gotoXColordepth"         => _("Gfx color depth"),
618             "gotoXHsync"              => _("Hsync"),
619             "gotoXVsync"              => _("Vsync"),
620             "AutoSync"                => _("Auto-Sync"),
621             "gotoLpdEnable"           => _("Printer service enabled"),
622             "gotoLpdServer"           => _("Spool server"),
623             "gotoScannerEnable"       => _("Scanner enabled"),
624             "gotoXKbModel"            => _("Keyboard model"),
625             "gotoXKbLayout"           => _("Keyboard layout"),
626             "gotoXKbVariant"          => _("Keyboard variant"),
627             "gotoXMouseType"          => _("Mouse type"),
628             "gotoXMouseport"          => _("Mouse port"),
629             "goFonHardware"           => _("Telephone hardware"))
630           ));
631   }
634 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
635 ?>