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("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"));
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");
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 if($this->is_ogroup_member){
170 $this->XMethods["default"]= _("inherited");
171 }
172 $this->XMethods["xdmcp"] = _("XDMCP");
173 $this->XMethods["ldm"] = _("LDM");
174 $this->XMethods["rdp"] = _("Windows RDP");
175 $this->XMethods["citrix"] = _("ICA client");
177 /* Get selected gotoXdmcpServer
178 */
179 $this->selected_xdmcp_servers = array();
180 if(isset($this->attrs['gotoXdmcpServer'])){
181 for($i = 0 ; $i < $this->attrs['gotoXdmcpServer']['count'] ; $i++){
182 $this->selected_xdmcp_servers[] = $this->attrs['gotoXdmcpServer'][$i];
183 }
184 }
186 /* Load phone hardware list
187 */
188 $ldap= $this->config->get_ldap_link();
189 $ldap->cd($this->config->current['BASE']);
190 $ldap->search("(objectClass=goFonHardware)", array('cn', 'description'));
191 while ($attrs= $ldap->fetch()){
192 $cn= $attrs['cn'][0];
193 if (isset($attrs['description'])){
194 $description= " - ".$attrs['description'][0];
195 } else {
196 $description= "";
197 }
198 $this->hardware_list[$cn]= "$cn$description";
199 }
201 /* Eventually colorize phones */
202 $ldap->cd($this->config->current['BASE']);
203 $ldap->search("(goFonHardware=*)",array('cn','dn','goFonHardware'));
204 while($attrs = $ldap->fetch()){
205 $cn = $attrs['goFonHardware'][0];
206 if(isset($this->hardware_list[$cn])){
207 $this->used_hardware[$cn]= $cn;
208 }
209 }
210 $this->hardware_list["automatic"]= _("automatic");
211 ksort($this->hardware_list);
213 /* Convert gotoLpdEnable
214 */
215 $this->gotoLpdEnable= preg_match("/yes/i",$this->gotoLpdEnable);
218 /* Load hardware list */
219 $ldap= $this->config->get_ldap_link();
220 $ldap->cd($this->config->current['BASE']);
221 $map= array(
222 "gotoXResolution",
223 "gotoXColordepth",
224 "gotoXKbModel",
225 "gotoXKbLayout",
226 # "gotoXDriver",
227 "gotoXdmcpServer",
228 "gotoXKbVariant",
229 "gotoXMouseType",
230 "gotoXMethod",
231 "gotoXMouseport");
232 $ldap->search("(&(objectClass=gotoWorkstationTemplate)(member=".LDAP::prepare4filter($this->dn)."))",$map);
233 if ($ldap->count() == 1){
234 $attrs= $ldap->fetch();
235 foreach ($map as $name){
236 if (!isset($attrs[$name][0])){
237 continue;
238 }
240 switch ($name){
241 # case 'gotoXDriver':
242 # $this->XDrivers = array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XDrivers;
243 # break;
244 case 'gotoXMethod':
245 $this->XMethods = array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XMethods;
246 if(isset($attrs['gotoXdmcpServer'])){
247 for($i = 0 ; $i < $attrs['gotoXdmcpServer']['count'] ; $i++){
248 $this->inherited_xdmcp_servers[] = $attrs['gotoXdmcpServer'][$i];
249 }
250 }
251 break;
252 case 'gotoXResolution':
253 $this->XResolutions= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XResolutions;
254 break;
255 case 'gotoXColordepth':
256 $this->XColordepths= array('default' => _("inherited").' ['.$attrs[$name][0].' '._('Bit').']') + $this->XColordepths;
257 break;
258 case 'gotoXKbModel':
259 $this->XKbModels= array('default' => _("inherited").' ['.$attrs[$name][0].']') + $this->XKbModels;
260 break;
261 case 'gotoXKbLayout':
262 $this->XKbLayouts= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XKbLayouts;
263 break;
264 case 'gotoXKbVariant':
265 $this->XKbVariants= array('default' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->XKbVariants;
266 break;
267 case 'gotoXMouseType':
268 $this->MouseTypes= array('AUTO' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->MouseTypes;
269 break;
270 case 'gotoXMouseport':
271 $this->MousePorts= array('AUTO' => _("inherited").' ['.$attrs[$name][0].']' ) + $this->MousePorts;
272 break;
273 }
274 }
275 }
277 if(preg_match("/\+/",$this->gotoXHsync)){
278 $this->AutoSync = true;
279 $this->gotoXHsync = preg_replace("/\+/","-",$this->gotoXHsync);
280 $this->gotoXVsync = preg_replace("/\+/","-",$this->gotoXVsync);
281 }
282 }
285 function execute()
286 {
287 /* Call parent execute */
288 plugin::execute();
290 if($this->is_account && !$this->view_logged){
291 $this->view_logged = TRUE;
292 new log("view","terminal/".get_class($this),$this->dn);
293 }
295 /* Do we need to flip is_account state? */
296 if (isset($_POST['modify_state'])){
297 $this->is_account= !$this->is_account;
298 }
300 /* Do we represent a valid terminal? */
301 if (!$this->is_account && $this->parent === NULL){
302 $display= "<img alt=\"\" src=\"images/stop.png\" align=\"middle\"> <b>".
303 _("This 'dn' has no terminal features.")."</b>";
304 return ($display);
305 }
307 /* Show main page */
308 $smarty= get_smarty();
310 /* Assign acls */
311 $tmp= $this->plInfo();
312 foreach($tmp['plProvidedAcls'] as $name => $translation){
313 $smarty->assign($name."ACL",$this->getacl($name));
314 }
316 if(isset($_POST['gotoXdmcpServer_add']) && isset($_POST['XdmcpAddServer'])){
317 if(!in_array($_POST['gotoXdmcpServer_add'],$this->selected_xdmcp_servers)){
318 $this->selected_xdmcp_servers[] = $_POST['gotoXdmcpServer_add'];
319 }
320 }
322 if(isset($_POST['selected_xdmcp_servers']) && isset($_POST['XdmcpDelServer'])){
323 $tmp = array();
324 foreach($this->selected_xdmcp_servers as $name){
325 if(!in_array($name,$_POST['selected_xdmcp_servers'])){
326 $tmp[] = $name;
327 }
328 }
329 $this->selected_xdmcp_servers = $tmp;
330 }
332 /* Arrays */
333 foreach(array("XMethods", "XDrivers", "XResolutions", "XColordepths",
334 "XKbModels","XKbVariants","MouseTypes", "MousePorts") as $val){
335 $smarty->assign("$val", $this->$val);
336 }
337 $smarty->assign("XKbLayouts", $this->XKbLayouts);
338 $smarty->assign("XKbLayoutKeys",array_flip($this->XKbLayouts));
339 $smarty->assign("nfsservers", $this->config->data['SERVERS']['NFS']);
342 /* Create a list of useable servers for the currently selected
343 * connection type.
344 */
345 $xdmcp_types = $this->config->data['SERVERS']['TERMINAL_SESSION_TYPES'];
346 $available_servers = array();
347 foreach($xdmcp_types as $servername =>$supported_types){
348 if(in_array(strtoupper($this->gotoXMethod),$supported_types)){
349 $available_servers[] = $servername;
350 }
351 }
353 /* Append additional information to invalid server selections.
354 */
355 $tmp = array();
356 foreach($this->selected_xdmcp_servers as $server){
357 if(in_array($server,$available_servers)){
358 $tmp[$server] = $server;
359 }else{
360 $tmp[$server] = $server." - "._("Unsupported");
361 }
362 }
364 /* Remove already selected servers from available list.
365 */
366 foreach($available_servers as $key => $server){
367 if(isset($tmp[$server])){
368 unset($available_servers[$key]);
369 }
370 }
372 $smarty->assign("selected_xdmcp_servers", $tmp);
373 $smarty->assign("inherited_xdmcp_servers", $this->inherited_xdmcp_servers);
374 $smarty->assign("available_xdmcp_servers", $available_servers);
376 /* Variables - select */
377 foreach(array("gotoXMethod","gotoXDriver", "gotoXResolution", "gotoXColordepth",
378 "gotoLpdServer", "gotoXKbModel", "gotoXKbLayout",
379 "gotoXKbVariant", "gotoXMouseType", "gotoXMouseport") as $val){
381 $smarty->assign($val."_select", $this->$val);
382 }
384 /* Variables */
385 foreach(array("gotoXHsync", "gotoXVsync") as $val){
386 $smarty->assign($val, $this->$val);
387 }
388 $smarty->assign("staticAddress", "");
390 /* Checkboxes */
391 foreach(array("gotoLpdEnable", "gotoScannerEnable") as $val){
392 if ($this->$val == TRUE) {
393 $smarty->assign("$val", "checked");
394 } else {
395 $smarty->assign("$val", "");
396 }
397 }
399 /* Phone stuff */
400 $smarty->assign ("goFonHardware", $this->goFonHardware);
401 $hl= "<select size=\"1\" name=\"goFonHardware\" title=\"".
402 _("Choose the phone located at the current terminal")."\" >\n";
403 foreach ($this->hardware_list as $cn => $description){
404 if ($cn == $this->goFonHardware){
405 $selected= "selected";
406 } else {
407 $selected= "";
408 }
409 if (isset($this->used_hardware[$cn])){
410 $color= "style=\"color:#A0A0A0\"";
411 } else {
412 $color= "";
413 }
414 $hl.= " <option $color label=\"$cn\" value=\"$cn\" $selected>$description</option>\n";
415 }
416 $hl.= "</select>\n";
417 $smarty->assign ("hardware_list", $hl);
418 $smarty->assign ("gotoXMonitor", $this->gotoXMonitor);
420 $smarty->assign("AutoSyncACL",$this->getacl("AutoSync"));
422 $smarty->assign("AutoSyncCHK"," ");
423 if($this->AutoSync){
424 $smarty->assign("AutoSyncCHK"," checked ");
425 $smarty->assign("gotoXVsyncACL", preg_replace("/w/","",$this->getacl("gotoXVsync")));
426 $smarty->assign("gotoXHsyncACL", preg_replace("/w/","",$this->getacl("gotoXHsync")));
427 }
429 /* Show main page */
430 return($smarty->fetch (get_template_path('terminalService.tpl', TRUE,dirname(__FILE__))));
431 }
433 function remove_from_parent()
434 {
435 new log("remove","terminal/".get_class($this),$this->dn,array_keys($this->attrs));
436 $this->handle_post_events("remove");
438 plugin::remove_from_parent();
439 $ldap = $this->config->get_ldap_link();
440 $ldap->cd($this->dn);
441 $ldap->modify($this->attrs);
442 show_ldap_error($ldap->get_error(),_("Could not remove terminal service."));
443 }
446 /* Save data to object */
447 function save_object()
448 {
449 plugin::save_object();
451 /* Save checkbox state */
452 if (isset ($_POST['gotoXMethod'])){
453 foreach (array("gotoLpdEnable", "gotoScannerEnable") as $val){
455 if($this->acl_is_writeable($val)){
456 if (!isset ($_POST["$val"])){
457 $this->$val= FALSE;
458 } else {
459 $this->$val= TRUE;
460 }
461 }
462 }
463 }
465 if(isset($_POST['gotoXDriver'])){
466 if(isset($_POST['AutoSync'])){
467 $this->AutoSync = true;
468 }else{
469 $this->AutoSync = false;
470 }
471 }
473 /* Default entries can use blank hsync/vsync entries */
474 if ($this->dn != "" && $this->cn != "default" && $this->cn != "default"){
476 /* But only if no auto sync is enabled... */
477 if (!$this->AutoSync){
479 /* Check vsync for correct usage */
480 $val= preg_replace ("/\s/", "", $this->gotoXVsync);
481 if (!preg_match ("/^\d+(\.\d+)?([-]\d+(\.\d+)?)?$/", $val) && $this->acl_is_writeable("gotoXVsync")){
483 $message[]= _("Please specify a valid VSync range.");
484 } elseif ($this->acl_is_writeable("gotoXVsync")){
485 list($v1,$v2)= preg_split ("/[-+]/", $val);
486 if ($v2 != ""){
487 if ($v1 > $v2){
488 $message[]= _("Please specify a valid VSync range.");
489 }
490 }
491 }
493 /* Check hsync for correct usage */
494 $val= preg_replace ("/\s/", "", $this->gotoXHsync);
495 if (!preg_match ("/^\d+(\.\d+)?([-]\d+(\.\d+)?)?$/", $val) && $this->acl_is_writeable("gotoXHsync")){
497 $message[]= _("Please specify a valid HSync range.");
498 } elseif ($this->acl_is_writeable("gotoXHsync")){
499 list($v1,$v2)= preg_split ("/[-+]/", $val);
500 if ($v2 != ""){
501 if ($v1 > $v2){
502 $message[]= _("Please specify a valid HSync range.");
503 }
504 }
505 }
506 }
507 }
508 }
511 /* Check supplied data */
512 function check()
513 {
514 /* Call common method to give check the hook */
515 $message= plugin::check();
517 if($this->gotoXMethod != "default"){
518 $xdmcp_types = $this->config->data['SERVERS']['TERMINAL_SESSION_TYPES'];
519 $available_servers = array();
520 foreach($xdmcp_types as $servername =>$supported_types){
521 if(in_array(strtoupper($this->gotoXMethod),$supported_types)){
522 $available_servers[] = $servername;
523 }
524 }
525 foreach($this->selected_xdmcp_servers as $server){
526 if(!in_array($server,$available_servers)){
527 $message[] = _("Remote desktop settings contains servers that do not support the selected connection method.");
528 break;
529 }
530 }
531 }
533 return ($message);
534 }
537 /* Save to LDAP */
538 function save()
539 {
540 /* Convert to string */
541 $this->gotoLpdEnable= $this->gotoLpdEnable?"Yes":"No";
543 if($this->is_ogroup){
544 $this->objectclasses = array("gotoWorkstationTemplate");
545 }else{
546 $this->objectclasses = array("GOhard");
547 }
549 plugin::save();
551 /* Strip out 'default' values */
552 foreach(array("gotoXMethod","gotoXDriver", "gotoXResolution", "gotoXColordepth",
553 "gotoLpdServer", "gotoXKbModel", "gotoXKbLayout",
554 "gotoXKbVariant", "gotoXMouseType", "gotoXMouseport") as $val){
556 if ($this->attrs[$val] == "default"){
557 $this->attrs[$val]= array();
558 }
559 }
561 if($this->gotoXMethod == "default"){
562 $this->attrs['gotoXdmcpServer'] = array();
563 $this->attrs['gotoXMethod'] = array();
564 }else{
565 $this->attrs['gotoXdmcpServer'] = array_values($this->selected_xdmcp_servers);
566 }
569 if($this->AutoSync){
570 $this->attrs['gotoXHsync'] = "30+55";
571 $this->attrs['gotoXVsync'] = "50+70";
572 }
574 /* Write back to ldap */
575 $ldap= $this->config->get_ldap_link();
576 $ldap->cd($this->dn);
577 $this->cleanup();
578 $ldap->modify ($this->attrs);
579 new log("modify","terminal/".get_class($this),$this->dn,array_keys($this->attrs),$ldap->get_error());
581 show_ldap_error($ldap->get_error(), sprintf(_("Saving of object system terminal/service with dn '%s' failed."),$this->dn));
582 $this->handle_post_events("modify");
583 }
586 function getListOfXDrivers()
587 {
588 $drivers = array("default" => "["._("unknown")."]");
590 /* Generate a list of xdrivers from CONFIG_DIR/xdrivers */
591 if (file_exists(CONFIG_DIR.'/xdrivers')){
592 $xdrivers = file (CONFIG_DIR.'/xdrivers');
593 foreach ($xdrivers as $line){
594 if (!preg_match ("/^#/", $line)){
595 $drivers[]= trim($line);
596 }
597 }
598 } else {
599 foreach( array(
600 "ati", "atimisc", "chips", "cirrus", "cyrix", "fbdev", "fglrx",
601 "i128", "i740", "i810", "imstt", "mga", "neomagic", "newport", "nsc", "nv", "nvidia",
602 "r128", "radeon", "rendition", "s3", "s3virge", "savage", "siliconmotion",
603 "sis", "tdfx", "tga", "trident", "tseng", "vesa", "vga", "vmware") as $driver){
604 $drivers[] = $driver;
605 }
606 }
607 return($drivers);
608 }
611 /* Return plugin informations for acl handling */
612 static function plInfo()
613 {
614 return (array(
615 "plShortName" => _("Service"),
616 "plDescription" => _("Terminal service"),
617 "plSelfModify" => FALSE,
618 "plDepends" => array(),
619 "plPriority" => 3,
620 "plSection" => array("administration"),
621 "plCategory" => array("terminal"),
623 "plProvidedAcls"=> array(
624 "gotoXMonitor" => _("Monitor"),
625 "gotoXMethod" => _("Method"),
626 "gotoXdmcpServer" => _("Remote desktop"),
627 "gotoXDriver" => _("Gfx driver"),
628 "gotoXResolution" => _("Gfx resolution"),
629 "gotoXColordepth" => _("Gfx color depth"),
630 "gotoXHsync" => _("Hsync"),
631 "gotoXVsync" => _("Vsync"),
632 "AutoSync" => _("Auto-Sync"),
633 "gotoLpdEnable" => _("Printer service enabled"),
634 "gotoLpdServer" => _("Spool server"),
635 "gotoScannerEnable" => _("Scanner enabled"),
636 "gotoXKbModel" => _("Keyboard model"),
637 "gotoXKbLayout" => _("Keyboard layout"),
638 "gotoXKbVariant" => _("Keyboard variant"),
639 "gotoXMouseType" => _("Mouse type"),
640 "gotoXMouseport" => _("Mouse port"),
641 "goFonHardware" => _("Telephone hardware"))
642 ));
643 }
644 }
646 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
647 ?>