Code

c4feeee8c28b71013480cb671829c924f38f1207
[gosa.git] / plugins / gofon / conference / class_phoneConferenceGeneric.inc
1 <?php
2 /*
3    This code is part of GOsa (https://gosa.gonicus.de)
4    Copyright (C) 2003  Cajus Pollmeier
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
21 class conference extends plugin
22 {
23   /* department attributes */
24   var $cn                                                 = "";
25   var $description              = "";
26   var $base                               = "";
27   var $ou                                               = "";
28   var $goFonPIN                         = "";
29   var $old_cn         = "";
31   var $goFonConferenceOption    = "";
32   var $goFonConferenceOption_P  = "";   // Set PIN 
33   var $goFonConferenceOption_r  = "";   // record Conference
34   var $goFonConferenceOption_M  = "";   // Play Music opn hold
35   var $goFonConferenceOption_s  = "";   // activate menu
36   var $goFonConferenceOption_i  = "";   // announce new and leaving user
37   var $goFonConferenceOption_c  = "";   // Count User
38   var $goFonConferenceOption_D  = "";   // Conference Type, no PIN/PIN
40   var $goFonConferenceOwner     = "";
41   var $goFonHomeServer          = "0";      // Home server of the conference
42   var $init_HomeServer          = "0";      // Initial home server of the conference
43   var $goFonHomeServers         = array();  // All available home servers
45   var $goFonConferenceOptionFormat              = "";
46   var $goFonConferenceOptionLifetime    = "";
47   var $telephoneNumber          = "";
49   var $old_tele_number          = false;
50   var $old_dn;
52   /* Headpage attributes */
53   var $last_dep_sorting= "invalid";
54   var $departments= array();
56   var $dialog ;
58   /* attribute list for save action */
59   var $attributes= array("cn","base", "description", "goFonPIN","goFonConferenceOption_P","goFonConferenceOption_r",
60       "goFonConferenceOption_M","goFonConferenceOption_s","goFonConferenceOption_i","goFonConferenceOption_c","goFonHomeServer",
61       "goFonConferenceOption_D","goFonConferenceOptionFormat","goFonConferenceOptionLifetime","telephoneNumber","goFonConferenceOwner");
63   var $objectclasses= array("top", "goFonConference");
65   function conference ($config, $dn, $plugin= NULL)
66   {
67     plugin::plugin($config, $dn, $plugin);
68     $this->is_account   = TRUE;
69     $this->ui                       = get_userinfo();
70     $this->orig_dn              = $dn;
72     
73     /* Check server configurations
74      * Load all server configuration in $this->goFonHomeServers if available
75      *  and use first server as default if necessary.
76      */
77     $a_SETUP= array();
78     if(array_key_exists('config',$_SESSION) &&
79        array_key_exists('SERVERS',$_SESSION['config']->data) &&
80        array_key_exists('FON',$_SESSION['config']->data['SERVERS']) &&
81        count($_SESSION['config']->data['SERVERS']['FON']) &&
82        is_callable("mysql_connect")
83        ) {
85       /* Set available server */
86       $this->goFonHomeServers = $_SESSION['config']->data['SERVERS']['FON'];
88       /* Set default server */
89       if($this->dn == "new"){
90         $this->goFonHomeServer = $this->goFonHomeServers[0]['DN'];
91       }
93       /* Remember inital home server, to be able to remove old entries */
94       $this->init_HomeServer = $this->goFonHomeServer;
96       /* get config */
97       if(!isset($this->goFonHomeServers[$this->goFonHomeServer])){
98         print_red(sprintf(_("The specified home server '%s' is not available in GOsa server configuration. Saving this account will create a new entry on the server '%s'. Use cancel if you do not want to create a new entry while ignoring old accounts."),$this->goFonHomeServer, $this->goFonHomeServers[0]['DN']));
100         $this->goFonHomeServer = $this->goFonHomeServers[0]['DN'];
101         $this->init_HomeServer = $this->goFonHomeServers[0]['DN'];
102       }
103       $cur_cfg = $this->goFonHomeServers[$this->goFonHomeServer]; 
104     }
106     /* Set base */
107     if ($this->dn == "new"){
108       $ui= get_userinfo();
109       if(isset($_SESSION['CurrentMainBase'])){
110         $this->base = $_SESSION['CurrentMainBase'];
111       }else{
112         $this->base= dn2base($ui->dn);
113       }
114     } else {
116       /* The base is something like this 
117           "cn=Confis,ou=conferences,ou=asterisk,ou=configs,ou=systems," */
118       $this->base= preg_replace ("/^[^,]+,[^,]+,[^,]+,[^,]+,[^,]+,/", "", $this->dn);
119     }
121     $ui= get_userinfo();
122     $acl= get_permissions ($ui->dn, $this->ui->subtreeACL);
123     $this->acl= get_module_permission($acl, "conference", $ui->dn);
124     $this->goFonConferenceOwner=$this->ui->dn;
126     /* Parse Options ... 
127      * Parameter|Lifetime|number
128      */
129     if($this->dn!="new"){
130       $tmp1= split("\|",$this->attrs['goFonConferenceOption'][0]);
132       for($i = 0 ; $i < strlen($tmp1[0]);$i++){
133         $varname = "goFonConferenceOption_".$tmp1[0][$i];
134         if($tmp1[0][$i]=="d"){
135           $this->goFonConferenceOption_D = $tmp1[0][$i];    
136         }else{
137           $this->$varname = $tmp1[0][$i];    
138         }
139       }
141       $this->goFonConferenceOptionLifetime  = $tmp1[1];
142       $this->old_tele_number                = $this->telephoneNumber;
143     }
144     $this->old_dn = $this->dn;
145     $this->old_cn = $this->cn;
146   }
149   function execute()
150   {
151     /* Call parent execute */
152     plugin::execute();
154     $smarty= get_smarty();
156     $smarty->assign("bases" ,$this->config->idepartments);
157     $smarty->assign("base"  ,$this->base);
159     $once = true; 
160     foreach($_POST as $name => $value){
161       if(preg_match("/^chooseBase/",$name) && $once){
162         $once = false;
163         $this->dialog = new baseSelectDialog($this->config);
164         $this->dialog->setCurrentBase($this->base);
165       }
166     }
168     /* Dialog handling */
169     if(is_object($this->dialog)){
170       /* Must be called before save_object */
171       $this->dialog->save_object();
173       if($this->dialog->isClosed()){
174         $this->dialog = false;
175       }elseif($this->dialog->isSelected()){
176         $this->base = $this->dialog->isSelected();
177         $this->dialog= false;
178       }else{
179         return($this->dialog->execute());
180       }
181     }
183     /* Create array with goFonHomeServer */
184     $tmp = array();
185     foreach($this->goFonHomeServers as $dn => $val){
186       if(!is_numeric($dn)){
187         $tmp[$dn]  = $val['SERVER'];
188       }
189     }
190     $smarty->assign("goFonHomeServers",$tmp);
191     $smarty->assign("goFonConferenceOptions",               array("D"=>"Conference ","d"=>"Conference without PIN"));
192     $smarty->assign("goFonConferenceOptionFormats",     array("WAV"=>"Wave","GSM"=>"GSM","WAV49"=>"Wave49"));
193     $smarty->assign("goFonConferenceOption",        $this->goFonConferenceOption_D);
195     foreach ($this->attributes as $val){
196       $smarty->assign("$val", $this->$val);
197       $smarty->assign("$val"."ACL", chkacl($this->acl, "$val"));
198       if(!$this->$val){
199         $smarty->assign($val."CHK", "");
200       }else{
201         $smarty->assign($val."CHK", " checked ");
202       }
203     }
205     if($_SESSION['js']==1){
206       if($this->goFonConferenceOption_P != "P"){
207         $smarty->assign("goFonPINACL"," disabled ");
208         $smarty->assign("goFonPIN","");
209       }
210       if($this->goFonConferenceOption_r != "r"){
211         $smarty->assign("goFonConferenceOptionFormatACL"," disabled ");
212       }
213     }
214     return($smarty->fetch (get_template_path('generic.tpl', TRUE)));
215   }
218   function remove_from_parent()
219   {
220     /* Check if 'old' home server is available in gosa FON server configuration 
221      * Try to remove this entry from database and display errors.  
222      */
223     if(isset($this->goFonHomeServers[$this->goFonHomeServer])){
224       $str = $this->SQL_remove_me(true); 
225       if($str){
226         print_red($str);
227         return false;
228       }
229     }else{
230       print_red(_("Could not remove the conference entry from database on home server (%s). Please check your asterisk database configuration."));
231       return false;
232     }
234     /* Remove ldap entry */
235     $ldap= $this->config->get_ldap_link();
236     $ldap->cd ($this->dn);
237     $ldap->recursive_remove();
239     /* Optionally execute a command after we're done */
240     $this->handle_post_events('remove');
241   }
244   /* Save data to object */
245   function save_object()
246   {
247     plugin::save_object();
248     if(isset($_POST['cn'])){
249       foreach(array("goFonConferenceOption_P","goFonConferenceOption_r","goFonConferenceOption_M","goFonConferenceOption_s",
250             "goFonConferenceOption_i","goFonConferenceOption_c","goFonConferenceOption_D","goFonPIN") as $attrs){
251         if(isset($_POST[$attrs])){
252           $this->$attrs = $_POST[$attrs];
253         }else{
254           $this->$attrs = false;
255         }
256       }
257     }
258   }
261   function check_database_accessibility()
262   {
263     /* Check if mysql extension is available */
264     if(!is_callable("mysql_pconnect")){
265       return(_("Can't save any changes to asterisk database, there is currently no mysql extension available in your php setup."));
266     }
268     /********************
269      * Check currently selected home server 
270      ********************/
272     $cfg_Current  = $this->goFonHomeServers[$this->goFonHomeServer];
273     $r_current    =  @mysql_pconnect($cfg_Current['SERVER'],$cfg_Current['LOGIN'],$cfg_Current['PASSWORD']);
274     if(!$r_current){
275       gosa_log(@mysql_error($r_current));
276       return(sprintf(_("The MySQL home server '%s' isn't reachable as user '%s', check GOsa log for mysql error."),
277             $cfg_Current['SERVER'],$cfg_Current['LOGIN']));
278     }
279     $db_current  =  @mysql_select_db($cfg_Current['DB'],$r_current);
280     if(!$db_current){
281       gosa_log(@mysql_error($r_current));
282       mysql_close($r_current);
283       return( sprintf(_("Can't select database '%s' on home server '%s'."),$cfg_Current['DB'],$cfg_Current['SERVER']));
284     }
286     /********************
287      * Check init home server 
288      ********************/
290     if($this->goFonHomeServers != $this->init_HomeServer){
291       $cfg_Init  = $this->goFonHomeServers[$this->init_HomeServer] ;
292       $r_init    =  @mysql_pconnect($cfg_Init['SERVER'],$cfg_Init['LOGIN'],$cfg_Init['PASSWORD']);
293       if(!$r_init){
294         gosa_log(@mysql_error($r_init));
295         return(sprintf(_("The MySQL initial home server '%s' isn't reachable as user '%s', check GOsa log for mysql error."),
296               $cfg_Init['SERVER'],$cfg_Init['LOGIN']));
297       }
298       $db_init  =  @mysql_select_db($cfg_Init['DB'],$r_init);
299       if(!$db_init){
300         gosa_log(@mysql_error($r_init));
301         mysql_close($r_init);
302         return( sprintf(_("Can't select database '%s' on initial home server '%s'."),$cfg_Init['DB'],$cfg_Init['SERVER']));
303       }
304     }
305   }
307   /* Check values */
308   function check()
309   {
310     /* Call common method to give check the hook */
311     $message= plugin::check();
313     if($this->is_number_used()){
314       $message[] =  $this->is_number_used();
315     }
317     /* Check if previously selected server is still available */
318     if($this->initially_was_account && !isset($this->goFonHomeServers[$this->goFonHomeServer])){
319       $message[]= sprintf(_("The previously selected asterisk home server (%s) is no longer available."),preg_replace("/,/",", ",$this->goFonHomeServer));
320       return($message);
321     }
323     if((!empty($this->goFonPIN)||($this->goFonConferenceOption_P=="P"))&&($this->goFonConferenceOption_D=="d")){
324       $message[] =_("You have specified a conference 'without PIN' ... please leave the PIN fields empty.");
325     }
327     if((empty($this->goFonPIN))&&($this->goFonConferenceOption_P=="P")&&($this->goFonConferenceOption_D=="D")){
328       $message[]= _("Please enter a PIN.");
329     }
331     if(empty($this->cn)){
332       $message[] =_("Please enter a name for the conference.");
333     }
335     if(!is_numeric($this->telephoneNumber)){
336       $message[] =_("Only numeric chars are allowed in Number field.");
337     }
339     if(!((is_numeric($this->goFonConferenceOptionLifetime))||(empty($this->goFonConferenceOptionLifetime)))){
340       $message[] =_("Only numbers are allowed in Lifetime.");
341     }
343     /* Check if add could be successful */    
344     $str = $this->SQL_add_me(false);
345     if(!empty($str)){
346       $message[] = $str;
347     }
349     return $message;
350   }
353   function SQL_add_me($save)
354   {
355     /* Check if there is at least on server configuration */
356     if(!count($this->goFonHomeServers)){
357       return( _("There is currently no asterisk server defined. Possibly you are missing a server that handles the asterisk management (goFonServer). Your settings can't be saved to asterisk database."));
358     }
359   
360     /********************
361      * Get configuration and try to connect 
362      ********************/
364     /* Check if databases are reachable, returns an error string if anything fails  */
365     $error_str = $this->check_database_accessibility();
366     if($error_str){
367       return($error_str);
368     }
370     /* Remove old entries, returns an error string if anything fails  */
371     $error_str = $this->SQL_remove_me($save);
372     if($error_str){
373       return($error_str);
374     }
376     /* Connect to current database to be able to add new entries */
377     $cfg_Current  = $this->goFonHomeServers[$this->goFonHomeServer] ;
378     $res_cur      =  @mysql_pconnect($cfg_Current['SERVER'],$cfg_Current['LOGIN'],$cfg_Current['PASSWORD']);
379     $db_cur       =  @mysql_select_db($cfg_Current['DB'],$res_cur);
381     /********************
382      * Remove entries that could cause trouble  
383      ********************/
385     /* If the current home server is different to the initial home server,
386      *  there may be already some entries with the given telephoneNumber and/or cn.
387      * We must remove those entries to avoid duplicate use of the same extension name.
388      */
389     if($this->goFonHomeServer != $this->init_HomeServer){
390       $query = "SELECT id FROM ".$cfg_Current['EXT_TABLE']." WHERE exten='".$this->telephoneNumber."' OR '".$this->cn."';";
391       $res = @mysql_query($query,$res_cur);
392       if(!$res){
393         gosa_log(@mysql_error($res_cur));
394         return(_("Can not check if there are already some entries with given telephone number and/or cn in the destination home server.").
395             "&nbsp;"._("Please have a look a the gosa logfiles."));
396       }
397       if($save && mysql_affected_rows($res_cur)) {
398         $SQL = "DELETE FROM ".$cfg_Current['EXT_TABLE']." 
399           WHERE   (exten='".$this->telephoneNumber."') 
400           OR    (exten='".$this->cn."')";
402         /* Query and ensure that everything went fine */
403         $res =  @mysql_query($SQL,$res_cur);
404         if(!$res){
405           gosa_log(@mysql_error($res_cur));
406           return(_("Can not remove entries with some telephone number and/or cn from destination home server.").
407               "&nbsp;"._("Please have a look a the gosa logfiles."));
408         }
409       }
410     }
412     /********************
413      * Add new conference entry  
414      ********************/
415     if((!empty($this->telephoneNumber))&&($save==true)){
417       /* Create string out of conference Flags */
418       $parameter  ="";
419       foreach(array("goFonConferenceOption_P","goFonConferenceOption_r","goFonConferenceOption_M","goFonConferenceOption_s",
420             "goFonConferenceOption_i","goFonConferenceOption_c","goFonConferenceOption_D") as $attrs){
421         $parameter .= $this->$attrs;
422       }
424       $i=1;
425       $EXT=array();
426       $context="GOsa";
427       // Set Language to German
428       $EXT[$i]['exten']   =$this->telephoneNumber;
429       $EXT[$i]['context'] = $context;
430       $EXT[$i]['priority']= $i;
431       $EXT[$i]['app']     ="SetLanguage";
432       $EXT[$i]['appdata'] ="de";
433       $i++;      
435       if($this->goFonConferenceOption_r == "r"){
437         // Recordingformat for conference
438         $EXT[$i]['exten']   =$this->telephoneNumber;
439         $EXT[$i]['context'] =$context;
440         $EXT[$i]['priority']= $i;
441         $EXT[$i]['app']     ="Setvar";
442         $EXT[$i]['appdata'] ="MEETME_RECORDINGFORMAT=".$this->goFonConferenceOptionFormat;
443         $i++;      
445       }
447       // Answer Call
448       $EXT[$i]['exten']   =$this->telephoneNumber;
449       $EXT[$i]['context'] =$context;
450       $EXT[$i]['priority']=$i;
451       $EXT[$i]['app']     ="answer";
452       $EXT[$i]['appdata'] ="";
453       $i++;      
455       // Start Conference 
456       $EXT[$i]['exten']   =$this->telephoneNumber;
457       $EXT[$i]['context'] =$context;
458       $EXT[$i]['priority']=$i;
459       $EXT[$i]['app']     ="MeetMe";
461       if(empty($this->goFonPIN)) {
462         $EXT[$i]['appdata'] =$this->telephoneNumber."|".$parameter;
463       }else{
464         $EXT[$i]['appdata'] =$this->telephoneNumber."|".$parameter."|".$this->goFonPIN;
465       }
466       $i++; 
468       // Start Conference 
469       $EXT[$i]['exten']   =$this->cn;
470       $EXT[$i]['context'] =$context;
471       $EXT[$i]['priority']=1;
472       $EXT[$i]['app']     ="Goto";
473       $EXT[$i]['appdata'] =$this->telephoneNumber."|1";
474       $SQL=array();
476       foreach($EXT as $keytop => $valtop){
477         $s_keys = "";
478         $s_values = ""; 
479         foreach($valtop as $key=>$val){
480           $s_keys   .="`".$key."`,";
481           $s_values .="'".$val."',";
482         }
483         $s_keys   =preg_replace("/\,$/","",$s_keys); 
484         $s_values =preg_replace("/\,$/","",$s_values); 
485         $SQL[]="INSERT INTO ".$cfg_Current['EXT_TABLE']." (".$s_keys.") VALUES (".$s_values.");"; 
486       }
487       foreach($SQL as $sqlsyn){
488         mysql_query($sqlsyn,$res_cur);
489       }
490     } 
492     @mysql_close($res_cur);
493   }
496   /* Remove initial entry from database 
497    * This function checks if there is an entry in the 
498    *  initial home server that uses this->old_cn or $this->old_tele_number
499    *  and removes this entries. 
500    * This function is called from save and remove_from parent.
501    * 
502    *  The parameter '$save' is false if we just 
503    *   want to check if a remove is possible. 
504    *  And true if we realy want to remove the entries.   
505    */
506   function SQL_remove_me($save)
507   {
508     /* check database access */
509     $str = $this->check_database_accessibility();
510     if($str){
511       return($str);
512     }
514     /* Connect to old database */
515     $cfg_Init  = $this->goFonHomeServers[$this->init_HomeServer] ;
516     $r_init    =  @mysql_pconnect($cfg_Init['SERVER'],$cfg_Init['LOGIN'],$cfg_Init['PASSWORD']);
517     $db_init   =  @mysql_select_db($cfg_Init['DB'],$r_init);
518    
519     /* Check if there is an old entry */
520     $query = "SELECT id FROM ".$cfg_Init['EXT_TABLE']." WHERE exten='".$this->old_tele_number."' OR '".$this->old_cn."';";
521     $res = @mysql_query($query,$r_init);
522     if(!$res){
523       gosa_log(@mysql_error($r_init));
524       return(_("Can not check if entry exists in old database. Please have a look a the gosa logfiles."));
525     }
526    
527     /* There are entries using this cn and/or phone number */
528     if($save && mysql_affected_rows($r_init)) {
529       $SQL = "DELETE FROM ".$cfg_Init['EXT_TABLE']." 
530       WHERE   (exten='".$this->old_tele_number."') 
531         OR    (exten='".$this->old_cn."')";
532   
533       /* Query and ensure that everything went fine */
534       $res =  @mysql_query($SQL,$r_init);
535       if(!$res){
536         gosa_log(@mysql_error($r_init));
537         return(_("Can not remove old entries from initial home server. Please have a look a the gosa logfiles."));
538       }
540     }//ENDE  old num availiable ...
541     @mysql_close($r_init);
542     return(false);
543   }
547   /* This function checks if the given phonenumbers are available or already in use*/
548   function is_number_used()
549   {
550     $ldap= $this->config->get_ldap_link();
551     $ldap->cd($this->config->current['BASE']);
552     $ldap->search("(|(objectClass=goFonAccount)(objectClass=goFonQueue)(objectClass=goFonConference))", array("telephoneNumber","cn","uid"));
553     while($attrs = $ldap->fetch()) {
554       unset($attrs['telephoneNumber']['count']);
555       foreach($attrs['telephoneNumber'] as $tele){
556         if(!isset($attrs['cn'][0])) $attrs['cn'][0]=$attrs['dn'];
557         if(!isset($attrs['uid'][0])) $attrs['uid'][0]=$attrs['dn'];
558         $numbers[$tele]=$attrs;
559       }
560     }
562     $num = $this->telephoneNumber;
563     if((isset($numbers[$num]))&&(($numbers[$num]['cn'][0]!=$this->old_cn))){
564       if(isset($numbers[$num]['uid'][0])){
565         return sprintf(_("The specified telephonenumber '%s' is already assigned to '%s'."),$num,$numbers[$num]['uid'][0]);
566       }else{
567         return sprintf(_("The specified telephonenumber '%s' is already assigned to '%s'."),$num,$numbers[$num]['cn'][0]);
568       }
569     }
570   }
573   /* Save to LDAP */
574   function save()
575   {
577     if((!isset($this->attrs['goFonPIN']))){
578       $pin_use = false;
579     }else{
580       $pin_use = true;
581     }
583     plugin::save();
585     if(empty($this->old_tele_number)){
586       $this->old_tele_number= $this->telephoneNumber;
587     }
589     $this->SQL_add_me(true);
591     if(empty($this->goFonConferenceOption_P)){
592       if($pin_use){
593         $this->attrs['goFonPIN']=array();
594       }else{
595         unset($this->attrs['goFonPIN']);
596       }
597     }
598     $this->attrs['goFonConferenceOption']="";
599     foreach(array("goFonConferenceOption_P","goFonConferenceOption_r","goFonConferenceOption_M","goFonConferenceOption_s",
600           "goFonConferenceOption_i","goFonConferenceOption_c","goFonConferenceOption_D","goFonConferenceOptionFormat") as $attrs){
601       $this->attrs['goFonConferenceOption'] .= $this->$attrs;
602       unset($this->attrs[$attrs]);         
603     }
605     $this->attrs['goFonConferenceOption'].="|".$this->goFonConferenceOptionLifetime;
606     unset($this->attrs['goFonConferenceOptionLifetime']);
608     /* Write back to ldap */
610     unset($this->attrs['base']);
612     foreach($this->attributes as $atr){
613       if(chkacl($this->acl, $atr)!=""){
614         unset($this->attrs[$atr]);
615       }
616     }
618     $ldap= $this->config->get_ldap_link();
619     $ldap->cd($this->config->current['BASE']);
620     $ldap->cat($this->dn, array('dn'));
622     if ($ldap->count()){
623       $ldap->cd($this->dn);
624       $this->cleanup();
625       $ldap->modify ($this->attrs); 
626       $this->handle_post_events('modify');
627     } else {
628       $ldap->create_missing_trees(preg_replace('/^[^,]+,/', '', $this->dn));
629       $ldap->cd($this->dn);
630       $ldap->add($this->attrs);
631       $this->handle_post_events('add');
632     }
633     show_ldap_error($ldap->get_error(), _("Saving phone conference failed"));
635     /* Optionally execute a command after we're done */
636     $this->postcreate();
637   }
641 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
642 ?>