Code

a6e1c410f0de0ed3de5f8c19a146e1f98412f291
[gosa.git] / gosa-core / include / class_gosaSupportDaemon.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id$$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 class gosaSupportDaemon
24 {
25   private $s_host       = "";
26   private $i_port       = 0;
27   private $s_encryption_key = "";
29   private $o_sock       = NULL;
30   private $f_timeout    = 2;
31   private $s_error      = "";
32   private $b_error      = FALSE;
34   private $is_connected     = FALSE;
37   /*! \brief  Creates a new gosaSupportDaemon object.
38     @param string   Host    The Host where the daemon is running on.  
39     @param integer  Port    The port which the daemon use.
40     @param string   Key     The encryption string.
41     @param boolean  Connect Directly connect to daemon socket.
42     @param float    Timeout The timelimit for all socket actions.
43    */
44   public function __construct($connect=TRUE,$timeout=2)
45   {
46     #FIXME: bad idea about referencing global variables from within classes
47     global $config;
49     # load from config, store statically
50     if (isset($config->current['GOSA_SI'])){
52       if ($this->s_host == ""){
53         $this->s_host= preg_replace("/^.*@([^:]+):.*$/", "$1", $config->current['GOSA_SI']);
54         $this->i_port= preg_replace("/^.*@[^:]+:(.*)$/", "$1", $config->current['GOSA_SI']);
55         $this->s_encryption_key = preg_replace("/^(.*)@[^:]+:.*$/", "$1", $config->current['GOSA_SI']);
56       }
58       $this->f_timeout = $timeout;
59       if($connect){
60         $this->connect();
61       }
62     }
63   }
66   /*! \brief  Establish daemon connection. 
67     @return boolean Returns true if the connection was succesfully established. 
68    */
69   public function connect()
70   {
71     $this->o_sock = new Socket_Client($this->s_host,$this->i_port,TRUE,$this->f_timeout);
72     if($this->o_sock->connected()){ 
73       $this->o_sock->setEncryptionKey($this->s_encryption_key); 
74       $this->is_connected = TRUE;
75     }else{
76       $this->set_error($this->o_sock->get_error());
77       $this->disconnect();
78       new log("debug","gosaSupportDaemon::connect()", "Could not connect to server.", array(),$this->get_error());
79     }
80     return($this->is_connected);
81   }
84   /*! \brief  Disconnect from gosa daemon.
85    */
86   public function disconnect()
87   {
88     $this->o_sock->close();
89     $this->is_connected = FALSE;
90   }
93   /*! \brief  Sets an error message, which can be returned with get_error().
94     @param  string  The Error message,
95    */
96   private function set_error($str)
97   {
98     $this->b_error = TRUE;
99     $this->s_error = $str;
100   }
103   /*! \brief  Sets an error message, which can be returned with get_error().
104     @param  string  The Error message,
105    */
106   private function reset_error()
107   {
108     $this->b_error = FALSE;
109     $this->s_error = "";
110   }
113   /*! \brief  Checks if an error occured.
114     @return boolean returns TRUE or FALSE, whether there is an error or not.
115    */
116   public function is_error()
117   {
118     return($this->b_error);
119   }
122   /*! \brief  Returns the last error. 
123     @return Returns the last error.
124    */
125   public function get_error()
126   {
127     $str = $this->s_error;
128     $str = preg_replace("/ /","&nbsp;",$str);
129     return($str);
130   }
133   /*! \brief  Returns an array containing all queued entries.
134     @return Array All queued entries as an array.
135    */
136   public function get_queued_entries($event_types = array("*"),$from=-1,$to=-1,$sort="timestamp DESC")
137   {
138     $this->reset_error();
139     $ret = array();
141     $tags = "";
142     foreach($event_types as $type){
143       $tags .= "<phrase><headertag>".$type."</headertag></phrase>";
144     }
145     if(count($event_types) > 1){
146       $tags = "<connector>or</connector>".$tags;
147     }
148     if(count($event_types)){
149       $tags = "<where><clause>".$tags."</clause></where>";
150     }
152     $xml_msg = "<xml>
153       <header>gosa_query_jobdb</header>
154       <target>GOSA</target>
155       <source>GOSA</source>
156       ".$tags."
158       <orderby>".$sort."</orderby>";
159 if($from != -1 && $to != -1){
160 $xml_msg.= "
161       <limit>
162        <from>".$from."</from>
163        <to>".$to."</to>
164       </limit>";
166 $xml_msg.= "
167       </xml>";
169     if($this->connect()){
170       $this->o_sock->write($xml_msg);
171       $str = trim($this->o_sock->read());
172       $entries = $this->xml_to_array($str);
173       if(isset($entries['XML']) && is_array($entries['XML'])){
175         /* Check if returned values represent a valid answer */
176         if(isset($entries['XML'])){
177           
178           /* Unset header tags */
179           foreach(array("HEADER","SOURCE","TARGET") as $type){
180             unset($entries['XML'][$type]);
181           }
182           $ret = $entries['XML']; 
183         }
184       }
185     }
186     
187     /* Remove session ID. No one is interested in this... */
188     unset($ret['SESSION_ID']);
190     return($ret);
191   }
194   /*! \brief  Checks if the given ids are used queue ids.
195     @param  Array   The ids we want to check..
196     @return Array   An array containing all ids as index and TRUE/FALSE as value. 
197    */
198   public function ids_exist($ids)
199   {
200     if(!is_array($ids)){
201       trigger_error("Requires an array as parameter.");
202       return;
203     }
204     $this->reset_error();
206     $ret = array();
208     $xml_msg = "<xml>
209       <header>gosa_query_jobdb</header>
210       <target>GOSA</target>
211       <source>GOSA</source>
212       <where>
213       <clause>
214       <connector>or</connector>";
215     foreach($ids as $id){
216       $xml_msg .= "<phrase>
217         <operator>eq</operator>
218         <id>".$id."</id>
219         </phrase>";
220     }
221     $xml_msg .= "</clause>
222       </where>
223       </xml>";
225     if($this->connect()){
226       $this->o_sock->write($xml_msg);
227       $str = trim($this->o_sock->read());
228       $entries = $this->xml_to_array($str);
229       if(isset($entries['XML']) && is_array($entries['XML'])){
230         foreach($entries['XML'] as $entry){
231           if(isset($entry['ID'])){
232             $ret[] = $entry['ID'];
233           }
234         }
235       }
236     }
237     return($ret);
238   }
241   /*! \brief  Returns an entry containing all requested ids.
242     @param  Array   The IDs of the entries we want to return.
243     @return Array   Of the requested entries. 
244    */
245   public function get_entries_by_mac($macs)
246   {
247     if(!is_array($macs)){
248       trigger_error("Requires an array as parameter.");
249       return;
250     }
251     $this->reset_error();
253     $ret = array();
255     $xml_msg = "<xml>
256       <header>gosa_query_jobdb</header>
257       <target>GOSA</target>
258       <source>GOSA</source>
259       <where>
260       <clause>
261       <connector>or</connector>";
262     foreach($macs as $mac){
263       $xml_msg .= "<phrase>
264         <operator>eq</operator>
265         <macaddress>".$mac."</macaddress>
266         </phrase>";
267     }
268     $xml_msg .= "</clause>
269       </where>
270       </xml>";
272     if($this->connect()){
273       $this->o_sock->write($xml_msg);
274       $str = trim($this->o_sock->read());
275       $entries = $this->xml_to_array($str); 
276       if(isset($entries['XML'])){
277         foreach($entries['XML'] as $name => $entry){
278           if(preg_match("/^ANSWER[0-9]*$/",$name)){
279             $ret[$name] = $entry;
280           }
281         }
282       }
283     }
284     return($ret);
285   }
288   /*! \brief  Returns an entry containing all requested ids.
289     @param  Array   The IDs of the entries we want to return.
290     @return Array   Of the requested entries. 
291    */
292   public function get_entries_by_id($ids)
293   {
294     if(!is_array($ids)){
295       trigger_error("Requires an array as parameter.");
296       return;
297     }
298     $this->reset_error();
300     $ret = array();
302     $xml_msg = "<xml>
303       <header>gosa_query_jobdb</header>
304       <target>GOSA</target>
305       <source>GOSA</source>
306       <where>
307       <clause>
308       <connector>or</connector>";
309     foreach($ids as $id){
310       $xml_msg .= "<phrase>
311         <operator>eq</operator>
312         <id>".$id."</id>
313         </phrase>";
314     }
315     $xml_msg .= "</clause>
316       </where>
317       </xml>";
319     if($this->connect()){
320       $this->o_sock->write($xml_msg);
321       $str = trim($this->o_sock->read());
322       $entries = $this->xml_to_array($str); 
323       if(isset($entries['XML'])){
324         foreach($entries['XML'] as $name => $entry){
325           if(preg_match("/^ANSWER[0-9]*$/",$name)){
326             $ret[$name] = $entry;
327           }
328         }
329       }
330     }
331     return($ret);
332   }
335   /*! \brief  Checks if the given id is in use.
336     @param  Integer The ID of the entry.
337     @return Boolean TRUE if entry exists. 
338    */
339   public function id_exists($id)
340   {
341     if(!is_numeric($id)){
342       trigger_error("Requires an integer as parameter.");
343       return;
344     }
346     $this->reset_error();
348     $xml_msg = "<xml>
349       <header>gosa_query_jobdb</header>
350       <target>GOSA</target>
351       <source>GOSA</source>
352       <where>
353       <clause>
354       <phrase>
355       <operator>eq</operator>
356       <id>".$id."</id>
357       </phrase>
358       </clause>
359       </where>
360       </xml>";
362     if($this->connect()){
363       $this->o_sock->write($xml_msg);
364       $str = trim($this->o_sock->read());
365       $entries = $this->xml_to_array($str); 
366       if( isset($entries['XML']['HEADER']) && 
367           $entries['XML']['HEADER']=="answer" && 
368           isset($entries['XML']['ANSWER1'])){
369         return(TRUE);
370       }
371     }
372     return(FALSE);
373   }
376   /*! \brief  Returns an entry from the gosaSupportQueue
377     @param  Integer The ID of the entry we want to return.
378     @return Array   Of the requested entry. 
379    */
380   public function get_entry_by_id($id)
381   {
382     if(!is_numeric($id)){
383       trigger_error("Requires an integer as parameter.");
384       return;
385     }
386     $this->reset_error();
387   
388     $ret = array();
389     $xml_msg = "<xml>
390       <header>gosa_query_jobdb</header>
391       <target>GOSA</target>
392       <source>GOSA</source>
393       <where>
394       <clause>
395       <phrase>
396       <operator>eq</operator>
397       <id>".$id."</id>
398       </phrase>
399       </clause>
400       </where>
401       </xml>";
402     if($this->connect()){
403       $this->o_sock->write($xml_msg);
404       $str = trim($this->o_sock->read());
405       $entries = $this->xml_to_array($str); 
406       if( isset($entries['XML']['HEADER']) &&
407           $entries['XML']['HEADER']=="answer" &&
408           isset($entries['XML']['ANSWER1'])){
409         $ret = $entries['XML']['ANSWER1'];
410       }
411     }
412     return($ret);
413   }
416   /*! \brief  Removes a set of entries from the GOsa support queue. 
417     @param  Array The IDs to remove.
418     @return Boolean True on success.
419    */
420   public function remove_entries($ids)
421   {
422     if(!is_array($ids)){
423       trigger_error("Requires an array as parameter.");
424       return;
425     }
427     $this->reset_error();
429     $ret = array();
431     $xml_msg = "<xml>
432       <header>gosa_delete_jobdb_entry</header>
433       <target>GOSA</target>
434       <source>GOSA</source>
435       <where>
436       <clause>
437       <connector>or</connector>";
438     foreach($ids as $id){
439       $xml_msg .= "<phrase>
440         <operator>eq</operator>
441         <id>".$id."</id>
442         </phrase>";
443     }
444     $xml_msg .= "</clause>
445       </where>
446       </xml>";
448     if($this->connect()){
449       $this->o_sock->write($xml_msg);
450       $str = $this->o_sock->read();
451       $entries = $this->xml_to_array($str);
452       if(isset($entries['XML']) || isset($entries['COUNT'])){
453         new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::remove_entries()", $ids,"SUCCESS");
454         return(TRUE);
455       }else{
456         new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::remove_entries()", $ids,"FAILED ".$this->get_error());
457       }
458     }
459     return(FALSE);
460   }
464   /*! \brief  Removes an entry from the GOsa support queue. 
465     @param  Integer The ID of the entry we want to remove.
466     @return Boolean True on success.
467    */
468   public function remove_entry($id)
469   {
470     return($this->remove_entries(array($id)));
471   }
474   /*! \brief  Parses the given xml string into an array 
475     @param  String XML string  
476     @return Array Returns an array containing the xml structure. 
477    */
478   private function xml_to_array($xml)
479   {
480     $params = array();
481     $level = array();
482     $parser  = xml_parser_create_ns();
483     xml_parse_into_struct($parser, $xml, $vals, $index);
485     $err_id = xml_get_error_code($parser);
486     if($err_id){
487       xml_parser_free($parser);
488     }else{
489       xml_parser_free($parser);
491       foreach ($vals as $xml_elem) {
492         if ($xml_elem['type'] == 'open') {
493           if (array_key_exists('attributes',$xml_elem)) {
494             list($level[$xml_elem['level']],$extra) = array_values($xml_elem['attributes']);
495           } else {
496             $level[$xml_elem['level']] = $xml_elem['tag'];
497           }
498         }
499         if ($xml_elem['type'] == 'complete') {
500           $start_level = 1;
501           $php_stmt = '$params';
502           while($start_level < $xml_elem['level']) {
503             $php_stmt .= '[$level['.$start_level.']]';
504             $start_level++;
505           }
506           $php_stmt .= '[$xml_elem[\'tag\']] = $xml_elem[\'value\'];';
507           @eval($php_stmt);
508         }
509       }
510     }
512     if(!isset($params['XML'])){
513       if (!array_key_exists('XML', $params)){
514         $this->set_error(_("Cannot not parse XML!"));
515       }
516       $params = array("COUNT" => 0);
517     }
519     return($params); 
520   }
523   /*! \brief  Updates an entry with a set of new values, 
524     @param  Integer The ID of the entry, we want to update.
525     @param  Array   The variables to update.   
526     @return Boolean Returns TRUE on success. 
527    */
528   public function update_entries($ids,$data)
529   {
530     $this->reset_error();
531     if(!is_array($ids)){
532       trigger_error("Requires an array as first parameter.");
533       return;
534     }
536     if(!is_array($data)){
537       trigger_error("Requires an array as second parameter.");
538       return;
539     }
541     $attr = "";
542     foreach($data as $key => $value){
543       if(is_array($value)){
544         foreach($value as $sub_value){
545           $attr.= "<$key>".strtolower($sub_value)."</$key>\n";
546         }
547       }else{
548         $attr.= "<$key>".strtolower($value)."</$key>\n";
549       }
550     }
552     $xml_msg = "<xml>
553       <header>gosa_update_status_jobdb_entry</header>
554       <target>GOSA</target>
555       <source>GOSA</source>
556       <where>
557       <clause>
558       <connector>or</connector>";
559     foreach($ids as $id){
560       $xml_msg .= "<phrase>
561         <operator>eq</operator>
562         <id>".$id."</id>
563         </phrase>";
564     }
565     $xml_msg .= "</clause>
566       </where>
567       <update>
568       ".$attr." 
569       </update>
570       </xml>";
572     if($this->connect()){
574       $this->o_sock->write($xml_msg);
575       $str      = trim($this->o_sock->read());
576       $entries = $this->xml_to_array($str);
577       if(isset($entries['XML'])){
578         if(isset($entries['XML']['ERROR_STRING'])) {
579           $this->set_error($entries['XML']['ERROR_STRING']);
580           new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::update_entries()", $ids,"FAILED setting (".$attr.") error was ".$this->get_error());
581           return(FALSE);
582         }
583         new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::update_entries()", $ids,"SUCCESS");
584         return(TRUE);
585       }
586     }
587     return(FALSE);
588   }
591   /*! \brief  Returns the number of currently queued objects.
592       @return Integer  
593    */
594   public function number_of_queued_entries()
595   {
596     $xml_msg ="<xml><header>gosa_count_jobdb</header><target>GOSA</target><source>GOSA</source></xml>";
597     $this->connect();
598     if($this->connect()){
599       $this->o_sock->write($xml_msg);
600       $str     = trim($this->o_sock->read());
601       $entries = $this->xml_to_array($str);
602       if(isset($entries['XML'])){
603         return($entries['XML']['COUNT']);
604       }
605     }
606     return(-1);
607   } 
610   public function send_data($header, $to, $data= array(), $answer_expected = FALSE)
611   {
612     $xml_message= "";
614     /* Prepare data */
615     foreach ($data as $key => $value){
616       if(is_array($value)){
617         foreach($value as $sub_val){
618           $xml_message.= "<$key>$sub_value</$key>";
619         }
620       }else{
621         $xml_message.= "<$key>$value</$key>";
622       }
623     }
625     /* Multiple targets? */
626     if (!is_array($to)){
627       $to_targets= array($to);
628     } else {
629       $to_targets= $to;
630     }
632     /* Build target strings */
633     $target ="";
634     foreach($to_targets as $to){
635       $target.= "<target>$to</target>";
636     }
638     return $this->_send("<xml><header>$header</header><source>GOSA</source>$target".$xml_message."</xml>",$answer_expected);
639   }
642   /* Allows simply appending a new DaemonEvent 
643    */
644   public function append($event)
645   {
646     if(!($event instanceof DaemonEvent)){
647       return(FALSE);
648     }
649   
650     $this->reset_error();
652     /* Add to queue if new 
653      */
654     if($event->is_new()){
656       $request_answer = FALSE;
657       if($event->get_type() == SCHEDULED_EVENT){
658         $action = $event->get_schedule_action();
659       }elseif($event->get_type() == TRIGGERED_EVENT){
660         $action = $event->get_trigger_action();
661       }else{
662         trigger_error("Unknown type of queue event given.");
663         return(FALSE);
664       }
666       /* Get event informations, like targets..
667        */
668       $targets    = $event->get_targets();
669       $data       = $event->save();
671       /* Append an entry for each target 
672        */
673       foreach($targets as $target){
674         $data['macaddress'] = $target;
675         $this->send_data($action,$target,$data,$request_answer);
677         if($this->is_error()){
678           return(FALSE);
679         }
680       }
681       return(TRUE);
682     }else{
684       /* Updated edited entry.
685        */
686       $id                 = $event->get_id();
687       $data               = $event->save();
688       return($this->update_entries(array($id),$data));
689     }
691     return(FALSE);
692   }
695 /*! \brief  Returns an array containing all queued entries.
696     @return Array All queued entries as an array.
697    */
698   public function _send($data, $answer_expected= FALSE)
699   {
700     $this->reset_error();
701     $ret = array();
703     if($this->connect()){
704       $this->o_sock->write($data);
705       if ($answer_expected){
706         $str = trim($this->o_sock->read());
707         $entries = $this->xml_to_array($str);
708         if(isset($entries['XML']) && is_array($entries['XML'])){
709           $ret = $entries;
710           if(isset($entries['XML']['ERROR_STRING'])) {
711             $this->set_error($entries['XML']['ERROR_STRING']);
712             new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::_send()", array($data=>$data),"FAILED ".$this->get_error());
713           }else{
714             new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::_send()", array($data=>$data),"SUCCESS");
715           }
716         }
717       }else{
718         new log("debug","DaemonEvent (IDS) ", "gosaSupportDaemon::_send()", array($data=>$data),"Fire & forget, not result.! ".$this->get_error());
719       }
720     }
721     return($ret);
722   }
725   static function send($header, $to, $data= array(), $answer_expected = FALSE)
726   {
727     $xml_message= "";
729     /* Get communication object */
730     $d= new gosaSupportDaemon(TRUE,10);
732     /* Prepare data */
733     foreach ($data as $key => $value){
734       if(is_array($value)){
735         foreach($value as $sub_val){
736           $xml_message.= "<$key>$sub_value</$key>";
737         }
738       }else{
739         $xml_message.= "<$key>$value</$key>";
740       }
741     }
743     /* Multiple targets? */
744     if (!is_array($to)){
745       $to_targets= array($to);
746     } else {
747       $to_targets= $to;
748     }
750     /* Build target strings */
751     $target ="";
752     foreach($to_targets as $to){
753       $target.= "<target>$to</target>";
754     }
756     return $d->_send("<xml><header>$header</header><source>GOSA</source>$target".$xml_message."</xml>",$answer_expected);
757   }
760   /*! \brief  Removes all jobs from the queue that are tiggered with a specific macAddress.
761       @param  String  $mac  The mac address for which we want to remove all jobs.      
762    */
763   function clean_queue_from_mac($mac)
764   {
765     global $config;
767     /* First of all we have to check which jobs are startet 
768      *  for $mac 
769      */
770     $xml_msg ="<xml><header>gosa_query_jobdb</header><target>GOSA</target><source>GOSA</source><where><clause><phrase><macaddress>".$mac."</macaddress></phrase></clause></where></xml>";  
771     
772     new log("debug","DaemonEvent ", "gosaSupportDaemon::clean_queue_from_mac()", array($mac => $mac)," start cleaning.");
773  
774     $data = $this->_send($xml_msg,TRUE);
775     if(is_array($data) && isset($data['XML'])){
776       $already_aborted = FALSE;
777       foreach($data['XML']  as $name => $entry){
778         if(preg_match("/answer[0-9]*/i",$name)){
779           $entry['STATUS'] = strtoupper($entry['STATUS']);
780           switch($entry['STATUS']){
782             case 'PROCESSING' :
784               /* Send abort event, but only once 
785                */
786               if($already_aborted){
787                 break;
788               }elseif(class_available("DaemonEvent_faireboot")){
789                 $already_aborted = TRUE;
790                 $tmp = new DaemonEvent_faireboot($config);
791                 $tmp->add_targets(array($mac));
792                 $tmp->set_type(TRIGGERED_EVENT);
793                 if(!$this->append($tmp)){
794                   msg_dialog::display(_("Error"), sprintf(_("Cannot send abort event for entry %s!"),$entry['ID']) , ERROR_DIALOG);
795                   new log("debug","DaemonEvent ", "gosaSupportDaemon::clean_queue_from_mac()", array($mac => $mac),
796                       "FAILED, could not send 'DaemonEvent_faireboot' for entry ID (".$entry['ID'].") - ".$this->get_error());
797                 }else{
798                   new log("debug","DaemonEvent ", "gosaSupportDaemon::clean_queue_from_mac()", array($mac => $mac),
799                       "SUCCESS, send 'DaemonEvent_faireboot' for entry ID (".$entry['ID'].")");
800                 }
801                 ;break;
802               }else{
803                 /* Couldn't find abort event, just remove entry */
804               }
806             case 'WAITING':
807             case 'ERROR':
808             default :
809             
810               /* Simply remove entries from queue. 
811                *  Failed or waiting events, can be removed without any trouble.
812                */ 
813               if(!$this->remove_entries(array($entry['ID']))){
814                 msg_dialog::display(_("Error"), sprintf(_("Cannot remove entry %s!"),$entry['ID']) , ERROR_DIALOG);
815               }
816               ;break;
817           }
818     
819         }
820       }
821     }
822   }
825 static function ping($target)
827   if (tests::is_mac($target)){
828     /* Get communication object */
829     $d= new gosaSupportDaemon(TRUE,0.5);
830     $answer= $d->_send("<xml><header>gosa_ping</header><source>GOSA</source><target>$target</target></xml>", TRUE);
831     return (count($answer) ? TRUE:FALSE);
832   }
834   return (FALSE);
839 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
840 ?>