diff --git a/file_example/file_example.routing.yml b/file_example/file_example.routing.yml index ecd85e4..e7d7273 100644 --- a/file_example/file_example.routing.yml +++ b/file_example/file_example.routing.yml @@ -1,25 +1,51 @@ -file_example.fileapi: - path: '/examples/file_example' - defaults: - _form: '\Drupal\file_example\Form\FileExampleReadWriteForm' - _title: 'File Example: Use the File API to read/write a file' - requirements: - _permission: 'use file example' - # In order to view files created with our demo stream wrapper class, # we need to use hook_file_download to grant any access. This route # will make sure that we have an external URL for these files, and that # our hook is called. # # In our implementation, access to the files is actually managed by -# permissions defined in file_example.permissions.yml. +# permissions defined in file_example.permissions.yml. Since we also want our +# URLs to be served similar to how private: and temporary: URI are served by +# core, we also need to modify how the routing system handles the tail portion +# of the URL. Unlike Drupal 7, Drupal 8 does not ordinarily allow a "menu tail"; +# URLs need to be of a definite length or the router will not process them. To +# get around this, we also implement a "path processor", which we define as a +# service in our services file. Our path processor will do the extra steps needed +# to process our session file URLs. # +# @see file_example.services.yml # @see file_example_file_download() # +file_example.files: + path: '/examples/file_example/files/{scheme}' + defaults: + _controller: 'Drupal\system\FileDownloadController::download' + scheme: session + requirements: + _access: 'TRUE' + +# In addition to the file_example.files route, which is actually matched by the router, +# we also need a route defintion to make our URLs. This is never referenced by the +# routing system, but is used by our stream wrapper class to create external URLs. +# +# @see FileExampleSessionStreamWrapper::getExternalUrl() +# file_example.files.session: - path: '/example/file_example/files/{scheme}' + path: '/examples/file_example/files/{filepath}' defaults: _controller: '\Drupal\system\FileDownloadController::download' scheme: session requirements: + # Permissive regex to allow slashes in filepath see + # http://symfony.com/doc/current/cookbook/routing/slash_in_parameter.html + filepath: .+ _access: 'TRUE' + +file_example.fileapi: + path: '/examples/file_example' + defaults: + _form: '\Drupal\file_example\Form\FileExampleReadWriteForm' + _title: 'File Example: Use the File API to read/write a file' + requirements: + _permission: 'use file example' + diff --git a/file_example/file_example.services.yml b/file_example/file_example.services.yml index 03532ec..8077889 100644 --- a/file_example/file_example.services.yml +++ b/file_example/file_example.services.yml @@ -8,10 +8,27 @@ # service, but the better way to do this is to have the system autoload it by tagging the service, # as we do here. # +# We also want to securely serve up our fake session files. We'd like to use the same nice +# file paths that Core uses for private files. Since Drupal 8 no longer allows us to have +# "menu tails" (i.e., extra/parts/of/the/path after the default part of the path), we need +# to get some router superpowers. Our route (in file_example.routing.yml) will "gather up" +# the path with with a regular expression. But we need to do a little more that that. We +# also need to convince the routing system to see our weird, extra long route route. We +# do that using a "Path Processor". We register the path_process.sessions service with special +# tags to get it loaded for when the Drupal's routing system decides which path should get +# used. +# # @see src/StreamWrapper/FileExampleSessionStreamWrapper.php +# @see src/PathProcessor/PathProcessorSessions.php +# @see file_example.routing.yml # services: file_example.stream_wrapper: class: Drupal\file_example\StreamWrapper\FileExampleSessionStreamWrapper tags: - { name: stream_wrapper, scheme: session } + + path_processor.sessions: + class: Drupal\file_example\PathProcessor\PathProcessorSessions + tags: + - { name: path_processor_inbound, priority: 200 } diff --git a/file_example/src/PathProcessor/PathProcessorSessions.php b/file_example/src/PathProcessor/PathProcessorSessions.php new file mode 100644 index 0000000..7593166 --- /dev/null +++ b/file_example/src/PathProcessor/PathProcessorSessions.php @@ -0,0 +1,35 @@ +query->has('file')) { + $file_path = preg_replace('|^\/examples\/file_example\/files\/|', '', $path); + $request->query->set('file', $file_path); + // We return the route we want to match. + return '/examples/file_example/files'; + } + return $path; + } + +} diff --git a/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php b/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php index a172f53..60ac9a8 100644 --- a/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php +++ b/file_example/src/StreamWrapper/FileExampleSessionStreamWrapper.php @@ -227,14 +227,7 @@ class FileExampleSessionStreamWrapper implements StreamWrapperInterface { */ public function getExternalUrl() { $path = str_replace('\\', '/', $this->getTarget()); - return $this->url('file_example.files.session', ['scheme' => 'session', 'file' => $path], ['absolute' => TRUE]); - } - - /** - * We have no concept of chmod, so just return TRUE. - */ - public function chmod($mode) { - return TRUE; + return $this->url('file_example.files.session', ['filepath' => $path, 'scheme' => 'session'], ['absolute' => TRUE]); } /** @@ -373,7 +366,10 @@ class FileExampleSessionStreamWrapper implements StreamWrapperInterface { * @see http://www.php.net/manual/streamwrapper.stream-metadata.php */ public function stream_metadata($path, $option, $value) { - return FALSE; + // We don't really do any of these, but we want to reassure the calling code + // that there is no problem with chown or chgrp, even though we do not + // actually support these. + return TRUE; } diff --git a/file_example/src/Tests/FileExampleTest.php b/file_example/src/Tests/FileExampleTest.php index ea16d9d..2879319 100644 --- a/file_example/src/Tests/FileExampleTest.php +++ b/file_example/src/Tests/FileExampleTest.php @@ -100,7 +100,7 @@ class FileExampleTest extends WebTestBase { $this->drupalPostForm('examples/file_example', $edit, t('Check to see if file exists')); $this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), 'Verify that file does not yet exist.'); - debug( + debug((string) t('Processing button=%button, scheme=%scheme, dir=%dirname, file=%filename', array( '%button' => $button, @@ -131,7 +131,7 @@ class FileExampleTest extends WebTestBase { // Click the link provided that is an easy way to get the data for // checking and make sure that the data we put in is what we get out. - if (!in_array($scheme, array('private', 'temporary'))) { + if (!in_array($scheme, array())) { $this->clickLink(t('this URL')); // assertText give sketchy answers when the content is *exactly* the contents of the // buffer, so let's do something less fragile.