Seditio Source
Root |
./othercms/b2evolution_7.2.3/inc/files/model/_file.funcs.php
<?php
/**
 * This file implements various File handling functions.
 *
 * 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.' );


if( !
function_exists('fnmatch') )
{
   
/**
     * A replacement for fnmatch() which needs PHP 4.3 and a POSIX compliant system (Windows is not).
     *
     * @author jk at ricochetsolutions dot com {@link http://php.net/manual/function.fnmatch.php#71725}
     */
   
function fnmatch($pattern, $string)
    {
       return
preg_match( '#^'.strtr(preg_quote($pattern, '#'), array('\*' => '.*', '\?' => '.')).'$#i', $string);
    }
}


/**
 * Converts bytes to readable bytes/kb/mb/gb, like "12.45mb"
 *
 * @param integer bytes
 * @param boolean use HTML <abbr> tags
 * @param boolean Display full text of size type when $htmlabbr == false
 * @return string bytes made readable
 */
function bytesreadable( $bytes, $htmlabbr = true, $display_size_type = true )
{
    static
$types = NULL;

    if( empty(
$bytes) )
    {
        return
T_('Empty');
    }

    if( !isset(
$types) )
    {
// generate once:
       
$types = array(
           
0 => array( 'abbr' => /* TRANS: Abbr. for "Bytes" */ T_('B.'), 'text' => T_('Bytes') ),
           
1 => array( 'abbr' => /* TRANS: Abbr. for "Kilobytes" */ T_('KB'), 'text' => T_('Kilobytes') ),
           
2 => array( 'abbr' => /* TRANS: Abbr. for Megabytes */ T_('MB'), 'text' => T_('Megabytes') ),
           
3 => array( 'abbr' => /* TRANS: Abbr. for Gigabytes */ T_('GB'), 'text' => T_('Gigabytes') ),
           
4 => array( 'abbr' => /* TRANS: Abbr. for Terabytes */ T_('TB'), 'text' => T_('Terabytes') )
        );
    }

    for(
$i = 0; $bytes > 1024; $i++ )
    {
       
$bytes /= 1024;
    }

   
// Format to maximum of 1 digit after .
   
$precision = max( 0, ( 1 -floor(log($bytes)/log(10))) );
   
$r = sprintf( '%.'.$precision.'f', $bytes );

   
$r .= $htmlabbr ? ( '&nbsp;<abbr title="'.$types[$i]['text'].'">' ) : ' ';
   
$r .= $types[$i]['abbr'];
   
$r .= $htmlabbr ? '</abbr>' : ( $display_size_type ? ' ('.$types[$i]['text'].')' : '' );

   
// $r .= ' '.$precision;

   
return $r;
}


/**
 * Converts readable bytes to bytes
 *
 * @param string readable bytes
 * @return integer bytes
 */
function return_bytes( $val ) {
   
$val = trim( $val );
       
$re = '/([0-9]+)([a-zA-Z]*)/';
       
preg_match( $re, $val, $matches );

        if(
$matches )
        {
           
$last = strtolower( $matches[2] );

           
$val = intval( $matches[1] );
            switch(
$last ) {
               
// The 'G' modifier is available since PHP 5.1.0
               
case 'tb':
                   
$val *= 1024;
                case
'g':
                case
'gb':
                   
$val *= 1024;
                case
'm':
                case
'mb':
                   
$val *= 1024;
                case
'k':
                case
'kb':
                   
$val *= 1024;
            }

        return
$val;
        }
        else
        {
            return
false;
        }
}


/**
 * Get an array of all directories (and optionally files) of a given
 * directory, either flat (one-dimensional array) or multi-dimensional (then
 * dirs are the keys and hold subdirs/files).
 *
 * Note: there is no ending slash on dir names returned.
 *
 * @param string the path to start
 * @param array of params
 * @return false|array false if the first directory could not be accessed,
 *                     array of entries otherwise
 */
function get_filenames( $path, $params = array() )
{
    global
$Settings;

   
$params = array_merge( array(
           
'inc_files'      => true,  // include files (not only directories)
           
'inc_dirs'       => true,  // include directories (not the directory itself!)
           
'flat'           => true,  // return a one-dimension-array
           
'recurse'        => true,  // recurse into subdirectories
           
'basename'       => false, // get the basename only
           
'trailing_slash' => false, // add trailing slash
           
'inc_hidden'     => true,  // inlcude hidden files, directories and content
           
'inc_evocache'   => false, // exclude evocache directories and content
           
'inc_temp'       => true,  // include temporary files and directories
       
), $params );

   
$r = array();

   
$path = trailing_slash( $path );

    if(
$dir = @opendir( $path ) )
    {
        while( (
$file = readdir( $dir ) ) !== false )
        {
            if(
$file == '.' || $file == '..' )
            {
// Skip these reserved names
               
continue;
            }

           
// asimo> Also check if $Settings is not empty because this function is called from the install srcipt too, where $Settings is not initialized yet
           
if( ! $params['inc_evocache'] && ! empty( $Settings ) && $file == $Settings->get( 'evocache_foldername' ) )
            {
// Do not load evocache directories
               
continue;
            }

           
// Check for hidden status...
           
if( ( ! $params['inc_hidden'] ) && ( substr( $file, 0, 1 ) == '.' ) )
            {
// Do not load & show hidden files and folders (prefixed with .)
               
continue;
            }

           
// Check for temp directories/files
           
if( ! $params['inc_temp'] && $file == 'upload-tmp' )
            {
// Do not load temporary files and directories
               
continue;
            }

            if(
is_dir( $path.$file ) )
            {
                if(
$params['flat'] )
                {
                    if(
$params['inc_dirs'] )
                    {
                       
$directory_name = $params['basename'] ? $file : $path.$file;
                        if(
$params['trailing_slash'] )
                        {
                           
$directory_name = trailing_slash( $directory_name );
                        }

                       
$r[] = $directory_name;
                    }
                    if(
$params['recurse'] )
                    {
                       
$rSub = get_filenames( $path.$file, $params );
                        if(
$rSub )
                        {
                           
$r = array_merge( $r, $rSub );
                        }
                    }
                }
                else
                {
                   
$r[ $file ] = get_filenames( $path.$file, $params );
                }
            }
            elseif(
$params['inc_files'] )
            {
               
$r[] = $params['basename'] ? $file : $path.$file;
            }
        }
       
closedir( $dir );
    }
    else
    {
        return
false;
    }

    return
$r;
}


/**
 * Get a list of available admin skins.
 *
 * This checks if there's a _adminUI.class.php in there.
 *
 * @return array  List of directory names that hold admin skins or false, if the admin skins driectory does not exist.
 */
function get_admin_skins()
{
    global
$adminskins_path, $admin_subdir, $adminskins_subdir;

   
$filename_params = array(
           
'inc_files'        => false,
           
'recurse'        => false,
           
'basename'      => true,
        );
   
$dirs_in_adminskins_dir = get_filenames( $adminskins_path, $filename_params );

    if(
$dirs_in_adminskins_dir === false )
    {
        return
false;
    }

   
$r = array();
    if(
$dirs_in_adminskins_dir )
    {
        foreach(
$dirs_in_adminskins_dir as $l_dir )
        {
            if( !
file_exists($adminskins_path.$l_dir.'/_adminUI.class.php') )
            {
                continue;
            }
           
$r[] = $l_dir;
        }
    }
    return
$r;
}


/**
 * Get size of a directory, including anything (especially subdirs) in there.
 *
 * @param string the dir's full path
 */
function get_dirsize_recursive( $path )
{
   
$files = get_filenames( $path );
   
$total = 0;

    if( !empty(
$files ) )
    {
        foreach(
$files as $lFile )
        {
           
$total += filesize($lFile);
        }
    }

    return
$total;
}


/**
 * Deletes a dir recursively, wiping out all subdirectories!!
 *
 * @param string The dir
 * @return boolean False on failure
 */
function rmdir_r( $path )
{
   
$path = trailing_slash( $path );

   
$r = true;

    if( !
cleardir_r( $path ) )
    {
       
$r = false;
    }

    if( ! @
rmdir( $path ) )
    {
       
$r = false;
    }

    return
$r;
}


/**
 * Clear contents of a directory, but do not delete the directory itself
 *
 * @param string the directory path
 * @return boolean False on failure (may be only partial), true on success.
 */
function cleardir_r( $path )
{
   
$path = trailing_slash( $path );
   
// echo "<br>rmdir_r($path)";

   
$r = true; // assume success

   
if( $dir = @opendir($path) )
    {
        while( (
$file = readdir($dir) ) !== false )
        {
            if(
$file == '.' || $file == '..' )
                continue;

           
$adfp_filepath = $path.$file;

           
// echo "<br> - $os_filepath ";

           
if( is_dir( $adfp_filepath ) && ! is_link($adfp_filepath) )
            {
// Note: we do NOT follow symlinks
                // echo 'D';
               
if( ! rmdir_r( $adfp_filepath ) )
                {
                   
$r = false;
                }
            }
            else
            {
// File or symbolic link
                //echo 'F/S';
               
if( ! @unlink( $adfp_filepath ) )
                {
                   
$r = false;
                }
            }
        }
       
closedir($dir);
    }
    else
    {
       
$r = false;
    }

    return
$r;
}


/**
 * Fast check if the requested file is image
 *
 * @param string File path
 * @return boolean TRUE - if file is image
 */
function is_image_file( $file_path )
{
    if( !
file_exists( $file_path ) )
    {
        return
false;
    }

    if(
substr( $file_path, -4 ) == '.svg' )
    {    
// Consider all SVG file is image, because not SVG files has a mime type as 'image/svg':
       
return true;
    }

   
$file_info = finfo_open( FILEINFO_MIME_TYPE );
   
$file_type = finfo_file( $file_info, $file_path );

    return
in_array( $file_type, array( 'image/png', 'image/jpeg', 'image/gif', 'image/svg', 'image/svg+xml' ) );
}

/**
 * Get the size of an image file
 *
 * @param string absolute file path
 * @param string what property/format to get: 'width', 'height', 'widthxheight',
 *               'type', 'string' (as for img tags), 'widthheight_assoc' (array
 *               with keys "width" and "height", else 'widthheight' (numeric array)
 * @return mixed false if no image, otherwise what was requested through $param
 */
function imgsize( $path, $param = 'widthheight' )
{
   
/**
     * Cache image sizes
     */
   
global $cache_imgsize;

    if( isset(
$cache_imgsize[$path]) )
    {
       
$size = $cache_imgsize[$path];
    }
    elseif( !
is_image_file( $path ) || // Fast check for image file
           
! ( $size = @getimagesize( $path ) ) ) // Try to get size of image file
   
{    // If file is not image, or size could not get by some reason:
       
return false;
    }
    else
    {
       
$cache_imgsize[$path] = $size;
    }

    if(
$param == 'width' )
    {
        return
$size[0];
    }
    elseif(
$param == 'height' )
    {
        return
$size[1];
    }
    elseif(
$param == 'widthxheight' )
    {
        return
$size[0].'x'.$size[1];
    }
    elseif(
$param == 'type' )
    {
        switch(
$size[2] )
        {
            case
1: return 'gif';
            case
2: return 'jpg';
            case
3: return 'png';
            case
4: return 'swf';
            default: return
'unknown';
        }
    }
    elseif(
$param == 'string' )
    {
        return
$size[3];
    }
    elseif(
$param == 'widthheight_assoc' )
    {
        return array(
'width' => $size[0], 'height' => $size[1] );
    }
    else
    {
// default: 'widthheight'
       
return array( $size[0], $size[1] );
    }
}


/**
 * Remove leading slash, if any.
 *
 * @param string
 * @return string
 */
function no_leading_slash( $path )
{
    if( isset(
$path[0]) && $path[0] == '/' )
    {
        return
substr( $path, 1 );
    }
    else
    {
        return
$path;
    }
}


/**
 * Returns canonicalized pathname of a directory + ending slash
 *
 * @param string absolute path to be reduced ending with slash
 * @return string absolute reduced path, slash terminated or NULL if the path could not get canonicalized.
 */
function get_canonical_path( $ads_path )
{
   
// We probably don't need the windows backslashes replacing any more but leave it for safety because it doesn't hurt:
   
$ads_path = str_replace( '\\', '/', $ads_path );

   
$is_absolute = is_absolute_pathname($ads_path);

   
// Make sure there's a trailing slash
   
$ads_path = trailing_slash($ads_path);

    while(
strpos($ads_path, '//') !== false )
    {
       
$ads_path = str_replace( '//', '/', $ads_path );
    }
    while(
strpos($ads_path, '/./') !== false )
    {
       
$ads_path = str_replace( '/./', '/', $ads_path );
    }
   
$parts = explode('/', $ads_path);
    for(
$i = 0; $i < count($parts); $i++ )
    {
        if(
$parts[$i] != '..' )
        {
            continue;
        }
        if(
$i <= 0 || $parts[$i-1] == '' || substr($parts[$i-1], -1) == ':' /* windows drive letter */ )
        {
            return
NULL;
        }
       
// Remove ".." and the part before it
       
unset($parts[$i-1], $parts[$i]);
       
// Respin array
       
$parts = array_values($parts);
       
$i = $i-2;
    }
   
$ads_realpath = implode('/', $parts);

   
// pre_dump( 'get_canonical_path()', $ads_path, $ads_realpath );

   
if( strpos( $ads_realpath, '..' ) !== false )
    {    
// Path malformed:
       
return NULL;
    }

    if(
$is_absolute && ! strlen($ads_realpath) )
    {
        return
NULL;
    }

    return
$ads_realpath;
}


/**
 * Fix the length of a given file name based on the global $filename_max_length setting.
 *
 * @param string the file name
 * @param string the index before we should remove the over characters
 * @return string the modified filename if the length was above the max length and the $remove_before_index param was correct. The original filename otherwie.
 */
function fix_filename_length( $filename, $remove_before_index )
{
    global
$filename_max_length;

   
$filename_length = strlen( $filename );
    if(
$filename_length > $filename_max_length )
    {
       
$difference = $filename_length - $filename_max_length;
        if(
$remove_before_index > $difference )
        {
// Fix file name length only if the filename part before the 'remove index' contains more characters then what we have to remove
           
$filename = substr_replace( $filename, '', $remove_before_index - $difference, $difference );
        }
    }
    return
$filename;
}


/**
 * Process filename:
 *  - convert to lower case
 *  - replace consecutive dots with one dot
 *  - if force_validation is true, then replace every not valid character to '_'
 *  - check if file name is valid
 *
 * @param string file name (by reference) - this file name will be processed
 * @param boolean force validation ( replace not valid characters to '_' without warning )
 * @param boolean Crop long file name to $filename_max_length
 * @param object FileRoot
 * @param string Path in the FileRoot
 * @return error message if the file name is not valid, false otherwise
 */
function process_filename( & $filename, $force_validation = false, $autocrop_long_file = false, $FileRoot = NULL, $path = '' )
{
    if( empty(
$filename ) )
    {
        return
T_( 'Empty file name is not valid.' );
    }

    if(
$autocrop_long_file && ! empty( $FileRoot ) )
    {
// Crop long file names
       
global $filename_max_length, $Messages;

        if( ! empty(
$filename_max_length ) &&
               
$filename_max_length > 8 &&
               
strlen( $filename ) > $filename_max_length )
        {
// Limit file name by $filename_max_length
           
$new_file_ext_pos = strrpos( $filename, '.' );
           
$new_file_name = substr( $filename, 0, $new_file_ext_pos );
           
$new_file_name = trim( substr( $new_file_name, 0, $filename_max_length - 8 ) );
           
$new_file_ext = substr( $filename, $new_file_ext_pos - strlen( $filename ) + 1 );

           
$fnum = 1;
           
$FileCache = & get_FileCache();
            do
            {
// Make file name unique
               
$filename = $new_file_name.'-'.$fnum.'.'.$new_file_ext;
               
$newFile = & $FileCache->get_by_root_and_path( $FileRoot->type, $FileRoot->in_type_ID, trailing_slash( $path ).$filename, true );
               
$fnum++;
            }
            while(
$newFile->exists() );
           
$filename = $newFile->get( 'name' );

           
$Messages->add( T_('The filename was too long. It has been shortened.'), 'warning' );
        }
    }

    if(
$force_validation )
    {
// replace every not valid characters
       
$filename = preg_replace( '/[^a-z0-9\-_.]+/i', '_', $filename );
       
// Make sure the filename length doesn't exceed the maximum allowed. Remove characters from the end of the filename ( before the extension ) if required.
       
$extension_pos = strrpos( $filename, '.' );
       
$filename = fix_filename_length( $filename, strrpos( $filename, '.', ( $extension_pos ? $extension_pos : strlen( $filename ) ) ) );
    }

   
// check if the file name contains consecutive dots, and replace them with one dot without warning ( keep only one dot '.' instead of '...' )
   
$filename = preg_replace( '/\.(\.)+/', '.', utf8_strtolower( $filename ) );

    if(
$error_filename = validate_filename( $filename ) )
    {
// invalid file name
       
return $error_filename;
    }

   
// on success
   
return false;
}


/**
 * Check for valid filename and extension of the filename (no path allowed). (MB)
 *
 * @uses $FiletypeCache, $settings or $force_regexp_filename form _advanced.php
 *
 * @param string filename to test
 * @param mixed true/false to allow locked filetypes. NULL means that FileType will decide
 * @return nothing if the filename is valid according to the regular expression and the extension too, error message if not
 */
function validate_filename( $filename, $allow_locked_filetypes = NULL )
{
    global
$Settings, $force_regexp_filename, $filename_max_length;

    if(
strpos( $filename, '..' ) !== false )
    {
// consecutive dots are not allowed in file name
       
return sprintf( T_('&laquo;%s&raquo; is not a valid filename.').' '.T_( 'Consecutive dots are not allowed.' ), $filename );
    }

    if(
strlen( $filename ) > $filename_max_length )
    {
// filename is longer then the maximum allowed
       
return sprintf( T_('&laquo;%s&raquo; is not a valid filename.').' '.sprintf( T_( 'Max %d characters are allowed on filenames.' ), $filename_max_length ), $filename );
    }

   
// Check filename
   
if( $force_regexp_filename )
    {
// Use the regexp from _advanced.php
       
if( !preg_match( ':'.str_replace( ':', '\:', $force_regexp_filename ).':', $filename ) )
        {
// Invalid filename
           
return sprintf( T_('&laquo;%s&raquo; is not a valid filename.'), $filename );
        }
    }
    else
    {    
// Use the regexp from SETTINGS
       
if( !preg_match( ':'.str_replace( ':', '\:', $Settings->get( 'regexp_filename' ) ).':', $filename ) )
        {
// Invalid filename
           
return sprintf( T_('&laquo;%s&raquo; is not a valid filename.'), $filename );
        }
    }

   
// Check extension filename
   
if( preg_match( '#\.([a-zA-Z0-9\-_]+)$#', $filename, $match ) )
    {
// Filename has a valid extension
       
$FiletypeCache = & get_FiletypeCache();
        if(
$Filetype = & $FiletypeCache->get_by_extension( strtolower( $match[1] ) , false ) )
        {
            if(
$Filetype->is_allowed( $allow_locked_filetypes ) )
            {
// Filename has an unlocked extension or we allow locked extensions
               
return;
            }
            else
            {    
// Filename hasn't an allowed extension
               
return sprintf( T_('Admins can upload/rename/edit this file type only if %s in the <a %s>configuration files</a>'),
                       
'<code>$admins_can_manipulate_sensitive_files = true</code>', 'href="'.get_manual_url( 'advanced-php' ).'"' );
            }
        }
        else
        {
// Filename hasn't an allowed extension
           
return sprintf( T_('&laquo;%s&raquo; has an unrecognized extension.'), $filename );
        }
    }
    else
    {
// Filename hasn't a valid extension
       
return sprintf( T_('&laquo;%s&raquo; has not a valid extension.'), $filename );
    }
}


/**
 * Check for valid dirname (no path allowed). ( MB )
 *
 * @uses $Settings or $force_regexp_dirname form _advanced.php
 * @param string dirname to test
 * @return nothing if the dirname is valid according to the regular expression, error message if not
 */
function validate_dirname( $dirname )
{
    global
$Settings, $force_regexp_dirname, $filename_max_length;

    if(
$dirname != '..' )
    {
        if(
strlen( $dirname ) > $filename_max_length )
        {
// Don't allow longer directory names then the max file name length
           
return sprintf( T_('&laquo;%s&raquo; is not a valid directory name.'), $dirname ).' '.sprintf( T_( 'Max %d characters are allowed.' ), $filename_max_length );
        }

        if( !empty(
$force_regexp_dirname ) )
        {
// Use the regexp from _advanced.php
           
if( preg_match( ':'.str_replace( ':', '\:', $force_regexp_dirname ).':', $dirname ) )
            {
// Valid dirname
               
return;
            }
        }
        else
        {
// Use the regexp from SETTINGS
           
if( preg_match( ':'.str_replace( ':', '\:', $Settings->get( 'regexp_dirname' ) ).':', $dirname ) )
            {
// Valid dirname
               
return;
            }
        }
    }

    return
sprintf( T_('&laquo;%s&raquo; is not a valid directory name.'), $dirname );
}


/**
 * Check if file rename is acceptable
 *
 * used when renaming a file, File settings
 *
 * @param string the new name
 * @param boolean true if it is a directory, false if not
 * @param string the absolute path of the parent directory
 * @param boolean true if user has permission to all kind of fill types, false otherwise
 * @return mixed NULL if the rename is acceptable, error message if not
 */
function check_rename( & $newname, $is_dir, $dir_path, $allow_locked_filetypes )
{
    global
$dirpath_max_length;

   
// Check if provided name is okay:
   
$newname = trim( strip_tags($newname) );

    if(
$is_dir )
    {
        if(
$error_dirname = validate_dirname( $newname ) )
        {
// invalid directory name
           
syslog_insert( sprintf( 'Invalid name is detected for folder %s', '[['.$newname.']]' ), 'warning', 'file' );
            return
$error_dirname;
        }
        if(
$dirpath_max_length < ( strlen( $dir_path ) + strlen( $newname ) ) )
        {
// The new path length would be too long
           
syslog_insert( sprintf( 'The renamed file %s is too long for the folder', '[['.$newname.']]' ), 'warning', 'file' );
            return
T_('The new name is too long for this folder.');
        }
    }
    elseif(
$error_filename = validate_filename( $newname, $allow_locked_filetypes ) )
    {
// Not a file name or not an allowed extension
       
syslog_insert( sprintf( 'The renamed file %s has an unrecognized extension', '[['.$newname.']]' ), 'warning', 'file' );
        return
$error_filename;
    }

    return
NULL;
}


/**
 * Return a string with upload restrictions ( allowed extensions, max file size )
 *
 * @param array Params
 */
function get_upload_restriction( $params = array() )
{
   
$params = array_merge( array(
           
'block_before'       => '',
           
'block_after'        => '<br />',
           
'block_separator'    => '<br />',
           
'title_before'       => '<strong>',
           
'title_after'        => '</strong>: ',
           
'ext_separator'      => ', ',
           
'ext_last_separator' => ' &amp; ',
        ),
$params );

    global
$DB, $Settings;
   
$restrictNotes = array();

    if(
is_logged_in( false ) )
    {
       
$condition = ( check_user_perm( 'files', 'all' ) && !empty( $admins_can_manipulate_sensitive_files ) ) ? '' : 'ftyp_allowed <> "admin"';
    }
    else
    {
       
$condition = 'ftyp_allowed = "any"';
    }

    if( !empty(
$condition ) )
    {
       
$condition = ' WHERE '.$condition;
    }

   
// Get list of recognized file types (others are not allowed to get uploaded)
    // dh> because FiletypeCache/DataObjectCache has no interface for getting a list, this dirty query seems less dirty to me.
   
$allowed_extensions = $DB->get_col( 'SELECT ftyp_extensions FROM T_filetypes'.$condition );
   
$allowed_extensions = implode( ' ', $allowed_extensions ); // implode with space, ftyp_extensions can hold many, separated by space
    // into array:
   
$allowed_extensions = preg_split( '~\s+~', $allowed_extensions, -1, PREG_SPLIT_NO_EMPTY );
   
// readable:
   
$allowed_extensions = implode_with_and( $allowed_extensions, $params['ext_separator'], $params['ext_last_separator'] );

   
$restrictNotes[] = $params['title_before'].T_('Allowed file extensions').$params['title_after'].$allowed_extensions;

    if(
$Settings->get( 'upload_maxkb' ) )
    {
// We want to restrict on file size:
       
$restrictNotes[] = $params['title_before'].T_('Maximum allowed file size').$params['title_after'].bytesreadable( $Settings->get( 'upload_maxkb' ) * 1024 );
    }

    return
$params['block_before'].implode( $params['block_separator'], $restrictNotes ).$params['block_after'];
}


/**
 * Return the path without the leading {@link $basepath}, or if not
 * below {@link $basepath}, just the basename of it.
 *
 * Do not use this for file handling.  JUST for displaying! (DEBUG MESSAGE added)
 *
 * @param string Path
 * @return string Relative path or even base name.
 *   NOTE: when $debug, the real path gets appended.
 */
function rel_path_to_base( $path )
{
    global
$basepath, $debug;

   
// Remove basepath prefix:
   
if( preg_match( '~^('.preg_quote($basepath, '~').')(.+)$~', $path, $match ) )
    {
       
$r = $match[2];
    }
    else
    {
       
$r = basename($path).( is_dir($path) ? '/' : '' );
    }

   
/* fp> The following MUST be moved to the caller if needed:
    if( $debug )
    {
        $r .= ' [DEBUG: '.$path.']';
    }
    */

   
return $r;
}


/**
 * Get the directories of the supplied path as a radio button tree.
 *
 * @todo fp> Make a DirTree class (those static hacks suck)
 *
 * @param FileRoot A single root or NULL for all available.
 * @param string the root path to use
 * @param boolean add radio buttons ?
 * @param string used by recursion
 * @param string what kind of action do the user ( we need this to check permission )
 *             fp>asimo : in what case can this be something else than "view" ?
 *             asimo>fp : on the _file_upload.view, we must show only those roots where current user has permission to add files
 * @return string
 */
function get_directory_tree( $Root = NULL, $ads_full_path = NULL, $ads_selected_full_path = NULL, $radios = false, $rds_rel_path = NULL, $is_recursing = false, $action = 'view' )
{
    static
$instance_ID = 0;
    static
$fm_highlight;

   
// A folder might be highlighted (via "Locate this directory!")
   
if( ! isset($fm_highlight) )
    {
       
$fm_highlight = param('fm_highlight', 'string', '');
    }


    if( !
$is_recursing )
    {    
// This is not a recursive call (yet):
        // Init:
       
$instance_ID++;
       
$ret = '<ul class="clicktree">';
    }
    else
    {
       
$ret = '';
    }

   
// ________________________ Handle Roots ______________________
   
if( $Root === NULL )
    {
// We want to list all roots:
       
$_roots = FileRootCache::get_available_FileRoots();

        foreach(
$_roots as $l_Root )
        {
            if( !
check_user_perm( 'files', $action, false, $l_Root ) )
            {    
// current user does not have permission to "view" (or other $action) this root
               
continue;
            }
           
$subR = get_directory_tree( $l_Root, $l_Root->ads_path, $ads_selected_full_path, $radios, '', true );
            if( !empty(
$subR['string'] ) )
            {
               
$ret .= '<li>'.$subR['string'].'</li>';
            }
        }
    }
    else
    {
       
// We'll go through files in current dir:
       
$Nodelist = new Filelist( $Root, trailing_slash($ads_full_path) );
       
check_showparams( $Nodelist );
       
$Nodelist->load();
       
$Nodelist->sort( 'name' );
       
$has_sub_dirs = $Nodelist->count_dirs();

       
$id_path = 'id_path_'.$instance_ID.md5( $ads_full_path );

       
$r = '<span class="folder_in_tree"';

        if(
strpos( $ads_selected_full_path, $ads_full_path ) === 0 )
        {
// This is the current or parent open path
           
$dir_is_opened = true;

            if(
$fm_highlight && $fm_highlight == substr($rds_rel_path, 0, -1) )
            {
               
$r .= ' id="fm_highlighted"';
                unset(
$fm_highlight);
            }
        }
        else
        {
           
$dir_is_opened = false;
        }

       
$r .= '>';

        if(
$radios )
        {
// Optional radio input to select this path:
           
$root_and_path = format_to_output( implode( '::', array($Root->ID, $rds_rel_path) ), 'formvalue' );

           
$r .= '<input type="radio" name="root_and_path" value="'.$root_and_path.'" id="radio_'.$id_path.'"';

            if(
$dir_is_opened )
            {    
// This is the current open path
               
$r .= ' checked="checked"';
            }

           
//.( ! $has_sub_dirs ? ' style="margin-right:'.get_icon( 'collapse', 'size', array( 'size' => 'width' ) ).'px"' : '' )
           
$r .= ' /> &nbsp; &nbsp;';
        }

       
// Folder Icon + Name:
       
$url = regenerate_url( 'root,path', 'root='.$Root->ID.'&amp;path='.rawurlencode( $rds_rel_path ) );
       
$label = action_icon( T_('Open this directory in the file manager'), 'folder', $url )
            .
'<a href="'.$url.'"
            title="'
.T_('Open this directory in the file manager').'">'
           
.( empty($rds_rel_path) ? $Root->name : basename( $ads_full_path ) )
            .
'</a>';

       
// Handle potential subdir:
       
if( ! $has_sub_dirs )
        {    
// No subdirs
           
$r .= get_icon( 'expand', 'noimg', array( 'class'=>'' ) ).$label.'</span>';
        }
        else
        {
// Process subdirs
           
$r .= get_icon( $dir_is_opened ? 'collapse' : 'expand', 'imgtag', array(
                       
'data-dir-path' => $Root->ID.':'.$rds_rel_path,
                       
'style'=>'margin:0 2px'
                   
) )
                .
$label.'</span>';

            if(
$dir_is_opened )
            {    
// Load sub-directories only of currently opened directory:
               
$r .= //'<br>- '.$ads_full_path.'<br>- '.$ads_selected_full_path.'<br>- '.$rds_rel_path
                           
'<ul class="clicktree">'."\n";

                while(
$l_File = & $Nodelist->get_next( 'dir' ) )
                {
                   
$r_sub = get_directory_tree( $Root, $l_File->get_full_path(), $ads_selected_full_path, $radios, $l_File->get_rdfs_rel_path(), true );

                   
$r .= '<li>'.$r_sub.'</li>';
                }
               
$r .= '</ul>';
            }
        }

        if(
$is_recursing )
        {
            return
$r;
        }
        else
        {
           
$ret .= '<li>'.$r.'</li>';
        }
    }

    if( !
$is_recursing )
    {
       
$ret .= '</ul>';
    }

    return
$ret;
}


/**
 * Create a directory recursively.
 *
 * @todo dh> simpletests for this (especially for open_basedir)
 *
 * @param string directory name
 * @param integer permissions
 * @return boolean
 */
function mkdir_r( $dirName, $chmod = NULL )
{
    return
evo_mkdir( $dirName, $chmod, true );
}


/**
 * Create a directory
 *
 * @param string Directory path
 * @param integer Permissions
 * @param boolean Create a dir recursively
 * @return boolean TRUE on success
 */
function evo_mkdir( $dir_path, $chmod = NULL, $recursive = false )
{
    if(
is_dir( $dir_path ) )
    {
// already exists:
       
return true;
    }

    if( @
mkdir( $dir_path, 0777, $recursive ) )
    {
// Directory is created succesfully
       
if( $chmod === NULL )
        {
// Get default permissions
           
global $Settings;
           
$chmod = $Settings->get( 'fm_default_chmod_dir' );
        }

        if( ! empty(
$chmod ) )
        {
// Set the dir rights by chmod() function because mkdir() doesn't provide this operation correctly
           
chmod( $dir_path, is_string( $chmod ) ? octdec( $chmod ) : $chmod );
        }

        return
true;
    }

    return
false;
}


/**
 * Copy directory recursively or one file
 *
 * @param string Source path
 * @param string Destination path
 * @param string Name of folder name for the copied folder, Use NULL to get a folder name from source
 * @param array What directories should be excluded
 * @return boolean TRUE on success, FALSE when no permission
 */
function copy_r( $source, $dest, $new_folder_name = NULL, $exclude_dirs = array() )
{
   
$result = true;

    if(
is_dir( $source ) )
    {
// Copy directory recusively
       
if( in_array( basename( $source ), $exclude_dirs ) )
        {
// Don't copy this folder
           
return true;
        }
        if( ! (
$dir_handle = @opendir( $source ) ) )
        {
// Unable to open dir
           
return false;
        }
       
$source_folder = is_null( $new_folder_name ) ? basename( $source ) : $new_folder_name;
        if( !
mkdir_r( $dest.'/'.$source_folder ) )
        {
// No rights to create a dir
           
return false;
        }
        while(
$file = readdir( $dir_handle ) )
        {
            if(
$file != '.' && $file != '..' )
            {
                if(
is_dir( $source.'/'.$file ) )
                {
// Copy the files of subdirectory
                   
$result = copy_r( $source.'/'.$file, $dest.'/'.$source_folder, NULL, $exclude_dirs ) && $result;
                }
                else
                {
// Copy one file of the directory
                   
$result = @copy( $source.'/'.$file, $dest.'/'.$source_folder.'/'.$file ) && $result;
                }
            }
        }
       
closedir( $dir_handle );
    }
    else
    {
// Copy a file and check destination folder for existing
       
$dest_folder = preg_replace( '#(.+)/[^/]+$#', '$1', $dest );
        if( !
file_exists( $dest_folder ) )
        {
// Create destination folder recursively if it doesn't exist
           
if( ! mkdir_r( $dest_folder ) )
            {
// Unable to create a destination folder
               
return false;
            }
        }
       
// Copy a file
       
$result = @copy( $source, $dest );
    }

    return
$result;
}


/**
 * Move files from one folder to another recursively
 *
 * @param string Source folder path
 * @param string Destination folder path
 * @return boolean TRUE on success, FALSE when no permission
 */
function move_files_r( $source_dir_path, $dest_dir_path )
{
   
$result = false;

    if( ! (
$dir_handle = @opendir( $source_dir_path ) ) )
    {    
// Unable to open dir:
       
return $result;
    }

   
$source_dir_path = rtrim( $source_dir_path, '/' );
   
$dest_dir_path = rtrim( $dest_dir_path, '/' );

    while(
$file = readdir( $dir_handle ) )
    {
        if(
$file == '.' || $file == '..' )
        {    
// Skip reserved folders:
           
continue;
        }
        if(
is_dir( $source_dir_path.'/'.$file ) )
        {    
// Copy a folder recursively:
           
$result = copy_r( $source_dir_path.'/'.$file, $dest_dir_path ) && $result;
           
// Remove a folder recursively:
           
$result = rmdir_r( $source_dir_path.'/'.$file ) && $result;
        }
        else
        {    
// Copy a file:
           
$result = @copy( $source_dir_path.'/'.$file, $dest_dir_path.'/'.$file ) && $result;
           
// Remove a file:
           
$result = @unlink( $source_dir_path.'/'.$file ) && $result;
        }
    }

   
// Close the folder handler:
   
$result = closedir( $dir_handle ) && $result;

    return
$result;
}


/**
 * Change file and folder permissions recursively
 *
 * @param string Directory path
 * @param integer Permissions for directories, NULL - to use general setting "Permissions for new folders"
 * @param integer Permissions for files, NULL - to use general setting "Permissions for new files"
 * @return boolean TRUE on success
 */
function chmod_r( $dir_path, $chmod_dir = NULL, $chmod_file = NULL )
{
    global
$Settings;

   
$result = false;

    if( !
is_dir( $dir_path ) )
    {    
// Skip not directory path:
       
return $result;
    }

    if( ! (
$dir_handle = @opendir( $dir_path ) ) )
    {    
// Unable to open dir:
       
return $result;
    }

   
// Get default permissions:
   
$chmod_dir = ( $chmod_dir === NULL ? $Settings->get( 'fm_default_chmod_dir' ) : $chmod_dir );
   
$chmod_file = ( $chmod_file === NULL ? $Settings->get( 'fm_default_chmod_file' ) : $chmod_file );

    if( ! empty(
$chmod_dir ) )
    {    
// Change permissions for directory:
       
$result = @chmod( $dir_path, is_string( $chmod_dir ) ? octdec( $chmod_dir ) : $chmod_dir ) && $result;
    }

    while(
$file = readdir( $dir_handle ) )
    {
        if(
$file == '.' || $file == '..' )
        {    
// Skip reserved folders:
           
continue;
        }
        if(
is_dir( $dir_path.'/'.$file ) )
        {    
// Change permissions for directory and all files inside:
           
$result = chmod_r( $dir_path.'/'.$file, $chmod_dir, $chmod_file ) && $result;
        }
        elseif( ! empty(
$chmod_file ) )
        {    
// Change permissions for file:
           
$result = @chmod( $dir_path.'/'.$file, is_string( $chmod_file ) ? octdec( $chmod_file ) : $chmod_file ) && $result;
        }
    }

   
// Close the folder handler:
   
$result = closedir( $dir_handle ) && $result;

    return
$result;
}


/**
 * Is the given path absolute (non-relative)?
 *
 * @return boolean
 */
function is_absolute_pathname($path)
{
   
$pathlen = strlen($path);
    if( !
$pathlen )
    {
        return
false;
    }

    if(
is_windows() )
    {
// windows e-g: (note: "XY:" can actually happen as a drive ID in windows; I have seen it once in 2009 on MY XP sp3 after plugin in & plugin out an USB stick like 26 times over 26 days! (with sleep/hibernate in between)
       
return ( $pathlen > 1 && $path[1] == ':' );
    }
    else
    {
// unix
       
return ( $path[0] == '/' );
    }
}


/**
 * Controller helper
 */
function file_controller_build_tabs()
{
    global
$AdminUI, $blog, $admin_url;

   
$AdminUI->add_menu_entries(
           
'files',
            array(
                   
'browse' => array(
                       
'text' => T_('Browse'),
                       
'href' => $admin_url.'?ctrl=files' ),
                    )
                );

    if(
check_user_perm( 'options', 'view' ) )
    {    
// Permission to view settings:
       
$AdminUI->add_menu_entries(
           
'files',
            array(
               
'settings' => array(
                   
'text' => T_('Settings'),
                   
'href' => $admin_url.'?ctrl=fileset',
                    )
                )
            );

       
$AdminUI->add_menu_entries(
            array(
'files', 'settings'),
            array(
                   
'settings' => array(
                       
'text' => T_('Settings'),
                       
'href' => $admin_url.'?ctrl=fileset' ),
                   
'filetypes' => array(
                       
'text' => T_('File types'),
                       
'href' => $admin_url.'?ctrl=filetypes' ),
                )
            );
    }

    if(
check_user_perm( 'options', 'edit' ) )
    {
// Permission to edit settings:
       
$AdminUI->add_menu_entries(
           
'files',
            array(
               
'moderation' => array(
                   
'text' => T_('Moderation'),
                   
'href' => $admin_url.'?ctrl=filemod',
                   
'entries' => array(
                       
'likes' => array(
                           
'text' => T_('Likes'),
                           
'href' => $admin_url.'?ctrl=filemod&amp;tab=likes' ),
                       
'suspicious' => array(
                           
'text' => T_('Suspicious'),
                           
'href' => $admin_url.'?ctrl=filemod&amp;tab=suspicious' ),
                       
'duplicates' => array(
                           
'text' => T_('Duplicates'),
                           
'href' => $admin_url.'?ctrl=filemod&amp;tab=duplicates' ),
                        )
                    )
                )
            );
    }

}


/**
 * Rename evocache folders after File settings update, whe evocahe folder name was chaned
 *
 * @param string old evocache folder name
 * @param string new evocache folder name
 * @return bool true on success
 */
function rename_cachefolders( $oldname, $newname )
{
   
load_class( 'files/model/_filerootcache.class.php', 'FileRootCache' );

   
$available_Roots = FileRootCache::get_available_FileRoots();

   
$slash_oldname = '/'.$oldname;

   
$result = true;
    foreach(
$available_Roots as $fileRoot )
    {
       
$filename_params = array(
               
'inc_files'        => false,
               
'inc_evocache'    => true,
            );
       
$dirpaths = get_filenames( $fileRoot->ads_path, $filename_params );

        foreach(
$dirpaths as $dirpath )
        {
// search ?evocache folders
           
$dirpath_length = strlen( $dirpath );
            if(
$dirpath_length < 10 )
            {
// The path is to short, can not contains ?evocache folder name
               
continue;
            }
           
// searching at the end of the path -> '/' character + ?evocache, length = 1 + 9
           
$path_end = substr( $dirpath, $dirpath_length - 10 );
            if(
$path_end == $slash_oldname )
            {
// this is a ?evocache folder
               
$new_dirpath = substr_replace( $dirpath, $newname, $dirpath_length - 9 );
               
// result is true only if all rename call return true (success)
               
$result = $result && @rename( $dirpath, $new_dirpath );
            }
        }
    }
    return
$result;
}


/**
 * Delete any ?evocache folders.
 *
 * @param Log Pass a Log object here to have error messages added to it.
 * @return integer Number of deleted dirs.
 */
function delete_cachefolders( $Log = NULL )
{
    global
$media_path, $Settings;

    if( !isset(
$Settings ) )
    {    
// This function can be called on install process before initialization of $Settings, Exit here
       
return false;
    }

   
// Get this, just in case someone comes up with a different naming:
   
$evocache_foldername = $Settings->get( 'evocache_foldername' );

   
$filename_params = array(
           
'inc_files'        => false,
           
'inc_evocache'    => true,
        );
   
$dirs = get_filenames( $media_path, $filename_params );

   
$deleted_dirs = 0;
    foreach(
$dirs as $dir )
    {
       
$basename = basename($dir);
        if(
$basename == '.evocache' || $basename == '_evocache' || $basename == $evocache_foldername )
        {    
// Delete .evocache directory recursively
           
if( rmdir_r( $dir ) )
            {
               
$deleted_dirs++;
            }
            elseif(
$Log )
            {
               
$Log->add( sprintf( T_('Could not delete directory: %s'), $dir ), 'error' );
            }
        }
    }
    return
$deleted_dirs;
}


/**
 * Check and set the given FileList object fm_showhidden and fm_showevocache params
 */
function check_showparams( & $Filelist )
{
    global
$UserSettings;

    if(
$UserSettings->param_Request( 'fm_showhidden', 'fm_showhidden', 'integer', 0 ) )
    {
       
$Filelist->_show_hidden_files = true;
    }

    if(
$UserSettings->param_Request( 'fm_showevocache', 'fm_showevocache', 'integer', 0 ) )
    {
       
$Filelist->_show_evocache = true;
    }
}


/**
 * Process file uploads (this can process multiple file uploads at once)
 *
 * @param string FileRoot id string
 * @param string the upload dir relative path in the FileRoot
 * @param boolean Shall we create path dirs if they do not exist?
 * @param boolean Shall we check files add permission for current_User?
 * @param boolean upload quick mode
 * @param boolean show warnings if filename is not valid
 * @param integer minimum size for pictures in pixels (width and height)
 * @return mixed NULL if upload was impossible to complete for some reason (wrong fileroot ID, insufficient user permission, etc.)
 *                        array, which contains uploadedFiles, failedFiles, renamedFiles and renamedMessages
 */
function process_upload( $root_ID, $path, $create_path_dirs = false, $check_perms = true, $upload_quickmode = true, $warn_invalid_filenames = true, $min_size = 0 )
{
    global
$Settings, $Plugins, $Messages, $current_User, $force_upload_forbiddenext, $admins_can_manipulate_sensitive_files;

    if( empty(
$_FILES) )
    {    
// We have NO uploaded files to process...
       
return NULL;
    }

   
/**
     * Remember failed files (and the error messages)
     * @var array
     */
   
$failedFiles = array();
   
/**
     * Remember uploaded files
     * @var array
     */
   
$uploadedFiles = array();
   
/**
     * Remember renamed files
     * @var array
     */
   
$renamedFiles = array();
   
/**
     * Remember renamed Messages
     * @var array
     */
   
$renamedMessages = array();

   
$FileRootCache = & get_FileRootCache();
   
$fm_FileRoot = & $FileRootCache->get_by_ID($root_ID, true);
    if( !
$fm_FileRoot )
    {
// fileRoot not found:
       
return NULL;
    }

    if(
$check_perms && ( !isset( $current_User ) || check_user_perm( 'files', 'add', false, $fm_FileRoot ) ) )
    {
// Permission check required but current User has no permission to upload:
       
return NULL;
    }

   
// Let's get into requested list dir...
   
$non_canonical_list_path = $fm_FileRoot->ads_path.$path;
   
// Dereference any /../ just to make sure, and CHECK if directory exists:
   
$ads_list_path = get_canonical_path( $non_canonical_list_path );

   
// check if the upload dir exists
   
if( !is_dir( $ads_list_path ) )
    {
        if(
$create_path_dirs )
        {
// Create path
           
mkdir_r( $ads_list_path );
        }
        else
        {
// This case should not happen! If it happens then there is a bug in the code where this function was called!
           
return NULL;
        }
    }

   
// Get param arrays for all uploaded files:
   
$uploadfile_title = param( 'uploadfile_title', 'array:string', array() );
   
$uploadfile_alt = param( 'uploadfile_alt', 'array:string', array() );
   
$uploadfile_desc = param( 'uploadfile_desc', 'array:string', array() );
   
$uploadfile_name = param( 'uploadfile_name', 'array:string', array() );

   
// LOOP THROUGH ALL UPLOADED FILES AND PROCESS EACH ONE:
   
foreach( $_FILES['uploadfile']['name'] as $lKey => $lName )
    {
        if( empty(
$lName ) )
        {
// No file name:
           
if( $upload_quickmode
                 
|| !empty( $uploadfile_title[$lKey] )
                 || !empty(
$uploadfile_alt[$lKey] )
                 || !empty(
$uploadfile_desc[$lKey] )
                 || !empty(
$uploadfile_name[$lKey] ) )
            {
// User specified params but NO file! Warn the user:
               
$failedFiles[$lKey] = T_( 'Please select a local file to upload.' );
               
param_error( 'uploadfile[]', NULL, $failedFiles[$lKey] );
            }
           
// Abort upload for this file:
           
continue;
        }

        if(
$Settings->get( 'upload_maxkb' )
                &&
$_FILES['uploadfile']['size'][$lKey] > $Settings->get( 'upload_maxkb' )*1024 )
        {
// File is larger than allowed in settings:
           
$failedFiles[$lKey] = sprintf(
                   
T_('The file is too large: %s but the maximum allowed is %s.'),
                   
bytesreadable( $_FILES['uploadfile']['size'][$lKey] ),
                   
bytesreadable($Settings->get( 'upload_maxkb' )*1024) );
           
// Abort upload for this file:
           
continue;
        }

        if( ( !(
$_FILES['uploadfile']['error'][$lKey] ) ) && ( !empty( $min_size ) ) )
        {
// If there is no error and a minimum size is required, check if the uploaded picture satisfies the "minimum size" criteria
           
$image_sizes = imgsize( $_FILES['uploadfile']['tmp_name'][$lKey], 'widthheight' );
            if(
$image_sizes[0] < $min_size || $image_sizes[1] < $min_size )
            {    
// Abort upload for this file:
               
$failedFiles[$lKey] = sprintf(
                   
T_( 'Your profile picture must have a minimum size of %dx%d pixels.' ),
                   
$min_size,
                   
$min_size );
                continue;
            }
        }

        if(
$_FILES['uploadfile']['error'][$lKey] )
        {
// PHP itself has detected an error!:
           
switch( $_FILES['uploadfile']['error'][$lKey] )
            {
                case
UPLOAD_ERR_FORM_SIZE:
                   
// The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form.
                    // This can easily be edited by the user/hacker, so we do not use it.. file size gets checked for real just above.
                   
break;

                case
UPLOAD_ERR_INI_SIZE:
                   
// File is larger than allowed in php.ini:
                   
$failedFiles[$lKey] = 'The file exceeds the upload_max_filesize directive in php.ini.'; // Configuration error, no translation
                   
break;

                case
UPLOAD_ERR_PARTIAL:
                   
$failedFiles[$lKey] = T_('The file was only partially uploaded.');
                    break;

                case
UPLOAD_ERR_NO_FILE:
                   
// Is probably the same as empty($lName) before.
                   
$failedFiles[$lKey] = T_('No file was uploaded.');
                    break;

                case
6: // numerical value of UPLOAD_ERR_NO_TMP_DIR
                # (min_php: 4.3.10, 5.0.3) case UPLOAD_ERR_NO_TMP_DIR:
                    // Missing a temporary folder.
                   
$failedFiles[$lKey] = 'Temporary upload dir is missing! (upload_tmp_dir in php.ini)'; // Configuration error, no translation
                   
break;

                default:
                   
$failedFiles[$lKey] = T_('An unknown error has occurred!').' Error code #'.$_FILES['uploadfile']['error'][$lKey];
                    break;
            }
           
// Abort upload for this file:
           
continue;
        }

        if( ! isset(
$_FILES['uploadfile']['_evo_fetched_url'][$lKey]) // skip check for fetched URLs
           
&& ! is_uploaded_file( $_FILES['uploadfile']['tmp_name'][$lKey] ) )
        {
// Ensure that a malicious user hasn't tried to trick the script into working on files upon which it should not be working.
           
$failedFiles[$lKey] = T_('The file does not seem to be a valid upload! It may exceed the upload_max_filesize directive in php.ini.');
           
// Abort upload for this file:
           
continue;
        }

       
// Use new name on server if specified:
       
$newName = !empty( $uploadfile_name[ $lKey ] ) ? $uploadfile_name[ $lKey ] : $lName;
       
// validate file name
       
if( $error_filename = process_filename( $newName, !$warn_invalid_filenames, true, $fm_FileRoot, $path ) )
        {
// Not a valid file name or not an allowed extension:
           
$failedFiles[$lKey] = $error_filename;
           
syslog_insert( sprintf( 'The uploaded file %s has an unrecognized extension', '[['.$newName.']]' ), 'warning', 'file' );
           
// Abort upload for this file:
           
continue;
        }

       
// Check if the uploaded file type is an image, and if is an image then try to fix the file extension based on mime type
        // If the mime type is a known mime type and user has right to upload files with this kind of file type,
        // this part of code will check if the file extension is the same as admin defined for this file type, and will fix it if it isn't the same
        // Note: it will also change the jpeg extensions to jpg.
       
$uploadfile_path = $_FILES['uploadfile']['tmp_name'][$lKey];
       
// this image_info variable will be used again to get file thumb
       
$image_info = getimagesize($uploadfile_path);
        if(
$image_info )
        {
// This is an image, validate mimetype vs. extension:
           
$image_mimetype = $image_info['mime'];
           
$FiletypeCache = & get_FiletypeCache();
           
// Get correct file type based on mime type
           
$correct_Filetype = $FiletypeCache->get_by_mimetype( $image_mimetype, false, false );

           
// Check if file type is known by us, and if it is allowed for upload.
            // If we don't know this file type or if it isn't allowed we don't change the extension! The current extension is allowed for sure.
           
if( $correct_Filetype && $correct_Filetype->is_allowed() )
            {
// A FileType with the given mime type exists in database and it is an allowed file type for current User
                // The "correct" extension is a plausible one, proceed...
               
$correct_extension = $correct_Filetype->get_extensions();
               
$correct_extension = array_shift( $correct_extension );
               
$path_info = pathinfo($newName);
               
$current_extension = $path_info['extension'];

               
// change file extension to the correct extension, but only if the correct extension is not restricted, this is an extra security check!
               
if( strtolower($current_extension) != strtolower($correct_extension) && ( !in_array( $correct_extension, $force_upload_forbiddenext ) ) )
                {
// change the file extension to the correct extension
                   
$old_name = $newName;
                   
$newName = $path_info['filename'].'.'.$correct_extension;
                   
$Messages->add( sprintf(T_('The extension of the file &laquo;%s&raquo; has been corrected. The new filename is &laquo;%s&raquo;.'), $old_name, $newName), 'warning' );
                }
            }
        }

       
// Get File object for requested target location:
       
$oldName = strtolower( $newName );
        list(
$newFile, $oldFile_thumb ) = check_file_exists( $fm_FileRoot, $path, $newName, $image_info );
       
$newName = $newFile->get( 'name' );

       
// Trigger plugin event
       
if( $Plugins->trigger_event_first_false( 'AfterFileUpload', array(
                   
'File' => & $newFile,
                   
'name' => & $_FILES['uploadfile']['name'][$lKey],
                   
'type' => & $_FILES['uploadfile']['type'][$lKey],
                   
'tmp_name' => & $_FILES['uploadfile']['tmp_name'][$lKey],
                   
'size' => & $_FILES['uploadfile']['size'][$lKey],
                ) ) )
        {
           
// Plugin returned 'false'.
            // Abort upload for this file:
           
continue;
        }

       
// Attempt to move the uploaded file to the requested target location:
       
if( isset($_FILES['uploadfile']['_evo_fetched_url'][$lKey]) )
        {
// fetched remotely
           
if( ! rename( $_FILES['uploadfile']['tmp_name'][$lKey], $newFile->get_full_path() ) )
            {
               
$failedFiles[$lKey] = T_('An unknown error occurred when moving the uploaded file on the server.');
               
// Abort upload for this file:
               
continue;
            }
        }
        elseif( !
move_uploaded_file( $_FILES['uploadfile']['tmp_name'][$lKey], $newFile->get_full_path() ) )
        {
           
syslog_insert( sprintf( 'File %s could not be uploaded', '[['.$newFile->get_name().']]' ), 'warning', 'file', $newFile->ID );
           
$failedFiles[$lKey] = T_('An unknown error occurred when moving the uploaded file on the server.');
           
// Abort upload for this file:
           
continue;
        }

       
// change to default chmod settings
       
if( $newFile->chmod( NULL ) === false )
        {
// add a note, this is no error!
           
$Messages->add( sprintf( T_('Could not change permissions of &laquo;%s&raquo; to default chmod setting.'), $newFile->dget('name') ), 'note' );
        }

       
// Refreshes file properties (type, size, perms...)
       
$newFile->load_properties();

        if( ! empty(
$oldFile_thumb ) )
        {
// The file name was changed!
           
if( $image_info )
            {
               
$newFile_thumb = $newFile->get_preview_thumb( 'fulltype' );
            }
            else
            {
               
$newFile_thumb = $newFile->get_size_formatted();
            }
           
//$newFile_size = bytesreadable ($_FILES['uploadfile']['size'][$lKey]);
           
$renamedMessages[$lKey]['message'] = sprintf( T_('"%s was renamed to %s. Would you like to replace %s with the new version instead?'),
                                                         
'&laquo;'.$oldName.'&raquo;', '&laquo;'.$newName.'&raquo;', '&laquo;'.$oldName.'&raquo;' );
           
$renamedMessages[$lKey]['oldThumb'] = $oldFile_thumb;
           
$renamedMessages[$lKey]['newThumb'] = $newFile_thumb;
           
$renamedFiles[$lKey]['oldName'] = $oldName;
           
$renamedFiles[$lKey]['newName'] = $newName;
        }

       
// Store extra info about the file into File Object:
       
if( isset( $uploadfile_title[$lKey] ) )
        {
// If a title text has been passed... (does not happen in quick upload mode)
           
$newFile->set( 'title', trim( strip_tags($uploadfile_title[$lKey])) );
        }
        if( isset(
$uploadfile_alt[$lKey] ) )
        {
// If an alt text has been passed... (does not happen in quick upload mode)
           
$newFile->set( 'alt', trim( strip_tags($uploadfile_alt[$lKey])) );
        }
        if( isset(
$uploadfile_desc[$lKey] ) )
        {
// If a desc text has been passed... (does not happen in quick upload mode)
           
$newFile->set( 'desc', trim( strip_tags($uploadfile_desc[$lKey])) );
        }

       
// Store File object into DB:
       
$newFile->dbsave();
       
report_user_upload( $newFile );
       
$uploadedFiles[] = $newFile;
    }

   
prepare_uploaded_files( $uploadedFiles );

    return array(
'uploadedFiles' => $uploadedFiles, 'failedFiles' => $failedFiles, 'renamedFiles' => $renamedFiles, 'renamedMessages' => $renamedMessages );
}


/**
 * Prepare the uploaded files
 *
 * @param array Uploaded files
 */
function prepare_uploaded_files( $uploadedFiles )
{
    if(
count( $uploadedFiles ) == 0 )
    {    
// No uploaded files
       
return;
    }

    foreach(
$uploadedFiles as $File )
    {
       
$Filetype = & $File->get_Filetype();
        if(
$Filetype )
        {
            if(
in_array( $Filetype->mimetype, array( 'image/jpeg', 'image/gif', 'image/png' ) ) )
            {    
// Image file
               
prepare_uploaded_image( $File, $Filetype->mimetype );
            }
        }
    }
}


/**
 * Prepare image file (Resize, Rotate and etc.)
 *
 * @param object File
 * @param string mimetype
 */
function prepare_uploaded_image( $File, $mimetype )
{
    global
$Settings, $Messages;

   
$thumb_width = $Settings->get( 'fm_resize_width' );
   
$thumb_height = $Settings->get( 'fm_resize_height' );
   
$thumb_quality = $Settings->get( 'fm_resize_quality' );

   
$do_resize = false;
    if(
$Settings->get( 'fm_resize_enable' ) &&
       
$thumb_width > 0 && $thumb_height > 0 )
    {    
// Image resizing is enabled
       
list( $image_width, $image_height ) = explode( 'x', $File->get_image_size() );
        if(
$image_width > $thumb_width || $image_height > $thumb_height )
        {    
// This image should be resized
           
$do_resize = true;
        }
    }

   
load_funcs( 'files/model/_image.funcs.php' );

   
$resized_imh = null;
    if(
$do_resize )
    {
// Resize image
       
resize_image( $File, $thumb_width, $thumb_height, $mimetype, $thumb_quality );
    }
    else
    {
        if(
$mimetype == 'image/jpeg' )
        {    
// JPEG, do autorotate if EXIF Orientation tag is defined
           
exif_orientation( $File->get_full_path(), $resized_imh, true );
        }
    }
}


/**
 * Reports user file upload by inserting system log entry
 *
 * @param object File uploaded by user
 */
function report_user_upload( $File )
{
    global
$current_User;

   
load_funcs( 'files/model/_file.funcs.php' );

   
syslog_insert( sprintf( '%s has uploaded the file %s -- Size: %s',
        (
is_logged_in() ? 'User #'.$current_User->ID.'([['.$current_User->login.']])' : 'Anonymous user' ),
       
'[['.$File->get_full_path().']]', bytesreadable( $File->get_size(), false ) ), 'info', 'file', $File->ID );
}


/**
 * Handles warnings and errors when attempting to read EXIF data
 */
function exif_read_data_error_handler( $errno, $errstr )
{
    global
$Messages;

    switch(
$errno )
    {
        case
E_WARNING:
           
$Messages->add( T_('Unable to read EXIF data'), 'warning' );
            break;

        default:
           
$Messages->add( T_('Unknown error').': <code>'.$errstr.'</code>', 'error' );
    }
}


/**
 * Rotate the JPEG image if EXIF Orientation tag is defined
 *
 * @param string File name (with full path)
 * @param resource Image resource ( result of the function imagecreatefromjpeg() ) (by reference)
 * @param boolean TRUE - to save the rotated image in the end of this function
 */
function exif_orientation( $file_name, & $imh/* = null*/, $save_image = false )
{
    global
$Settings;

    if( !
$Settings->get( 'exif_orientation' ) )
    {
// Autorotate is disabled
       
return;
    }

    if( !
function_exists('exif_read_data') )
    {
// Exif extension is not loaded
       
return;
    }

   
$image_info = array();
   
getimagesize( $file_name, $image_info );
    if( ! isset(
$image_info['APP1'] ) || ( strpos( $image_info['APP1'], 'Exif' ) !== 0 ) )
    {
// This file format is not an 'Exchangeable image file format' so there are no Exif data to read
       
return;
    }

   
set_error_handler( 'exif_read_data_error_handler' );
    if( (
$exif_data = exif_read_data( $file_name ) ) === false )
    {
// Could not read Exif data
       
return;
    }
   
restore_error_handler();

    if( !( isset(
$exif_data['Orientation'] ) && in_array( $exif_data['Orientation'], array( 3, 6, 8 ) ) ) )
    {
// Exif Orientation tag is not defined OR we don't interested in current value
       
return;
    }

   
load_funcs( 'files/model/_image.funcs.php' );

    if(
is_null( $imh ) )
    {
// Create image resource from file name
       
$imh = imagecreatefromjpeg( $file_name );
    }

    if( !
$imh )
    {
// Image resource is incorrect
       
return;
    }

    switch(
$exif_data['Orientation'] )
    {
        case
3:    // Rotate for 180 degrees
           
$imh = @imagerotate( $imh, 180, 0 );
            break;

        case
6:    // Rotate for 90 degrees to the right
           
$imh = @imagerotate( $imh, 270, 0 );
            break;

        case
8:    // Rotate for 90 degrees to the left
           
$imh = @imagerotate( $imh, 90, 0 );
            break;
    }

    if( !
$imh )
    {    
// Image resource is incorrect
       
return;
    }

    if(
$save_image )
    {    
// Save rotated image
       
save_image( $imh, $file_name, 'image/jpeg' );
    }
}


/**
 * Check if file exists in the target location with the given name. Used during file upload.
 *
 * @param FileRoot target file Root
 * @param string target path
 * @param string file name
 * @param array the new file image_info
 * @return array contains two elements
 *             first elements is a new File object
 *             second element is the existing file thumb, or empty string, if the file doesn't exists
 */
function check_file_exists( $fm_FileRoot, $path, $newName, $image_info = NULL )
{
    global
$filename_max_length;

   
// Get File object for requested target location:
   
$FileCache = & get_FileCache();
   
$newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$newName, true );

   
// Unset this flag to check for each file even if it is the same from cache:
   
unset( $newFile->_exists );

   
$num_ext = 0;
   
$oldName = $newName;

   
$oldFile_thumb = "";
    while(
$newFile->exists() )
    {
// The file already exists in the target location!
       
$num_ext++;
       
$ext_pos = strrpos( $newName, '.' );
        if(
$num_ext == 1 )
        {
            if(
$newFile->is_image() && $image_info == NULL )
            {    
// Get image info only for real image files:
               
$image_info = getimagesize( $newFile->get_full_path() );
            }
            if(
$ext_pos === false )
            {    
// If file/folder name has no '.' dot:
               
$newName .= '-'.$num_ext;
            }
            else
            {
               
$newName = substr_replace( $newName, '-'.$num_ext.'.', $ext_pos, 1 );
            }
            if(
$image_info )
            {    
// Get thumbnail of old image:
               
$oldFile_thumb = $newFile->get_preview_thumb( 'fulltype' );
            }
            else
            {    
// Get formatted file of not image file:
               
$oldFile_thumb = $newFile->get_size_formatted();
            }
        }
        else
        {
            if(
$ext_pos === false )
            {    
// If file/folder name has no '.' dot:
               
$ext_pos = strlen( $newName );
            }
           
$replace_length = strlen( '-'.($num_ext-1) );
           
$newName = substr_replace( $newName, '-'.$num_ext, $ext_pos-$replace_length, $replace_length );
        }
        if(
strlen( $newName ) > $filename_max_length )
        {
           
$newName = fix_filename_length( $newName, strrpos( $newName, '-' ) );
            if(
$error_filename = process_filename( $newName, true ) )
            {
// The file name is still not valid
               
syslog_insert( sprintf( 'Invalid file name %s has found during file exists check', '[['.$newName.']]' ), 'warning', 'file' );
               
debug_die( 'Invalid file name has found during file exists check: '.$error_filename );
            }
        }
       
$newFile = & $FileCache->get_by_root_and_path( $fm_FileRoot->type, $fm_FileRoot->in_type_ID, trailing_slash($path).$newName, true );
    }

    return array(
$newFile, $oldFile_thumb );
}


/**
 * Remove files with the given ids
 *
 * @param array file ids to remove, default to remove all orphan file IDs
 * @param integer remove files older than the given hour, default NULL will remove all
 * @return integer the number of removed files
 */
function remove_orphan_files( $file_ids = NULL, $older_than = NULL, $remove_empty_comment_folders = false )
{
    global
$DB, $localtimenow;
   
// asimo> This SQL query should use file class delete_restrictions array (currently T_links and T_users is explicitly used)
    // select orphan comment attachment file ids
   
$sql = 'SELECT file_ID FROM T_files
                WHERE file_ID NOT IN (
                    SELECT * FROM (
                        ( SELECT DISTINCT link_file_ID FROM T_links
                            WHERE link_file_ID IS NOT NULL ) UNION
                        ( SELECT DISTINCT user_avatar_file_ID FROM T_users
                            WHERE user_avatar_file_ID IS NOT NULL ) ) AS linked_files )'
;

    if(
$file_ids !== NULL )
    {
// Remove only from the given files
       
$sql .= ' AND file_ID IN ( '.implode( ',', $file_ids ).' )';
    }
    else
    {
// Remove only the files in the comment folders
       
$sql .= ' AND ( file_path LIKE "comments/p%" OR file_path LIKE "anonymous_comments/p%" )';
    }

    if(
$remove_empty_comment_folders )
    {
// init "folders to delete" array
       
$delete_folders = array();
    }

   
$result = $DB->get_col( $sql );
   
$FileCache = & get_FileCache();
   
$FileCache->load_list( $result );
   
$count = 0;
    foreach(
$result as $file_ID )
    {
       
$File = $FileCache->get_by_ID( $file_ID, false, false );
        if(
$older_than != NULL && $File->exists() )
        {
// we have to check if the File is older than the given value
           
$datediff = $localtimenow - filemtime( $File->_adfp_full_path );
            if(
$datediff > $older_than * 3600 ) // convert hours to seconds
           
{ // not older
               
continue;
            }
        }

        if(
$remove_empty_comment_folders )
        {
           
$rel_path = $File->get_rdfp_rel_path();
           
$folder_path = dirname( $File->get_full_path() );
        }

       
// delete the file
       
if( $File->unlink() )
        {
           
$count++;
            if(
$remove_empty_comment_folders && ! in_array( $folder_path, $delete_folders )
                &&
preg_match( '/^(anonymous_)?comments\/p(\d+)\/.*$/', $rel_path ) )
            {
// Collect comment attachments folders to delete the empty folders later
               
$delete_folders[] = $folder_path;
            }
        }
    }

   
// Delete the empty folders
   
if( $remove_empty_comment_folders && count( $delete_folders ) )
    {
        foreach(
$delete_folders as $delete_folder )
        {
            if(
file_exists( $delete_folder ) )
            {
// Delete folder only if it is empty, Hide an error if folder is not empty
               
@rmdir( $delete_folder );
            }
        }
    }

   
// Clear FileCache to save memory
   
$FileCache->clear();

    return
$count;
}


/**
 * Get available icons for file types
 *
 * @return array 'key'=>'name'
 */
function get_available_filetype_icons()
{
   
$icons = array(
       
''               => T_('Unknown'),
       
'file_empty'     => T_('Empty'),
       
'file_image'     => T_('Image'),
       
'file_document'  => T_('Document'),
       
'file_www'       => T_('Web file'),
       
'file_log'       => T_('Log file'),
       
'file_sound'     => T_('Audio file'),
       
'file_video'     => T_('Video file'),
       
'file_message'   => T_('Message'),
       
'file_pdf'       => T_('PDF'),
       
'file_php'       => T_('PHP script'),
       
'file_encrypted' => T_('Encrypted file'),
       
'file_zip'       => T_('Zip archive'),
       
'file_tar'       => T_('Tar archive'),
       
'file_tgz'       => T_('Tgz archive'),
       
'file_pk'        => T_('Archive'),
       
'file_doc'       => T_('Microsoft Word'),
       
'file_xls'       => T_('Microsoft Excel'),
       
'file_ppt'       => T_('Microsoft PowerPoint'),
       
'file_pps'       => T_('Microsoft PowerPoint Slideshow'),
    );

    return
$icons;
}


/**
 * Copy file from source path to destination path (Used on import)
 *
 * @param string Path of source file
 * @param string FileRoot id string
 * @param string the upload dir relative path in the FileRoot
 * @param boolean Shall we check files add permission for current_User?
 * @return mixed NULL if import was impossible to complete for some reason (wrong fileroot ID, insufficient user permission, etc.)
 *               file ID of new inserted file in DB
 */
function copy_file( $file_path, $root_ID, $path, $check_perms = true )
{
    global
$current_User, $force_upload_forbiddenext;

   
$FileRootCache = & get_FileRootCache();
   
$fm_FileRoot = & $FileRootCache->get_by_ID($root_ID, true);
    if( !
$fm_FileRoot )
    {    
// fileRoot not found:
       
return NULL;
    }

    if(
$check_perms && ( !isset( $current_User ) || check_user_perm( 'files', 'add', false, $fm_FileRoot ) ) )
    {    
// Permission check required but current User has no permission to upload:
       
return NULL;
    }

   
// Let's get into requested list dir...
   
$non_canonical_list_path = $fm_FileRoot->ads_path.$path;
   
// Dereference any /../ just to make sure, and CHECK if directory exists:
   
$ads_list_path = get_canonical_path( $non_canonical_list_path );

   
// check if the upload dir exists
   
if( !is_dir( $ads_list_path ) )
    {    
// Create path
       
mkdir_r( $ads_list_path );
    }

   
// Get file name from full path:
   
$newName = basename( $file_path );
   
// validate file name
   
if( $error_filename = process_filename( $newName, true ) )
    {    
// Not a valid file name or not an allowed extension:
       
syslog_insert( sprintf( 'File %s is not valid or not an allowed extension', '[['.$newName.']]' ), 'warning', 'file' );
       
// Abort import for this file:
       
return NULL;
    }

   
// Check if the imported file type is an image, and if is an image then try to fix the file extension based on mime type
    // If the mime type is a known mime type and user has right to import files with this kind of file type,
    // this part of code will check if the file extension is the same as admin defined for this file type, and will fix it if it isn't the same
    // Note: it will also change the jpeg extensions to jpg.
    // this image_info variable will be used again to get file thumb
   
$image_info = getimagesize( $file_path );
    if(
$image_info )
    {    
// This is an image, validate mimetype vs. extension:
       
$image_mimetype = $image_info['mime'];
       
$FiletypeCache = & get_FiletypeCache();
       
// Get correct file type based on mime type
       
$correct_Filetype = $FiletypeCache->get_by_mimetype( $image_mimetype, false, false );

       
// Check if file type is known by us, and if it is allowed for upload.
        // If we don't know this file type or if it isn't allowed we don't change the extension! The current extension is allowed for sure.
       
if( $correct_Filetype && $correct_Filetype->is_allowed() )
        {    
// A FileType with the given mime type exists in database and it is an allowed file type for current User
            // The "correct" extension is a plausible one, proceed...
           
$correct_extensions = $correct_Filetype->get_extensions();
           
$correct_extension = array_shift( $correct_extensions );
           
$path_info = pathinfo($newName);
           
$current_extension = $path_info['extension'];

           
// change file extension to the correct extension, but only if the correct extension is not restricted, this is an extra security check!
           
if( strtolower($current_extension) != strtolower($correct_extension) && ( !in_array( $correct_extension, $force_upload_forbiddenext ) ) )
            {    
// change the file extension to the correct extension
               
$old_name = $newName;
               
$newName = $path_info['filename'].'.'.$correct_extension;
            }
        }
    }

   
// Get File object for requested target location:
   
$oldName = strtolower( $newName );
    list(
$newFile, $oldFile_thumb ) = check_file_exists( $fm_FileRoot, $path, $newName, $image_info );
   
$newName = $newFile->get( 'name' );

    if( !
copy( $file_path, $newFile->get_full_path() ) )
    {    
// Abort import for this file:
       
return NULL;
    }

   
// change to default chmod settings
   
$newFile->chmod( NULL );

   
// Refreshes file properties (type, size, perms...)
   
$newFile->load_properties();

   
// Store File object into DB:
   
if( $newFile->dbsave() )
    {    
// Success
       
return $newFile->ID;
    }
    else
    {    
// Failure
       
return NULL;
    }
}


/**
 * Create links between users and image files from the users profile_pictures folder
 */
function create_profile_picture_links()
{
    global
$DB;

   
load_class( 'files/model/_filelist.class.php', 'Filelist' );
   
load_class( 'files/model/_fileroot.class.php', 'FileRoot' );
   
$path = 'profile_pictures';

   
$FileRootCache = & get_FileRootCache();
   
$UserCache = & get_UserCache();

   
// SQL query to get all users and limit by page below
   
$users_SQL = new SQL();
   
$users_SQL->SELECT( '*' );
   
$users_SQL->FROM( 'T_users' );
   
$users_SQL->ORDER_BY( 'user_ID' );

   
$page = 0;
   
$page_size = 100;
    while(
count( $UserCache->cache ) > 0 || $page == 0 )
    {
// Load users by 100 at one time to avoid errors about memory exhausting
       
$users_SQL->LIMIT( ( $page * $page_size ).', '.$page_size );
       
$UserCache->clear();
       
$UserCache->load_by_sql( $users_SQL );

        while( (
$iterator_User = & $UserCache->get_next(/* $user_ID, false, false */) ) != NULL )
        {
// Iterate through UserCache)
           
$FileRootCache->clear();
           
$user_FileRoot = & $FileRootCache->get_by_type_and_ID( 'user', $iterator_User->ID );
            if( !
$user_FileRoot )
            {
// User FileRoot doesn't exist
               
continue;
            }

           
$ads_list_path = get_canonical_path( $user_FileRoot->ads_path.$path );
           
// Previously uploaded avatars
           
if( !is_dir( $ads_list_path ) )
            {
// profile_picture folder doesn't exists in the user root dir
               
continue;
            }

           
$user_avatar_Filelist = new Filelist( $user_FileRoot, $ads_list_path );
           
$user_avatar_Filelist->load();

            if(
$user_avatar_Filelist->count() > 0 )
            {    
// profile_pictures folder is not empty
               
$info_content = '';
               
$LinkOwner = new LinkUser( $iterator_User );
                while(
$lFile = & $user_avatar_Filelist->get_next() )
                {
// Loop through all Files:
                   
$fileName = $lFile->get_name();
                    if(
process_filename( $fileName ) )
                    {
// The file has invalid file name, don't create in the database
                       
syslog_insert( sprintf( 'Invalid file name %s has been found in a user folder', '[['.$fileName.']]' ), 'info', 'user', $iterator_User->ID );
                       
// TODO: asimo> we should collect each invalid file name here, and send an email to the admin
                       
continue;
                    }
                   
$lFile->load_meta( true );
                    if(
$lFile->is_image() )
                    {
                       
$lFile->link_to_Object( $LinkOwner );
                    }
                }
            }
        }

       
// Increase page number to get next portion of users
       
$page++;
    }

   
// Clear cache data
   
$UserCache->clear();
   
$FileRootCache->clear();
}


/**
 * Create .htaccess and sample.htaccess files with deny rules in the folder
 *
 * @param string Directory path
 * @return boolean TRUE if files have been created successfully
 */
function create_htaccess_deny( $dir )
{
    if( !
mkdir_r( $dir, NULL ) )
    {
        return
false;
    }

   
$htaccess_files = array(
           
$dir.'.htaccess',
           
$dir.'sample.htaccess'
       
);

   
$htaccess_content = '# We don\'t want web users to access any file in this directory'."\r\n".
       
'Order Deny,Allow'."\r\n".
       
'Deny from All';

    foreach(
$htaccess_files as $htaccess_file )
    {
        if(
file_exists( $htaccess_file ) )
        {
// File already exists
           
continue;
        }

       
$handle = @fopen( $htaccess_file, 'w' );

        if( !
$handle )
        {
// File cannot be created
           
return false;
        }

       
fwrite( $handle, $htaccess_content );
       
fclose( $handle );
    }

    return
true;
}


/**
 * Display a button to quick upload the files by drag&drop method
 *
 * @param integer ID of FileRoot object
 */
function display_dragdrop_upload_button( $params = array() )
{
    global
$blog, $Settings, $b2evo_icons_type, $DB, $admins_can_manipulate_sensitive_files;

   
$params = array_merge( array(
           
'before'                 => '',
           
'after'                  => '',
           
'fileroot_ID'            => 0, // Root type and ID, e.g. collection_1
           
'path'                   => '', // Subpath for the file/folder
           
'listElement'            => 'null',
           
'list_element'           => NULL, //jQuery selector of list element
           
'list_style'             => 'list',  // 'list' or 'table'
           
'template'               => '<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="'.TS_('Drop files here').'">
                                                                <div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
                                                                        <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
                                                                </div>
                                                                <div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
                                                                        <span class="qq-upload-drop-area-text-selector"></span>
                                                                </div>
                                                                <div class="qq-upload-button-selector qq-upload-button">
                                                                        <div>'
.TS_('Upload a file').'</div>
                                                                </div>
                                                                <span class="qq-drop-processing-selector qq-drop-processing">
                                                                        <span>'
.TS_('Processing dropped files...').'</span>
                                                                        <span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
                                                                </span>
                                                                <ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
                                                                        <li>
                                                                                <div class="qq-progress-bar-container-selector">
                                                                                        <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
                                                                                </div>
                                                                                <span class="qq-upload-spinner-selector qq-upload-spinner"></span>
                                                                                <img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
                                                                                <span class="qq-upload-file-selector qq-upload-file"></span>
                                                                                <span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
                                                                                <input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
                                                                                <span class="qq-upload-size-selector qq-upload-size"></span>
                                                                                <button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">'
.TS_('Cancel').'</button>
                                                                                <button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">'
.TS_('Retry').'</button>
                                                                                <button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">'
.TS_('Delete').'</button>
                                                                                <span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
                                                                        </li>
                                                                </ul>

                                                                <dialog class="qq-alert-dialog-selector">
                                                                        <div class="qq-dialog-message-selector"></div>
                                                                        <div class="qq-dialog-buttons">
                                                                                <button type="button" class="qq-cancel-button-selector">'
.TS_('Close').'</button>
                                                                        </div>
                                                                </dialog>

                                                                <dialog class="qq-confirm-dialog-selector">
                                                                        <div class="qq-dialog-message-selector"></div>
                                                                        <div class="qq-dialog-buttons">
                                                                                <button type="button" class="qq-cancel-button-selector">'
.TS_('No').'</button>
                                                                                <button type="button" class="qq-ok-button-selector">'
.TS_('Yes').'</button>
                                                                        </div>
                                                                </dialog>

                                                                <dialog class="qq-prompt-dialog-selector">
                                                                        <div class="qq-dialog-message-selector"></div>
                                                                        <input type="text">
                                                                        <div class="qq-dialog-buttons">
                                                                                <button type="button" class="qq-cancel-button-selector">'
.TS_('Cancel').'</button>
                                                                                <button type="button" class="qq-ok-button-selector">'
.TS_('Ok').'</button>
                                                                        </div>
                                                                </dialog>
                                                        </div>'
,
           
'display_support_msg'    => true, // Display info under button about that current supports drag&drop
           
'additional_dropzone'    => '', // jQuery selector of additional drop zone
           
'filename_before'        => '', // Append this text before file name on success uploading of new file,
                                                                     // Used a mask $file_path$ to replace it with File->get_rdfp_rel_path()
           
'LinkOwner'              => NULL, // Use it if you want to link a file to Item/Comment right after uploading
           
'display_status_success' => true, // Display status text about successful uploading
           
'status_conflict_place'  => 'default', // Where we should write a message about conflict:
                                                                                         //    'default' - in the element ".qq-upload-status"
                                                                                         //    'before_button' - before button to solve a conflict
           
'conflict_file_format'   => 'simple', // 'simple' - file name text, 'full_path_link' - a link with text as full file path
           
'resize_frame'           => false, // Resize frame on upload new image
           
'table_headers'          => '', // Use this html text as table headers when first file is loaded
           
'noresults'              => '',
           
'fieldset_prefix'        => '', // Fieldset prefix, Use different prefix to display several fieldset on same page, e.g. for normal and internal comments
           
'table_id'               => 'attachments_fieldset_table', // ID of table with files (without 'fieldset_prefix')
       
), $params );

   
$LinkOwner = & $params['LinkOwner'];

   
$FileRootCache = & get_FileRootCache();
   
$fm_FileRoot = $FileRootCache->get_by_ID( $params['fileroot_ID'] );

    if( !
check_perm_upload_files( $LinkOwner, $fm_FileRoot ) )
    {    
// Don't display the button if current user has no permission to upload to the selected file root:
       
return;
    }

   
// Initialize quick uploader:
   
init_fileuploader_js( ( is_admin_page() ? 'rsc_url' : 'blog' ), ( $LinkOwner !== NULL /* Sortable only attached files */ ) );

   
$root_and_path = $params['fileroot_ID'].'::'.$params['path'];
   
$quick_upload_url = get_htsrv_url().'quick_upload.php'
       
.'?b2evo_icons_type='.$b2evo_icons_type
       
.( empty( $blog ) ? '' : '&blog='.$blog )
        .
'&prefix='.$params['fieldset_prefix'];

    echo
$params['before'];

    if(
$LinkOwner !== NULL && $LinkOwner->is_temp() )
    {    
// Use this field to know a form is submitted with temporary link owner(when object is creating and still doesn't exist in DB):
       
echo '<input type="hidden" name="temp_link_owner_ID_nojs" value="'.$LinkOwner->get_ID().'" />';
       
// Set correct name only when JS is enabled:
       
echo '<script>document.querySelector( "input[name=temp_link_owner_ID_nojs]" ).setAttribute( "name", "temp_link_owner_ID" )</script>';
    }

   
// Get list of allowed filetype extensions
   
if( is_logged_in( false ) )
    {
       
$condition = ( check_user_perm( 'files', 'all' ) && !empty($admins_can_manipulate_sensitive_files) ) ? '' : 'ftyp_allowed <> "admin"';
    }
    else
    {
       
$condition = 'ftyp_allowed = "any"';
    }

    if( !empty(
$condition ) )
    {
       
$condition = ' WHERE '.$condition;
    }
   
$allowed_extensions = $DB->get_col( 'SELECT ftyp_extensions FROM T_filetypes'.$condition );
   
$allowed_extensions = implode( ' ', $allowed_extensions );
   
$allowed_extensions = explode( ' ', $allowed_extensions );
   
sort( $allowed_extensions );

   
?>
   <div id="<?php echo $params['fieldset_prefix']; ?>file-uploader" style="width:100%">
        <noscript>
            <p><?php echo T_('Please enable JavaScript to use file uploader.'); ?></p>
        </noscript>
    </div>
    <input id="saveBtn" type="submit" style="display:none" name="saveBtn" value="<?php echo T_('Save modified files'); ?>" class="ActionButton" />
   
    <?php
    $dragdrop_upload_button_config
= array(
           
'fieldset_prefix'          => $params['fieldset_prefix'],
           
'draggable_button_text'    => T_('Drag & Drop files to upload here <br /><span>or click to manually select files...</span>'),
           
'draggable_note_text'      => T_('Your browser supports full upload functionality.'),
           
'nondraggable_button_text' => T_('Click to manually select files...'),
           
'nondraggable_note_text'   => T_('Your browser does not support full upload functionality: You can only upload files one by one and you cannot use Drag & Drop.'),

           
'quickupload_url' => $quick_upload_url.'&'.url_crumb( 'file' ),
           
'root_and_path'   => $root_and_path,
           
'crumb_file'      => url_crumb( 'file' ),
           
'link_owner'      => $LinkOwner !== NULL ? $LinkOwner->type.'_'.$LinkOwner->get_ID().'_'.intval( $LinkOwner->is_temp() ) : NULL,
           
'fm_mode'         => isset( $params['fm_mode'] ) ? $params['fm_mode'] : NULL,
           
'list_element'    => $params['list_element'],
           
'extra_dropzones' => empty( $params['additional_dropzone'] ) ? '[]' : $params['additional_dropzone'],
           
'list_style'      => $params['list_style'],
           
'size_limit'      => $Settings->get( 'upload_maxkb' ) * 1024,

           
'msg_type_error'         => /* TRANS: strings in {} must NOT be translated */ T_('{file} has an invalid extension. Only {extensions} are allowed.'),
           
'msg_size_error'         => /* TRANS: strings in {} must NOT be translated */ T_('{file} cannot be uploaded because it is too large ({fileSize}). The maximum allowed upload size is {sizeLimit}.'),
           
'msg_min_size_error'     => /* TRANS: strings in {} must NOT be translated */ T_('{file} is too small. The minimum file size is {minSizeLimit}.'),
           
'msg_empty_error'        => /* TRANS: strings in {} must NOT be translated */ T_('{file} is empty. Please select non-empty files.'),
           
'msg_on_leave'           => T_('Files are currently being uploaded. If you leave this page now, the upload will be cancelled.'),
           
'msg_format_progress'    => /* TRANS: strings in {} must NOT be translated */ T_('Uploading {total_size}...'),
           
'msg_upload_error'       => T_('Upload ERROR'),
           
'msg_dropped_connection' => T_('Server dropped the connection.'),
           
'msg_upload_ok'          => T_('Upload OK'),
           
'msg_upload_conflict'    => T_('Upload Conflict'),
           
'msg_replace_file'       => T_('Use this new file to replace the old file'),
           
'msg_revert'             => T_('Revert'),
           
'msg_old_file'           => T_('(Old File)'),

           
'size_symbol_kb' => /* TRANS: Abbr. for Kilobytes */ T_('KB'),
           
'size_symbol_mb' =>  /* TRANS: Abbr. for Megabytes */ T_('MB'),
           
'size_symbol_gb' =>  /* TRANS: Abbr. for Gigabytes */ T_('GB'),
           
'size_symbol_tb' =>  /* TRANS: Abbr. for Terabytes */ T_('TB'),
           
'size_symbol_pb' =>  /* TRANS: Abbr. for Petabytes */ T_('PB'),
           
'size_symbol_eb' =>  /* TRANS: Abbr. for Exabytes  */ T_('EB'),

           
'validation_size_limit'   => min( array( return_bytes( ini_get('post_max_size') ), return_bytes( ini_get('upload_max_filesize') ), $Settings->get( 'upload_maxkb') * 1024 ) ),
           
'allowed_extensions'      => $allowed_extensions,
           
'table_headers'           => format_to_js( $params['table_headers'] ),
           
'table_id'                => $params['table_id'],
           
'resize_frame'            => $params['resize_frame'] ? true : false,
           
'warning_icon'            => get_icon( 'warning_yellow' ),
           
'filename_before'         => str_replace( "'", "\'", $params['filename_before'] ),
           
'display_status_success'  => $params['display_status_success'] ? true : false,
           
'display_support_message' => $params['display_support_msg'] ? true : false,
           
'status_conflict_place'   => $params['status_conflict_place'],
           
'button_class'            => button_class( 'text' ),
           
'no_results'              => preg_replace( "/\s+/", " ", $params['noresults'] ),
           
'fileroot_ID'             => $params['fileroot_ID'],
           
'path'                    => $params['path'],
           
'conflict_file_format'    => $params['conflict_file_format'],
           
'crumb_conflictfiles'     => get_crumb( 'conflictfiles' ),
        );
   
    if(
is_ajax_request() )
    {
       
?>
       <script>
        jQuery( document ).ready( function() {
            if( typeof( window.evo_init_dragdrop_button_config ) == 'undefined' )
            {
                window.evo_init_dragdrop_button_config = {};
            }
            window.evo_init_dragdrop_button_config['fieldset_<?php echo $params['fieldset_prefix'];?>'] = <?php echo evo_json_encode( $dragdrop_upload_button_config );?>;
            window.init_uploader( evo_init_dragdrop_button_config['fieldset_<?php echo $params['fieldset_prefix'];?>'] );
        } );
        </script>
        <?php
   
}
    else
    {
       
expose_var_to_js( 'fieldset_'.$params['fieldset_prefix'], $dragdrop_upload_button_config, 'evo_init_dragdrop_button_config' );
    }
   
?>

    <script type="text/template" id="<?php echo $params['fieldset_prefix']; ?>qq-template">
    <?php echo $params['template'];?>
   </script>

    <?php

   
echo $params['after'];
}


/**
 * Replace the old file with the new one
 *
 * @param string Root type: 'user', 'group' or 'collection'
 * @param integer ID of the user, the group or the collection the file belongs to...
 * @param string Subpath for this file/folder, relative the associated root, including trailing slash (if directory)
 * @param string Name of NEW file
 * @param string Name of OLD file
 * @param boolean TRUE to display message
 * @return boolean|string TRUE on success, otherwise an error message
 */
function replace_old_file_with_new( $root_type, $root_in_type_ID, $path, $new_name, $old_name, $display_message = true )
{
   
$error_message = '';

    if( empty(
$new_name ) )
    {
       
$error_message = T_( 'The new file name is empty!' );
    }
    elseif( empty(
$old_name ) )
    {
       
$error_message = T_( 'The old file name is empty!' );
    }

    if( empty(
$error_message ) )
    {
       
$FileCache = & get_FileCache();
       
$newFile = & $FileCache->get_by_root_and_path( $root_type, $root_in_type_ID, trailing_slash( $path ).$new_name, true );
       
$oldFile = & $FileCache->get_by_root_and_path( $root_type, $root_in_type_ID, trailing_slash( $path ).$old_name, true );
       
$new_filename = $newFile->get_name();
       
$old_filename = $oldFile->get_name();
       
$dir = $newFile->get_dir();
       
$oldFile->rm_cache();
       
$newFile->rm_cache();

       
// rename new uploaded file to temp file name
       
$index = 0;
       
$temp_filename = 'temp'.$index.'-'.$new_filename;
        while(
file_exists( $dir.$temp_filename ) )
        {
// find an unused filename
           
$index++;
           
$temp_filename = 'temp'.$index.'-'.$new_filename;
        }
    }

   
// @rename will overwrite a file with the same name if exists. In this case it shouldn't be a problem.
   
if( empty( $error_message ) && ( ! @rename( $newFile->get_full_path(), $dir.$temp_filename ) ) )
    {
// rename new file to temp file name failed
       
$error_message = sprintf( T_( 'The new file could not be renamed to %s' ), $temp_filename );
    }

    if( empty(
$error_message ) && ( ! @rename( $oldFile->get_full_path(), $dir.$new_filename ) ) )
    {
// rename original file to the new file name failed
       
$error_message = sprintf( T_( 'The original file could not be renamed to %s. The new file is now named %s.' ), $new_filename, $temp_filename );
    }

    if( empty(
$error_message ) && ( ! @rename( $dir.$temp_filename, $dir.$old_filename ) ) )
    {
// rename new file to the original file name failed
       
$error_message = sprintf( T_( 'The new file could not be renamed to %s. It is now named %s.' ), $old_filename, $temp_filename );
    }

    if(
$display_message )
    {
        global
$Messages;
        if( empty(
$error_message ) )
        {
           
$Messages->add( sprintf( T_( '%s has been replaced with the new version!' ), $old_filename ), 'success' );
        }
        else
        {
           
$Messages->add( $error_message, 'error' );
        }
    }

    return empty(
$error_message ) ? true : $error_message;
}


/**
 * Check if directory is empty
 *
 * @param string Directory path
 * @param boolean TRUE - to decide when dir is not empty if at least one file exists in subdirectories,
 *                FALSE - to decide - if dir contains even only empty subdirectories
 * @return boolean TRUE if directory is empty
 */
function is_empty_directory( $dir, $recursive = true )
{
   
$result = true;

    if( empty(
$dir ) || ! file_exists( $dir ) || ! is_readable( $dir ) )
    {
// Return TRUE if dir doesn't exist or it is not readbale
       
return $result;
    }

   
// Fix dir path if slash is missed at the end
   
$dir = rtrim( $dir, '/' ).'/';

   
$handle = opendir( $dir );
    while( (
$file = readdir( $handle ) ) !== false )
    {
        if(
$file != '.' && $file != '..' )
        {
// Check what is it, dir or file?
           
if( $recursive && is_dir( $dir.$file ) )
            {
// It is a directory - try to find the files inside
               
$result = is_empty_directory( $dir.$file, $recursive );
                if(
$result === false )
                {
// A file was found inside the directory
                   
break;
                }
            }
            else
            {
// It is a file then directory is not empty
               
$result = false;
                break;
// Stop here
           
}
        }
    }
   
closedir( $handle );

    return
$result;
}


/**
 * Open temporary file
 *
 * @param string Name of the temporary file (changed by reference)
 * @return resource|string File handle OR Error text
 */
function open_temp_file( & $temp_file_name )
{
   
$temp_handle = tmpfile();

    if(
$temp_handle === false )
    {
// Error on create a temp file on system temp dir
       
global $media_path;

       
$temp_path = $media_path.'upload-tmp/';

       
$temp_folder_exists = file_exists( $temp_path );
        if( !
$temp_folder_exists )
        {
// Temp folder doesn't exist yet
            // Try to create it
           
$temp_folder_exists = @mkdir( $temp_path, 0755 );
            if(
$temp_folder_exists )
            {
// If temp folder has been created try to create .htaccess to prevent listing
               
if( $htaccess_handle = @fopen( $temp_path.'.htaccess', 'w+' ) )
                {
                   
fwrite( $htaccess_handle, 'deny from all' );
                   
fclose( $htaccess_handle );
                }
            }
        }

        if(
$temp_folder_exists )
        {
// Try to create a temp file on media temp path
           
$temp_file_name = tempnam( $temp_path, 'qck_' );
        }

        if( !
$temp_folder_exists || $temp_file_name === false )
        {
// Error on create a temp file on media temp path
           
return sprintf( T_( 'Unable to create temporary upload file. PHP needs write permissions on %s or %s.' ),
               
'<b>'.sys_get_temp_dir().'</b>',
               
'<b>'.$temp_path.'</b>' );
        }

       
// Create a file handle for new temp file on media path
       
$temp_handle = fopen( $temp_file_name, 'r+' );
    }

   
// File handle
   
return $temp_handle;
}


/**
 * Initialize JavaScript for AJAX loading of popup window to edit file properties
 *
 * @param array Params
 */
function echo_file_properties()
{
    global
$admin_url;

   
// Initialize JavaScript to build and open window:
   
echo_modalwindow_js();
?>
<script>
    //<![CDATA[
    // Window to edit file
    function file_properties( root, path, file, link_owner_type, link_owner_ID, from )
    {
        openModalWindow( '<span class="loader_img loader_file_edit absolute_center" title="<?php echo T_('Loading...'); ?>"></span>',
            '80%', '', true,
            '<?php echo TS_('File properties'); ?>',
            '<?php echo TS_('Save Changes!'); ?>', true, true );
        jQuery.ajax(
        {
            type: 'POST',
            url: '<?php echo $admin_url; ?>',
            data:
            {
                'ctrl': 'files',
                'action': 'edit_properties',
                'root': root,
                'path': path,
                'fm_selected': [ file ],
                'mode': 'modal',
                'link_owner_type': typeof( link_owner_type ) == 'undefined' ? '' : link_owner_type,
                'link_owner_ID': typeof( link_owner_ID ) == 'undefined' ? '' : link_owner_ID,
                'from': typeof( from ) == 'undefined' ? '' : from,
                'crumb_file': '<?php echo get_crumb( 'file' ); ?>',
            },
            success: function( result )
            {
                openModalWindow( result, '80%', '',true,
                    '<?php echo TS_('File properties'); ?>',
                    '<?php echo TS_('Save Changes!'); ?>', false, true );
            }
        } );

        if( typeof( link_owner_type ) != 'undefined' &&
            typeof( link_owner_ID ) != 'undefined' &&
            ( typeof( evo_form_file_properties__is_initialized ) == 'undefined' || evo_form_file_properties__is_initialized !== true ) )
        {    // If we edit file properties from links/attachments table we should submit the form by AJAX:
            evo_form_file_properties__is_initialized = true; // Use this flag in order to don't init the form submitting twice:
            jQuery( document ).on( 'submit', 'form#fm_properties_checkchanges', function( e )
            {
                var form = jQuery( this );
                e.preventDefault();
                // Close modal window:
                closeModalWindow();
                // Submit form by AJAX request:
                jQuery.ajax(
                {
                    type: 'POST',
                    url: jQuery( this ).attr( 'action' ),
                    data: jQuery( this ).serialize() + '&mode=link',
                    success: function()
                    {    // On success updating,
                        // Refresh links/attachments table:
                        evo_link_refresh_list( form.find( '[name=link_owner_type]' ).val(), form.find( '[name=link_owner_ID]' ).val() );
                    },
                    error: function( jqXHR )
                    {    // On failed updating,
                        // Display error:
                        alert( jqXHR.responseText.replace( /<.+?>/g, "\n" ) );
                    }
                } );
            } );
        }
        return false;
    }
    //]]>
</script>
<?php
}


/**
 * Get root and relative file path by absolute path
 *
 * @param string Absolute path
 * @param boolean TRUE - to extract data from cache path like 'blogs/home/_evocache/image.jpg/fit-80x80.jpg'
 * @return boolean|array FALSE - if root and path are not detected, Array with keys 'root' and 'path'
 *
 * Examples:
 * get_root_path_by_abspath( 'shared/global/sunset/sunset.jpg' ) => array( root => 'shared_0', path => 'sunset/sunset.jpg' )
 * get_root_path_by_abspath( 'users/admin/admin.jpg' ) => array( root => 'user_1', path => 'admin.jpg' )
 * get_root_path_by_abspath( 'blogs/home/backgrounds/background1.jpg' ) => array( root => 'collection_1', path => 'backgrounds/background1.jpg' )
 * get_root_path_by_abspath( 'skins/bootstrap_main_skin/skinshot.png' ) => array( root => 'skins_0', path => 'bootstrap_main_skin/skinshot.png' )
 * - for cache paths (used to restored missing cached images which are loaded with old url):
 * get_root_path_by_abspath( 'shared/global/sunset/_evocache/sunset.jpg/fit-80x80.jpg?mtime=1486119491', true ) => array( root => 'shared_0', path => 'sunset/sunset.jpg' )
 * get_root_path_by_abspath( 'users/admin/_evocache/admin.jpg/crop-top-320x320.jpg?mtime=1486119491', true ) => array( root => 'user_1', path => 'admin.jpg' )
 * get_root_path_by_abspath( 'blogs/home/_evocache/image.jpg/fit-80x80.jpg?mtime=1486969646', true ) => array( root => 'collection_1', path => 'image.jpg' )
 * get_root_path_by_abspath( 'skins/bootstrap_main_skin/_evocache/skinshot.png/fit-80x80.png?mtime=1486969005', true ) => array( root => 'skins_0', path => 'bootstrap_main_skin/skinshot.png' )
 *
 */
function get_root_path_by_abspath( $abspath, $is_cache_path = false )
{
    if( empty(
$abspath ) )
    {    
// If absolute path is empty do NOT try to decode it:
       
return false;
    }

   
load_class( 'files/model/_fileroot.class.php', 'FileRoot' );

   
$abspath = preg_split( '#[/\\\\]#', $abspath );

    switch(
$abspath[0] )
    {
        case
'users':
           
// User media dir:
           
$UserCache = & get_UserCache();
            if(
$file_User = & $UserCache->get_by_login( $abspath[1] ) )
            {    
// User is found by login:
               
$root = FileRoot::gen_ID( 'user', $file_User->ID );
               
$start_relpath = 2;
            }
            break;

        case
'blogs':
           
// Collection media dir:
           
$BlogCache = & get_BlogCache();
            if(
$file_Blog = & $BlogCache->get_by_urlname( $abspath[1], false ) )
            {    
// Blog is found by urlname:
               
$root = FileRoot::gen_ID( 'collection', $file_Blog->ID );
               
$start_relpath = 2;
            }
            break;

        case
'skins':
           
// Skins dir:
           
$root = FileRoot::gen_ID( 'skins', 0 );
           
$start_relpath = 1;
            break;

        case
'shared':
           
// Shared media dir:
           
$root = FileRoot::gen_ID( 'shared', 0 );
           
$start_relpath = 2;
            break;

        case
'import':
           
// Import media dir:
           
$root = FileRoot::gen_ID( 'import', 0 );
           
$start_relpath = 1;
            break;

        case
'emailcampaign':
           
// Email campaign media dir:
           
$EmailCampaignCache = & get_EmailCampaignCache();
            if(
$file_EmailCampaign = & $EmailCampaignCache->get_by_ID( $abspath[1], false ) )
            {    
// Email campaign is found by ID:
               
$root = FileRoot::gen_ID( 'emailcampaign', $file_EmailCampaign->ID );
               
$start_relpath = 1;
            }
            break;
    }

    if( ! empty(
$root ) )
    {    
// Get relative path only if root is detected:
       
$relpath = '';
       
$abspath_length = count( $abspath );
       
// For cache path like 'blogs/home/_evocache/image.jpg/fit-80x80.jpg' exclude the last because it is a not path part and just a size info:
       
$last_path_index = ( $is_cache_path ? $abspath_length - 1 : $abspath_length );

        for(
$f = $start_relpath; $f < $last_path_index; $f++ )
        {
            if(
$is_cache_path && $f == $abspath_length - 3 )
            {    
// Skip this because it is a evocache folder in the abspath like 'blogs/home/_evocache/image.jpg/fit-80x80.jpg':
               
continue;
            }
           
$relpath .= $abspath[ $f ].( $f < $last_path_index - 1 ? DIRECTORY_SEPARATOR : '' );
        }

        if( ! empty(
$relpath ) )
        {    
// Return data only if they both are detected:
           
return array(
                   
'root' => $root,
                   
'path' => $relpath,
                );
        }
    }

   
// Data are not found correctly:
   
return false;
}


/**
 * Get a File object or create one given an absolute path
 *
 * @param string Absolute path of file
 * @param boolean create meta data in DB if it doesn't exist yet? (generates a $File->ID)
 * @return mixed File a {@link File} object OR NULL if file does not exist
 */
function & get_file_by_abspath( $abspath, $force_create_meta = false )
{
   
$root_path = get_root_path_by_abspath( $abspath );
    if(
$root_path )
    {
       
$FileRootCache = & get_FileRootCache();
        if(
$FileRoot = $FileRootCache->get_by_ID( $root_path['root'] ) )
        {
           
$FileCache = & get_FileCache();
            if(
$File = & $FileCache->get_by_root_and_path( $FileRoot->type, $FileRoot->in_type_ID, $root_path['path'] ) && $File->exists() )
            {
               
$File->load_meta( $force_create_meta );
                return
$File;
            }
        }
    }

   
$r = NULL;
    return
$r;
}


/**
 * Get image file for use in social media tag based on $disp
 *
 * @param string $disp
 * @param boolean Force to look through attachments
 * @return obj File object
 */
function get_social_tag_image_file( $disp )
{
    global
$Settings;

   
$social_tag_image_File = NULL;

    switch(
$disp )
    {
        case
'single':
        case
'page':
            global
$MainList;

           
$Item = & $MainList->get_by_idx( 0 );
           
// Get info for og:image tag
           
if( ! is_null( $Item ) )
            {
               
$social_tag_image_File = get_social_media_image( $Item );
            }
            break;

        case
'posts':
           
$intro_Item = & get_featured_Item( $disp, NULL, true );
            if(
$intro_Item && $intro_Item->is_intro() )
            {
               
$social_tag_image_File = get_social_media_image( $intro_Item, array(
                       
'use_item_cat_fallback' => false,
                       
'use_coll_fallback' => false,
                       
'use_site_fallback' => false ) );
            }

            if( empty(
$social_tag_image_File ) )
            {
                global
$disp_detail;

                if(
in_array( $disp_detail, array( 'posts-cat', 'posts-topcat-nointro', 'posts-subcat-nointro' ) ) )
                {
// No intro post or intro post has no image, use current category's social media boiler plate or category image as fallback:
                   
global $MainList;
                   
// Get current category ID:
                   
$cat_ID = $MainList->filters['cat_single'];
                   
$ChapterCache = & get_ChapterCache();
                   
$FileCache = & get_FileCache();
                    if(
$cat_ID && $current_Chapter = & $ChapterCache->get_by_ID( $cat_ID ) )
                    {
// Try social media boilerplate image first:
                       
$social_media_image_file_ID = $current_Chapter->get( 'social_media_image_file_ID', false );
                        if(
$social_media_image_file_ID > 0 && $File = & $FileCache->get_by_ID( $social_media_image_file_ID  ) && $File->is_image() )
                        {
                           
$social_tag_image_File = $File;
                        }
                        else
                        {
// Try category image:
                           
$cat_image_file_ID = $current_Chapter->get( 'image_file_ID', false );
                            if(
$cat_image_file_ID > 0 && $File = & $FileCache->get_by_ID( $cat_image_file_ID ) && $File->is_image() )
                            {
                               
$social_tag_image_File = $File;
                            }
                        }
                    }
                }
            }

            if( empty(
$social_tag_image_File ) )
            {
// Use site social media boiler plate or logo:
               
$social_tag_image_File = get_social_media_image();
            }

            break;

        default:
           
// Other disps
   
}

    return
$social_tag_image_File;
}


/**
 * Check if current user has an access to upload new files
 *
 * @param object Link Owner
 * @param object File Root
 * @param boolean TRUE to assert if user dosn't have the required permission
 * @return boolean
 */
function check_perm_upload_files( $LinkOwner, $FileRoot, $assert = false )
{
    if( empty(
$LinkOwner ) )
    {    
// Check perm when we upload new files by file manager without linking to any object:
       
if( ! is_logged_in() && $assert )
        {    
// Halt the denied access:
           
debug_die( 'You have no permission to upload new files!' );
        }
        return
check_user_perm( 'files', 'add', $assert, $FileRoot );
    }
    else
    {    
// Check perm when we upload new files for the object like Item, Comment, Message or EmailCampaign:
       
return $LinkOwner->check_perm( 'add', $assert, $FileRoot );
    }
}


/**
 * Check if folder contains at least one file with requested extension
 *
 * @param string Folder path
 * @param string File extensions, separated by |
 * @return boolean
 */
function check_folder_with_extensions( $folder_path, $extensions )
{
   
$folder_files = get_filenames( $folder_path );

    foreach(
$folder_files as $folder_file )
    {
        if(
preg_match( '/\.('.$extensions.')$/i', $folder_file ) )
        {    
// First file with requested extension is found:
           
return true;
        }
    }

   
// Folder has no file with requested extension
   
return false;
}


/**
 * Callback function to sort thumbnail sizes array by width and height
 */
function sort_thumbnail_sizes_callback( $a, $b )
{
    if(
$a[1] == $b[1] )
    {
        return
$a[2] < $b[2] ? -1 : 1;
    }

    return (
$a[1] < $b[1] ? -1 : 1 );
}


/**
 * Helper function to display file last modification date in table cell
 *
 * @param object File
 * @return string
 */
function file_td_lastmod( & $File )
{
    global
$UserSettings;

    if(
$UserSettings->get( 'fm_showdate' ) == 'long' )
    {    
// Full format:
       
return '<span class="date">'.$File->get_lastmod_formatted( 'date' ).'</span> '
           
.'<span class="time">'.$File->get_lastmod_formatted( 'time' ).'</span>';
    }
    else
    {    
// Compact format:
       
return $File->get_lastmod_formatted( 'compact' );
    }
}


/**
 * Helper function to display file name in table cell
 *
 * @param object File
 * @return string
 */
function file_td_name( & $File )
{
    global
$UserSettings;

   
$r = '';

   
// Filename:
   
if( $File->is_dir() )
    {
// Directory
        // Link to open the directory in the current window
       
$r .= '<a href="'.$File->get_view_url().'">'.$File->dget( 'name' ).'</a>';
    }
    else
    {    
// File
       
if( $view_link = $File->get_view_link( $File->get_name(), NULL, NULL ) )
        {
           
$r .= '<span class="fname">'.$view_link.'</span>';
        }
        else
        {    
// File extension unrecognized
           
$r .= $File->dget( 'name' );
        }
    }

   
// File meta data:
   
$r .= '<span class="filemeta">';
   
// Optionally display IMAGE pixel size:
   
if( $UserSettings->get( 'fm_getimagesizes' ) &&
        (
$file_image_size = $File->get_image_size( 'widthxheight' ) ) !== false )
    {
       
$r .= ' ('.$file_image_size.')';
    }
   
// Optionally display meta data title:
   
if( $File->meta == 'loaded' )
    {    
// We have loaded meta data for this file:
       
$r .= ' - '.$File->title;
    }
   
$r .= '</span>';

    return
$r;
}


/**
 * Helper function to display file actions in table cell
 *
 * @param object File
 * @return string
 */
function file_td_actions( & $File, $except = array() )
{
    global
$admin_url;

    if( !
is_logged_in() ||
            ! (
$FileRoot = & $File->get_FileRoot() ) ||
            !
check_user_perm( 'files', 'edit_allowed', false, $FileRoot ) )
    {    
// User cannot edit files in the File Root:
       
return '';
    }

   
$action_url = $admin_url.'?ctrl=files&amp;root='.$FileRoot->ID
       
.'&amp;path='.rawurlencode( $File->get_dir_rel_path() )
        .
'&amp;fm_selected[]='.rawurlencode( $File->get_rdfp_rel_path() )
        .
'&amp;';
   
$action_crumb_url = $action_url.url_crumb( 'file' ).'&amp;';

   
$r = '';
    if(
$File->is_editable( check_user_perm( 'files', 'all', false ) ) )
    {
       
$r .= action_icon( T_('Edit file...'), 'edit', $action_crumb_url.'action=edit_file' );
    }
    else
    {
       
$r .= get_icon( 'edit', 'noimg' );
    }

    if(
$File->can_be_manipulated() )
    {
        if( !
in_array( 'edit', $except ) )
        {
           
$r .= action_icon( T_('Edit properties...'), 'properties', $action_crumb_url.'action=edit_properties', NULL, NULL, NULL,
                array(
'onclick' => 'return file_properties( \''.$FileRoot->ID.'\', \''.$File->get_dir_rel_path().'\', \''.$File->get_rdfp_rel_path().'\' )' ) );
        }
        if( !
in_array( 'move', $except ) )
        {
           
$r .= action_icon( T_('Move'), 'file_move', $action_url.'action=file_move&amp;fm_sources_root='.$FileRoot->ID );
        }
        if( !
in_array( 'copy', $except ) )
        {
           
$r .= action_icon( T_('Copy'), 'file_copy', $action_url.'action=file_copy&amp;fm_sources_root='.$FileRoot->ID );
        }
        if( !
in_array( 'delete', $except ) )
        {
           
$r .= action_icon( T_('Delete'), 'file_delete', $action_crumb_url.'action=delete' );
        }
    }

    return
$r;
}


/**
 * Move all files and sub-folders of single top level folder to top/root level of the folder
 *
 * @param string Top/root folder path
 * @return boolean
 */
function move_single_dir_to_top_level( $root_folder_path )
{
   
$root_folder_path = rtrim( $root_folder_path, '/' );

   
$folders_num = 1;
   
$files_num = 0;
   
$check_folder_path = $root_folder_path;
   
$top_empty_folder_path = NULL;
    while(
$folders_num == 1 && $files_num == 0 )
    {
        if( ! (
$dir_handle = @opendir( $check_folder_path ) ) )
        {    
// Unable to open dir:
           
break;
        }

       
$folders_num = 0;
       
$files_num = 0;
        while(
$file = readdir( $dir_handle ) )
        {
            if(
$file == '.' || $file == '..' )
            {    
// Skip reserved folders:
               
continue;
            }

            if(
is_dir( $check_folder_path.'/'.$file ) )
            {    
// Count folders:
               
$folders_num++;
               
$single_folder_name = $file;
            }
            else
            {    
// Count files:
               
$files_num++;
            }

            if(
$files_num == 2 )
            {    
// Stop if the folder already contains more 1 files or folder:
               
break;
            }
        }

       
// Close the folder handler:
       
closedir( $dir_handle );

        if(
$folders_num == 1 && $files_num == 0 )
        {    
// The current folder has only single folder, go to search next level down:
           
$check_folder_path = $check_folder_path.'/'.$single_folder_name;
            if(
$top_empty_folder_path === NULL )
            {    
// Store top empty folder in order to clear this below:
               
$top_empty_folder_path = $check_folder_path;
            }
        }
    }

    if(
$check_folder_path != $root_folder_path )
    {    
// Move contens of single root folder to top/root folder:
       
$result = move_files_r( $check_folder_path, $root_folder_path );
        if(
$top_empty_folder_path !== NULL && is_empty_directory( $top_empty_folder_path ) )
        {    
// Remove the single empty folder:
           
rmdir_r( $top_empty_folder_path );
        }
    }
    else
    {    
// No need to move because root folder has not only single folder:
       
$result = true;
    }

    return
$result;
}
?>