filebrowser-8.x-2.x-dev/src/File/DisplayFileList.php
src/File/DisplayFileList.php
<?php
namespace Drupal\filebrowser\File;
use Drupal;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\filebrowser\Events\MetadataEvent;
use Drupal\filebrowser\ServerFileList;
use Drupal\node\NodeInterface;
/**
* Class FileDisplayList.
*
* @package Drupal\filebrowser
* This class holds the list of files to be displayed on the filebrowser node.
* These files are retrieved from the filesystem and filtered for user and node access.
* The array produced by this class contains all data required to be fed into the
* presentation (list-view, icon-view class).
* FileDisplayList = obj
* ->data // data essential for this collection]
* ->files // files to be displayed]
*/
class DisplayFileList extends ControllerBase {
protected $data;
/**
* List of the files ready to be passes to presentation
* This list will appear on the node view
*
* @var array
*/
protected $files;
/**
* @var \Drupal\node\NodeInterface
*/
protected $node;
/**
* @var array $list;
*/
protected $items;
/**
* List of files as retrieved from the server source and
* already filters as per node and per user permissions
*
* @var array
*/
protected $serverFileList;
/**
* @var \Drupal\filebrowser\Services\Common
*/
protected $common;
/**
* @var \Drupal\filebrowser\Services\FilebrowserValidator
*/
protected $validator;
/**
* @var \Drupal\filebrowser\Services\FilebrowserStorage
*/
protected $storage;
/**
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* @var \Drupal\filebrowser\Filebrowser
*/
protected $filebrowser;
/**
* @var int
*/
protected $fid;
/**
* @var string
*/
protected $relativePath;
/**
* @var bool
*/
protected $isSubDir;
/**
* @var string
*/
protected $fsRoot;
/**
* {@inheritdoc}
*/
public function __construct(NodeInterface $node, $fid) {
$this->data = NULL;
$this->files = [];
$this->node = $node;
$this->filebrowser = $node->filebrowser;
$this->common = Drupal::service('filebrowser.common');
$this->storage = Drupal::service('filebrowser.storage');
$this->validator = Drupal::service('filebrowser.validator');
$this->user = Drupal::currentUser();
$this->fid = $fid;
$this->createFileDisplayList();
}
/**
* @return mixed
*/
public function get() {
return [
'data' => $this->data,
'files' => $this->files,
];
}
/**
* Creates the data required to build the Filebrowser listing in following steps
* - look at the request object and decide what files are requested
* - Retrieves the DB records for this Filebrowser
* - Get all the files from the server and filter them for *per-node* settings
* - Iterate over this file list and build the DB contents for storage
* - The DB contents should reflect the filtered file list from the server
* - The purpose of the DB contents is to provide an url over which files and
* folders can be requested/
* - return the list of files (DB contents) for display in the node.
* Display depends on the selected view. example: list view returns a table
* displaying one file per row.
*
* @return mixed
*/
protected function createFileDisplayList() {
// File list
$cid = 'filebrowser/' . $this->node->id() . '/' . $this->fid;
// you are requesting a subdir: node/23?fid=nn
if ($this->fid) {
$content = $this->storage->nodeContentLoadMultiple([$this->fid]);
if (empty($content[$this->fid])) {
// There is no DB data for this fid.
return false;
}
// When accessing a subdir relativePath will be /subdir[/other sub dir].
$this->relativePath = $content[$this->fid]['path'];
$this->fsRoot = $this->relativePath;
}
// you are requesting the basic node like: /node/23.
else {
$this->relativePath = '/';
}
$this->isSubDir = $this->relativePath != '/';
// If this is a sub dir, check if we may access it, else redirect to root_dir.
if ($this->isSubDir && !$this->common->canExploreSubFolders($this->node)) {
Drupal::messenger()
->addError($this->t('You\'re not allowed to browse sub folders.'));
return FALSE;
}
// Full path valid?
if ($this->fsRoot === FALSE) {
Drupal::messenger()
->addError($this->t('Configured folder is not readable or is not a directory.'));
return FALSE;
}
// Retrieve files from the system - returned list is filtered *per-node* settings.
$list = new ServerFileList($this->node, $this->relativePath);
$this->serverFileList = $list->getList();
// debug($this->serverFileList);
// Create DB contents and return the files for display ($result)
$result = $this->processServerFileList();
$this->files = $result;
$this->data['fid'] = $this->fid;
// Cache the results.
$cache = [
'files' => $this->files,
'data' => $this->data,
];
Drupal::cache()
->set($cid, $cache, -1,['filebrowser:node:' . $this->node->id()]);
return $this;
}
/**
* @return mixed
*/
protected function processServerFileList() {
/** @var /Drupal/filebrowser/File/DisplayFile $result_file */
$stats = ['folders_count' => 0, 'files_count' => 0, 'total_size' => 0];
$encoding = $this->filebrowser->encoding;
// Get the DB_contents for this nid
// first time after node creation $db_content = NULL; there is nothing in the DB
// If there is content it will return a filename as key, with next indexes:
// array ('nid' => '1', 'fid' => '3', 'root' => '/', 'path' => '/',)
$db_content = $this->storage->loadRecordsFromRoot($this->node->id(), $this->relativePath);
// debug($db_content, 'DB CONTENT');
// Iterate over file list from the server.
if (!is_null($this->serverFileList)) {
foreach ($this->serverFileList as $key => $fs_file) {
// Build file relative path.
$file_relative_path = $this->buildFileRelativePath($fs_file->filename, $encoding);
// Build database file record if it doesn't exist.
if (!isset($db_content[$file_relative_path])) {
$db_content[$file_relative_path] = [
'exists' => TRUE,
'nid' => $this->node->id(),
'root' => $this->relativePath,
'path' => $file_relative_path,
'description' => $this->t('No description.'),
'file_data' => $fs_file,
];
}
$db_content[$file_relative_path]['exists'] = TRUE;
$db_content[$file_relative_path]['display_name'] = $fs_file->filename;
$result_file = new DisplayFile($this->node->id());
$result_file->fileSetData($file_relative_path, $fs_file, $stats, $db_content[$file_relative_path], $this->fsRoot);
$result_list[$fs_file->filename] = $result_file;
}
}
// The abstracted filesystem does not provide . and .. files. Therefore
// we will create them manually.
if ($this->isSubDir) {
$subDir = new DisplayFile($this->node->id());
// Create the .. file data.
$result_list['..'] = $subDir->createSubdir($this->relativePath);
// Create the . file data.
$file = new DisplayFile($this->node->id());
$result_list['.'] = $file->createUpDir($this->relativePath);
// Set DB content for Up-directory. In this case the '/' folder.
$this->createUpDirContent($db_content['/']);
// Set DB record for current directory (. file)
if (!isset($db_content[$this->relativePath])) {
$db_content[$this->relativePath] = [
'exists' => TRUE,
'nid' => $this->node->id(),
'root' => $this->relativePath,
'path' => $this->relativePath,
];
}
$db_content[$this->relativePath]['exists'] = TRUE;
$db_content[$this->relativePath]['display_name'] = '.';
}
else {
// not a sub dir so we only set the . file and / DB data.
if (!isset($db_content['/'])) {
$db_content['/'] = [
'nid' => $this->node->id(),
'root' => '/',
'path' => '/',
];
}
$db_content['/']['exists'] = TRUE;
$db_content['/']['display_name'] = '.';
// changes to the File System Array.
$result_file = new DisplayFile($this->node->id());
$result_list['.'] = $result_file->createUpDir($this->relativePath);
}
// debug($db_content, 'END DB CONTENT');
// Set global folder properties.
$this->data['stats'] = $this->buildStatistics($result_list);
$this->data['relative_path'] = $this->relativePath;
$this->dbSync($db_content, $result_list, $this->data);
return $result_list;
}
/**
* Synchronizes what we seen on filesystem with what is stored in database
* We also build an access URL (link) for each file as it is why we stored
* this stuff
* in DB (have a unique ID for each file and path) to get rid of national character
* mess in URLS.
*
* @param array $db_content
* @param array $files
* List of DisplayFiles objects
* @param integer $subdir_fid
*/
protected function dbSync(&$db_content, &$files, $subdir_fid = NULL) {
/** @var DisplayFile $files [$key] */
$to_delete = [];
// Build the fragment to be used with folders.
$theme = Drupal::theme()->getActiveTheme()->getName();
$fragment = 'block-' . str_replace('_', '-', $theme) . '-page-title';
foreach ($db_content as $path => &$record) {
if (!isset($record['nid'])) {
}
if (!isset($record['exists'])) {
$to_delete[] = $record['fid'];
}
else {
if (!isset($record['fid'])) {
$record['fid'] = $this->storage->insertRecord($record);
}
$key = $record['display_name'];
$files[$key]->fid = $record['fid'];
$link = $this->makeLink($files[$key], $record['fid'], $fragment);
$files[$key]->link = $link->toRenderable();
$files[$key]->href = $link->getUrl();
// Fire an event so modules can create metadata for this file.
/** @var MetadataEvent $event */
$dispatcher = Drupal::service('event_dispatcher');
$e = new MetadataEvent($this->node->id(), $record['fid'], $files[$key], $subdir_fid, $this->filebrowser->visibleColumns);
$dispatcher->dispatch($e, 'filebrowser.metadata_event');
}
}
// A quick way to drip obsolete records.
if (count($to_delete)) {
$this->storage->deleteFileRecords($to_delete);
}
}
/**
* Creates links for the file list.
* @param DisplayFile $file
* @param int $fid
* @param string $fragment
*
* for file: 'http://drupal.dev/filebrowser/download/4'
* for folder: 'http://drupal.dev/node/1?fid=23#fragment
* @return \Drupal\Core\Link
*/
protected function makeLink(DisplayFile $file, $fid = NULL, $fragment = NULL) {
$options = [
'query' => [
'fid' => $fid,
],
];
if (isset($fragment)) {
$options['fragment'] = $fragment;
}
if ($file->displayName == '..') {
$display_name = $this->t('Go up');
return Link::createFromRoute($display_name, 'entity.node.canonical', ['node' => $this->node->id()], $options);
}
$name = $this->filebrowser->hideExtension ? pathinfo($file->displayName, PATHINFO_FILENAME) : $file->displayName;
if ($file->fileData->type != 'file') {
return Link::createFromRoute(
$name, 'entity.node.canonical',
['node' => $this->node->id()], $options);
}
else {
$settings = $this->filebrowser;
if ($settings->downloadManager == 'public') {
$file_uri = $this->filebrowser->folderPath . $file->relativePath;
$stream_wrapper = \Drupal::service('stream_wrapper_manager')
->getViaUri($file_uri);
$f_external_url = $stream_wrapper->getExternalUrl();
$external_host = $settings->externalHost;
if (!empty($external_host)) {
$host = \Drupal::request()->getSchemeAndHttpHost();
$f_external_url = str_replace($host, $external_host, $f_external_url);
}
$f_external_link = Link::fromTextAndUrl($name, Url::fromUri($f_external_url));
if (isset($f_external_link)) {
return $f_external_link;
}
}
}
// open file in a new page
$options['attributes'] = [
'target' => '_blank',
];
return Link::createFromRoute($name, 'filebrowser.page_download', ['fid' => $fid],$options);
}
/**
* Need this function to build the "Go-up" and Navigate-to-folder links in the icon view
* todo: solve this better by integrating with makeLink()
* @param \Drupal\filebrowser\File\DisplayFile $file
* @param null|int $fid
*/
protected function makeAnchor(DisplayFile $file, $fid = NULL) {
}
/**
* @param $fs_filename
* @param $encoding
*
* @return string
*/
protected function buildFileRelativePath($fs_filename, $encoding) {
$filename = $this->validator->encodingToFs($encoding, $fs_filename);
return $this->relativePath . ($this->relativePath != '/' ? '/' : '') . $filename;
}
/**
* @param $array
*
* @return void
*/
protected function createUpDirContent(&$array){
$parent_path = $this->parentFolder();
$content = $this->storage->loadRecordFromPath($this->node->id(), $parent_path);
if ($content) {
foreach ($content as $key => $value) {
$array[$key] = $value;
}
}
else {
Drupal::messenger()
->addError($this->t('No content in method LoadRecordFromPath'));
}
$array['exists'] = TRUE;
$array['display_name'] = '..';
}
/**
* @return string
*/
protected function parentFolder() {
$array = explode('/', $this->relativePath);
if (count($array) < 3) {
return '/';
}
else {
unset($array[count($array) - 1]);
return $result = implode('/', $array);
}
}
/**
* @param $list
*
* @return array
*/
protected function buildStatistics($list) {
$files = 0;
$folders = 0;
$total_size = 0;
foreach($list as $key => $item) {
if (in_array($key, ['.', '..'])) {
}
else {
if ($item->fileData->type == 'file') {
$files++;
$total_size = $total_size + $item->fileData->size;
}
else {
$folders++;
}
}
}
return [
'files' => $files,
'folders' => $folders,
'size' => $total_size,
];
}
}
