source: trunk/contactcenter/inc/class.bo_ldap_manager.inc.php @ 1600

Revision 1600, 19.4 KB checked in by rafaelraymundo, 15 years ago (diff)

Ticket #619 - Incluidos campos configuraveis (matricula/celular/setor) para o contactcenter

  • Property svn:eol-style set to native
  • Property svn:executable set to *
RevLine 
[2]1<?php
2  /***************************************************************************\
3  * eGroupWare - Contacts Center                                              *
4  * http://www.egroupware.org                                                 *
5  * Written by:                                                               *
6  *  - Raphael Derosso Pereira <raphaelpereira@users.sourceforge.net>         *
7  * ------------------------------------------------------------------------- *
8  *  This program is free software; you can redistribute it and/or modify it  *
9  *  under the terms of the GNU General Public License as published by the    *
10  *  Free Software Foundation; either version 2 of the License, or (at your   *
11  *  option) any later version.                                               *
12  \***************************************************************************/
13
[285]14       
[2]15        /*
16                This class is responsible for the LDAP control/generic functions and for
17                configuration gathering
18        */
[284]19        include_once('class.Thread.inc.php');
[2]20
[285]21        class bo_ldap_manager
[2]22        {
[285]23               
[2]24                var $srcs;
25
[285]26               
[2]27                function bo_ldap_manager ()
28                {
29                        if (!($this->srcs = $GLOBALS['phpgw']->session->appsession('bo_ldap_manager.srcs','contactcenter')))
30                        {
31                                $c = CreateObject('phpgwapi.config','contactcenter');
32                                $data = $c->read_repository();
[285]33                               
[2]34                                if (!$data or $data['cc_global_source0'] !== 'ldap')
35                                {
36                                        $this->srcs = null;
37                                        return;
38                                }
[285]39                               
[2]40                                $this->srcs = array(
41                                        1 => array(
42                                                'name'   => $data['cc_catalog_name'],
43                                                'host'   => $data['cc_ldap_host0'],
44                                                'dn'     => $data['cc_ldap_context0'],
45                                                'acc'    => $data['cc_ldap_browse_dn0'],
46                                                'pw'     => $data['cc_ldap_pw0'],
[1600]47                                                'obj'    => 'inetOrgPerson',
[285]48                                                'branch' => strtolower('ou'),
49                                                'visible' => $data['cc_ldap_query_automatic']
[2]50                                        )
51                                );
52                        }
53                }
54
55                function new_ldap_source ( $source_name, $charset, $host, $port, $dn_root, $dn_admin, $admin_pass, $contact_objectclass )
56                {
57                }
[285]58       
[2]59                /*
[285]60               
[2]61                        @function get_all_ldap_sources
62                        @abstract Returns an array containing all LDAP sources informations
63                        @author Raphael Derosso Pereira
[285]64               
[2]65                        @return array All LDAP information
66                                $return = array(
67                                        <id_source> => array(
68                                                'host' => (string),
69                                                'dn'   => (string),
70                                                'acc'  => (string),
[285]71                                                'pw'   => (string)   
[2]72                                        ),
73                                        ...
74                                )
[285]75                               
[2]76                        TODO: Return multiple sources...
77                */
78                function get_all_ldap_sources (  )
79                {
80                        return $this->srcs;
81                }
[285]82               
[284]83                /*
84                * @function get_external_ldap_sources
[1600]85                * @author Mï¿œrio Cï¿œsar Kolling <mario.kolling@serpro.gov.br>
[284]86                * @abstract returns an array with the external sources
87                * @return (array) the external sources
88                */
89
90                function get_external_ldap_sources()
91                {
92                        include(PHPGW_INCLUDE_ROOT . '/contactcenter/setup/external_catalogs.inc.php' );
93                        //include('external_catalogs.inc.php' );
94                        return $external_srcs;
95                }
96
97                /*
98                 * @function get_ldap_fields_association
99                 * @abstract get the fields associantion for ldap source
100                 * @return an array with attribute mappings
101                 */
[2]102                function get_ldap_fields_association ( $id_source )
103                {
[285]104                       
[2]105                        $op_iop = array(
[1600]106                                'contact.uidnumber'                => array('uidNumber'),
[2]107                                'contact.id_contact'               => array('dn'),
108                                'contact.photo'                    => array('jpegPhoto'),
109                                'contact.prefixes.prefix'          => false,
110                                'contact.alias'                    => array('alias'),
111                                'contact.given_names'              => array('givenName'),
112                                'contact.family_names'             => array('sn'),
113                                'contact.names_ordered'            => array('cn'),//,'displayName'),
114                                'contact.suffixes.suffix'          => false,
115                                'contact.birthdate'                => false,
116                                'contact.sex'                      => false,
117                                'contact.pgp_key'                  => false,
118                                'contact.notes'                    => false,
[285]119                                'contact.mail_forwarding_address' => array('mailForwardingAddress'),
120                                'contact.account_type' => array('phpgwAccountType'),
121                'contact.account_status'           => array('phpgwAccountStatus'),
122                'contact.account_visible'          => array('phpgwAccountVisible'),
[284]123                                'contact.object_class'             => array('objectClass'),
[2]124                                'contact.business_info.title'      => array('title'),
[1600]125                                'contact.business_info.department' => array('ou'), // Setor do empregado...
126                                'contact.business_info.empNumber'  => array('employeeNumber'), // Matricula do empregado
127
128                                'contact.business_info.celPhone'   => array('mobile'), // Celular empresarial do empregado
129
130                                'contact.company.company_name'     => array('o'),
[2]131                                'contact.company.company_notes'    => array('businessCategory'),
[285]132                               
[2]133                                'contact.contact_related.names_ordered' => 'contact.contact_related.typeof_relation.contact_relation_name',
134                                'contact.contact_related.typeof_relation.contact_relation_name' =>  array(
135                                        'manager'   => array('manager'),
136                                        'secretary' => array('secretary')
137                                ),
[285]138                               
[2]139                                'contact.address.address1'         => 'contact.address.typeof_address.contact_address_type_name',
140                                'contact.address.typeof_address.contact_address_type_name' => array(
141                                        'home' => array('street', 'st', 'postalAddress', 'homePostalAddress'),
142                                ),
[285]143                               
[2]144                                'contact.address.postal_code'      => 'contact.address.typeof_address.contact_address_type_name',
145                                'contact.address.typeof_address.contact_address_type_name' => array(
146                                        'home' => array('PostalCode'),
147                                ),
[285]148                               
[2]149                                'contact.address.city.city_name'   => 'contact.address.typeof_address.contact_address_type_name',
150                                'contact.address.typeof_address.contact_address_type_name' => array(
151                                        'home' => array('l'),
152                                ),
[285]153                               
[2]154                                'contact.address.city.state.state_name'       => 'contact.address.typeof_address.contact_address_type_name',
155                                'contact.address.typeof_address.contact_address_type_name' => array(
156                                        'home' => false,
157                                ),
[285]158                               
[2]159                                'contact.address.city.country.id_country'     => 'contact.address.typeof_address.contact_address_type_name',
160                                'contact.address.typeof_address.contact_address_type_name' => array(
161                                        'home' => array('c')
162                                ),
[285]163                               
[2]164                                'contact.connection.connection_value'         => 'contact.connection.typeof_connection.contact_connection_type_name',
165                                'contact.connection.typeof_connection.contact_connection_type_name' => array (
166                                        'email'  => array('mail'),
167                                        'phone'  => array('telephoneNumber'),
[1600]168                                //      'mobile' => array('mobile'),
169                                //      'pager'  => array('pager'), // idem ao comentario abaixo, do atributo fax;
170                                //      'fax'    => array('facsimileTelephoneNumber'), //linha comentada para nao trazer
171                                // o atributo fax do Ldap; correcao temporaria para nao exibir o fax no ContactCenter
172                                //(estava sobrepondo o telefone do usuario)
173
[2]174                                        'telex'  => array('telexNumber')
175                                ),
176                        );
[285]177                       
[2]178                        return $op_iop;
179                }
180
[284]181                /*
182                 * @function get_external_ldap_fields_association
[1600]183                 * @author Mï¿œrio Cï¿œsar Kolling <mario.kolling@serpro.gov.br>
[284]184                 * @abstract get the fields association for an external ldap_source
185                 * @return an array with attribute mappings
186                 */
187                function get_external_ldap_fields_association ( $id_source )
188                {
189                        include(PHPGW_INCLUDE_ROOT . '/contactcenter/setup/external_catalogs.inc.php' );
190                        //include('external_catalogs.inc.php' );
191                        return $external_mappings[$id_source];
192                }
193
194                /*
195                 * @function test_connection
[1600]196                 * @author Mï¿œrio Cï¿œsar Kolling <mario.kolling@serpro.gov.br>
[284]197                 * @abstract Test if we can bind to a ldap server in a reasonable time
198                 * @param (string) $host ldap server's hostname
199                 * @param (string) $account ldap bind dn
200                 * @param (string) $password a bind dn's password
201                 * @return (array) an array with the answer from the subprocess, null otherwise
202                 */
203                function test_connection($host, $account, $password, $timeout = 5)
204                {
205                        //opens a subprocess for nonblocking bind
206                        $tsearch = Thread::Create('class.ldap_assync.inc.php', array('host'     => $host,
207                                                                                                                                'account'       => $account,
208                                                                                                                                'password'      => $password
209                                                                                                                                )
210                        );
211
212                        // It's problably more efficient to let method readResponse control the timeout through
213                        // stream_select native timeout.
214                        $response = NULL;
215                        for ($i = 0; $i < $timeout; $i++)
216                        {
217                                if ($tsearch->isActive())
218                                {
219                                        sleep(1);
220                                        if (($response = $tsearch->readResponse()) !== NULL)
221                                        {
222                                                $tsearch->close();
223                                                return $response;
224                                        }
225
226                                }
227                                else
228                                {
229                                        $response = $tsearch->readResponse();
230                                        break;
231                                }
232                        }
233
234                        $tsearch->close();
235                        return null;
236                }
237
[2]238                /*!
[285]239               
[2]240                        @function get_ldap_tree
241                        @abstract Returns the LDAP tree corresponding to the specified level
242                        @author Raphael Derosso Pereira
[285]243                       
[2]244                        @param (integer) $id_source The ID of the LDAP source
[285]245                       
[2]246                        @param (string)  $context The context to be used as root branch
[285]247                               
[2]248                        @param (boolean) $recursive Make it a recursive construction.
249                                CAUTION! This is EXTREMELY SLOW on large LDAP databases,
250                                specially when they're not indexed
[285]251                */             
252                function get_ldap_tree($id_source, $context = false, $recursive = false)
[2]253                {
254                        if (!$this->srcs[$id_source])
255                        {
256                                return null;
257                        }
[285]258                       
[1600]259                        $ldap = $GLOBALS['phpgw']->common->ldapConnect($this->srcs[$id_source]['host'], $this->srcs[$id_source]['acc'],$this->srcs[$id_source]['pw'], false);
[2]260                        if (!$ldap)
261                        {
262                                return false;
263                        }
[285]264                       
[2]265                        if ($recursive)
266                        {
267                                $tree = $this->get_ldap_tree_recursive($ldap, $context, $this->srcs[$id_source]['obj'],$this->srcs[$id_source]['branch']);
268                                $tree['recursive'] = true;
269
270                                return $tree;
271                        }
[285]272                       
273                        return $this->get_ldap_tree_level($id_source, $ldap, $context, $this->srcs[$id_source]['obj'],$this->srcs[$id_source]['branch']);
[2]274                }
[285]275                // SERPRO
[2]276
[284]277                /*!
[2]278
[284]279                        @function get_external_ldap_tree
280                        @abstract Returns the LDAP external tree corresponding to the specified level
[1600]281                        @author Mï¿œrio Cï¿œsar Kolling <mario.kolling@serpro.gov.br>
[284]282                        @param (integer) $id_source The ID of the external LDAP source
283                        @param (string)  $context The context to be used as root branch
284                        @param (boolean) $recursive Make it a recursive construction.
285                                CAUTION! This is EXTREMELY SLOW on large LDAP databases,
286                                specially when they're not indexed
287                */
288                function get_external_ldap_tree($id_source, $context = false, $recursive = false)
289                {
290
291
292                        include(PHPGW_INCLUDE_ROOT . '/contactcenter/setup/external_catalogs.inc.php' );
293                        //include('external_catalogs.inc.php' );
[1600]294
[284]295                        if (!$external_srcs[$id_source])
296                        {
297                                return null;
298                        }
[1600]299
[284]300                        // calls test_connection first. If succeeded continue, return error message otherwise.
301                        if (!($response = $this->test_connection($external_srcs[$id_source]['host'], $external_srcs[$id_source]['acc'], $external_srcs[$id_source]['pw'], 10)))
302                        {
303                                return array(
304                                        'msg'           =>      lang("Catalog %1 temporarily unavailable. Please try again later!", $external_srcs[$id_source]['name']),
305                                        'timeout'       =>      'true'
306                                );
307                        }
308
309                        $ldap = $GLOBALS['phpgw']->common->ldapConnect($external_srcs[$id_source]['host'], $external_srcs[$id_source]['acc'],$external_srcs[$id_source]['pw'], false);
310                        if (!$ldap)
311                        {
312                                return false;
313                        }
314
315                        // Option recursive commented out
316                        /*
317                        if ($recursive)
318                        {
319                                $tree = $this->get_ldap_tree_recursive($ldap, $context, $this->srcs[$id_source]['obj'],$this->srcs[$id_source]['branch']);
320                                $tree['recursive'] = true;
321
322                                return $tree;
323                        }
324                        */
325
326                        return $this->get_ldap_tree_level($id_source, $ldap, $context, $external_srcs[$id_source]['obj'],$external_srcs[$id_source]['branch'], 1);
327                }
328
[2]329                /*!
330
331                        THIS FUNCTION IS NOT TESTED AND IS PROBABLY BROKEN!
332                        I WILL CORRECT IT IN THE NEAR FUTURE
333
334                */
335                function get_ldap_tree_recursive($resource, $context, $objectClass)
336                {
337                        $filter = '(!(objectClass='.$objectClass.'))';
338                        $result_res = ldap_list($resource, $context, $filter);
339
340                        if ($result_res === false)
341                        {
342                                return null;
343                        }
[285]344                       
[2]345                        $count = ldap_count_entries($resource,$result_res);
346                        if ( $count == 0 )
347                        {
348                                $filter = 'objectClass='.$objectClass;
349                                $result_res2 = ldap_list($resource, $context, $filter);
350                                $entries_count = ldap_count_entries($resource, $result_res2);
351
352                                if ($result_res2 !== false && $entries_count > 0)
353                                {
354                                        return $entries_count;
355                                }
356                                else
357                                {
358                                        return null;
359                                }
360                        }
[285]361                       
[2]362                        $entries = ldap_get_entries($resource, $result_res);
[285]363                       
[2]364                        for ($i = 0; $i < $entries['count']; $i++)
365                        {
366                                $subtree = $this->get_ldap_tree_recursive($resource, $entries[$i]['dn'], $objectClass);
[285]367                               
[2]368                                $dn_parts=ldap_explode_dn($entries[$i]['dn'],1);
[285]369                               
370                                if ($subtree !== null and is_array($subtree))
[2]371                                {
372                                        $tree[$i]['name'] = $dn_parts[0];
373                                        $tree[$i]['type'] = 'catalog_group';
374                                        $tree[$i]['recursive'] = true;
375                                        $tree[$i]['sub_branch'] = $subtree;
376                                }
377                                else if (is_int($subtree) and $subtree !== null)
378                                {
379                                        $tree[$i] = array(
380                                                'name'       => $dn_parts[0],
381                                                'type'       => 'catalog',
382                                                'class'      => 'global_contact_manager',
383                                                'icon'       => 'share-mini.png',
384                                                'value'      => $entries[$i]['dn'],
385                                                'sub_branch' => false
386                                        );
[285]387                                }
[2]388                        }
389
390                        if (is_array($tree))
391                        {
392                                return $tree;
393                        }
394                        else
395                        {
396                                return null;
397                        }
398                }
[285]399               
[2]400                function get_ldap_referrals($ds, $dn, $filter) {
[285]401                       
[2]402                        ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
403                        ldap_set_option($ds,LDAP_OPT_PROTOCOL_VERSION,3);
[285]404                       
[2]405                        if ($ds) {
406                            ldap_bind($ds);
[285]407                                $sr=ldap_list($ds,$dn, $filter);                   
[2]408                                $ref = ldap_first_reference($ds, $sr);
409                                $array_referral = array();
410                                $idx = 0;
[285]411                               
[2]412                                 while ($ref) {
413                                        $array_referral[$idx++] = ldap_get_dn($ds, $ref);
414                                        $ref = ldap_next_reference($ds, $ref);
415                                }
416                                return $array_referral;
417                        }
[285]418                        else
[2]419                                return false;
420                }
421
422                function get_ldap_sub_branches_referrals($ds, $dn, $filter) {
[285]423                       
[2]424                        $referral = $this -> get_ldap_referrals($ds, $dn, $filter);
425                        $sub_branches = array();
[285]426                       
[2]427                        for($i = 0; $i <count($referral); $i++) {
428                                $dn = str_replace("??base","",preg_replace('!^(ldap://[^/]+)/(.*$)!', '\\2', $referral[$i]));
[285]429                                $dn = explode(",",$dn);                         
[2]430                                $dn = strtoupper(str_replace("ou=", "",$dn[0]));
431                                $dn = str_replace("DC=", "",$dn);
[285]432                                                                                                                                                                               
[2]433                                $sub_branch = array(
434                                                                                        'name' => $dn,
435                                                                'type' => 'unknown',
436                                                                'value' => $referral[$i],
[285]437                                                                'sub_branch' => false           
438                                                                                );                                                                                                                     
439                                $sub_branches[$i] = $sub_branch;                       
[2]440                        }
441                        return $sub_branches;
442                }
[285]443               
[2]444
[284]445                function translate_accentuation($text)
[2]446                {
[284]447                        /*
[1600]448                         * Esta operaᅵᅵo resolve o problema causado pela conversï¿œo de caracteres acentuados realizada
449                         * pela funᅵᅵo ldap_explode_dn().
[284]450                         */
451
452                        return utf8_decode(preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $text));
453                }
454
455                function get_ldap_tree_level($id_source, $resource, $context, $objectClass, $branch_dn, $external = 0)
456                {
[1600]457                        if(strstr($context, "ldap://")){
458                                $refer_context = str_replace("??base","",preg_replace('!^(ldap://[^/]+)/(.*$)!', '\\2', $context));
459                                $host   = preg_replace('!^(ldap://[^/]+)/.*$!', '\\1', $context);
460                                $resource = ldap_connect($host);
461                                ldap_bind($resource);
462                        }
463
[284]464                        /*
465                         * TODO: Search timeouts
466                         */
[1600]467
468                        $dn_parts = ldap_explode_dn(($refer_context ? $refer_context : $context),1);
[23]469                        //$filter = '(!(objectClass='.$objectClass.'))';
470                        // Don't show OU's whith phpgwAccountVisible equal to '-1'
[284]471                        if ($external)
472                        {
473                                // external source: get all organizationalUnits
474                                $filter = '(objectClass=organizationalUnit)';
475                        }
476                        else
477                        {
478                                // get any objectClass except the objectClass used for the source
479                                // and whose attribute phpgwAccountVisible value is different from -1
480                                $filter = '(&(!(objectClass='.$objectClass.')) (!(phpgwAccountVisible=-1)))';
481                        }
[1600]482                        $result_res = @ldap_list($resource,  ($refer_context ? $refer_context : $context), $filter, array(), 0, 0);
483                        @ldap_sort($resource, $result_res, 'ou');
484
485                        // Timeouts commented out
486                        /*
487                        if ($result_res === false)
488                        {
[2]489                                return null;
[1600]490                        }
491                        */
492
[2]493                        $count = ldap_count_entries($resource,$result_res);
[1600]494
[2]495                        if ( $count == 0 )
496                        {
[284]497                                $filter = '(objectClass='.$objectClass.')';
498                                // Get only one attribute of the source's objectClass
[1600]499                                $result_res2 = @ldap_list($resource, ($refer_context ? $refer_context : $context), $filter, Array('cn'), 0, 1);
[2]500                                $entries_count = ldap_count_entries($resource, $result_res2);
501
502                                if ($result_res2 !== false && $entries_count > 0)
503                                {
504                                        return array(
[284]505                                                'name'       => $this->translate_accentuation($dn_parts[0]),
[2]506                                                'type'       => 'catalog',
507                                                'class'      => 'bo_global_ldap_catalog',
[284]508                                                // Pass the variable $external as a parameter to the constructor
509                                                'class_args' => array($id_source, $context, $external),
[2]510                                                'icon'       => 'globalcatalog-mini.png',
511                                                'value'      => $context,
512                                                'sub_branch' => false
513                                        );
514                                }
515                                else
516                                {
517                                        return array(
[284]518                                                'name' => $this->translate_accentuation($dn_parts[0]),
[2]519                                                'type' => 'empty'
520                                        );
521                                }
522                        }
[1600]523
[2]524                        $sub_branch_found = false;
525                        $i = 0;
526                        for ($entry = ldap_first_entry($resource, $result_res);
527                             $entry != false;
528                             $entry = ldap_next_entry($resource, $entry))
529                        {
530                                $dn = ldap_get_dn($resource, $entry);
531                                $dn_parts_1 = ldap_explode_dn($dn,1);
532                                $dn_parts_full = ldap_explode_dn($dn,0);
533                                list($group) = explode('=',$dn_parts_full[0]);
[284]534
[1600]535                                //Faz a comparaᅵᅵo do branch como case insensitive
[284]536                                if (strtolower($group) == strtolower($branch_dn) or $branch_dn === 'all')
[2]537                                {
538                                        $tree['sub_branch'][$i] = array(
[284]539                                                'name'  => $this->translate_accentuation($dn_parts_1[0]),
[2]540                                                'type'  => 'unknown',
[1600]541                                                'value' =>  ($refer_context ? $host."/" : "").$dn,
[284]542                                                'sub_branch' => false
[2]543                                        );
544                                        $sub_branch_found = true;
545                                }
546                                $i++;
547                        }
[1600]548
549                        if(! $refer_context) {
550                                $array_referral = $this -> get_ldap_sub_branches_referrals($resource, $context,'(objectClass=organizationalUnit)');
551                                for($z = 0; $z < count($array_referral); $z++) {
552                                        $tree['sub_branch'][$i++] = $array_referral[$z];
553                                }
554                        }
555
[2]556                        $filter = 'objectClass='.$objectClass;
[1600]557                        $result_res2 = @ldap_list($resource, ($refer_context ? $refer_context : $context), $filter, Array('cn'), 0, 1);
[2]558                        $entries_count = ldap_count_entries($resource, $result_res2);
559
560                        if ($result_res2 !== false && $entries_count > 0 && $sub_branch_found)
561                        {
[284]562                                $tree['name']       = $this->translate_accentuation($dn_parts[0]);
[2]563                                $tree['type']       = 'mixed_catalog_group';
564                                $tree['class']      = 'bo_global_ldap_catalog';
[284]565                                // Pass the variable $external as a parameter to the constructor
566                                $tree['class_args'] = array($id_source,$context,$external);
[2]567                                $tree['icon']       = 'globalcatalog-mini.png';
568                                $tree['value']      = $context;
569                        }
570                        elseif ($result_res2 !== false && $entries_count > 0 && !$sub_branch_found)
571                        {
572                                return array(
[284]573                                        'name'       => $this->translate_accentuation($dn_parts[0]),
[2]574                                        'type'       => 'catalog',
575                                        'class'      => 'bo_global_ldap_catalog',
[284]576                                        // Pass the variable $external as a parameter to the constructor
577                                        'class_args' => array($id_source, $context,$external),
[2]578                                        'icon'       => 'globalcatalog-mini.png',
579                                        'value'      => $context,
580                                        'sub_branch' => false
581                                );
582                        }
583                        else
584                        {
[284]585                                $tree['name']       = $this->translate_accentuation($dn_parts[0]);
[2]586                                $tree['type']       = 'catalog_group';
587                                $tree['class']      = 'bo_catalog_group_catalog';
[284]588                                // Pass the variable $external as a parameter to the constructor
589                                $tree['class_args'] = array('$this', '$this->get_branch_by_level($this->catalog_level[0])', $external);
[2]590                                $tree['value']      = $context;
591                                $tree['ldap']       = array('id_source' => $id_source, 'context' => $context);
592                        }
[285]593                       
[2]594                        usort($tree['sub_branch'], array($this, "compareTreeNodes"));
595                        return $tree;
596                }
597
598                function compareTreeNodes($a, $b)       {
[285]599                                               
[2]600                        return strnatcasecmp($a['name'], $b['name']);
[285]601                }       
[2]602
603        }
604?>
Note: See TracBrowser for help on using the repository browser.