<?php
if(!defined('ABSPATH')){
    exit;//Exit if accessed directly
}

class AIOWPSecurity_Utility_File
{
    
    /* This variable will be an array which will contain all of the files and/or directories we wish to check permissions for */
    public $files_and_dirs_to_check;
    
    function __construct(){
         /* Let's initiliaze our class variable array with all of the files and/or directories we wish to check permissions for.
         * NOTE: we can add to this list in future if we wish
         */

        //Get wp-config.php file path
        if ( !function_exists( 'get_home_path' ) ) require_once( ABSPATH. '/wp-admin/includes/file.php' );
        $wp_config_path = AIOWPSecurity_Utility_File::get_wp_config_file_path();
        $home_path = get_home_path();
        
        $this->files_and_dirs_to_check = array(
            array('name'=>'root directory','path'=>ABSPATH,'permissions'=>'0755'),
            array('name'=>'wp-includes/','path'=>ABSPATH."wp-includes",'permissions'=>'0755'),
            array('name'=>'.htaccess','path'=>$home_path.".htaccess",'permissions'=>'0644'),
            array('name'=>'wp-admin/index.php','path'=>ABSPATH."wp-admin/index.php",'permissions'=>'0644'),
            array('name'=>'wp-admin/js/','path'=>ABSPATH."wp-admin/js/",'permissions'=>'0755'),
            array('name'=>'wp-content/themes/','path'=>ABSPATH."wp-content/themes",'permissions'=>'0755'),
            array('name'=>'wp-content/plugins/','path'=>ABSPATH."wp-content/plugins",'permissions'=>'0755'),
            array('name'=>'wp-admin/','path'=>ABSPATH."wp-admin",'permissions'=>'0755'),
            array('name'=>'wp-content/','path'=>ABSPATH."wp-content",'permissions'=>'0755'),
            array('name'=>'wp-config.php','path'=>$wp_config_path,'permissions'=>'0640')
            //Add as many files or dirs as needed by following the convention above
        );

    }
    
    static function get_wp_config_file_path()
    {
        $wp_config_file = ABSPATH . 'wp-config.php';
        if(file_exists($wp_config_file)){
            return $wp_config_file;
        }
        else if (file_exists(dirname( ABSPATH ) . '/wp-config.php')){       
            return dirname( ABSPATH ) . '/wp-config.php';
        }
        return $wp_config_file;
    }
    
    static function write_content_to_file($file_path, $new_contents)
    {
        @chmod($file_path, 0777);
        if (is_writeable($file_path))
        {
            $handle = fopen($file_path, 'w');
            foreach( $new_contents as $line ) {
                fwrite($handle, $line);
            }
            fclose($handle);
            @chmod($file_path, 0644); //Let's change the file back to a secure permission setting
            return true;
	} else {
            return false;
	}
    }
    
    static function backup_a_file($src_file_path, $suffix = 'backup')
    {
        $backup_file_path = $src_file_path . '.' . $suffix;
        if (!copy($src_file_path, $backup_file_path)) {
            //Failed to make a backup copy
            return false;
        }
        return true;
    }

    static function backup_and_rename_wp_config($src_file_path, $prefix = 'backup')
    {
        global $aio_wp_security;
        
        //Check to see if the main "backups" directory exists - create it otherwise
        $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
        if (!AIOWPSecurity_Utility_File::create_dir($aiowps_backup_dir))
        {
            $aio_wp_security->debug_logger->log_debug("backup_and_rename_wp_config - Creation of backup directory failed!",4);
            return false;
        }
        
        $src_parts = pathinfo($src_file_path);
        $backup_file_name = $prefix . '.' . $src_parts['basename'];
        
        $backup_file_path = $aiowps_backup_dir . '/' . $backup_file_name;
        if (!copy($src_file_path, $backup_file_path)) {
            //Failed to make a backup copy
            return false;
        }
        return true;
    }
    
    static function backup_and_rename_htaccess($src_file_path, $suffix = 'backup')
    {
        global $aio_wp_security;
        
        //Check to see if the main "backups" directory exists - create it otherwise
        $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
        if (!AIOWPSecurity_Utility_File::create_dir($aiowps_backup_dir))
        {
            $aio_wp_security->debug_logger->log_debug("backup_and_rename_htaccess - Creation of backup directory failed!",4);
            return false;
        }
        
        $src_parts = pathinfo($src_file_path);
        $backup_file_name = $src_parts['basename'] . '.' . $suffix;
        
        $backup_file_path = $aiowps_backup_dir . '/' . $backup_file_name;
        if (!copy($src_file_path, $backup_file_path)) {
            //Failed to make a backup copy
            return false;
        }
        return true;
    }

    //Function which reads entire contents of a file and stores serialized contents into our global_meta table
    static function backup_file_contents_to_db($src_file_path, $key_description)
    {
        global $wpdb, $aio_wp_security;
        $file_contents = AIOWPSecurity_Utility_File::get_file_contents($src_file_path);
        
        $payload = serialize($file_contents);
        $date_time = current_time( 'mysql' );
        $data = array('date_time' => $date_time, 'meta_key1' => $key_description, 'meta_value2' => $payload);

        //First check if a backup entry already exists in the global_meta table
        $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
        $resultset = $wpdb->get_row("SELECT * FROM $aiowps_global_meta_tbl_name WHERE meta_key1 = '$key_description'", OBJECT);
        if($resultset){
            $where = array('meta_key1' => $key_description);
            $res = $wpdb->update($aiowps_global_meta_tbl_name, $data, $where);
        }else{
            $res = $wpdb->insert($aiowps_global_meta_tbl_name, $data);
        }

        if($res === false)
        {
            $aio_wp_security->debug_logger->log_debug("AIOWPSecurity_Utility_File::backup_file_contents_to_db() - Unable to write entry to DB",4);
        }
        return;
    }
    
    
    static function recursive_file_search($pattern='*', $flags = 0, $path='')
    {
        $paths=glob($path.'*', GLOB_MARK|GLOB_ONLYDIR|GLOB_NOSORT);
        if ($paths === FALSE){
            return FALSE;
        }
        $files=glob($path.$pattern, $flags);
        if ($files === FALSE){
            return FALSE;
        }
        foreach ($paths as $path) { $files=array_merge($files,AIOWPSecurity_Utility_File::recursive_file_search($pattern, $flags, $path)); }
        return $files;
    }
    
    /*
     * Useful when wanting to echo file contents to screen with <br /> tags
     */
    static function get_file_contents_with_br($src_file)
    {
        $file_contents = file_get_contents($src_file);        
        return nl2br($file_contents);
    }

    /*
     * Useful when wanting to echo file contents inside textarea
     */
    static function get_file_contents($src_file)
    {
        $file_contents = file_get_contents($src_file);        
        return $file_contents;
    }
    
    /*
     * Returns the file's permission value eg, "0755"
     */
    static function get_file_permission($filepath)
    {
        if (!function_exists('fileperms')) 
        {
            $perms = '-1';
        }
        else 
        {
            clearstatcache();
            $perms = substr(sprintf("%o", @fileperms($filepath)), -4);
        }
        return $perms;
    }

    /*
     * Checks if a write operation is possible for the file in question
     */
    static function is_file_writable($filepath)
    {
        $test_string = ""; //We will attempt to append an empty string at the end of the file for the test
        $write_result = @file_put_contents($filepath, $test_string, FILE_APPEND | LOCK_EX);
        if ($write_result === false)
        {
            return false;
        } 
        else
        {
            return true;
        }
    }

    static function download_a_file_option1($file_path, $file_name = '')
    {
        $file = $file_path;//Full ABS path to the file
        if(empty($file_name)){$file_name = basename($file);}

        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename='.$file_name);
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file));
        //ob_clean();
        //flush();
        readfile($file);
        exit;
    }
    
    static function download_content_to_a_file($output, $file_name = '')
    {
        if(empty($file_name)){$file_name = "aiowps_" . date("Y-m-d_H-i", time()).".txt";}

        header("Content-Encoding: UTF-8");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Content-Description: File Transfer");
        header("Content-type: application/octet-stream");
        header("Content-disposition: attachment; filename=" . $file_name);
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: " . strlen($output));
        echo $output;
        exit;
    }
    
    /*
     * This function will compare the current permission value for a file or dir with the recommended value.
     * It will compare the individual "execute", "write" and "read" bits for the "public", "group" and "owner" permissions.
     * If the permissions for an actual bit value are greater than the recommended value it returns '0' (=less secure)
     * Otherwise it returns '1' which means it is secure
     * Accepts permission value parameters in octal, ie, "0777" or "777"
     */
    static function is_file_permission_secure($recommended, $actual)
    {
        $result = 1; //initialize return result

        //Check "public" permissions
        $public_value_actual = substr($actual,-1,1); //get dec value for actual public permission
        $public_value_rec = substr($recommended,-1,1); //get dec value for recommended public permission

        $pva_bin = sprintf('%04b', $public_value_actual); //Convert value to binary
        $pvr_bin = sprintf('%04b', $public_value_rec); //Convert value to binary
        //Compare the "executable" bit values for the public actual versus the recommended
        if (substr($pva_bin,-1,1)<=substr($pvr_bin,-1,1))
        {
            //The "execute" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "execute" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }
        
        //Compare the "write" bit values for the public actual versus the recommended
        if (substr($pva_bin,-2,1)<=substr($pvr_bin,-2,1))
        {
            //The "write" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "write" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Compare the "read" bit values for the public actual versus the recommended
        if (substr($pva_bin,-3,1)<=substr($pvr_bin,-3,1))
        {
            //The "read" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "read" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Check "group" permissions
        $group_value_actual = substr($actual,-2,1);
        $group_value_rec = substr($recommended,-2,1);
        $gva_bin = sprintf('%04b', $group_value_actual); //Convert value to binary
        $gvr_bin = sprintf('%04b', $group_value_rec); //Convert value to binary

        //Compare the "executable" bit values for the group actual versus the recommended
        if (substr($gva_bin,-1,1)<=substr($gvr_bin,-1,1))
        {
            //The "execute" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "execute" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Compare the "write" bit values for the public actual versus the recommended
        if (substr($gva_bin,-2,1)<=substr($gvr_bin,-2,1))
        {
            //The "write" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "write" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Compare the "read" bit values for the public actual versus the recommended
        if (substr($gva_bin,-3,1)<=substr($gvr_bin,-3,1))
        {
            //The "read" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "read" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }
        
        //Check "owner" permissions
        $owner_value_actual = substr($actual,-3,1);
        $owner_value_rec = substr($recommended,-3,1);
        $ova_bin = sprintf('%04b', $owner_value_actual); //Convert value to binary
        $ovr_bin = sprintf('%04b', $owner_value_rec); //Convert value to binary

        //Compare the "executable" bit values for the group actual versus the recommended
        if (substr($ova_bin,-1,1)<=substr($ovr_bin,-1,1))
        {
            //The "execute" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "execute" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Compare the "write" bit values for the public actual versus the recommended
        if (substr($ova_bin,-2,1)<=substr($ovr_bin,-2,1))
        {
            //The "write" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "write" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        //Compare the "read" bit values for the public actual versus the recommended
        if (substr($ova_bin,-3,1)<=substr($ovr_bin,-3,1))
        {
            //The "read" bit is the same or less as the recommended value
            $result = 1*$result;
        }else
        {
            //The "read" bit is switched on for the actual value - meaning it is less secure
            $result = 0*$result;
        }

        return $result;
    }

    /*
     * Checks if a directory exists and creates one if it does not
     */
    static function create_dir($dirpath='')
    {
        $res = true;
        if ($dirpath != '')
        {
            //TODO - maybe add some checks to make sure someone is not passing a path with a filename, ie, something which has ".<extenstion>" at the end
            //$path_parts = pathinfo($dirpath);
            //$dirpath = $path_parts['dirname'] . '/' . $path_parts['basename'];
            if (!file_exists($dirpath))
            {
                $res = mkdir($dirpath, 0755);
            }
        }
        return $res;
    }

    static function get_attachment_id_from_url($attachment_url = '')
    {
        global $wpdb;
        $attachment_id = false;

        // If there is no url, return.
        if ('' == $attachment_url)return;

        // Get the upload directory paths
        $upload_dir_paths = wp_upload_dir();

        // Make sure the upload path base directory exists in the attachment URL, to verify that we're working with a media library image
        if (false !== strpos($attachment_url, $upload_dir_paths['baseurl'])) {
            // Remove the upload path base directory from the attachment URL
            $attachment_url = str_replace( $upload_dir_paths['baseurl'] . '/', '', $attachment_url );
            // Now run custom database query to get attachment ID from attachment URL
            $attachment_id = $wpdb->get_var( $wpdb->prepare( "SELECT wposts.ID FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta WHERE wposts.ID = wpostmeta.post_id AND wpostmeta.meta_key = '_wp_attached_file' AND wpostmeta.meta_value = '%s' AND wposts.post_type = 'attachment'", $attachment_url ) );
        }
        return $attachment_id;
    }


    /**
     * Will return an indexed array of files sorted by last modified timestamp
     * @param string $dir
     * @param string $sort (ASC, DESC)
     * @return array
     */
    static function scan_dir_sort_date($dir, $sort='DESC') {
        $files = array();
        foreach (scandir($dir) as $file) {
            $files[$file] = filemtime($dir . '/' . $file);
        }

        if ($sort === 'ASC') {
            asort($files);
        }
        else {
            arsort($files);
        }

        return array_keys($files);
    }

}
