source: trunk/phpgwapi/inc/class.vfs_sql.inc.php @ 1842

Revision 1842, 71.7 KB checked in by amuller, 14 years ago (diff)

Ticket #597 - Melhoria do FM, tratamento de janelas diferenciado

  • Property svn:eol-style set to native
  • Property svn:executable set to *
Line 
1<?php
2  /**************************************************************************\
3  * eGroupWare API - VFS                                                     *
4  * This file written by Jason Wies (Zone) <zone@phpgroupware.org>           *
5  * This class handles file/dir access for eGroupWare                        *
6  * Copyright (C) 2001 Jason Wies                                            *
7  * -------------------------------------------------------------------------*
8  * This library is part of the eGroupWare API                               *
9  * http://www.egroupware.org/api                                            *
10  * ------------------------------------------------------------------------ *
11  * This library is free software; you can redistribute it and/or modify it  *
12  * under the terms of the GNU Lesser General Public License as published by *
13  * the Free Software Foundation; either version 2.1 of the License,         *
14  * or any later version.                                                    *
15  * This library is distributed in the hope that it will be useful, but      *
16  * WITHOUT ANY WARRANTY; without even the implied warranty of               *
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
18  * See the GNU Lesser General Public License for more details.              *
19  * You should have received a copy of the GNU Lesser General Public License *
20  * along with this library; if not, write to the Free Software Foundation,  *
21  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA            *
22  \**************************************************************************/
23
24
25        /*!
26        @class vfs
27        @abstract Virtual File System with SQL backend
28        @description Authors: Zone
29        */
30
31        /* These are used in calls to extra_sql () */
32        define ('VFS_SQL_SELECT', 1);
33        define ('VFS_SQL_DELETE', 2);
34        define ('VFS_SQL_UPDATE', 4);
35
36        class vfs extends vfs_shared
37        {
38                var $working_id;
39                var $working_lid;
40                var $my_home;
41                var $meta_types;
42                var $now;
43                var $file_actions;
44
45                /*!
46                @function vfs
47                @abstract constructor, sets up variables
48                */
49                function vfs ()
50                {
51                        $this->vfs_shared ();
52                        $this->basedir = $GLOBALS['phpgw_info']['server']['files_dir'];
53                        $this->working_id = $GLOBALS['phpgw_info']['user']['account_id'];
54                        $this->working_lid = $GLOBALS['phpgw']->accounts->id2name($this->working_id);
55                        $this->my_home = $this->fakebase.'/'.$this->working_lid;
56                        $this->now = date ('Y-m-d H:i:s');
57
58                        /*
59                           File/dir attributes, each corresponding to a database field.  Useful for use in loops
60                           If an attribute was added to the table, add it here and possibly add it to
61                           set_attributes ()
62
63                           set_attributes now uses this array().   07-Dec-01 skeeter
64                        */
65
66                        $this->attributes[] = 'deleteable';
67                        $this->attributes[] = 'content';
68                        $this->attributes[] = 'type';
69
70                        /*
71                           Decide whether to use any actual filesystem calls (fopen(), fread(),
72                           unlink(), rmdir(), touch(), etc.).  If not, then we're working completely
73                           in the database.
74                        */
75                        $this->file_actions = $GLOBALS['phpgw_info']['server']['file_store_contents'] == 'filesystem' ||
76                                !$GLOBALS['phpgw_info']['server']['file_store_contents'];
77
78                        // test if the files-dir is inside the document-root, and refuse working if so
79                        //
80                        if ($this->file_actions && $this->in_docroot($this->basedir))
81                        {
82                                $GLOBALS['phpgw']->common->phpgw_header();
83                                if ($GLOBALS['phpgw_info']['flags']['noheader'])
84                                {
85                                        echo parse_navbar();
86                                }
87                                echo '<p align="center"><font color="red"><b>'.lang('Path to user and group files HAS TO BE OUTSIDE of the webservers document-root!!!')."</b></font></p>\n";
88                                $GLOBALS['phpgw']->common->phpgw_exit();
89                        }
90                        /*
91                           These are stored in the MIME-type field and should normally be ignored.
92                           Adding a type here will ensure it is normally ignored, but you will have to
93                           explicitly add it to acl_check (), and to any other SELECT's in this file
94                        */
95
96                        $this->meta_types = array ('journal', 'journal-deleted');
97
98                        /* We store the linked directories in an array now, so we don't have to make the SQL call again */
99                        if ($GLOBALS['phpgw_info']['server']['db_type']=='mssql'
100                                || $GLOBALS['phpgw_info']['server']['db_type']=='sybase')
101                        {
102                                $query = $GLOBALS['phpgw']->db->query ("SELECT directory, name, link_directory, link_name FROM phpgw_vfs WHERE CONVERT(varchar,link_directory) != '' AND CONVERT(varchar,link_name) != ''" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__,__FILE__);
103                        }
104                        else
105                        {
106                                $query = $GLOBALS['phpgw']->db->query ("SELECT directory, name, link_directory, link_name FROM phpgw_vfs WHERE (link_directory IS NOT NULL or link_directory != '') AND (link_name IS NOT NULL or link_name != '')" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__,__FILE__);
107                        }
108
109                        $this->linked_dirs = array ();
110                        while ($GLOBALS['phpgw']->db->next_record ())
111                        {
112                                $this->linked_dirs[] = $GLOBALS['phpgw']->db->Record;
113                        }
114                }
115
116                /*!
117                @function in_docroot
118                @abstract test if $path lies within the webservers document-root
119                */
120                function in_docroot($path)
121                {
122                        $docroots = array(PHPGW_SERVER_ROOT,$_SERVER['DOCUMENT_ROOT']);
123
124                        foreach ($docroots as $docroot)
125                        {
126                                $len = strlen($docroot);
127
128                                if ($docroot == substr($path,0,$len))
129                                {
130                                        $rest = substr($path,$len);
131
132                                        if (!strlen($rest) || $rest[0] == DIRECTORY_SEPARATOR)
133                                        {
134                                                return True;
135                                        }
136                                }
137                        }
138                        return False;
139                }
140
141                /*!
142                @function extra_sql
143                @abstract Return extra SQL code that should be appended to certain queries
144                @param query_type The type of query to get extra SQL code for, in the form of a VFS_SQL define
145                @result Extra SQL code
146                */
147                function extra_sql ($data)
148                {
149                        if (!is_array ($data))
150                        {
151                                $data = array ('query_type' => VFS_SQL_SELECT);
152                        }
153
154                        if ($data['query_type'] == VFS_SQL_SELECT || $data['query_type'] == VFS_SQL_DELETE || $data['query_type'] = VFS_SQL_UPDATE)
155                        {
156                                $sql = ' AND ((';
157
158                                foreach ($this->meta_types as $num => $type)
159                                {
160                                        if ($num)
161                                                $sql .= ' AND ';
162
163                                        $sql .= "mime_type != '$type'";
164                                }
165
166                                $sql .= ') OR mime_type IS NULL)';
167                        }
168
169                        return ($sql);
170                }
171
172                /*!
173                @function add_journal
174                @abstract Add a journal entry after (or before) completing an operation,
175                          and increment the version number.  This function should be used internally only
176                @discussion Note that state_one and state_two are ignored for some VFS_OPERATION's, for others
177                            they are required.  They are ignored for any "custom" operation
178                            The two operations that require state_two:
179                            operation                   state_two
180                            VFS_OPERATION_COPIED        fake_full_path of copied to
181                            VFS_OPERATION_MOVED         fake_full_path of moved to
182
183                            If deleting, you must call add_journal () before you delete the entry from the database
184                @param string File or directory to add entry for
185                @param relatives Relativity array
186                @param operation The operation that was performed.  Either a VFS_OPERATION define or
187                                  a non-integer descriptive text string
188                @param state_one The first "state" of the file or directory.  Can be a file name, size,
189                                  location, whatever is appropriate for the specific operation
190                @param state_two The second "state" of the file or directory
191                @param incversion Boolean True/False.  Increment the version for the file?  Note that this is
192                                   handled automatically for the VFS_OPERATION defines.
193                                   i.e. VFS_OPERATION_EDITED would increment the version, VFS_OPERATION_COPIED
194                                   would not
195                @result Boolean True/False
196                */
197                function add_journal ($data)
198                {
199                        if (!is_array ($data))
200                        {
201                                $data = array ();
202                        }
203
204                        $default_values = array
205                                (
206                                        'relatives'     => array (RELATIVE_CURRENT),
207                                        'state_one'     => False,
208                                        'state_two'     => False,
209                                        'incversion'    => True
210                                );
211
212                        $data = array_merge ($this->default_values ($data, $default_values), $data);
213
214                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
215
216                        $p = $this->path_parts (array ('string' => $data['string'], 'relatives' => array ($data['relatives'][0])));
217
218                        /* We check that they have some sort of access to the file other than read */
219                        if (!$this->acl_check (array ('string' => $p->fake_full_path, 'relatives' => array ($p->mask), 'operation' => PHPGW_ACL_WRITE)) &&
220                                !$this->acl_check (array ('string' => $p->fake_full_path, 'relatives' => array ($p->mask), 'operation' => PHPGW_ACL_EDIT)) &&
221                                !$this->acl_check (array ('string' => $p->fake_full_path, 'relatives' => array ($p->mask), 'operation' => PHPGW_ACL_DELETE)))
222                        {
223                                return False;
224                        }
225
226                        if (!$this->file_exists (array ('string' => $p->fake_full_path, 'relatives' => array ($p->mask))))
227                        {
228                                return False;
229                        }
230
231                        $ls_array = $this->ls (array (
232                                        'string' => $p->fake_full_path,
233                                        'relatives' => array ($p->mask),
234                                        'checksubdirs' => False,
235                                        'mime_type'     => False,
236                                        'nofiles'       => True
237                                )
238                        );
239                        $file_array = $ls_array[0];
240
241                        $sql = 'INSERT INTO phpgw_vfs (';
242                        $sql2 .= ' VALUES (';
243
244                        for ($i = 0; list ($attribute, $value) = each ($file_array); $i++)
245                        {
246                                if ($attribute == 'file_id' || $attribute == 'content')
247                                {
248                                        continue;
249                                }
250
251                                if ($attribute == 'owner_id')
252                                {
253                                        $value = $account_id;
254                                }
255
256                                if ($attribute == 'created')
257                                {
258                                        $value = $this->now;
259                                }
260
261                                if ($attribute == 'modified' && !$modified)
262                                {
263                                        unset ($value);
264                                }
265
266                                if ($attribute == 'mime_type')
267                                {
268                                        $value = 'journal';
269                                }
270
271                                if ($attribute == 'comment')
272                                {
273                                        switch ($data['operation'])
274                                        {
275                                                case VFS_OPERATION_CREATED:
276                                                        $value = 'Created';
277                                                        $data['incversion'] = True;
278                                                        break;
279                                                case VFS_OPERATION_EDITED:
280                                                        $value = 'Edited';
281                                                        $data['incversion'] = True;
282                                                        break;
283                                                case VFS_OPERATION_EDITED_COMMENT:
284                                                        $value = 'Edited comment';
285                                                        $data['incversion'] = False;
286                                                        break;
287                                                case VFS_OPERATION_COPIED:
288                                                        if (!$data['state_one'])
289                                                        {
290                                                                $data['state_one'] = $p->fake_full_path;
291                                                        }
292                                                        if (!$data['state_two'])
293                                                        {
294                                                                return False;
295                                                        }
296                                                        $value = 'Copied '.$data['state_one'].' to '.$data['state_two'];
297                                                        $data['incversion'] = False;
298                                                        break;
299                                                case VFS_OPERATION_MOVED:
300                                                        if (!$data['state_one'])
301                                                        {
302                                                                $data['state_one'] = $p->fake_full_path;
303                                                        }
304                                                        if (!$data['state_two'])
305                                                        {
306                                                                return False;
307                                                        }
308                                                        $value = 'Moved '.$data['state_one'].' to '.$data['state_two'];
309                                                        $data['incversion'] = False;
310                                                        break;
311                                                case VFS_OPERATION_DELETED:
312                                                        $value = 'Deleted';
313                                                        $data['incversion'] = False;
314                                                        break;
315                                                default:
316                                                        $value = $data['operation'];
317                                                        break;
318                                        }
319                                }
320
321                                /*
322                                   Let's increment the version for the file itself.  We keep the current
323                                   version when making the journal entry, because that was the version that
324                                   was operated on.  The maximum numbers for each part in the version string:
325                                   none.99.9.9
326                                */
327                                if ($attribute == 'version' && $data['incversion'])
328                                {
329                                        $version_parts = split ("\.", $value);
330                                        $newnumofparts = $numofparts = count ($version_parts);
331
332                                        if ($version_parts[3] >= 9)
333                                        {
334                                                $version_parts[3] = 0;
335                                                $version_parts[2]++;
336                                                $version_parts_3_update = 1;
337                                        }
338                                        elseif (isset ($version_parts[3]))
339                                        {
340                                                $version_parts[3]++;
341                                        }
342
343                                        if ($version_parts[2] >= 9 && $version_parts[3] == 0 && $version_parts_3_update)
344                                        {
345                                                $version_parts[2] = 0;
346                                                $version_parts[1]++;
347                                        }
348
349                                        if ($version_parts[1] > 99)
350                                        {
351                                                $version_parts[1] = 0;
352                                                $version_parts[0]++;
353                                        }
354
355                                        for ($i = 0; $i < $newnumofparts; $i++)
356                                        {
357                                                if (!isset ($version_parts[$i]))
358                                                {
359                                                        break;
360                                                }
361
362                                                if ($i)
363                                                {
364                                                        $newversion .= '.';
365                                                }
366
367                                                $newversion .= $version_parts[$i];
368                                        }
369
370                                        $this->set_attributes (array(
371                                                        'string'        => $p->fake_full_path,
372                                                        'relatives'     => array ($p->mask),
373                                                        'attributes'    => array(
374                                                                                'version' => $newversion
375                                                                        )
376                                                )
377                                        );
378                                }
379
380                                if (isset ($value))
381                                {
382                                        if ($i > 1)
383                                        {
384                                                $sql .= ', ';
385                                                $sql2 .= ', ';
386                                        }
387
388                                        $sql .= "$attribute";
389                                        $sql2 .= "'" . $this->clean_string (array ('string' => $value)) . "'";
390                                }
391                        }
392
393                        $sql .= ')';
394                        $sql2 .= ')';
395
396                        $sql .= $sql2;
397
398                        /*
399                           These are some special situations where we need to flush the journal entries
400                           or move the 'journal' entries to 'journal-deleted'.  Kind of hackish, but they
401                           provide a consistent feel to the system
402                        */
403                        if ($data['operation'] == VFS_OPERATION_CREATED)
404                        {
405                                $flush_path = $p->fake_full_path;
406                                $deleteall = True;
407                        }
408
409                        if ($data['operation'] == VFS_OPERATION_COPIED || $data['operation'] == VFS_OPERATION_MOVED)
410                        {
411                                $flush_path = $data['state_two'];
412                                $deleteall = False;
413                        }
414
415                        if ($flush_path)
416                        {
417                                $flush_path_parts = $this->path_parts (array(
418                                                'string'        => $flush_path,
419                                                'relatives'     => array (RELATIVE_NONE)
420                                        )
421                                );
422
423                                $this->flush_journal (array(
424                                                'string'        => $flush_path_parts->fake_full_path,
425                                                'relatives'     => array ($flush_path_parts->mask),
426                                                'deleteall'     => $deleteall
427                                        )
428                                );
429                        }
430
431                        if ($data['operation'] == VFS_OPERATION_COPIED)
432                        {
433                                /*
434                                   We copy it going the other way as well, so both files show the operation.
435                                   The code is a bad hack to prevent recursion.  Ideally it would use VFS_OPERATION_COPIED
436                                */
437                                $this->add_journal (array(
438                                                'string'        => $data['state_two'],
439                                                'relatives'     => array (RELATIVE_NONE),
440                                                'operation'     => 'Copied '.$data['state_one'].' to '.$data['state_two'],
441                                                'state_one'     => NULL,
442                                                'state_two'     => NULL,
443                                                'incversion'    => False
444                                        )
445                                );
446                        }
447
448                        if ($data['operation'] == VFS_OPERATION_MOVED)
449                        {
450                                $state_one_path_parts = $this->path_parts (array(
451                                                'string'        => $data['state_one'],
452                                                'relatives'     => array (RELATIVE_NONE)
453                                        )
454                                );
455
456                                $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET mime_type='journal-deleted' WHERE directory='".
457                                        $GLOBALS['phpgw']->db->db_addslashes($state_one_path_parts->fake_leading_dirs_clean)."' AND name='".
458                                        $GLOBALS['phpgw']->db->db_addslashes($state_one_path_parts->fake_name_clean)."' AND mime_type='journal'");
459
460                                /*
461                                   We create the file in addition to logging the MOVED operation.  This is an
462                                   advantage because we can now search for 'Create' to see when a file was created
463                                */
464                                $this->add_journal (array(
465                                                'string'        => $data['state_two'],
466                                                'relatives'     => array (RELATIVE_NONE),
467                                                'operation'     => VFS_OPERATION_CREATED
468                                        )
469                                );
470                        }
471
472                        /* This is the SQL query we made for THIS request, remember that one? */
473                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
474
475                        /*
476                           If we were to add an option of whether to keep journal entries for deleted files
477                           or not, it would go in the if here
478                        */
479                        if ($data['operation'] == VFS_OPERATION_DELETED)
480                        {
481                                $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET mime_type='journal-deleted' WHERE directory='".
482                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
483                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."' AND mime_type='journal'");
484                        }
485
486                        return True;
487                }
488
489                /*!
490                @function flush_journal
491                @abstract Flush journal entries for $string.  Used before adding $string
492                @discussion flush_journal () is an internal function and should be called from add_journal () only
493                @param string File/directory to flush journal entries of
494                @param relatives Realtivity array
495                @param deleteall Delete all types of journal entries, including the active Create entry.
496                                  Normally you only want to delete the Create entry when replacing the file
497                                  Note that this option does not effect $deleteonly
498                @param deletedonly Only flush 'journal-deleted' entries (created when $string was deleted)
499                @result Boolean True/False
500                */
501                function flush_journal ($data)
502                {
503                        if (!is_array ($data))
504                        {
505                                $data = array ();
506                        }
507
508                        $default_values = array
509                                (
510                                        'relatives'     => array (RELATIVE_CURRENT),
511                                        'deleteall'     => False,
512                                        'deletedonly'   => False
513                                );
514
515                        $data = array_merge ($this->default_values ($data, $default_values), $data);
516
517                        $p = $this->path_parts (array(
518                                        'string'        => $data['string'],
519                                        'relatives'     => array ($data['relatives'][0])
520                                )
521                        );
522
523
524                        $sql = "DELETE FROM phpgw_vfs WHERE directory='".
525                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean).SEP
526                                .$GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'";
527
528                        if (!$data['deleteall'])
529                        {
530                                $sql .= " AND (mime_type != 'journal' AND comment != 'Created')";
531                        }
532
533                        $sql .= "  AND (mime_type='journal-deleted'";
534
535                        if (!$data['deletedonly'])
536                        {
537                                $sql .= " OR mime_type='journal'";
538                        }
539
540                        $sql .= ")";
541                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
542
543                        if ($query)
544                        {
545                                return True;
546                        }
547                        else
548                        {
549                                return False;
550                        }
551                }
552
553                /*
554                 * See vfs_shared
555                 */
556                function get_journal ($data)
557                {
558                        if (!is_array ($data))
559                        {
560                                $data = array ();
561                        }
562
563                        $default_values = array
564                                (
565                                        'relatives'     => array (RELATIVE_CURRENT),
566                                        'type'  => False
567                                );
568
569                        $data = array_merge ($this->default_values ($data, $default_values), $data);
570
571                        $p = $this->path_parts (array(
572                                        'string'        => $data['string'],
573                                        'relatives'     => array ($data['relatives'][0])
574                                )
575                        );
576
577                        if (!$this->acl_check (array(
578                                        'string' => $p->fake_full_path,
579                                        'relatives' => array ($p->mask)
580                                )))
581                        {
582                                return False;
583                        }
584
585                        $sql = "SELECT * FROM phpgw_vfs WHERE directory='".
586                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
587                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'";
588
589                        if ($data['type'] == 1)
590                        {
591                                $sql .= " AND mime_type='journal'";
592                        }
593                        elseif ($data['type'] == 2)
594                        {
595                                $sql .= " AND mime_type='journal-deleted'";
596                        }
597                        else
598                        {
599                                $sql .= " AND (mime_type='journal' OR mime_type='journal-deleted')";
600                        }
601
602                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
603
604                        while ($GLOBALS['phpgw']->db->next_record ())
605                        {
606                                $rarray[] = $GLOBALS['phpgw']->db->Record;
607                        }
608
609                        return $rarray;
610                }
611
612                /*
613                 * See vfs_shared
614                 */
615                function acl_check ($data)
616                {
617
618                        if (!is_array ($data))
619                        {
620                                $data = array ();
621                        }
622       
623                        $default_values = array
624                                (
625                                        'relatives'     => array (RELATIVE_CURRENT),
626                                        'operation'     => PHPGW_ACL_READ,
627                                        'must_exist'    => False
628                                );
629
630                        $data = array_merge ($this->default_values ($data, $default_values), $data);
631
632                        /* Accommodate special situations */
633                        if ($this->override_acl || $data['relatives'][0] == RELATIVE_USER_APP)
634                        {
635                                return True;
636                        }
637
638                        /* ExpressoLivre principle: In your home you do what you want*/
639                        if (strpos($data['string'],$this->my_home) === 0)
640                                return True;
641                        if ($data['relatives'][0] == RELATIVE_NONE || $data['relatives'][0] == RELATIVE_ALL)
642                        {
643                                $path = explode('/',$data['string']);
644                                $data['string'] = '/'.$path[1].'/'.$path[2];
645                        }
646
647
648
649                        if (!$data['owner_id'])
650                        {
651                                $p = $this->path_parts (array(
652                                                'string'        => $data['string'],
653                                                'relatives'     => array ($data['relatives'][0])
654                                        )
655                                );
656
657                                /* Temporary, until we get symlink type files set up */
658                                if ($p->outside)
659                                {
660                                        return True;
661                                }
662
663                                /* Read access is always allowed here, but nothing else is */
664                                if ($data['string'] == '/' || $data['string'] == $this->fakebase)
665                                {
666                                        if ($data['operation'] == PHPGW_ACL_READ)
667                                        {
668                                                return True;
669                                        }
670                                        else
671                                        {
672                                                return False;
673                                        }
674                                }
675
676                                /* If the file doesn't exist, we get ownership from the parent directory */
677                                if (!$this->file_exists (array(
678                                                'string'        => $p->fake_full_path,
679                                                'relatives'     => array ($p->mask)
680                                        ))
681                                )
682                                {
683                                        if ($data['must_exist'])
684                                        {
685                                                return False;
686                                        }
687
688                                        $data['string'] = $p->fake_leading_dirs;
689                                        $p2 = $this->path_parts (array(
690                                                        'string'        => $data['string'],
691                                                        'relatives'     => array ($p->mask)
692                                                )
693                                        );
694
695                                        if (!$this->file_exists (array(
696                                                        'string'        => $data['string'],
697                                                        'relatives'     => array ($p->mask)
698                                                ))
699                                        )
700                                        {
701                                                return False;
702                                        }
703                                }
704                                else
705                                {
706                                        $p2 = $p;
707                                }
708
709                                /*
710                                   We don't use ls () to get owner_id as we normally would,
711                                   because ls () calls acl_check (), which would create an infinite loop
712                                 */
713                                $owner_id = $this->ownerOf($p2->fake_leading_dirs_clean,$p2->fake_name_clean);
714                        }
715                        else
716                        {
717                                $owner_id = $data['owner_id'];
718                        }
719
720                        $user_id = $GLOBALS['phpgw_info']['user']['account_id'];
721
722                        /* They always have access to their own files */
723                        if ($owner_id == $user_id)
724                        {
725                                return True;
726                        }
727
728                        /* Check if they're in the group */
729                        $memberships = $GLOBALS['phpgw']->accounts->membership ($user_id);
730
731                        if (is_array ($memberships))
732                        {
733                                foreach ($memberships as $group_array)
734                                {
735                                        if ($owner_id == $group_array['account_id'])
736                                        {
737                                                $group_ok = 1;
738                                                break;
739                                        }
740                                }
741                        }
742
743                        $acl = CreateObject ('phpgwapi.acl', $owner_id);
744                        $acl->account_id = $owner_id;
745                        $acl->read_repository ();
746
747                        $rights = $acl->get_rights ($user_id);
748
749                        /* Add privileges from the groups this user belongs to */
750                        if (is_array ($memberships))
751                        {
752                                foreach ($memberships as $group_array)
753                                {
754                                        $rights |= $acl->get_rights ($group_array['account_id']);
755                                }
756                        }
757
758                        if ($rights & $data['operation'])
759                        {
760                                return True;
761                        }
762                        elseif (!$rights && $group_ok)
763                        {
764                                $conf = CreateObject('phpgwapi.config', 'phpgwapi');
765                                $conf->read_repository();
766                                if ($conf->config_data['acl_default'] == 'grant')
767                                {
768                                        return True;
769                                }
770                                else
771                                {
772                                        return False;
773                                }
774                        }
775                        else
776                        {
777                                return False;
778                        }
779                }
780                function ownerOf($base,$path){
781                        $query = $GLOBALS['phpgw']->db->query ("SELECT owner_id FROM phpgw_vfs WHERE directory='".
782                        $GLOBALS['phpgw']->db->db_addslashes($base)."' AND name='".
783                        $GLOBALS['phpgw']->db->db_addslashes($path)."' ".$this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
784                        $GLOBALS['phpgw']->db->next_record();
785                        $owner_id = $GLOBALS['phpgw']->db->Record['owner_id'];
786                        if (!$owner_id)
787                        {
788                                $owner_id = 0;
789                        }
790                        return $owner_id;
791                }
792
793                /*used to safe memory in downloads*/
794                function print_content ($data)
795                {
796                        if (!is_array ($data))
797                        {
798                                $data = array ();
799                        }
800
801                        $default_values = array
802                                (
803                                        'relatives'     => array (RELATIVE_CURRENT)
804                                );
805
806                        $data = array_merge ($this->default_values ($data, $default_values), $data);
807
808                        $p = $this->path_parts (array(
809                                        'string'        => $data['string'],
810                                        'relatives'     => array ($data['relatives'][0])
811                                )
812                        );
813
814                        if (!$this->acl_check (array(
815                                        'string'        => $p->fake_full_path,
816                                        'relatives'     => array ($p->mask),
817                                        'operation'     => PHPGW_ACL_READ
818                                ))
819                        )
820                        {
821                                return False;
822                        }
823                        session_write_close();
824                                if ($fp = fopen ($p->real_full_path, 'rb'))
825                                {
826                                        for ($i=0; $i<=filesize($p->real_full_path); $i+=10240)
827                                        {
828                                                echo fread($fp, $i);
829                                                flush();
830                                                usleep(50);
831                                        }
832                                        fclose ($fp);
833                                }
834                                else
835                                {
836                                        return False;
837                                }
838                        return True;
839                }
840
841                /*
842                 * See vfs_shared
843                 */
844                function read ($data)
845                {
846                        if (!is_array ($data))
847                        {
848                                $data = array ();
849                        }
850
851                        $default_values = array
852                                (
853                                        'relatives'     => array (RELATIVE_CURRENT)
854                                );
855
856                        $data = array_merge ($this->default_values ($data, $default_values), $data);
857
858                        $p = $this->path_parts (array(
859                                        'string'        => $data['string'],
860                                        'relatives'     => array ($data['relatives'][0])
861                                )
862                        );
863
864                        if (!$this->acl_check (array(
865                                        'string'        => $p->fake_full_path,
866                                        'relatives'     => array ($p->mask),
867                                        'operation'     => PHPGW_ACL_READ
868                                ))
869                        )
870                        {
871                                return False;
872                        }
873
874                        $conf = CreateObject('phpgwapi.config', 'phpgwapi');
875                        $conf->read_repository();
876                        if ($this->file_actions || $p->outside)
877                        {
878                                if ($fp = fopen ($p->real_full_path, 'rb'))
879                                {
880                                        $contents = fread ($fp, filesize ($p->real_full_path));
881                                        fclose ($fp);
882                                }
883                                else
884                                {
885                                        $contents = False;
886                                }
887                        }
888                        else
889                        {
890                                $ls_array = $this->ls (array(
891                                                'string'        => $p->fake_full_path,
892                                                'relatives'     => array ($p->mask),
893                                        )
894                                );
895
896                                $contents = $ls_array[0]['content'];
897                        }
898
899                        return $contents;
900                }
901
902                /*
903                 * See vfs_shared
904                 */
905                function write ($data)
906                {
907                        if (!is_array ($data))
908                        {
909                                $data = array ();
910                        }
911
912                        $path = explode('/',$data['string']);
913                        $quota = $this->get_quota(array('string' => '/'.$path[1].'/'.$path[2]));
914                        if ($quota > 0 && $this->get_size('/'.$path[1].'/'.$path[2]) >= $quota * 1024 * 1024)
915                                return false;
916
917
918                        $default_values = array
919                                (
920                                        'relatives'     => array (RELATIVE_CURRENT),
921                                        'content'       => ''
922                                );
923
924                        $data = array_merge ($this->default_values ($data, $default_values), $data);
925
926                        $p = $this->path_parts (array(
927                                        'string'        => $data['string'],
928                                        'relatives'     => array ($data['relatives'][0])
929                                )
930                        );
931
932                        if ($this->file_exists (array (
933                                        'string'        => $p->fake_full_path,
934                                        'relatives'     => array ($p->mask)
935                                ))
936                        )
937                        {
938                                $acl_operation = PHPGW_ACL_EDIT;
939                                $journal_operation = VFS_OPERATION_EDITED;
940                        }
941                        else
942                        {
943                                $acl_operation = PHPGW_ACL_ADD;
944                        }
945                        umask(0177);
946
947                        /*
948                           If 'string' doesn't exist, touch () creates both the file and the database entry
949                           If 'string' does exist, touch () sets the modification time and modified by
950                        */
951                        $this->touch (array(
952                                        'string'        => $p->fake_full_path,
953                                        'relatives'     => array ($p->mask)
954                                )
955                        );
956
957                        $conf = CreateObject('phpgwapi.config', 'phpgwapi');
958                        $conf->read_repository();
959                        if ($this->file_actions)
960                        {
961                                if ($fp = fopen ($p->real_full_path, 'wb'))
962                                {
963                                        fwrite ($fp, $data['content']);
964                                        fclose ($fp);
965                                        $write_ok = 1;
966                                }
967                        }
968
969                        if ($write_ok || !$this->file_actions)
970                        {
971                                if ($this->file_actions)
972                                {
973                                        $set_attributes_array = array(
974                                                'size' => filesize ($p->real_full_path)
975                                        );
976                                }
977                                else
978                                {
979                                        $set_attributes_array = array (
980                                                'size'  => strlen ($data['content']),
981                                                'content'       => $data['content']
982                                        );
983                                }
984
985
986                                $this->set_attributes (array
987                                        (
988                                                'string'        => $p->fake_full_path,
989                                                'relatives'     => array ($p->mask),
990                                                'attributes'    => $set_attributes_array
991                                        )
992                                );
993
994                                if ($journal_operation)
995                                {
996                                        $this->add_journal (array(
997                                                        'string'        => $p->fake_full_path,
998                                                        'relatives'     => array ($p->mask),
999                                                        'operation'     => $journal_operation
1000                                                )
1001                                        );
1002                                }
1003
1004                                return True;
1005                        }
1006                        else
1007                        {
1008                                return False;
1009                        }
1010                }
1011
1012                /*
1013                 * See vfs_shared
1014                 */
1015                function touch ($data)
1016                {
1017                        if (!is_array ($data))
1018                        {
1019                                $data = array ();
1020                        }
1021
1022                        $default_values = array
1023                                (
1024                                        'relatives'     => array (RELATIVE_CURRENT)
1025                                );
1026
1027                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1028
1029                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1030                        $currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
1031
1032                        $p = $this->path_parts (array(
1033                                        'string'        => $data['string'],
1034                                        'relatives'     => array ($data['relatives'][0])
1035                                )
1036                        );
1037
1038                        umask (0177);
1039
1040                        if ($this->file_actions)
1041                        {
1042                                /*
1043                                   PHP's touch function will automatically decide whether to
1044                                   create the file or set the modification time
1045                                */
1046                                $rr = @touch ($p->real_full_path);
1047
1048                                if ($p->outside)
1049                                {
1050                                        return $rr;
1051                                }
1052                        }
1053
1054                        /* We, however, have to decide this ourselves */
1055                        if ($this->file_exists (array(
1056                                        'string'        => $p->fake_full_path,
1057                                        'relatives'     => array ($p->mask)
1058                                ))
1059                        )
1060                        {
1061                                if (!$this->acl_check (array(
1062                                                'string'        => $p->fake_full_path,
1063                                                'relatives'     => array ($p->mask),
1064                                                'operation'     => PHPGW_ACL_EDIT
1065                                        )))
1066                                {
1067                                        return False;
1068                                }
1069
1070                                $vr = $this->set_attributes (array(
1071                                                'string'        => $p->fake_full_path,
1072                                                'relatives'     => array ($p->mask),
1073                                                'attributes'    => array(
1074                                                                        'modifiedby_id' => $account_id,
1075                                                                        'modified' => $this->now
1076                                                                )
1077                                                )
1078                                        );
1079                        }
1080                        else
1081                        {
1082                                if (!$this->acl_check (array(
1083                                                'string'        => $p->fake_full_path,
1084                                                'relatives'     => array ($p->mask),
1085                                                'operation'     => PHPGW_ACL_ADD
1086                                        ))
1087                                )
1088                                {
1089                                        return False;
1090                                }
1091
1092                                $query = $GLOBALS['phpgw']->db->query ("INSERT INTO phpgw_vfs (owner_id, directory, name) VALUES ($this->working_id, '".
1093                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."', '".
1094                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."')", __LINE__, __FILE__);
1095
1096                                $this->set_attributes(array(
1097                                        'string'        => $p->fake_full_path,
1098                                        'relatives'     => array ($p->mask),
1099                                        'attributes'    => array (
1100                                                                'createdby_id' => $account_id,
1101                                                                'created' => $this->now,
1102                                                                'size' => 0,
1103                                                                'deleteable' => 'Y',
1104                                                                'app' => $currentapp
1105                                                        )
1106                                        )
1107                                );
1108                                $this->correct_attributes (array(
1109                                                'string'        => $p->fake_full_path,
1110                                                'relatives'     => array ($p->mask)
1111                                        )
1112                                );
1113       
1114                                $this->add_journal (array(
1115                                                'string'        => $p->fake_full_path,
1116                                                'relatives'     => array ($p->mask),
1117                                                'operation'     => VFS_OPERATION_CREATED
1118                                        )
1119                                );
1120                        }
1121
1122                        if ($rr || $vr || $query)
1123                        {
1124                                return True;
1125                        }
1126                        else
1127                        {
1128                                return False;
1129                        }
1130                }
1131
1132                /*
1133                 * See vfs_shared
1134                 * If $data['symlink'] the file is symlinked instead of copied
1135                 */
1136                function cp ($data)
1137                {
1138                        if (!is_array ($data))
1139                        {
1140                                $data = array ();
1141                        }
1142                        if ($data['relatives'][1] == RELATIVE_NONE)
1143                                $path = explode(SEP,$data['to']);
1144                        else
1145                                $path = explode(SEP,$this->my_home);
1146                        $quota = $this->get_quota(array('string' => SEP.$path[1].SEP.$path[2]));
1147                        $size = $this->get_size(array('string' => SEP.$path[1].SEP.$path[2], 'relatives' => $data['relatives'][1]));
1148
1149                        if ($quota > 0 && ($quota * 1024 * 1024) < $size)
1150                                return false;
1151
1152                        $default_values = array
1153                                (
1154                                        'relatives'     => array (RELATIVE_CURRENT, RELATIVE_CURRENT)
1155                                );
1156
1157                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1158
1159                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1160
1161                        $f = $this->path_parts (array(
1162                                        'string'        => $data['from'],
1163                                        'relatives'     => array ($data['relatives'][0])
1164                                )
1165                        );
1166
1167                        $t = $this->path_parts (array(
1168                                        'string'        => $data['to'],
1169                                        'relatives'     => array ($data['relatives'][1])
1170                                )
1171                        );
1172
1173                        if (!$this->acl_check (array(
1174                                        'string'        => $f->fake_full_path,
1175                                        'relatives'     => array ($f->mask),
1176                                        'operation'     => PHPGW_ACL_READ
1177                                ))
1178                        )
1179                        {
1180                                return False;
1181                        }
1182
1183                        if ($exists = $this->file_exists (array(
1184                                        'string'        => $t->fake_full_path,
1185                                        'relatives'     => array ($t->mask)
1186                                ))
1187                        )
1188                        {
1189                                if (!$this->acl_check (array(
1190                                                'string'        => $t->fake_full_path,
1191                                                'relatives'     => array ($t->mask),
1192                                                'operation'     => PHPGW_ACL_EDIT
1193                                        ))
1194                                )
1195                                {
1196                                        return False;
1197                                }
1198                        }
1199                        else
1200                        {
1201                                if (!$this->acl_check (array(
1202                                                'string'        => $t->fake_full_path,
1203                                                'relatives'     => array ($t->mask),
1204                                                'operation'     => PHPGW_ACL_ADD
1205                                        ))
1206                                )
1207                                {
1208                                        return False;
1209                                }
1210                        }
1211
1212                        umask(0177);
1213
1214                        if ($this->file_type (array(
1215                                        'string'        => $f->fake_full_path,
1216                                        'relatives'     => array ($f->mask)
1217                                )) != 'Directory'
1218                        )
1219                        {
1220                                if ($this->file_actions)
1221                                {
1222                                        if (@$data['symlink'])
1223                                        {
1224                                                if ($exists)
1225                                                {
1226                                                        @unlink($t->real_full_path);
1227                                                }
1228                                                if (!symlink($f->real_full_path, $t->real_full_path))
1229                                                {
1230                                                        return False;
1231                                                }
1232                                        }
1233                                        elseif (!copy ($f->real_full_path, $t->real_full_path))
1234                                        {
1235                                                return False;
1236                                        }
1237
1238                                        $size = filesize ($t->real_full_path);
1239                                }
1240                                else
1241                                {
1242                                        $content = $this->read (array(
1243                                                        'string'        => $f->fake_full_path,
1244                                                        'relatives'     => array ($f->mask)
1245                                                )
1246                                        );
1247
1248                                        $size = strlen ($content);
1249                                }
1250
1251                                if ($t->outside)
1252                                {
1253                                        return True;
1254                                }
1255
1256                                $ls_array = $this->ls (array(
1257                                                'string'        => $f->fake_full_path,
1258                                                'relatives'     => array ($f->mask),
1259                                                'checksubdirs'  => False,
1260                                                'mime_type'     => False,
1261                                                'summary'       => True,
1262                                                'nofiles'       => True
1263                                        )
1264                                );
1265                                $record = $ls_array[0];
1266
1267                                if ($this->file_exists (array(
1268                                                'string'        => $data['to'],
1269                                                'relatives'     => array ($data['relatives'][1])
1270                                        ))
1271                                )
1272                                {
1273                                        $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET owner_id='$this->working_id', directory='".
1274                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_leading_dirs_clean)."', name='".
1275                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_name_clean)."' WHERE owner_id='$this->working_id' AND directory='".
1276                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_leading_dirs_clean)."' AND name='".
1277                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_name_clean)."'" . $this->extra_sql (VFS_SQL_UPDATE), __LINE__, __FILE__);
1278                               
1279                                        $set_attributes_array = array (
1280                                                'createdby_id' => $account_id,
1281                                                'created' => $this->now,
1282                                                'size' => $size,
1283                                                'mime_type' => $record['mime_type'],
1284                                                'deleteable' => $record['deleteable'],
1285                                                'comment' => $record['comment'],
1286                                                'app' => $record['app']
1287                                        );
1288
1289                                        if (!$this->file_actions)
1290                                        {
1291                                                $set_attributes_array['content'] = $content;
1292                                        }
1293
1294                                        $this->set_attributes(array(
1295                                                'string'        => $t->fake_full_path,
1296                                                'relatives'     => array ($t->mask),
1297                                                'attributes'    => $set_attributes_array
1298                                                )
1299                                        );
1300
1301                                        $this->add_journal (array(
1302                                                        'string'        => $t->fake_full_path,
1303                                                        'relatives'     => array ($t->mask),
1304                                                        'operation'     => VFS_OPERATION_EDITED
1305                                                )
1306                                        );
1307                                }
1308                                else
1309                                {
1310                                        $this->touch (array(
1311                                                        'string'        => $t->fake_full_path,
1312                                                        'relatives'     => array ($t->mask)
1313                                                )
1314                                        );
1315
1316                                        $set_attributes_array = array (
1317                                                'createdby_id' => $account_id,
1318                                                'created' => $this->now,
1319                                                'size' => $size,
1320                                                'mime_type' => $record['mime_type'],
1321                                                'deleteable' => $record['deleteable'],
1322                                                'comment' => $record['comment'],
1323                                                'app' => $record['app']
1324                                        );
1325
1326                                        if (!$this->file_actions)
1327                                        {
1328                                                $set_attributes_array['content'] = $content;
1329                                        }
1330
1331                                        $this->set_attributes(array(
1332                                                        'string'        => $t->fake_full_path,
1333                                                        'relatives'     => array ($t->mask),
1334                                                        'attributes'    => $set_attributes_array
1335                                                )
1336                                        );
1337                                        if (!(strpos(strtoupper($record['mime_type']),'IMAGE') === FALSE))
1338                                        {               
1339                                                $this->set_summary(array(
1340                                                        'string'=> $data['to'],
1341                                                        'relatives' => array ($data['relatives'][1]),
1342                                                        'summary'=> $record['summary']
1343                                                ));
1344                                                unset($record['summary']);
1345                                        }
1346                                }
1347                                $this->correct_attributes (array(
1348                                                'string'        => $t->fake_full_path,
1349                                                'relatives'     => array ($t->mask)
1350                                        )
1351                                );
1352                        }
1353                        else    /* It's a directory */
1354                        {
1355                                /* First, make the initial directory */
1356                                $this->mkdir (array(
1357                                                'string'        => $data['to'],
1358                                                'relatives'     => array ($data['relatives'][1])
1359                                        )
1360                                );
1361
1362                                /* Next, we create all the directories below the initial directory */
1363                                foreach($this->ls (array(
1364                                                'string'        => $f->fake_full_path,
1365                                                'relatives'     => array ($f->mask),
1366                                                'checksubdirs'  => True,
1367                                                'mime_type'     => 'Directory'
1368                                        )) as $entry)
1369                                {
1370                                        $newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
1371                                        $this->mkdir (array(
1372                                                        'string'        => $newdir.'/'.$entry['name'],
1373                                                        'relatives'     => array ($t->mask)
1374                                                )
1375                                        );
1376                                }
1377
1378                                /* Lastly, we copy the files over */
1379                                foreach($this->ls (array(
1380                                                'string'        => $f->fake_full_path,
1381                                                'relatives'     => array ($f->mask)
1382                                        )) as $entry)
1383                                {
1384                                        if ($entry['mime_type'] == 'Directory')
1385                                        {
1386                                                continue;
1387                                        }
1388
1389                                        $newdir = ereg_replace ("^$f->fake_full_path", "$t->fake_full_path", $entry['directory']);
1390                                        $this->cp (array(
1391                                                        'from'  => "$entry[directory]/$entry[name]",
1392                                                        'to'    => "$newdir/$entry[name]",
1393                                                        'relatives'     => array ($f->mask, $t->mask)
1394                                                )
1395                                        );
1396                                }
1397                        }
1398
1399                        if (!$f->outside)
1400                        {
1401                                $this->add_journal (array(
1402                                                'string'        => $f->fake_full_path,
1403                                                'relatives'     => array ($f->mask),
1404                                                'operation'     => VFS_OPERATION_COPIED,
1405                                                'state_one'     => NULL,
1406                                                'state_two'     => $t->fake_full_path
1407                                        )
1408                                );
1409                        }
1410
1411                        return True;
1412                }
1413
1414                /*
1415                 * See vfs_shared
1416                 */
1417                function mv ($data)
1418                {
1419                        if (!is_array ($data))
1420                        {
1421                                $data = array ();
1422                        }
1423                        if ($data['relatives'][1] == RELATIVE_NONE)
1424                        {
1425                                $path = explode('/',$data['to']);
1426                                $quota = $this->get_quota(array('string' => '/'.$path[1].'/'.$path[2]));
1427                                $size = $this->get_size(array('string' => '/'.$path[1].'/'.$path[2], 'relatives' => $data['relatives'][1]));
1428                        }
1429                        else
1430                        {
1431                                $quota = $this->get_quota(array('string' => $this->my_home));
1432                                $size = $this->get_size(array('string' => $this->my_home, 'relatives' => $data['relatives'][1]));
1433                        }
1434                        if ($quota > 0 && $size >= $quota * 1024 * 1024)
1435                                return false;
1436
1437
1438                        $default_values = array
1439                                (
1440                                        'relatives'     => array (RELATIVE_CURRENT, RELATIVE_CURRENT)
1441                                );
1442
1443                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1444
1445                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1446
1447                        $f = $this->path_parts (array(
1448                                        'string'        => $data['from'],
1449                                        'relatives'     => array ($data['relatives'][0])
1450                                )
1451                        );
1452
1453                        $t = $this->path_parts (array(
1454                                        'string'        => $data['to'],
1455                                        'relatives'     => array ($data['relatives'][1])
1456                                )
1457                        );
1458
1459                        if (!$this->acl_check (array(
1460                                        'string'        => $data['to'],
1461                                        'relatives'     => array ($t->mask),
1462                                        'operation'     => PHPGW_ACL_ADD
1463                                ))
1464                        )
1465                        {
1466                                return False;
1467                        }
1468
1469                        if ($this->file_exists (array(
1470                                        'string'        => $t->fake_full_path,
1471                                        'relatives'     => array ($t->mask)
1472                                ))
1473                        )
1474                        {
1475                                if (!$this->acl_check (array(
1476                                                'string'        => $data['to'],
1477                                                'relatives'     => array ($t->mask),
1478                                                'operation'     => PHPGW_ACL_EDIT
1479                                        ))
1480                                )
1481                                {
1482                                        return False;
1483                                }
1484                        }
1485
1486                        umask (0177);
1487
1488                        /* We can't move directories into themselves */
1489                        if (($this->file_type (array(
1490                                        'string'        => $f->fake_full_path,
1491                                        'relatives'     => array ($f->mask)
1492                                ) == 'Directory'))
1493                                && ereg ("^$f->fake_full_path", $t->fake_full_path)
1494                        )
1495                        {
1496                                if (($t->fake_full_path == $f->fake_full_path) || substr ($t->fake_full_path, strlen ($f->fake_full_path), 1) == '/')
1497                                {
1498                                        return False;
1499                                }
1500                        }
1501
1502                        if ($this->file_exists (array(
1503                                        'string'        => $f->fake_full_path,
1504                                        'relatives'     => array ($f->mask)
1505                                ))
1506                        )
1507                        {
1508                                /* We get the listing now, because it will change after we update the database */
1509                                $ls = $this->ls (array(
1510                                                'string'        => $f->fake_full_path,
1511                                                'relatives'     => array ($f->mask)
1512                                        )
1513                                );
1514
1515                                if ($this->file_exists (array(
1516                                                'string'        => $t->fake_full_path,
1517                                                'relatives'     => array ($t->mask)
1518                                        ))
1519                                )
1520                                {
1521                                        $this->rm (array(
1522                                                        'string'        => $t->fake_full_path,
1523                                                        'relatives'     => array ($t->mask)
1524                                                )
1525                                        );
1526                                }
1527
1528                                /*
1529                                   We add the journal entry now, before we delete.  This way the mime_type
1530                                   field will be updated to 'journal-deleted' when the file is actually deleted
1531                                */
1532                                if (!$f->outside)
1533                                {
1534                                        $this->add_journal (array(
1535                                                        'string'        => $f->fake_full_path,
1536                                                        'relatives'     => array ($f->mask),
1537                                                        'operation'     => VFS_OPERATION_MOVED,
1538                                                        'state_one'     => $f->fake_full_path,
1539                                                        'state_two'     => $t->fake_full_path
1540                                                )
1541                                        );
1542                                }
1543
1544                                /*
1545                                   If the from file is outside, it won't have a database entry,
1546                                   so we have to touch it and find the size
1547                                */
1548                                if ($f->outside)
1549                                {
1550                                        $size = filesize ($f->real_full_path);
1551
1552                                        $this->touch (array(
1553                                                        'string'        => $t->fake_full_path,
1554                                                        'relatives'     => array ($t->mask)
1555                                                )
1556                                        );
1557                                        $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET size=$size WHERE directory='".
1558                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_leading_dirs_clean)."' AND name='".
1559                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_UPDATE)), __LINE__, __FILE__);
1560                                }
1561                                elseif (!$t->outside)
1562                                {
1563                                        $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET name='".
1564                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_name_clean)."', directory='".
1565                                                $GLOBALS['phpgw']->db->db_addslashes($t->fake_leading_dirs_clean)."' WHERE directory='".
1566                                                $GLOBALS['phpgw']->db->db_addslashes($f->fake_leading_dirs_clean)."' AND name='".
1567                                                $GLOBALS['phpgw']->db->db_addslashes($f->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_UPDATE)), __LINE__, __FILE__);
1568                                }
1569
1570                                $this->set_attributes(array(
1571                                                'string'        => $t->fake_full_path,
1572                                                'relatives'     => array ($t->mask),
1573                                                'attributes'    => array (
1574                                                                        'modifiedby_id' => $account_id,
1575                                                                        'modified' => $this->now
1576                                                                )
1577                                        )
1578                                );
1579
1580                                $this->correct_attributes (array(
1581                                                'string'        => $t->fake_full_path,
1582                                                'relatives'     => array ($t->mask)
1583                                        )
1584                                );
1585
1586                                if ($this->file_actions)
1587                                {
1588                                        $rr = rename ($f->real_full_path, $t->real_full_path);
1589                                }
1590
1591                                /*
1592                                   This removes the original entry from the database
1593                                   The actual file is already deleted because of the rename () above
1594                                */
1595                                if ($t->outside)
1596                                {
1597                                        $this->rm (array(
1598                                                        'string'        => $f->fake_full_path,
1599                                                        'relatives'     => $f->mask
1600                                                )
1601                                        );
1602                                }
1603                        }
1604                        else
1605                        {
1606                                return False;
1607                        }
1608
1609                        if ($this->file_type (array(
1610                                        'string'        => $t->fake_full_path,
1611                                        'relatives'     => array ($t->mask)
1612                                )) == 'Directory'
1613                        )
1614                        {
1615                                /* We got $ls from above, before we renamed the directory */
1616                                foreach ($ls as $entry)
1617                                {
1618                                        $newdir = ereg_replace ("^$f->fake_full_path", $t->fake_full_path, $entry['directory']);
1619                                        $newdir_clean = $this->clean_string (array ('string' => $newdir));
1620
1621                                        $query = $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET directory='".
1622                                                $GLOBALS['phpgw']->db->db_addslashes($newdir_clean)."' WHERE file_id='$entry[file_id]'" .
1623                                                $this->extra_sql (array ('query_type' => VFS_SQL_UPDATE)), __LINE__, __FILE__);
1624                                        $this->correct_attributes (array(
1625                                                        'string'        => "$newdir/$entry[name]",
1626                                                        'relatives'     => array ($t->mask)
1627                                                )
1628                                        );
1629                                }
1630                        }
1631
1632                        $this->add_journal (array(
1633                                        'string'        => $t->fake_full_path,
1634                                        'relatives'     => array ($t->mask),
1635                                        'operation'     => VFS_OPERATION_MOVED,
1636                                        'state_one'     => $f->fake_full_path,
1637                                        'state_two'     => $t->fake_full_path
1638                                )
1639                        );
1640
1641                        return True;
1642                }
1643
1644                /*
1645                 * See vfs_shared
1646                 */
1647                function rm ($data)
1648                {
1649                        if (!is_array ($data))
1650                        {
1651                                $data = array ();
1652                        }
1653
1654                        $default_values = array
1655                                (
1656                                        'relatives'     => array (RELATIVE_CURRENT)
1657                                );
1658
1659                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1660
1661                        $p = $this->path_parts (array(
1662                                        'string'        => $data['string'],
1663                                        'relatives'     => array ($data['relatives'][0])
1664                                )
1665                        );
1666                        if (!$this->file_exists (array(
1667                                        'string'        => $data['string'],
1668                                        'relatives'     => array ($data['relatives'][0])
1669                                ))
1670                        )
1671                        {
1672                                if ($this->file_actions)
1673                                {
1674                                        $rr = unlink ($p->real_full_path);
1675                                }
1676                                else
1677                                {
1678                                        $rr = True;
1679                                }
1680
1681                                if ($rr)
1682                                {
1683                                        return True;
1684                                }
1685                                else
1686                                {
1687                                        return False;
1688                                }
1689                        }
1690
1691                        if ($this->file_type (array(
1692                                        'string'        => $data['string'],
1693                                        'relatives'     => array ($data['relatives'][0])
1694                                )) != 'Directory'
1695                        )
1696                        {
1697                                $this->add_journal (array(
1698                                                'string'        => $p->fake_full_path,
1699                                                'relatives'     => array ($p->mask),
1700                                                'operation'     => VFS_OPERATION_DELETED
1701                                        )
1702                                );
1703
1704                                $query = $GLOBALS['phpgw']->db->query ("DELETE FROM phpgw_vfs WHERE directory='".
1705                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
1706                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'".$this->extra_sql (array ('query_type' => VFS_SQL_DELETE)), __LINE__, __FILE__);
1707
1708                                if ($this->file_actions)
1709                                {
1710                                        $rr = unlink ($p->real_full_path);
1711                                }
1712                                else
1713                                {
1714                                        $rr = True;
1715                                }
1716
1717                                if ($query || $rr)
1718                                {
1719                                        return True;
1720                                }
1721                                else
1722                                {
1723                                        return False;
1724                                }
1725                        }
1726                        else
1727                        {
1728                                $ls = $this->ls (array(
1729                                                'string'        => $p->fake_full_path,
1730                                                'relatives'     => array ($p->mask)
1731                                        )
1732                                );
1733
1734                                /* First, we cycle through the entries and delete the files */
1735                                foreach($ls as $entry)
1736                                {
1737                                        if ($entry['mime_type'] == 'Directory')
1738                                        {
1739                                                continue;
1740                                        }
1741
1742                                        $this->rm (array(
1743                                                        'string'        => "$entry[directory]/$entry[name]",
1744                                                        'relatives'     => array ($p->mask)
1745                                                )
1746                                        );
1747                                }
1748
1749                                /* Now we cycle through again and delete the directories */
1750                                foreach ($ls as $entry)
1751                                {
1752                                        if ($entry['mime_type'] != 'Directory')
1753                                        {
1754                                                continue;
1755                                        }
1756
1757                                        /* Only the best in confusing recursion */
1758                                        $this->rm (array(
1759                                                        'string'        => "$entry[directory]/$entry[name]",
1760                                                        'relatives'     => array ($p->mask)
1761                                                )
1762                                        );
1763                                }
1764
1765                                /* If the directory is linked, we delete the placeholder directory */
1766                                $ls_array = $this->ls (array(
1767                                                'string'        => $p->fake_full_path,
1768                                                'relatives'     => array ($p->mask),
1769                                                'checksubdirs'  => False,
1770                                                'mime_type'     => False,
1771                                                'nofiles'       => True
1772                                        )
1773                                );
1774                                $link_info = $ls_array[0];
1775
1776                                if ($link_info['link_directory'] && $link_info['link_name'])
1777                                {
1778                                        $path = $this->path_parts (array(
1779                                                        'string'        => $link_info['directory'] . '/' . $link_info['name'],
1780                                                        'relatives'     => array ($p->mask),
1781                                                        'nolinks'       => True
1782                                                )
1783                                        );
1784
1785                                        if ($this->file_actions)
1786                                        {
1787                                                rmdir ($path->real_full_path);
1788                                        }
1789                                }
1790
1791                                /* Last, we delete the directory itself */
1792                                $this->add_journal (array(
1793                                                'string'        => $p->fake_full_path,
1794                                                'relatives'     => array ($p->mask),
1795                                                'operaton'      => VFS_OPERATION_DELETED
1796                                        )
1797                                );
1798
1799                                $query = $GLOBALS['phpgw']->db->query ("DELETE FROM phpgw_vfs WHERE directory='".
1800                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
1801                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" .
1802                                        $this->extra_sql (array ('query_type' => VFS_SQL_DELETE)), __LINE__, __FILE__);
1803
1804                                if ($this->file_actions)
1805                                {
1806                                        rmdir ($p->real_full_path);
1807                                }
1808
1809                                return True;
1810                        }
1811                }
1812
1813                /*
1814                 * See vfs_shared
1815                 */
1816                function mkdir ($data)
1817                {
1818                        if (!is_array ($data))
1819                        {
1820                                $data = array ();
1821                        }
1822
1823                        $default_values = array
1824                                (
1825                                        'relatives'     => array (RELATIVE_CURRENT)
1826                                );
1827
1828                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1829
1830                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1831                        $currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
1832
1833                        $p = $this->path_parts (array(
1834                                        'string'        => $data['string'],
1835                                        'relatives'     => array ($data['relatives'][0])
1836                                )
1837                        );
1838
1839                        if (!$this->acl_check (array(
1840                                        'string'        => $p->fake_full_path,
1841                                        'relatives'     => array ($p->mask),
1842                                        'operation'     => PHPGW_ACL_ADD)
1843                                )
1844                        )
1845                        {
1846                                return False;
1847                        }
1848
1849                        /* We don't allow /'s in dir names, of course */
1850                        if (ereg ("/", $p->fake_name))
1851                        {
1852                                return False;
1853                        }
1854
1855                        umask (077);
1856
1857                        if ($this->file_actions)
1858                        {
1859                                if (!@is_dir($p->real_leading_dirs_clean))      // eg. /home or /group does not exist
1860                                {
1861                                        if (!@mkdir($p->real_leading_dirs_clean,0770))  // ==> create it
1862                                        {
1863                                                return False;
1864                                        }
1865                                }
1866                                if (@is_dir($p->real_full_path))        // directory already exists
1867                                {
1868                                        $this->update_real($data,True);         // update its contents
1869                                }
1870                                elseif (!@mkdir ($p->real_full_path, 0770))
1871                                {
1872                                        return False;
1873                                }
1874                        }
1875
1876                        if (!$this->file_exists (array(
1877                                        'string'        => $p->fake_full_path,
1878                                        'relatives'     => array ($p->mask)
1879                                ))
1880                        )
1881                        {
1882                                $query = $GLOBALS['phpgw']->db->query ("INSERT INTO phpgw_vfs (owner_id, name, directory) VALUES ($this->working_id, '".
1883                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."', '".
1884                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."')", __LINE__, __FILE__);
1885       
1886                                $this->set_attributes(array(
1887                                        'string'        => $p->fake_full_path,
1888                                        'relatives'     => array ($p->mask),
1889                                        'attributes'    => array (
1890                                                                'createdby_id' => $account_id,
1891                                                                'size' => 4096,
1892                                                                'mime_type' => 'Directory',
1893                                                                'created' => $this->now,
1894                                                                'deleteable' => 'Y',
1895                                                                'app' => $currentapp
1896                                                        )
1897                                        )
1898                                );
1899
1900                                $this->correct_attributes (array(
1901                                                'string'        => $p->fake_full_path,
1902                                                'relatives'     => array ($p->mask)
1903                                        )
1904                                );
1905
1906                                $this->add_journal (array(
1907                                                'string'        => $p->fake_full_path,
1908                                                'relatives'     => array ($p->mask),
1909                                                'operation'     => VFS_OPERATION_CREATED
1910                                        )
1911                                );
1912                        }
1913                        else
1914                        {
1915                                return False;
1916                        }
1917
1918                        return True;
1919                }
1920
1921                /*
1922                 * See vfs_shared
1923                 */
1924                function make_link ($data)
1925                {
1926                        if (!is_array ($data))
1927                        {
1928                                $data = array ();
1929                        }
1930
1931                        $default_values = array
1932                                (
1933                                        'relatives'     => array (RELATIVE_CURRENT, RELATIVE_CURRENT)
1934                                );
1935
1936                        $data = array_merge ($this->default_values ($data, $default_values), $data);
1937
1938                        $account_id = $GLOBALS['phpgw_info']['user']['account_id'];
1939                        $currentapp = $GLOBALS['phpgw_info']['flags']['currentapp'];
1940
1941                        $vp = $this->path_parts (array(
1942                                        'string'        => $data['vdir'],
1943                                        'relatives'     => array ($data['relatives'][0])
1944                                )
1945                        );
1946
1947                        $rp = $this->path_parts (array(
1948                                        'string'        => $data['rdir'],
1949                                        'relatives'     => array ($data['relatives'][1])
1950                                )
1951                        );
1952
1953                        if (!$this->acl_check (array(
1954                                        'string'        => $vp->fake_full_path,
1955                                        'relatives'     => array ($vp->mask),
1956                                        'operation'     => PHPGW_ACL_ADD
1957                                ))
1958                        )
1959                        {
1960                                return False;
1961                        }
1962
1963                        if ((!$this->file_exists (array(
1964                                        'string'        => $rp->real_full_path,
1965                                        'relatives'     => array ($rp->mask)
1966                                )))
1967                                && !mkdir ($rp->real_full_path, 0770))
1968                        {
1969                                return False;
1970                        }
1971
1972                        if (!$this->mkdir (array(
1973                                        'string'        => $vp->fake_full_path,
1974                                        'relatives'     => array ($vp->mask)
1975                                ))
1976                        )
1977                        {
1978                                return False;
1979                        }
1980
1981                        $size = $this->get_size (array(
1982                                        'string'        => $rp->real_full_path,
1983                                        'relatives'     => array ($rp->mask)
1984                                )
1985                        );
1986
1987                        $this->set_attributes(array(
1988                                        'string'        => $vp->fake_full_path,
1989                                        'relatives'     => array ($vp->mask),
1990                                        'attributes'    => array (
1991                                                                'link_directory' => $rp->real_leading_dirs,
1992                                                                'link_name' => $rp->real_name,
1993                                                                'size' => $size
1994                                                        )
1995                                )
1996                        );
1997
1998                        $this->correct_attributes (array(
1999                                        'string'        => $vp->fake_full_path,
2000                                        'relatives'     => array ($vp->mask)
2001                                )
2002                        );
2003
2004                        return True;
2005                }
2006                function summary ($data)
2007                {
2008
2009                        if (!$this->acl_check (array(
2010                                        'string'        => $data['path'].'/'.$data['string'],
2011                                        'relatives'     => array (RELATIVE_NONE),
2012                                        'operation'     => PHPGW_ACL_READ
2013                                ))
2014                        )
2015                        {
2016                                return False;
2017                        }
2018
2019                        $query = "SELECT summary FROM phpgw_vfs WHERE directory = '"
2020                                .$data['path']."' AND name = '".$data['string']."' and mime_type != 'journal' and mime_type != 'journal-deleted' LIMIT 1";
2021                        if ($GLOBALS['phpgw']->db->query($query) && $GLOBALS['phpgw']->db->next_record()){
2022                                $val = $GLOBALS['phpgw']->db->row();
2023                                return $val['summary'];
2024                        }
2025                }
2026
2027                function set_summary($data){
2028                        if (!is_array ($data))
2029                        {
2030                                $data = array ();
2031                        }
2032
2033                        $default_values = array
2034                                (
2035                                        'relatives'     => array (RELATIVE_CURRENT),
2036                                        'attributes'    => array ()
2037                                );
2038
2039                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2040
2041                        $p = $this->path_parts (array(
2042                                        'string'        => $data['string'],
2043                                        'relatives'     => array ($data['relatives'][0])
2044                                )
2045                        );
2046                        if (!$this->acl_check (array(
2047                                        'string'        => $p->fake_full_path,
2048                                        'relatives'     => array ($p->mask),
2049                                        'operation'     => PHPGW_ACL_EDIT
2050                                ))
2051                        )
2052                        {
2053                                return False;
2054                        }
2055                        if (!$this->file_exists (array(
2056                                        'string'        => $data['string'],
2057                                        'relatives'     => array ($data['relatives'][0])
2058                                ))
2059                        )
2060                        {
2061                                return False;
2062                        }
2063                        $ls_array = $this->ls (array(
2064                                        'string'        => $p->fake_full_path,
2065                                        'relatives'     => array ($p->mask),
2066                                        'checksubdirs'  => False,
2067                                        'nofiles'       => True
2068                                )
2069                        );
2070                        $record = $ls_array[0];
2071                        $data['summary'] = pg_escape_bytea($data['summary']);
2072                        $sql = 'UPDATE phpgw_vfs SET summary = \''.$data['summary'].'\'';
2073                        $sql .= ' WHERE file_id='.(int) $ls_array[0]['file_id'];
2074                        $sql .= $this->extra_sql (array ('query_type' => VFS_SQL_UPDATE));
2075                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
2076                }
2077
2078                /*
2079                 * See vfs_shared
2080                 */
2081                function set_attributes ($data)
2082                {
2083                        if (!is_array ($data))
2084                        {
2085                                $data = array ();
2086                        }
2087
2088                        $default_values = array
2089                                (
2090                                        'relatives'     => array (RELATIVE_CURRENT),
2091                                        'attributes'    => array ()
2092                                );
2093
2094                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2095
2096                        $p = $this->path_parts (array(
2097                                        'string'        => $data['string'],
2098                                        'relatives'     => array ($data['relatives'][0])
2099                                )
2100                        );
2101
2102                        /*
2103                           This is kind of trivial, given that set_attributes () can change owner_id,
2104                           size, etc.
2105                        */
2106                        if (!$this->acl_check (array(
2107                                        'string'        => $p->fake_full_path,
2108                                        'relatives'     => array ($p->mask),
2109                                        'operation'     => PHPGW_ACL_EDIT
2110                                ))
2111                        )
2112                        {
2113                                return False;
2114                        }
2115
2116                        if (!$this->file_exists (array(
2117                                        'string'        => $data['string'],
2118                                        'relatives'     => array ($data['relatives'][0])
2119                                ))
2120                        )
2121                        {
2122                                return False;
2123                        }
2124
2125                        /*
2126                           All this voodoo just decides which attributes to update
2127                           depending on if the attribute was supplied in the 'attributes' array
2128                        */
2129
2130                        $ls_array = $this->ls (array(
2131                                        'string'        => $p->fake_full_path,
2132                                        'relatives'     => array ($p->mask),
2133                                        'checksubdirs'  => False,
2134                                        'nofiles'       => True
2135                                )
2136                        );
2137                        $record = $ls_array[0];
2138
2139                        $sql = 'UPDATE phpgw_vfs SET ';
2140
2141                        $change_attributes = 0;
2142
2143                        foreach ($this->attributes as $attribute)
2144                        {
2145                                if (isset ($data['attributes'][$attribute]))
2146                                {
2147                                        /*
2148                                           Indicate that the EDITED_COMMENT operation needs to be journaled,
2149                                           but only if the comment changed
2150                                        */
2151                                        if ($attribute == 'comment' && $data['attributes'][$attribute] != $record[$attribute])
2152                                        {
2153                                                $edited_comment = 1;
2154                                        }
2155
2156                                        if ($change_attributes > 0)
2157                                        {
2158                                                $sql .= ', ';
2159                                        }
2160
2161                                        // RalfBecker 2004/07/24:
2162                                        // this is only a hack to fix bug [ 991222 ] Error uploading file
2163                                        // the whole class need to be reworked with the new db-functions
2164                                        if (!isset($this->column_defs))
2165                                        {
2166                                                $table_defs = $GLOBALS['phpgw']->db->get_table_definitions('phpgwapi','phpgw_vfs');
2167                                                $this->column_defs = $table_defs['fd'];
2168                                        }
2169                                        $sql .= $attribute.'=' .$GLOBALS['phpgw']->db->quote($data['attributes'][$attribute],$this->column_defs[$attribute]['type']);
2170
2171                                        $change_attributes++;
2172                                }
2173                        }
2174
2175                        if (!$change_attributes)
2176                        {
2177                                return True;    // nothing to do
2178                        }
2179                        $sql .= ' WHERE file_id='.(int) $record['file_id'];
2180                        $sql .= $this->extra_sql (array ('query_type' => VFS_SQL_UPDATE));
2181                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
2182
2183                        if ($query)
2184                        {
2185                                if ($edited_comment)
2186                                {
2187                                        $this->add_journal (array(
2188                                                        'string'        => $p->fake_full_path,
2189                                                        'relatives'     => array ($p->mask),
2190                                                        'operation'     => VFS_OPERATION_EDITED_COMMENT
2191                                                )
2192                                        );
2193                                }
2194
2195                                return True;
2196                        }
2197                        else
2198                        {
2199                                return False;
2200                        }
2201                }
2202
2203                /*!
2204                @function correct_attributes
2205                @abstract Set the correct attributes for 'string' (e.g. owner)
2206                @param string File/directory to correct attributes of
2207                @param relatives Relativity array
2208                @result Boolean True/False
2209                */
2210                function correct_attributes ($data)
2211                {
2212                        if (!is_array ($data))
2213                        {
2214                                $data = array ();
2215                        }
2216
2217                        $default_values = array
2218                                (
2219                                        'relatives'     => array (RELATIVE_CURRENT)
2220                                );
2221
2222                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2223
2224                        $p = $this->path_parts (array(
2225                                        'string'        => $data['string'],
2226                                        'relatives'     => array ($data['relatives'][0])
2227                                )
2228                        );
2229
2230                        if ($p->fake_leading_dirs != $this->fakebase && $p->fake_leading_dirs != '/')
2231                        {
2232                                $ls_array = $this->ls (array(
2233                                                'string'        => $p->fake_leading_dirs,
2234                                                'relatives'     => array ($p->mask),
2235                                                'checksubdirs'  => False,
2236                                                'nofiles'       => True
2237                                        )
2238                                );
2239                                $set_attributes_array = Array(
2240                                        'owner_id' => $ls_array[0]['owner_id']
2241                                );
2242                        }
2243                        elseif (preg_match ("+^$this->fakebase\/(.*)$+U", $p->fake_full_path, $matches))
2244                        {
2245                                $set_attributes_array = Array(
2246                                        'owner_id' => $GLOBALS['phpgw']->accounts->name2id ($matches[1])
2247                                );
2248                        }
2249                        else
2250                        {
2251                                $set_attributes_array = Array(
2252                                        'owner_id' => 0
2253                                );
2254                        }
2255
2256                        $this->set_attributes (array(
2257                                        'string'        => $p->fake_full_name,
2258                                        'relatives'     => array ($p->mask),
2259                                        'attributes'    => $set_attributes_array
2260                                )
2261                        );
2262
2263                        return True;
2264                }
2265
2266                /*
2267                 * See vfs_shared
2268                 */
2269                function file_type ($data)
2270                {
2271                        if (!is_array ($data))
2272                        {
2273                                $data = array ();
2274                        }
2275
2276                        $default_values = array
2277                                (
2278                                        'relatives'     => array (RELATIVE_CURRENT)
2279                                );
2280
2281                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2282
2283                        $p = $this->path_parts (array(
2284                                        'string'        => $data['string'],
2285                                        'relatives'     => array ($data['relatives'][0])
2286                                )
2287                        );
2288
2289                        if (!$this->acl_check (array(
2290                                        'string'        => $p->fake_full_path,
2291                                        'relatives'     => array ($p->mask),
2292                                        'operation'     => PHPGW_ACL_READ,
2293                                        'must_exist'    => True
2294                                ))
2295                        )
2296                        {
2297                                return False;
2298                        }
2299
2300                        if ($p->outside)
2301                        {
2302                                if (is_dir ($p->real_full_path))
2303                                {
2304                                        return ('Directory');
2305                                }
2306
2307                                /*
2308                                   We don't return an empty string here, because it may still match with a database query
2309                                   because of linked directories
2310                                */
2311                        }
2312
2313                        /*
2314                           We don't use ls () because it calls file_type () to determine if it has been
2315                           passed a directory
2316                        */
2317                        $db2 = $GLOBALS['phpgw']->db;
2318                        $db2->query ("SELECT mime_type FROM phpgw_vfs WHERE directory='".
2319                                $db2->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2320                                $db2->db_addslashes($p->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2321                        $db2->next_record ();
2322                        $mime_type = $db2->Record['mime_type'];
2323                        if(!$mime_type)
2324                        {
2325                                $mime_type = $this->get_ext_mime_type (array ('string' => $data['string']));
2326                                {
2327                                        $db2->query ("UPDATE phpgw_vfs SET mime_type='$mime_type' WHERE directory='".
2328                                                $db2->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2329                                                $db2->db_addslashes($p->fake_name_clean)."'" .
2330                                                $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2331                                }
2332                        }
2333
2334                        return $mime_type;
2335                }
2336
2337                /*
2338                 * See vfs_shared
2339                 */
2340                function file_exists ($data)
2341                {
2342                        if (!is_array ($data))
2343                        {
2344                                $data = array ();
2345                        }
2346
2347                        $default_values = array
2348                                (
2349                                        'relatives'     => array (RELATIVE_CURRENT)
2350                                );
2351
2352                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2353
2354                        $p = $this->path_parts (array(
2355                                        'string'        => $data['string'],
2356                                        'relatives'     => array ($data['relatives'][0])
2357                                )
2358                        );
2359
2360                        if ($p->outside)
2361                        {
2362                                $rr = file_exists ($p->real_full_path);
2363
2364                                return $rr;
2365                        }
2366
2367                        $db2 = $GLOBALS['phpgw']->db;
2368                        $db2->query ("SELECT name FROM phpgw_vfs WHERE directory='".
2369                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2370                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2371
2372                        if ($db2->next_record ())
2373                        {
2374                                return True;
2375                        }
2376                        else
2377                        {
2378                                return False;
2379                        }
2380                }
2381
2382                /*
2383                 * See vfs_shared
2384                 */
2385                function get_size ($data)
2386                {
2387                        if (!is_array ($data))
2388                        {
2389                                $data = array ();
2390                        }
2391
2392                        $default_values = array
2393                                (
2394                                        'relatives'     => array (RELATIVE_CURRENT),
2395                                        'checksubdirs'  => True
2396                                );
2397
2398                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2399
2400                        $p = $this->path_parts (array(
2401                                        'string'        => $data['string'],
2402                                        'relatives'     => array ($data['relatives'][0])
2403                                )
2404                        );
2405
2406                        if (!$this->acl_check (array(
2407                                        'string'        => $p->fake_full_path,
2408                                        'relatives'     => array ($p->mask),
2409                                        'operation'     => PHPGW_ACL_READ,
2410                                        'must_exist'    => True
2411                                ))
2412                        )
2413                        {
2414                                return False;
2415                        }
2416
2417                        /*
2418                           WIP - this should run through all of the subfiles/directories in the directory and tally up
2419                           their sizes.  Should modify ls () to be able to return a list for files outside the virtual root
2420                        */
2421                        if ($p->outside)
2422                        {
2423                                $size = filesize ($p->real_full_path);
2424
2425                                return $size;
2426                        }
2427
2428                        foreach($this->ls (array(
2429                                        'string'        => $p->fake_full_path,
2430                                        'relatives'     => array ($p->mask),
2431                                        'checksubdirs'  => $data['checksubdirs'],
2432                                        'nofiles'       => !$data['checksubdirs']
2433                                )) as $file_array)
2434                        {
2435                                /*
2436                                   Make sure the file is in the directory we want, and not
2437                                   some deeper nested directory with a similar name
2438                                */
2439/*
2440                                if (@!ereg ('^' . $file_array['directory'], $p->fake_full_path))
2441                                {
2442                                        continue;
2443                                }
2444*/
2445
2446                                $size += $file_array['size'];
2447                        }
2448
2449                        if ($data['checksubdirs'])
2450                        {
2451                                $query = $GLOBALS['phpgw']->db->query ("SELECT size FROM phpgw_vfs WHERE directory='".
2452                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2453                                        $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" .
2454                                        $this->extra_sql (array ('query_text' => VFS_SQL_SELECT)));
2455                                $GLOBALS['phpgw']->db->next_record ();
2456                                $size += $GLOBALS['phpgw']->db->Record[0];
2457                        }
2458
2459                        return $size;
2460                }
2461                /*
2462                 * get the quota defined for the path in sql table
2463                 */
2464                function get_quota($data){
2465                        if (!is_array ($data))
2466                        {
2467                                $data = array ();
2468                        }
2469
2470                        $default_values = array
2471                                (
2472                                        'relatives'     => array (RELATIVE_CURRENT)
2473                                );
2474
2475                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2476
2477                        $p = $this->path_parts (array(
2478                                        'string'        => $data['string'],
2479                                        'relatives'     => RELATIVE_NONE
2480                                )
2481                        );
2482
2483                        if (!$this->acl_check (array(
2484                                'string'        => $p->fake_full_path,
2485                                'relatives'     => $p->mask,
2486                                'operation'     => PHPGW_ACL_READ
2487                        ))
2488                        )
2489                        {
2490                                return False;
2491                        }
2492                        $query = $GLOBALS['phpgw']->db->query ("SELECT quota_size FROM phpgw_vfs_quota WHERE directory = '".$data['string']."' LIMIT 1;", __LINE__,__FILE__);
2493
2494                        $GLOBALS['phpgw']->db->next_record ();
2495                        $record = $GLOBALS['phpgw']->db->Record;
2496                        return $record['quota_size'];
2497                }
2498                function set_quota($data){
2499                        if (!is_array ($data))
2500                        {
2501                                $data = array ();
2502                        }
2503
2504                        $default_values = array
2505                                (
2506                                        'relatives'     => array (RELATIVE_CURRENT)
2507                                );
2508
2509                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2510
2511                        $p = $this->path_parts (array(
2512                                        'string'        => $data['string'],
2513                                        'relatives'     => array ($data['relatives'][0])
2514                                )
2515                        );
2516
2517                        if (!$this->acl_check (array(
2518                                'string'        => $p->fake_full_path,
2519                                'relatives'     => array ($p->mask),
2520                                'operation'     => PHPGW_ACL_READ
2521                        ))
2522                        )
2523                        {
2524                                return False;
2525                        }
2526                        return $GLOBALS['phpgw']->db->query("INSERT INTO phpgw_vfs_quota VALUES ('".$data['string']."',".$data['new_quota'].");", __LINE__,__FILE__);
2527                }
2528
2529
2530                /*!
2531                @function checkperms
2532                @abstract Check if $this->working_id has write access to create files in $dir
2533                @discussion Simple call to acl_check
2534                @param string Directory to check access of
2535                @param relatives Relativity array
2536                @result Boolean True/False
2537                */
2538                function checkperms ($data)
2539                {
2540                        if (!is_array ($data))
2541                        {
2542                                $data = array ();
2543                        }
2544
2545                        $default_values = array
2546                                (
2547                                        'relatives'     => array (RELATIVE_CURRENT)
2548                                );
2549
2550                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2551
2552                        $p = $this->path_parts (array(
2553                                        'string'        => $data['string'],
2554                                        'relatives'     => array ($data['relatives'][0])
2555                                )
2556                        );
2557
2558                        if (!$this->acl_check (array(
2559                                        'string'        => $p->fake_full_path,
2560                                        'relatives'     => array ($p->mask),
2561                                        'operation'     => PHPGW_ACL_ADD
2562                                ))
2563                        )
2564                        {
2565                                return False;
2566                        }
2567                        else
2568                        {
2569                                return True;
2570                        }
2571                }
2572
2573                /*
2574                 * See vfs_shared
2575                 * If $data['readlink'] then a readlink is tryed on the real file
2576                 * If $data['file_id'] then the file_id is used instead of a path
2577                 */
2578                function ls ($data)
2579                {
2580                        if (!is_array ($data))
2581                        {
2582                                $data = array ();
2583                        }
2584
2585                        $default_values = array
2586                                (
2587                                        'relatives'     => array (RELATIVE_CURRENT),
2588                                        'checksubdirs'  => True,
2589                                        'mime_type'     => False,
2590                                        'nofiles'       => False,
2591                                        'orderby'       => 'directory'
2592                                );
2593
2594                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2595
2596                        $p = $this->path_parts (array(
2597                                        'string'        => $data['string'],
2598                                        'relatives'     => array ($data['relatives'][0])
2599                                )
2600                        );
2601                        $dir = $p->fake_full_path;
2602                        if($data['summary'])
2603                                $this->attributes['summary'] = 'summary';
2604
2605                        /* If they pass us a file or 'nofiles' is set, return the info for $dir only */
2606                        if (@$data['file_id']
2607                                || ((($type = $this->file_type (array(
2608                                        'string'        => $dir,
2609                                        'relatives'     => array ($p->mask)
2610                                )) != 'Directory'))
2611                                || ($data['nofiles'])) && !$p->outside
2612                        )
2613                        {
2614                                /* SELECT all, the, attributes */
2615                                $sql = 'SELECT ';
2616
2617                                foreach ($this->attributes as $num => $attribute)
2618                                {
2619                                        if ($num)
2620                                        {
2621                                                $sql .= ', ';
2622                                        }
2623
2624                                        $sql .= $attribute;
2625                                }
2626
2627                                $sql .= " FROM phpgw_vfs WHERE ";
2628                                if (@$data['file_id'])
2629                                {
2630                                        $sql .= 'file_id='.(int)$data['file_id'];
2631                                }
2632                                else
2633                                {
2634                                        $sql .= " directory='".$GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND".
2635                                                " name='".$GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'".
2636                                                $this->extra_sql (array ('query_type' => VFS_SQL_SELECT));
2637                                }
2638                                $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
2639
2640                                $GLOBALS['phpgw']->db->next_record ();
2641                                $record = $GLOBALS['phpgw']->db->Record;
2642
2643                                /* We return an array of one array to maintain the standard */
2644                                $rarray = array ();
2645                                foreach($this->attributes as $attribute)
2646                                {
2647                                        if ($attribute == 'mime_type' && !$record[$attribute])
2648                                        {
2649                                                $db2 = $GLOBALS['phpgw']->db;
2650                                                $record[$attribute] = $this->get_ext_mime_type (array(
2651                                                                'string' => $p->fake_name_clean
2652                                                        )
2653                                                );
2654
2655                                                if($record[$attribute])
2656                                                {
2657                                                        $db2->query ("UPDATE phpgw_vfs SET mime_type='".$record[$attribute]."' WHERE directory='".
2658                                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2659                                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2660                                                }
2661                                        }
2662
2663                                        $rarray[0][$attribute] = $record[$attribute];
2664                                }
2665                                if ($this->file_actions && @$data['readlink'])  // test if file is a symlink and get it's target
2666                                {
2667                                        $rarray[0]['symlink'] = @readlink($p->real_full_path);
2668                                }
2669                                if($data['summary'])
2670                                        unset($this->attributes['summary']);
2671
2672                                return $rarray;
2673                        }
2674
2675                        //WIP - this should recurse using the same options the virtual part of ls () does
2676                        /* If $dir is outside the virutal root, we have to check the file system manually */
2677                        if ($p->outside)
2678                        {
2679                                if ($this->file_type (array(
2680                                                'string'        => $p->fake_full_path,
2681                                                'relatives'     => array ($p->mask)
2682                                        )) == 'Directory'
2683                                        && !$data['nofiles']
2684                                )
2685                                {
2686                                        $dir_handle = opendir ($p->real_full_path);
2687                                        while ($filename = readdir ($dir_handle))
2688                                        {
2689                                                if ($filename == '.' || $filename == '..')
2690                                                {
2691                                                        continue;
2692                                                }
2693
2694                                                $rarray[] = $this->get_real_info (array(
2695                                                                'string'        => $p->real_full_path . SEP . $filename,
2696                                                                'relatives'     => array ($p->mask)
2697                                                        )
2698                                                );
2699                                        }
2700                                }
2701                                else
2702                                {
2703                                        $rarray[] = $this->get_real_info (array(
2704                                                        'string'        => $p->real_full_path,
2705                                                        'relatives'     => array ($p->mask)
2706                                                )
2707                                        );
2708                                }
2709
2710                                return $rarray;
2711                        }
2712
2713                        /* $dir's not a file, is inside the virtual root, and they want to check subdirs */
2714                        /* SELECT all, the, attributes FROM phpgw_vfs WHERE file=$dir */
2715                        $sql = 'SELECT ';
2716                        if (!$this->acl_check (array (
2717                                'string' => $p->fake_full_path,
2718                                'relatives' => array ($p->mask),
2719                                'operation' => PHPGW_ACL_PRIVATE)
2720                        ))
2721                        $query_type = " type != 1 AND";
2722                        else
2723                                $query_type = "";
2724
2725                        foreach($this->attributes as $num => $attribute)
2726                        {
2727                                if ($num)
2728                                {
2729                                        $sql .= ", ";
2730                                }
2731
2732                                $sql .= $attribute;
2733                        }
2734
2735                        $dir_clean = $this->clean_string (array ('string' => $dir));
2736                        $sql .= " FROM phpgw_vfs WHERE ".$query_type." directory LIKE '".$GLOBALS['phpgw']->db->db_addslashes($dir_clean)."%'";
2737                        $sql .= $this->extra_sql (array ('query_type' => VFS_SQL_SELECT));
2738
2739                        if ($data['mime_type'])
2740                        {
2741                                $sql .= " AND mime_type='".$data['mime_type']."'";
2742                        }
2743                        if (strlen($data['orderby']) > 0)
2744                                $sql .= ' ORDER BY '.$data['orderby'];
2745                        $query = $GLOBALS['phpgw']->db->query ($sql, __LINE__, __FILE__);
2746
2747                        $rarray = array ();
2748                        for ($i = 0; $GLOBALS['phpgw']->db->next_record (); $i++)
2749                        {
2750                                $record = $GLOBALS['phpgw']->db->Record;
2751
2752                                /* Further checking on the directory.  This makes sure /home/user/test won't match /home/user/test22 */
2753                                if (@!ereg ("^$dir(/|$)", $record['directory']))
2754                                {
2755                                        continue;
2756                                }
2757
2758                                /* If they want only this directory, then $dir should end without a trailing / */
2759                                if (!$data['checksubdirs'] && ereg ("^$dir/", $record['directory']))
2760                                {
2761                                        continue;
2762                                }
2763
2764                                foreach($this->attributes as $attribute)
2765                                {
2766                                        if ($attribute == 'mime_type' && !$record[$attribute])
2767                                        {
2768                                                $db2 = $GLOBALS['phpgw']->db;
2769                                                $record[$attribute] = $this->get_ext_mime_type (array(
2770                                                                'string'        => $p->fake_name_clean
2771                                                        )
2772                                                );
2773
2774                                                if($record[$attribute])
2775                                                {
2776                                                        $db2->query ("UPDATE phpgw_vfs SET mime_type='".$record[$attribute]."' WHERE directory='".
2777                                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2778                                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" . $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2779                                                }
2780                                        }
2781
2782                                        $rarray[$i][$attribute] = $record[$attribute];
2783                                }
2784                        }
2785
2786                        return $rarray;
2787                }
2788
2789                /*
2790                 * See vfs_shared
2791                 */
2792                function update_real ($data,$recursive = False)
2793                {
2794                        if (!is_array ($data))
2795                        {
2796                                $data = array ();
2797                        }
2798
2799                        $default_values = array
2800                                (
2801                                        'relatives'     => array (RELATIVE_CURRENT)
2802                                );
2803
2804                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2805
2806                        $p = $this->path_parts (array(
2807                                        'string'        => $data['string'],
2808                                        'relatives'     => array ($data['relatives'][0])
2809                                )
2810                        );
2811
2812                        if (file_exists ($p->real_full_path))
2813                        {
2814                                if (is_dir ($p->real_full_path))
2815                                {
2816                                        $dir_handle = opendir ($p->real_full_path);
2817                                        while ($filename = readdir ($dir_handle))
2818                                        {
2819                                                if ($filename == '.' || $filename == '..')
2820                                                {
2821                                                        continue;
2822                                                }
2823
2824                                                $rarray[] = $this->get_real_info (array(
2825                                                                'string'        => $p->fake_full_path . '/' . $filename,
2826                                                                'relatives'     => array (RELATIVE_NONE)
2827                                                        )
2828                                                );
2829                                        }
2830                                }
2831                                else
2832                                {
2833                                        $rarray[] = $this->get_real_info (array(
2834                                                        'string'        => $p->fake_full_path,
2835                                                        'relatives'     => array (RELATIVE_NONE)
2836                                                )
2837                                        );
2838                                }
2839
2840                                if (!is_array ($rarray))
2841                                {
2842                                        $rarray = array ();
2843                                }
2844
2845                                foreach($rarray as $num => $file_array)
2846                                {
2847                                        $p2 = $this->path_parts (array(
2848                                                        'string'        => $file_array['directory'] . '/' . $file_array['name'],
2849                                                        'relatives'     => array (RELATIVE_NONE)
2850                                                )
2851                                        );
2852
2853                                        /* Note the mime_type.  This can be "Directory", which is how we create directories */
2854                                        $set_attributes_array = Array(
2855                                                'size' => $file_array['size'],
2856                                                'mime_type' => $file_array['mime_type']
2857                                        );
2858
2859                                        if (!$this->file_exists (array(
2860                                                        'string'        => $p2->fake_full_path,
2861                                                        'relatives'     => array (RELATIVE_NONE)
2862                                                ))
2863                                        )
2864                                        {
2865                                                $this->touch (array(
2866                                                                'string'        => $p2->fake_full_path,
2867                                                                'relatives'     => array (RELATIVE_NONE)
2868                                                        )
2869                                                );
2870                                        }
2871                                        $this->set_attributes (array(
2872                                                        'string'        => $p2->fake_full_path,
2873                                                        'relatives'     => array (RELATIVE_NONE),
2874                                                        'attributes'    => $set_attributes_array
2875                                                )
2876                                        );
2877                                        if ($recursive && $file_array['mime_type'] == 'Directory')
2878                                        {
2879                                                $dir_data = $data;
2880                                                $dir_data['string'] = $file_array['directory'] . '/' . $file_array['name'];
2881                                                $this->update_real($dir_data,$recursive);
2882                                        }
2883                                }
2884                        }
2885                }
2886
2887                /* Helper functions */
2888
2889                /* This fetchs all available file system information for string (not using the database) */
2890                function get_real_info ($data)
2891                {
2892                        if (!is_array ($data))
2893                        {
2894                                $data = array ();
2895                        }
2896
2897                        $default_values = array
2898                                (
2899                                        'relatives'     => array (RELATIVE_CURRENT)
2900                                );
2901
2902                        $data = array_merge ($this->default_values ($data, $default_values), $data);
2903
2904                        $p = $this->path_parts (array(
2905                                        'string'        => $data['string'],
2906                                        'relatives'     => array ($data['relatives'][0])
2907                                )
2908                        );
2909
2910                        if (is_dir ($p->real_full_path))
2911                        {
2912                                $mime_type = 'Directory';
2913                        }
2914                        else
2915                        {
2916                                $mime_type = $this->get_ext_mime_type (array(
2917                                                'string'        => $p->fake_name
2918                                        )
2919                                );
2920
2921                                if($mime_type)
2922                                {
2923                                        $GLOBALS['phpgw']->db->query ("UPDATE phpgw_vfs SET mime_type='".$mime_type."' WHERE directory='".
2924                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_leading_dirs_clean)."' AND name='".
2925                                                $GLOBALS['phpgw']->db->db_addslashes($p->fake_name_clean)."'" .
2926                                                $this->extra_sql (array ('query_type' => VFS_SQL_SELECT)), __LINE__, __FILE__);
2927                                }
2928                        }
2929
2930                        $size = filesize ($p->real_full_path);
2931                        $rarray = array(
2932                                'directory' => $p->fake_leading_dirs,
2933                                'name' => $p->fake_name,
2934                                'size' => $size,
2935                                'mime_type' => $mime_type
2936                        );
2937
2938                        return ($rarray);
2939                }
2940        }
2941?>
Note: See TracBrowser for help on using the repository browser.