Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/files/model/_filelist.class.php
<?php
/**
 * This file implements the Filelist class.
 *
 * This file is part of the evoCore framework - {@link http://evocore.net/}
 * See also {@link https://github.com/b2evolution/b2evolution}.
 *
 * @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
 *
 * @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
 * Parts of this file are copyright (c)2004-2006 by Daniel HAHLER - {@link http://thequod.de/contact}.
 *
 * @package evocore
 */
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );


load_class( 'files/model/_file.class.php', 'File' );


/**
 * Holds a list of File objects.
 *
 * Can hold an arbitrary list of Files.
 * Can list files in a directory by itself.
 * Can walk recursively down a directory tree to list in "flat mode".
 * Can sort file list.
 * Can iterate through list.
 * Cannot hold files with different root type/ID.
 *
 * @see File
 * @package evocore
 */
class Filelist
{
   
/**
     * Flat mode? (all files recursive without dirs)
     * @param boolean
     */
   
var $flatmode;

   
/**
     * Do we want to include directories? (This gets used by {@link load()}).
     * @var boolean
     */
   
var $include_dirs = true;

   
/**
     * Do we want to include files? (This gets used by {@link load()}).
     * @var boolean
     */
   
var $include_files = true;

   
/**
     * The root of the file list.
     *
     * All files in this list MUST have that same FileRoot. Adding will fail otherwise.
     *
     * @var FileRoot
     */
   
var $_FileRoot;

   
/**
     * Path to list with trailing slash.
     *
     * false if we are constructing an arbitrary list (i-e not tied to a single directory)
     * fp> should use NULL instead of false
     *
     * @var string
     * @access protected
     */
   
var $_ads_list_path = false;

   
/**
     * Path to list reltive to root, with trailing slash
     *
     * false if we are constructing an arbitrary list (i-e not tied to a single directory)
     * fp> should use NULL instead of false
     *
     * @param string
     * @access protected
     */
   
var $_rds_list_path = false;

   
/**
     * Filename filter pattern
     *
     * Will be matched against the filename part (not the path)
     * NULL if disabled
     *
     * Can be a regular expression (see {@link Filelist::$_filter_is_regexp}), internally with delimiters/modifiers!
     *
     * Use {@link set_filter()} to set it.
     *
     * @var string
     * @access protected
     */
   
var $_filter = NULL;

   
/**
     * Is the filter a regular expression? NULL if unknown
     *
     * Use {@link set_filter()} to set it.
     *
     * @see Filelist::$_filter
     * @var boolean
     * @access protected
     */
   
var $_filter_is_regexp = NULL;

   
/**
     * The list of Files.
     * @var array
     * @access protected
     */
   
var $_entries = array();

   
/**
     * Index on File IDs (id => {@link $_entries} key).
     *
     * Note: fplanque>> what's the purpose of the md5 IDs??
     *
     * @todo make these direct links to &File objects
     * @var array
     * @access protected
     */
   
var $_md5_ID_index = array();

   
/**
     * Index on full paths (path => {@link $_entries} key).
     * @todo make these direct links to &File objects
     * @var array
     * @access protected
     */
   
var $_full_path_index = array();

   
/**
     * Index on (r)elative (s)lash terminated (f)ile/(d)irectory paths (rdfs_path => key into {@link $_entries}).
     *
     * @todo make these direct links to &File objects
     *
     * @var array
     * @access protected
     */
   
var $_rdfs_rel_path_index = array();

   
/**
     * Index on sort order (order # => {@link $_entries} key).
     * @todo make these direct links to &File objects
     * @var array
     * @access protected
     */
   
var $_order_index = array();

   
/**
     * Number of entries in the {@link $_entries} array
     *
     * Note: $_total_entries = $_total_dirs + $_total_files
     *
     * @var integer
     * @access protected
     */
   
var $_total_entries = 0;

   
/**
     * Number of directories
     * @var integer
     * @access protected
     */
   
var $_total_dirs = 0;

   
/**
     * Number of files
     * @var integer
     * @access protected
     */
   
var $_total_files = 0;

   
/**
     * Number of bytes
     * @var integer
     * @access protected
     */
   
var $_total_bytes = 0;

   
/**
     * Index of the current iterator position.
     *
     * This is the key of {@link $_order_index}
     *
     * @var integer
     * @access protected
     */
   
var $_current_idx = -1;

   
/**
     * What column is the list ordered on?
     *
     * Possible values are: 'name', 'path', 'type', 'size', 'lastmod', 'perms'
     *
     * @var string
     * @access protected
     */
   
var $_order = NULL;

   
/**
     * Are we sorting ascending (or descending)?
     *
     * NULL is default and means ascending for 'name', descending for the rest
     *
     * @var boolean
     * @access protected
     */
   
var $_order_asc = NULL;

   
/**
     * User preference: Sort dirs not at top
     *
     * @var boolean
     */
   
var $_dirs_not_at_top = false;

   
/**
     * User preference: Load and show hidden files?
     *
     * "Hidden files" are prefixed with a dot .
     *
     * @var boolean
     */
   
var $_show_hidden_files = false;

   
/**
     * User preference: Load and show _evocache folder?
     *
     * @var boolean
     */
   
var $_show_evocache = false;

   
/**
     * User preference: recursive size of dirs?
     *
     * The load() & sort() methods use this.
     *
     * @var boolean
     * @access protected
     */
   
var $_use_recursive_dirsize = false;


   
/**
     * Constructor
     *
     * @param FileRoot See FileRootCache::get_by_type_and_ID()
     * @param boolean|string Default path for the files, false if you want to create an arbitrary list; NULL for the Fileroot's ads_path.
     * @param integer ID of the user, the group or the collection the file belongs to...
     */
   
function __construct( $FileRoot, $path = NULL )
    {
        global
$AdminUI;

        if( !
is_object($FileRoot) )
        {
           
debug_die( 'Fatal: $FileRoot is no object!' );
        }

        if(
is_null($path) )
        {
           
$path = $FileRoot->ads_path;
        }
       
$this->_ads_list_path = $path;
       
$this->_FileRoot = & $FileRoot;

        if( ! empty(
$this->_ads_list_path) )
        {
           
// Get the subpath relative to root
           
$this->_rds_list_path = $this->rdfs_relto_root_from_adfs( $this->_ads_list_path );
        }
    }


   
/**
     * Loads or reloads the filelist entries.
     *
     * NOTE: this does not work for arbitrary lists!
     *
     * @uses $flatmode
     * @return boolean True on sucess, false on failure (not accessible)
     */
   
function load()
    {
        global
$Messages;

        if( !
$this->_ads_list_path )
        {    
// We have no path to load from: (happens when FM finds no available root OR we have an arbitrary)
            // echo 'Cannot load a filelist with no list path' ;
           
return false;
        }

       
// Clears the list (for RE-loads):
       
$this->_total_entries = 0;
       
$this->_total_bytes = 0;
       
$this->_total_files = 0;
       
$this->_total_dirs = 0;
       
$this->_entries = array();
       
$this->_md5_ID_index = array();
       
$this->_full_path_index = array();
       
$this->_rdfs_rel_path_index = array();
       
$this->_order_index = array();

       
// Attempt to list files for requested directory: (recursively if flat mode):
       
$filename_params = array(
               
'inc_files'        => $this->include_files,
               
'inc_dirs'        => $this->include_dirs,
               
'recurse'        => $this->flatmode,
               
'inc_hidden'    => $this->_show_hidden_files,
               
'inc_evocache'    => $this->_show_evocache,
            );
        if( (
$filepath_array = get_filenames( $this->_ads_list_path, $filename_params )) === false )
        {
           
$Messages->add( sprintf( T_('Cannot open directory &laquo;%s&raquo;!'), $this->_ads_list_path ), 'error' );
            return
false;
        }

       
// Loop through file list:
       
foreach( $filepath_array as $adfp_path )
        {
           
// Extract the filename from the full path
           
$name = basename( $adfp_path );

           
// Check for hidden status...
           
if( ( ! $this->_show_hidden_files) && (substr($name, 0, 1) == '.') )
            {
// Do not load & show hidden files (prefixed with .)
               
continue;
            }

           
// Check for _evocache...
           
if( ( ! $this->_show_evocache ) && ( $name == '_evocache') )
            {
// Do not load & show _evocache folder
               
continue;
            }

           
// Check filter...
           
if( $this->_filter !== NULL )
            {
// Filter: must match filename
               
if( $this->_filter_is_regexp )
                {
// Filter is a reg exp:
                   
if( ! preg_match( $this->_filter, $name ) )
                    {
// does not match the regexp filter
                       
continue;
                    }
                }
                else
                {
// Filter is NOT a regexp:
                   
if( ! fnmatch( '*'.trim( $this->_filter, '*' ).'*', $name ) )
                    {
                        continue;
                    }
                }
            }

           
// Extract the file's relative path to the root
           
$rdfp_path_relto_root = $this->rdfs_relto_root_from_adfs( $adfp_path );
           
// echo '<br>'.$rdfp_rel_path;

            // Add the file into current list:
           
$this->add_by_subpath( $rdfp_path_relto_root, true );
        }

        return
true;
    }


   
/**
     * Add a File object to the list (by reference).
     *
     * @param object File object (by reference)
     * @param boolean Has the file to exist to get added?
     * @return boolean true on success, false on failure
     */
   
function add( $File, $mustExist = false )
    {
        if( !(
$File instanceof file ) )
        {    
// Passed object is not a File!! :(
           
return false;
        }

       
// Integrity check:
       
if( $File->_FileRoot->ID != $this->_FileRoot->ID )
        {
           
debug_die( 'Adding file '.$File->_FileRoot->ID.':'.$File->get_rdfs_rel_path().' to filelist '.$this->_FileRoot->ID.' : root mismatch!' );
        }

        if(
$mustExist && ! $File->exists() )
        {    
// File does not exist..
           
return false;
        }


       
$this->_entries[$this->_total_entries] = $File;
       
$this->_md5_ID_index[$File->get_md5_ID()] = $this->_total_entries;
       
$this->_full_path_index[$File->get_full_path()] = $this->_total_entries;
       
$this->_rdfs_rel_path_index[$File->get_rdfs_rel_path()] = $this->_total_entries;
       
// add file to the end of current list:
       
$this->_order_index[$this->_total_entries] = $this->_total_entries;

       
// Count 1 more entry (file or dir)
       
$this->_total_entries++;

        if(
$File->is_dir() )
        {    
// Count 1 more directory
           
$this->_total_dirs++;
        }
        else
        {    
// Count 1 more file
           
$this->_total_files++;
        }

        if( !
$File->is_dir() )
        {    
// Count total bytes of files in this dir:
           
$this->_total_bytes += $this->get_File_size($File);
        }

        return
true;
    }


   
/**
     * Get the size of a given File, according to $_use_recursive_dirsize.
     * @param File
     * @return int bytes
     */
   
function get_File_size($File)
    {
        if(
$this->_use_recursive_dirsize )
        {
            return
$File->get_recursive_size();
        }
        else
        {
            return
$File->get_size();
        }
    }


   
/**
     * Get size of the file/dir, formatted to nearest unit (kb, mb, etc.)
     *
     * @uses bytesreadable()
     * @param File
     * @return string size as b/kb/mb/gd; or '&lt;dir&gt;'
     */
   
function get_File_size_formatted($File)
    {
        if(
$this->_use_recursive_dirsize || ! $File->is_dir() )
        {
            return
bytesreadable($File->get_recursive_size());
        }

        return
/* TRANS: short for '<directory>' */ T_('&lt;dir&gt;');
    }


   
/**
     * Update the name dependent caches
     *
     * This is especially useful after a name change of one of the files in the list
     */
   
function update_caches()
    {
       
$this->_md5_ID_index = array();
       
$this->_full_path_index = array();
       
$this->_rdfs_rel_path_index = array();

       
$count = 0;
        foreach(
$this->_entries as $loop_File )
        {
           
$this->_md5_ID_index[$loop_File->get_md5_ID()] = $count;
           
$this->_full_path_index[$loop_File->get_full_path()] = $count;
           
$this->_rdfs_rel_path_index[$loop_File->get_rdfs_rel_path()] = $count;
           
$count++;
        }
    }


   
/**
     * Add a file to the list, by filename.
     *
     * This is a stub for {@link Filelist::add()}.
     *
     * @param string Subpath for this file/folder, relative the associated root, including trailing slash (if directory)
     * @param boolean Has the file to exist to get added?
     * @return boolean true on success, false on failure (path not allowed,
     *                 file does not exist)
     */
   
function add_by_subpath( $rel_path, $mustExist = false )
    {
       
$FileRoot = & $this->get_FileRoot();

        if(
$FileRoot->contains( $rel_path ) )
        {    
// If a file is really contained in the FileRoot of this list:
           
$FileCache = & get_FileCache();
           
$NewFile = & $FileCache->get_by_root_and_path( $this->_FileRoot->type, $this->_FileRoot->in_type_ID, $rel_path );

           
// Add a file to this list:
           
return $this->add( $NewFile, $mustExist );
        }
    }


   
/**
     * Sort the entries by sorting the internal {@link $_order_index} array.
     *
     * @param string The order to use ('name', 'type', 'lastmod', .. )
     * @param boolean Ascending (true) or descending
     * @param boolean Sort directories at top?
     */
   
function sort( $order = NULL, $orderasc = NULL, $dirsattop = NULL )
    {
        if( !
$this->_total_entries )
        {
            return
false;
        }

        if(
$order !== NULL )
        {
// New order
           
$this->_order = $order;
        }
        elseif(
$this->_order === NULL )
        {
// Init
           
$this->_order = 'name';
        }

        if(
$orderasc !== NULL )
        {
// New ascending/descending setting
           
$this->_order_asc = $orderasc;
        }
        elseif(
$this->_order_asc === NULL )
        {
// Init: ascending for 'name' and 'path', else descending
           
$this->_order_asc = ( ( $this->_order == 'name' || $this->_order == 'path' ) ? 1 : 0 );
        }

        if(
$dirsattop !== NULL )
        {
           
$this->_dirs_not_at_top = ! $dirsattop;
        }

       
usort( $this->_order_index, array( $this, '_sort_callback' ) );

       
// Reset the iterator:
       
$this->restart();
    }


   
/**
     * usort callback function for {@link Filelist::sort()}
     *
     * @access protected
     * @return integer
     */
   
function _sort_callback( $a, $b )
    {
       
$FileA = & $this->_entries[$a];
       
$FileB = & $this->_entries[$b];

       
// What column are we sorting on?
        // TODO: dh> this should probably fallback to sorting by name always if $r==0
       
switch( $this->_order )
        {
            case
'size':
               
$r = $this->get_File_size($FileA) - $this->get_File_size($FileB);
                break;

            case
'path': // group by dir
               
$r = strcasecmp( $FileA->get_dir(), $FileB->get_dir() );
                if(
$r == 0 )
                {
                   
$r = strcasecmp( $FileA->get_name(), $FileB->get_name() );
                }
                break;

            case
'lastmod':
               
$r = $FileB->get_lastmod_ts() - $FileA->get_lastmod_ts();
                break;

            case
'perms':
               
// This will use literal representation ( 'r', 'r+w' / octal )
               
$r = strcasecmp( $FileA->get_perms(), $FileB->get_perms() );
                break;

            case
'fsowner':
               
$r = strcasecmp( $FileA->get_fsowner_name(), $FileB->get_fsowner_name() );
                break;

            case
'fsgroup':
               
$r = strcasecmp( $FileA->get_fsgroup_name(), $FileB->get_fsgroup_name() );
                break;

            case
'type':
                if(
$r = strcasecmp( $FileA->get_type(), $FileB->get_type() ) )
                {
                    break;
                }
               
// same type: continue to name:
           
default:
            case
'name':
               
$r = strnatcmp( $FileA->get_name(), $FileB->get_name() );
                if(
$r == 0 )
                {
// same name: look at path
                   
$r = strnatcmp( $FileA->get_dir(), $FileB->get_dir() );
                }
                break;
        }

        if( !
$this->_order_asc )
        {
// We want descending order: switch order
           
$r = - $r;
        }

        if( !
$this->_dirs_not_at_top )
        {    
// We want dirs to be on top, always:
           
if( $FileA->is_dir() && !$FileB->is_dir() )
            {
               
$r = -1;
            }
            elseif(
$FileB->is_dir() && !$FileA->is_dir() )
            {
               
$r = 1;
            }
        }

        return
$r;
    }


   
/**
     * Get the used order.
     *
     * @return string
     */
   
function get_sort_order()
    {
        return
$this->translate_order( $this->_order );
    }


   
/**
     * Get the link to sort by a column. Handle current order and appends an
     * icon to reflect the current state (ascending/descending), if the column
     * is the same we're sorting by.
     *
     * @todo get this outta here. This is NOT a displayable object.
     * We might want to have a "FileListResults" object that derives from Widget/Results/FilteredResults (the more the better)
     * This object is what the SQL or the ItemQuery object is to Results or to ItemList2. The model and the display should not be mixed.
     * IF NOT doing the clean objects, move this at least to file.funcs.
     *
     * @param string The type (name, path, size, ..)
     * @param string The text for the anchor.
     * @return string
     */
   
function get_sort_link( $type, $atext )
    {
        global
$AdminUI;

       
$newAsc = $this->_order == $type ? (1 - $this->is_sorting_asc()) :  1;

       
$r = '<a href="'.regenerate_url( 'fm_order,fm_orderasc', 'fm_order='.$type.'&amp;fm_orderasc='.$newAsc ).'" title="'.T_('Change Order').'"';

       
/**
         * @todo get this outta here. This is NOT a displayable object.
         * We might want to have a "FileListResults" object that derives from Widget/Results/FilteredResults (the more the better)
         * This object is what the SQL or the ItemQuery object is to Results or to ItemList2. The model and the display should not be mixed.
         * IF NOT doing the clean objects, move this at least to file.funcs.
         */
       
$result_params = $AdminUI->get_template('Results');


       
// Sorting icon:
       
if( $this->_order != $type )
        {
// Not sorted on this column:
           
$r .= ' class="basic_sort_link">'.$result_params['basic_sort_off'];
        }
        elseif(
$this->is_sorting_asc($type) )
        {
// We are sorting on this column , in ascneding order:
           
$r .=    ' class="basic_current">'.$result_params['basic_sort_asc'];
        }
        else
        {
// Descending order:
           
$r .=    ' class="basic_current">'.$result_params['basic_sort_desc'];
        }

       
$r .= ' '.$atext;


        return
$r.'</a>';
    }


   
/**
     * Reset the iterator
     */
   
function restart()
    {
       
$this->_current_idx = -1;
    }


   
/**
     * Are we sorting ascending?
     *
     * @param string The type (empty for current order type)
     * @return integer 1 for ascending sorting, 0 for descending
     */
   
function is_sorting_asc( $col = '' )
    {
        if(
$this->_order_asc === NULL )
        {
// We have not specified a sort order by now, use default:
           
if( empty($col) )
            {
               
$col = $this->_order;
            }
            return (
$col == 'name' || $col == 'path' ) ? 1 : 0;
        }
        else
        {    
// Use previsously specified sort order:
           
return ( $this->_order_asc ) ? 1 : 0;
        }
    }


   
/**
     * Set the filter.
     *
     * @param string Filter string (for regular expressions, if no delimiter/modifiers are included, we try magically adding them)
     * @param boolean Is the filter a regular expression? (it's a glob pattern otherwise)
     */
   
function set_filter( $filter_string, $filter_is_regexp )
    {
        global
$Messages;

       
$this->_filter_is_regexp = $filter_is_regexp;

        if(
$this->_filter_is_regexp && ! empty($filter_string) )
        {
            if( !
is_regexp( $filter_string, true ) )
            {
               
// Try with adding delimiters:
               
$filter_string_delim = '~'.str_replace( '~', '\~', $filter_string ).'~';
                if(
is_regexp( $filter_string_delim, true ) )
                {
                   
$filter_string = $filter_string_delim;
                }
                else
                {
                   
$Messages->add( sprintf( T_('The filter &laquo;%s&raquo; is not a regular expression.'), $filter_string ), 'error' );
                   
$filter_string = '~.*~';
                }
            }
        }

       
$this->_filter = empty($filter_string) ? NULL : $filter_string;
    }


   
/**
     * Is a filter active?
     *
     * @return boolean
     */
   
function is_filtering()
    {
        return (
$this->_filter !== NULL);
    }


   
/**
     * Does the list contain a specific File?
     *
     * @param File the File object to look for
     * @return boolean
     */
   
function contains( & $File )
    {
        return isset(
$this->_md5_ID_index[ $File->get_md5_ID() ] );
    }


   
/**
     * Return the current filter
     *
     * @param boolean add a note when it's a regexp or no filter?
     * @return string the filter
     */
   
function get_filter( $verbose = true )
    {
        if(
$this->_filter === NULL )
        {    
// Filtering is not active
           
return $verbose ? T_('No filter') : '';
        }
        else
        {    
// Filtering is active
           
return $this->_filter
                           
.( $verbose && $this->_filter_is_regexp ? ' ('.T_('regular expression').')' : '' );
        }
    }


   
/**
     * Is the current Filter a regexp?
     *
     * @return NULL|boolean true if regexp, NULL if no filter set
     */
   
function is_filter_regexp()
    {
        return
$this->_filter_is_regexp;
    }


   
/**
     * Get the total number of entries in the list.
     *
     * @return integer
     */
   
function count()
    {
        return
$this->_total_entries;
    }


   
/**
     * Get the total number of directories in the list
     *
     * @return integer
     */
   
function count_dirs()
    {
        return
$this->_total_dirs;
    }


   
/**
     * Get the total number of files in the list
     *
     * @return integer
     */
   
function count_files()
    {
        return
$this->_total_files;
    }


   
/**
     * Get the total number of bytes of all files in the list
     *
     * @return integer
     */
   
function count_bytes()
    {
        return
$this->_total_bytes;
    }


   
/**
     * Get the next entry and increment internal counter.
     *
     * @param string can be used to query only 'file's or 'dir's.
     * @return File object (by reference) on success, false on end of list
     */
   
function & get_next( $type = '' )
    {
        for(;;)
        {
            if( !isset(
$this->_order_index[$this->_current_idx + 1]) )
            {    
// End of list:
               
$r = false;
                return
$r;
            }
           
$this->_current_idx++;

           
$index = $this->_order_index[$this->_current_idx];
            if(
$type != '' )
            {
               
$is_dir = $this->_entries[$index]->is_dir();
                if( (
$type == 'dir' && ! $is_dir )
                    || (
$type == 'file' && $is_dir) )
                {
// we want another type
                   
continue;
                }
            }
            break;
        }

        return
$this->_entries[ $index ];
    }


   
/**
     * Get a file by its relative (to root) path.
     *
     * @param string the RELATIVE path (with ending slash for directories)
     * @return mixed File object (by reference) on success, false on failure.
     */
   
function & get_by_rdfs_path( $rdfs_path )
    {
       
// We probably don't need the windows backslashes replacing any more but leave it for safety because it doesn't hurt:
       
$path = str_replace( '\\', '/', $rdfs_path );

        if( isset(
$this->_rdfs_rel_path_index[ $rdfs_path ] ) )
        {
            return
$this->_entries[ $this->_rdfs_rel_path_index[ $rdfs_path ] ];
        }
        else
        {
           
$r = false;
            return
$r;
        }
    }


   
/**
     * Get a file by its full path.
     *
     * @param string the full/absolute path (with ending slash for directories)
     * @return mixed File object (by reference) on success, false on failure.
     */
   
function & get_by_full_path( $adfs_path )
    {
       
// We probably don't need the windows backslashes replacing any more but leave it for safety because it doesn't hurt:
       
$path = str_replace( '\\', '/', $adfs_path );

        if( isset(
$this->_full_path_index[ $adfs_path ] ) )
        {
            return
$this->_entries[ $this->_full_path_index[ $adfs_path ] ];
        }
        else
        {
           
$r = false;
            return
$r;
        }
    }


   
/**
     * Get a file by it's ID.
     *
     * @param string the ID (MD5 of path and name)
     * @return mixed File object (by reference) on success, false on failure.
     */
   
function & get_by_md5_ID( $md5id )
    {
        if( isset(
$this->_md5_ID_index[ $md5id ] ) )
        {
            return
$this->_entries[ $this->_md5_ID_index[ $md5id ] ];
        }
        else
        {
           
$r = false;
            return
$r;
        }
    }


   
/**
     * Get a file by index.
     *
     * @param integer Index of the entries (starting with 0)
     * @param boolean added by fp (set to false when it's a problem)
     * @return File
     */
   
function & get_by_idx( $index, $halt_on_error = true )
    {
        if( isset(
$this->_order_index[ $index ] ) )
        {
            return
$this->_entries[ $this->_order_index[ $index ] ];
        }
        elseif( !
$halt_on_error )
        {
           
$r = false;
            return
$r;
        }

       
debug_die( 'Requested file does not exist!' );
    }


   
/**
     * Get the FileLists FileRoot
     *
     * @return FileRoot
     */
   
function & get_FileRoot()
    {
        return
$this->_FileRoot;
    }


   
/**
     * Get the FileLists root type.
     *
     * @return string
     */
   
function get_root_type()
    {
        return
$this->_FileRoot->type;
    }


   
/**
     * Get the FileLists root ID (in_type_ID).
     *
     * @return FileRoot
     */
   
function get_root_ID()
    {
        return
$this->_FileRoot->in_type_ID;
    }


   
/**
     * Get absolute path to list.
     */
   
function get_ads_list_path()
    {
        return
$this->_ads_list_path;
    }

   
/**
     * Get path to list relative to root.
     */
   
function get_rds_list_path()
    {
        return
$this->_rds_list_path;
    }


   
/**
     * Get the path (and name) of a {@link File} relative to the {@link Filelist::$_FileRoot::$ads_path}.
     *
     * @param string
     * @return string
     */
   
function rdfs_relto_root_from_adfs( $adfs_path )
    {
       
// Check that the file is inside root:
       
if( substr( $adfs_path, 0, strlen($this->_FileRoot->ads_path) ) != $this->_FileRoot->ads_path )
        {
           
debug_die( 'rdfs_relto_root_from_adfs: Path is NOT inside of root!' );
        }

       
// Return only the relative part:
       
return substr( $adfs_path, strlen($this->_FileRoot->ads_path) );
    }


   
/**
     * Removes a {@link File} from the entries list.
     *
     * This handles indexes and number of total entries, bytes, files/dirs.
     *
     * @return boolean true on success, false if not found in list.
     */
   
function remove( & $File )
    {
        if( isset(
$this->_md5_ID_index[ $File->get_md5_ID() ] ) )
        {
           
$this->_total_entries--;
           
$this->_total_bytes -= $this->get_File_size($File);

            if(
$File->is_dir() )
            {
               
$this->_total_dirs--;
            }
            else
            {
               
$this->_total_files--;
            }

           
// unset from indexes
           
$index = $this->_full_path_index[ $File->get_full_path() ]; // current index
           
unset( $this->_entries[ $this->_md5_ID_index[ $File->get_md5_ID() ] ] );
            unset(
$this->_md5_ID_index[ $File->get_md5_ID() ] );
            unset(
$this->_full_path_index[ $File->get_full_path() ] );
            unset(
$this->_rdfs_rel_path_index[ $File->get_rdfs_rel_path() ] );

           
// get the ordered index right: move all next files downwards
           
$order_key = array_search( $index, $this->_order_index );
            unset(
$this->_order_index[$order_key] );
           
$this->_order_index = array_values( $this->_order_index );

            if(
$this->_current_idx > -1 && $this->_current_idx >= $order_key )
            {
// We have removed a file before or at the $order_key'th position
               
$this->_current_idx--;
            }
            return
true;
        }
        return
false;
    }


   
/**
     * Get the list of File entries.
     *
     * You can use a method on each object to get this as result instead of the object
     * itself.
     *
     * @param string Use this method on every File and put the result into the list.
     * @return array The array with the File objects or method results
     */
   
function get_array( $method = NULL )
    {
       
$r = array();

        if(
is_string($method) )
        {
            foreach(
$this->_order_index as $index )
            {
               
$r[] = $this->_entries[ $index ]->$method();
            }
        }
        else
        {
            foreach(
$this->_order_index as $index )
            {
               
$r[] = & $this->_entries[ $index ];
            }
        }

        return
$r;
    }


   
/**
     * Get a MD5 checksum over the entries.
     * Used to identify a unique filelist.
     *
     * @return string md5 hash
     */
   
function md5_checksum()
    {
        return
md5( serialize( $this->_entries ) );
    }


   
/**
     * Attempt to load meta data for all files in the list.
     *
     * Will attempt only once per file and cache the result.
     */
   
function load_meta()
    {
        global
$DB, $Debuglog;

       
$to_load = array();

        for(
$i=0; $i<count($this->_entries); $i++ )
        {    
// For each file:
           
$loop_File = & $this->_entries[$i];
           
// echo '<br>'.$loop_File->get_full_path();

           
if( $loop_File->meta != 'unknown' )
            {
// We have already loaded meta data:
               
continue;
            }

           
$to_load[] = $DB->quote( md5( $this->_FileRoot->type.$this->_FileRoot->in_type_ID.$loop_File->get_rdfp_rel_path(), true ) );
        }

        if(
count( $to_load ) )
        {    
// We have something to load...
            /**
             * @var FileCache
             */
           
$FileCache = & get_FileCache();

           
$rows = $DB->get_results( "
                SELECT *
                  FROM T_files
                 WHERE file_path_hash IN ("
.implode( ',', $to_load ).")",
               
OBJECT, 'Load FileList meta data' );

            if(
count($rows) )
            {
// Go through rows of loaded meta data...
               
foreach( $rows as $row )
                {
                   
// Retrieve matching File object:
                    /**
                     * @var File
                     */
                   
$loop_File = & $FileCache->get_by_root_and_path( $row->file_root_type, $row->file_root_ID, $row->file_path );

                   
// Associate meta data to File object:
                   
$loop_File->load_meta( false, $row );
                }
            }
        }

       
// For all Files that still have no meta data, memorize that we could not find any meta data
       
for( $i=0; $i<count($this->_entries); $i++ )
        {    
// For each file:
           
$loop_File = & $this->_entries[$i];

            if(
$loop_File->meta == 'unknown' )
            {
               
$loop_File->meta = 'notfound';
            }
        }

       
// Has sth been loaded?
       
return count( $to_load ) && count($rows);
    }


   
/**
     * Returns cwd, where the accessible directories (below root) are clickable
     *
     * @return string cwd as clickable html
     */
   
function get_cwd_clickable( $clickableOnly = true )
    {
        if( empty(
$this->_ads_list_path) )
        {
            return
' -- '.T_('No directory.').' -- ';
        }

       
// Get the part of the path which is not clickable:
       
$r = substr( $this->_FileRoot->ads_path, 0, strrpos( substr($this->_FileRoot->ads_path, 0, -1), '/' )+1 );

       
// get the part that is clickable
       
$clickabledirs = explode( '/', substr( $this->_ads_list_path, strlen($r) ) );

        if(
$clickableOnly )
        {
           
$r = '';
        }

       
$cd = '';
        foreach(
$clickabledirs as $nr => $dir )
        {
            if( empty(
$dir) )
            {
                break;
            }
            if(
$nr )
            {
               
$cd .= $dir.'/';
            }
           
$r .= '<a href="'.regenerate_url( 'path', 'path='.rawurlencode( $cd ) )
                    .
'" title="'.T_('Change to this directory').'">'.$dir.'</a>/';
        }

        return
$r;
    }

}

?>