FileExampleSubmitHandlerHelper.php

Namespace

Drupal\file_example

File

modules/file_example/src/FileExampleSubmitHandlerHelper.php

View source
<?php

namespace Drupal\file_example;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\file\FileRepositoryInterface;
use Drupal\file_example\Traits\DumperTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * A submit handler helper class for the file_example module.
 */
class FileExampleSubmitHandlerHelper {
    use stringTranslationTrait, DumperTrait;
    
    /**
     * Constructs a new FileExampleReadWriteForm page.
     *
     * @param \Drupal\file_example\FileExampleStateHelper $stateHelper
     *   The file example state helper.
     * @param \Drupal\file_example\FileExampleFileHelper $fileHelper
     *   The file example file helper.
     * @param \Drupal\file_example\FileExampleSessionHelperWrapper $sessionHelperWrapper
     *   The file example session helper wrapper.
     * @param \Drupal\Core\Messenger\MessengerInterface $messenger
     *   The messenger.
     * @param \Drupal\file\FileRepositoryInterface $fileRepository
     *   The file repository.
     * @param \Drupal\Core\File\FileSystemInterface $fileSystem
     *   The file system.
     * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
     *   The event dispatcher.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
     *   The module handler.
     *
     * @see https://php.watch/versions/8.0/constructor-property-promotion
     */
    public function __construct(FileExampleStateHelper $stateHelper, FileExampleFileHelper $fileHelper, FileExampleSessionHelperWrapper $sessionHelperWrapper, MessengerInterface $messenger, FileRepositoryInterface $fileRepository, FileSystemInterface $fileSystem, EventDispatcherInterface $eventDispatcher, ModuleHandlerInterface $moduleHandler) {
    }
    
    /**
     * Submit handler to write a managed file.
     *
     * A "managed file" is a file that Drupal tracks as a file entity.  It's the
     * standard way Drupal manages files in file fields and elsewhere.
     *
     * The key functions used here are:
     * - file_save_data(), which takes a buffer and saves it to a named file and
     *   also creates a tracking record in the database and returns a file object.
     *   In this function we use FileExists::Rename (the default) as the argument,
     *   which means that if there's an existing file, create a new non-colliding
     *   filename and use it.
     * - file_create_url(), which converts a URI in the form public://junk.txt or
     *   private://something/test.txt into a URL like
     *   http://example.com/sites/default/files/junk.txt.
     *    * @param array $form
     *   An associative array containing the structure of the form.
     *
     * @param array &$form
     *   The form array.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     */
    public function handleManagedFile(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $data = $form_values['write_contents'];
        $uri = !empty($form_values['destination']) ? $form_values['destination'] : NULL;
        // Managed operations work with a file object.
        $file_object = $this->fileRepository
            ->writeData($data, $uri, FileExists::Rename);
        if (!empty($file_object)) {
            $url = $this->fileHelper
                ->getExternalUrl($file_object);
            $this->stateHelper
                ->setDefaultFile($file_object->getFileUri());
            $file_data = $file_object->toArray();
            if ($url) {
                $this->messenger
                    ->addMessage($this->t('Saved managed file: %file to destination %destination (accessible via <a href=":url">this URL</a>, actual uri=<span id="uri">@uri</span>)', [
                    '%file' => print_r($file_data, TRUE),
                    '%destination' => $uri,
                    '@uri' => $file_object->getFileUri(),
                    ':url' => $url->toString(),
                ]));
            }
            else {
                // The stream type does not support URLs, so we cannot give a link to it.
                $this->messenger
                    ->addMessage($this->t('Saved managed file: %file to destination %destination (no URL, since this stream type does not support it)', [
                    '%file' => print_r($file_data, TRUE),
                    '%destination' => $uri,
                    '@uri' => $file_object->getFileUri(),
                ]));
            }
        }
        else {
            $this->messenger
                ->addMessage($this->t('Failed to save the managed file'), 'error');
        }
    }
    
    /**
     * Submit handler to write an unmanaged file.
     *
     * An unmanaged file is a file that Drupal does not track.  A standard
     * operating system file, in other words.
     *
     * The key functions used here are:
     * - FileSystemInterface::saveData(), which takes a buffer and saves it to a
     *   named file, but does not create any kind of tracking record in the
     *   database. This example uses FileExists::Replace for the third argument,
     *   meaning that, if there's an existing file at this location, it should be
     *   replaced.
     * - file_create_url(), which converts a URI in the form public://junk.txt or
     *   private://something/test.txt into a URL like
     *   http://example.com/sites/default/files/junk.txt.
     *    * @param array $form
     *   An associative array containing the structure of the form.
     *
     * @param array &$form
     *   The form array.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     */
    public function handleUnmanagedFile(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $data = $form_values['write_contents'];
        $destination = !empty($form_values['destination']) ? $form_values['destination'] : NULL;
        // With the unmanaged file we just get a filename back.
        $filename = $this->fileSystem
            ->saveData($data, $destination, FileExists::Replace);
        if ($filename) {
            $url = $this->fileHelper
                ->getExternalUrl($filename);
            $this->stateHelper
                ->setDefaultFile($filename);
            if ($url) {
                $this->messenger
                    ->addMessage($this->t('Saved file as %filename (accessible via <a href=":url">this URL</a>, uri=<span id="uri">@uri</span>)', [
                    '%filename' => $filename,
                    '@uri' => $filename,
                    ':url' => $url->toString(),
                ]));
            }
            else {
                $this->messenger
                    ->addMessage($this->t('Saved file as %filename (not accessible externally)', [
                    '%filename' => $filename,
                    '@uri' => $filename,
                ]));
            }
        }
        else {
            $this->messenger
                ->addMessage($this->t('Failed to save the file'), 'error');
        }
    }
    
    /**
     * Submit handler to write an unmanaged file using plain PHP functions.
     *
     * The key functions used here are:
     * - FileSystemInterface::saveData(), which takes a buffer and saves it to a
     *   named file, but does not create any kind of tracking record in the
     *   database.
     * - file_create_url(), which converts a URI in the form public://junk.txt or
     *   private://something/test.txt into a URL like
     *   http://example.com/sites/default/files/junk.txt.
     * - drupal_tempnam() generates a temporary filename for use.
     *
     * @param array $form
     *   An associative array containing the structure of the form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     */
    public function handleUnmanagedPhp(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $data = $form_values['write_contents'];
        $destination = !empty($form_values['destination']) ? $form_values['destination'] : NULL;
        if (empty($destination)) {
            // If no destination has been provided, use a generated name.
            $destination = $this->fileSystem
                ->tempnam('public://', 'file');
        }
        // With all traditional PHP functions we can use the stream wrapper notation
        // for a file as well.
        $fp = fopen($destination, 'w');
        // To demonstrate the fact that everything is based on streams, we'll do
        // multiple 5-character writes to put this to the file. We could easily
        // (and far more conveniently) write it in a single statement with
        // fwrite($fp, $data).
        $length = strlen($data);
        $write_size = 5;
        for ($i = 0; $i < $length; $i += $write_size) {
            $result = fwrite($fp, substr($data, $i, $write_size));
            if ($result === FALSE) {
                $this->messenger
                    ->addMessage($this->t('Failed writing to the file %file', [
                    '%file' => $destination,
                ]), 'error');
                fclose($fp);
                return;
            }
        }
        $url = $this->fileHelper
            ->getExternalUrl($destination);
        $this->stateHelper
            ->setDefaultFile($destination);
        if ($url) {
            $this->messenger
                ->addMessage($this->t('Saved file as %filename (accessible via <a href=":url">this URL</a>, uri=<span id="uri">@uri</span>)', [
                '%filename' => $destination,
                '@uri' => $destination,
                ':url' => $url->toString(),
            ]));
        }
        else {
            $this->messenger
                ->addMessage($this->t('Saved file as %filename (not accessible externally)', [
                '%filename' => $destination,
                '@uri' => $destination,
            ]));
        }
    }
    
    /**
     * Submit handler for reading a stream wrapper.
     *
     * Drupal now has full support for PHP's stream wrappers, which means that
     * instead of the traditional use of all the file functions
     * ($fp = fopen("/tmp/some_file.txt");) far more sophisticated and generalized
     * (and extensible) things can be opened as if they were files. Drupal itself
     * provides the public:// and private:// schemes for handling public and
     * private files. PHP provides file:// (the default) and http://, so that a
     * URL can be read or written (as in a POST) as if it were a file. In
     * addition, new schemes can be provided for custom applications. The Stream
     * Wrapper Example, if installed, implements a custom 'session' scheme you can
     * test with this example.
     *
     * Here we take the stream wrapper provided in the form. We grab the
     * contents with file_get_contents(). It is that simple:
     * file_get_contents("http://example.com") or
     * file_get_contents("public://example.txt") just works. Although not
     * necessary, we use FileSystemInterface::saveData() to save this file locally
     * and then find a local URL for it by using file_create_url().
     *
     * @param array $form
     *   An associative array containing the structure of the form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     */
    public function handleFileRead(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $uri = $form_values['file_ops_file'];
        if (empty($uri) or !is_file($uri)) {
            $this->messenger
                ->addMessage($this->t('The file "%uri" does not exist', [
                '%uri' => $uri,
            ]), 'error');
            return;
        }
        $filename = $this->fileSystem
            ->basename($uri);
        // To ensure that the filename is valid, strip off any potential file
        // portion from the stream wrapper. If the filename includes a malicious
        // file extension, it will be neutralized by the event subscriber of the
        // FileUploadSanitizeNameEvent. This process helps to maintain the security
        // of the system and prevent any potential harm from malicious files.
        $event = new FileUploadSanitizeNameEvent($filename, 'txt');
        $this->eventDispatcher
            ->dispatch($event);
        $dirname = $this->fileSystem
            ->dirname($uri);
        if (str_ends_with($dirname, '/')) {
            $filename = $dirname . $event->getFilename();
        }
        else {
            $filename = $dirname . '/' . $event->getFilename();
        }
        $buffer = file_get_contents($filename);
        if ($buffer) {
            $source_name = $this->fileSystem
                ->saveData($buffer, 'public://' . $event->getFilename());
            if ($source_name) {
                $url = $this->fileHelper
                    ->getExternalUrl($source_name);
                $this->stateHelper
                    ->setDefaultFile($source_name);
                if ($url) {
                    $this->messenger
                        ->addMessage($this->t('The file was read and copied to %filename which is accessible at <a href=":url">this URL</a>', [
                        '%filename' => $source_name,
                        ':url' => $url->toString(),
                    ]));
                }
                else {
                    $this->messenger
                        ->addMessage($this->t('The file was read and copied to %filename (not accessible externally)', [
                        '%filename' => $source_name,
                    ]));
                }
            }
            else {
                $this->messenger
                    ->addMessage($this->t('Failed to save the file'));
            }
        }
        else {
            // We failed to get the contents of the requested file.
            $this->messenger
                ->addMessage($this->t('Failed to retrieve the file %file', [
                '%file' => $uri,
            ]));
        }
    }
    
    /**
     * Submit handler to delete a file.
     *
     * @param array $form
     *   An associative array containing the structure of the form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     */
    public function handleFileDelete(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $uri = $form_values['file_ops_file'];
        // Since we don't know if the file is managed or not, look in the database
        // to see. Normally, code would be working with either managed or unmanaged
        // files, so this is not a typical situation.
        $file_object = $this->fileHelper
            ->getManagedFile($uri);
        // If a managed file, use file_delete().
        if (!empty($file_object)) {
            // While file_delete should return FALSE on failure,
            // it can currently throw an exception on certain cache states.
            try {
                // This no longer returns a result code.  If things go bad,
                // it will throw an exception:
                $file_object->delete();
                $this->messenger
                    ->addMessage($this->t('Successfully deleted managed file %uri', [
                    '%uri' => $uri,
                ]));
                $this->stateHelper
                    ->setDefaultFile($uri);
            } catch (\Exception $e) {
                $this->messenger
                    ->addMessage($this->t('Failed deleting managed file %uri. Result was %result', [
                    '%uri' => $uri,
                    '%result' => print_r($e->getMessage(), TRUE),
                ]), 'error');
            }
        }
        else {
            $result = $this->fileSystem
                ->delete($uri);
            if ($result !== TRUE) {
                $this->messenger
                    ->addError($this->t('Failed deleting unmanaged file %uri', [
                    '%uri' => $uri,
                ]));
            }
            else {
                $this->messenger
                    ->addMessage($this->t('Successfully deleted unmanaged file %uri', [
                    '%uri' => $uri,
                ]));
                $this->stateHelper
                    ->setDefaultFile($uri);
            }
        }
    }
    
    /**
     * Submit handler to check existence of a file.
     */
    public function handleFileExists(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $uri = $form_values['file_ops_file'];
        if (is_file($uri)) {
            $this->messenger
                ->addMessage($this->t('The file %uri exists.', [
                '%uri' => $uri,
            ]));
        }
        else {
            $this->messenger
                ->addMessage($this->t('The file %uri does not exist.', [
                '%uri' => $uri,
            ]));
        }
    }
    
    /**
     * Submit handler for directory creation.
     *
     * Here we create a directory and set proper permissions on it using
     * FileSystemInterface::prepareDirectory().
     */
    public function handleDirectoryCreate(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $directory = $form_values['directory_name'];
        // The options passed to FileSystemInterface::prepareDirectory() are a
        // bitmask, so we can specify
        // either FileSystemInterface::MODIFY_PERMISSIONS
        // (set permissions on the directory),
        // FileSystemInterface::CREATE_DIRECTORY,
        // or both together:
        // FileSystemInterface::MODIFY_PERMISSIONS |
        // FileSystemInterface::CREATE_DIRECTORY.
        // FileSystemInterface::MODIFY_PERMISSIONS
        // will set the permissions of the directory by default to 0755,
        // or to the value of the variable
        // 'file_chmod_directory'.
        if (!$this->fileSystem
            ->prepareDirectory($directory, FileSystemInterface::MODIFY_PERMISSIONS | FileSystemInterface::CREATE_DIRECTORY)) {
            $this->messenger
                ->addMessage($this->t('Failed to create %directory.', [
                '%directory' => $directory,
            ]), 'error');
        }
        else {
            $this->messenger
                ->addMessage($this->t('Directory %directory is ready for use.', [
                '%directory' => $directory,
            ]));
            $this->stateHelper
                ->setDefaultDirectory($directory);
        }
    }
    
    /**
     * Submit handler for directory deletion.
     *
     * @see \Drupal\Core\File\FileSystemInterface::deleteRecursive()
     */
    public function handleDirectoryDelete(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $directory = $form_values['directory_name'];
        $result = $this->fileSystem
            ->deleteRecursive($directory);
        if (!$result) {
            $this->messenger
                ->addMessage($this->t('Failed to delete %directory.', [
                '%directory' => $directory,
            ]), 'error');
        }
        else {
            $this->messenger
                ->addMessage($this->t('Recursively deleted directory %directory.', [
                '%directory' => $directory,
            ]));
            $this->stateHelper
                ->setDefaultDirectory($directory);
        }
    }
    
    /**
     * Submit handler to test directory existence.
     *
     * This actually just checks to see if the directory is writable.
     *
     * @param array $form
     *   FormAPI form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   FormAPI form state.
     */
    public function handleDirectoryExists(array &$form, FormStateInterface $form_state) {
        $form_values = $form_state->getValues();
        $directory = $form_values['directory_name'];
        $result = is_dir($directory);
        if (!$result) {
            $this->messenger
                ->addMessage($this->t('Directory %directory does not exist.', [
                '%directory' => $directory,
            ]));
        }
        else {
            $this->messenger
                ->addMessage($this->t('Directory %directory exists.', [
                '%directory' => $directory,
            ]));
        }
    }
    
    /**
     * Utility submit function to show the contents of $_SESSION.
     */
    public function handleShowSession(array &$form, FormStateInterface $form_state) {
        $dumper = $this->dumper();
        if ($this->isDevelDumper($dumper)) {
            // If the devel module is installed, use its nicer message format.
            $dumper->dump($this->sessionHelperWrapper
                ->getStoredData(), $this->t('Entire $_SESSION["file_example"]'));
        }
        else {
            $this->messenger
                ->addMessage(sprintf('<pre>%s</pre>', print_r($this->sessionHelperWrapper
                ->getStoredData(), TRUE)));
        }
    }
    
    /**
     * Utility submit function to reset the demo.
     *
     * @param array $form
     *   FormAPI form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   FormAPI form state.
     *
     * @todo Note this does NOT clear any managed file references in Drupal's DB.
     *   It might be a good idea to add this.
     *   https://www.drupal.org/project/examples/issues/2985471
     */
    public function handleResetSession(array &$form, FormStateInterface $form_state) {
        $this->stateHelper
            ->deleteDefaultState();
        $this->sessionHelperWrapper
            ->clearStoredData();
        $this->messenger
            ->addMessage('Session reset.');
    }
    
    /**
     * Checks if the given object is an instance of DevelDumperInterface.
     *
     * @param object $object
     *   The object to check.
     *
     * @return bool
     *   Returns TRUE if the object is an instance of DevelDumperInterface,
     *   FALSE otherwise.
     */
    protected function isDevelDumper(object $object) : bool {
        if ($this->moduleHandler
            ->moduleExists('devel')) {
            return in_array('DevelDumperInterface', class_implements($object));
        }
        return FALSE;
    }

}

Classes

Title Deprecated Summary
FileExampleSubmitHandlerHelper A submit handler helper class for the file_example module.