From 62e53ffa8d402e6bfe091a8f3c02a3bf80b5e449 Mon Sep 17 00:00:00 2001
From: Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
Date: Thu, 4 Feb 2021 17:34:21 +1100
Subject: [PATCH] MDL-70752 filesystem: Add FTP alternative filesystem

---
 lib/classes/file_system_ftp.php | 184 ++++++++++++++++++++++++++++++++
 1 file changed, 184 insertions(+)
 create mode 100644 lib/classes/file_system_ftp.php

diff --git a/lib/classes/file_system_ftp.php b/lib/classes/file_system_ftp.php
new file mode 100644
index 00000000000..f73a0cea695
--- /dev/null
+++ b/lib/classes/file_system_ftp.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ * FTP File System.
+ *
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * File system class used for low level access to real files via ftp (aieee! don't do it!).
+ *
+ * @package   core_files
+ * @category  files
+ * @copyright 2017 Damyon Wiese
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_file_system_ftp extends file_system {
+
+    public function setup_instance() {
+        global $CFG;
+
+        $this->dirpermissions = $CFG->directorypermissions;
+        $this->filepermissions = $CFG->filepermissions;
+
+        $this->cachedir = make_request_directory();
+        $this->check_root_files_exist();
+    }
+
+    protected function check_root_files_exist() {
+        // Make sure the file pool directory exists.
+        $filedir = $this->get_root_path();
+        if (!is_dir($filedir)) {
+            if (!mkdir($filedir, $this->dirpermissions, true)) {
+                // Permission trouble.
+                throw new file_exception('storedfilecannotcreatefiledirs');
+            }
+
+            // Place warning file in file pool root.
+            $warningfile = $this->get_root_path('warning.txt');
+            if (!file_exists($warningfile)) {
+                file_put_contents($warningfile,
+                    'This directory contains the content of uploaded files and is controlled by Moodle code. ' .
+                    'Do not manually move, change or rename any of the files and subdirectories here.');
+            }
+        }
+    }
+
+    protected function get_remote_path_from_hash($contenthash) {
+        return $this->get_root_path($contenthash);
+    }
+
+    protected function get_local_path_from_hash($contenthash, $fetchifnotfound = false) {
+        $this->setup_instance();
+        $localurl = $this->cachedir . DIRECTORY_SEPARATOR . $contenthash;
+
+        if ($fetchifnotfound && !is_readable($localurl)) {
+            $remoteurl = $this->get_remote_path_from_hash($contenthash);
+            copy($remoteurl, $localurl);
+        }
+
+        return $localurl;
+    }
+
+    public function get_root_url() {
+        global $CFG;
+
+        static $path = null;
+
+        if ($path === null) {
+            $path = "ftp://{$CFG->file_system_ftp_user}:{$CFG->file_system_ftp_pass}@" .
+                "{$CFG->file_system_ftp_host}/{$CFG->file_system_ftp_path}";
+        }
+
+        return $path;
+    }
+
+    public function get_root_path($file = null) {
+        $path = $this->get_root_url() . DIRECTORY_SEPARATOR . 'filedir';
+        if ($file) {
+            $path .= DIRECTORY_SEPARATOR . $file;
+        }
+
+        return $path;
+    }
+
+    /**
+     * Copy content of file to given pathname.
+     *
+     * @param stored_file $file The file to be copied
+     * @param string $target real path to the new file
+     * @return bool success
+     */
+    public function copy_content_from_storedfile(stored_file $file, $target) {
+        return copy($this->get_remote_path_from_storedfile($file), $target);
+    }
+
+    /**
+     * Removes the file.
+     *
+     * @param string $contenthash
+     */
+    public function remove_file($contenthash) {
+        if (!self::is_file_removable($contenthash)) {
+            return false;
+        }
+        $contentfile = $this->get_remote_path_from_hash($contenthash, false);
+
+        unlink($contentfile);
+
+        return true;
+    }
+
+    /**
+     * Add file content to sha1 pool.
+     *
+     * @param string $pathname Path to file currently on disk
+     * @param string $contenthash SHA1 hash of content if known (performance only)
+     * @return array (contenthash, filesize, newfile)
+     */
+    public function add_file_from_path($pathname, $contenthash = null) {
+        if (!is_readable($pathname)) {
+            throw new file_exception('storedfilecannotread', '', $pathname);
+        }
+
+        $filesize = filesize($pathname);
+        if ($filesize === false) {
+            throw new file_exception('storedfilecannotread', '', $pathname);
+        }
+        if (is_null($contenthash)) {
+            $contenthash = sha1_file($pathname);
+        }
+        if (is_null($contenthash)) {
+            throw new file_exception('storedfilecannotread', '', $pathname);
+        }
+
+        // Copy the file to it's local position. This helps perf in some
+        // situations.
+        $localpath = $this->get_local_path_from_hash($contenthash, false);
+        copy($pathname, $localpath);
+
+        $remotepath = $this->get_remote_path_from_hash($contenthash, false);
+
+        $newfile = false;
+        if (!file_exists($remotepath)) {
+            $newfile = true;
+            $result = copy($localpath, $remotepath);
+
+            if ($result === false) {
+                throw new file_exception('storedfilecannotcreatefile');
+            }
+        }
+        return [$contenthash, $filesize, $newfile];
+    }
+
+    /**
+     * Add string content to sha1 pool.
+     *
+     * @param string $content file content - binary string
+     * @return array (contenthash, filesize, newfile)
+     */
+    public function add_file_from_string($content) {
+        $contenthash = sha1($content);
+        $filesize = strlen($content);
+
+        if ($content === '') {
+            return [$contenthash, $filesize, false];
+        }
+
+        $newfile = false;
+        $remotepath = $this->get_remote_path_from_hash($contenthash, false);
+        if (!file_exists($remotepath)) {
+            $newfile = true;
+            // Store the file remotely first - it's likely to be used again.
+            $localpath = $this->get_local_path_from_hash($contenthash, false);
+            file_put_contents($localpath, $content);
+
+            copy($localpath, $remotepath);
+        }
+
+        return [$contenthash, $filesize, $newfile];
+    }
+
+}
-- 
2.25.1

