diff --git a/phpBB/adm/style/acp_storage.html b/phpBB/adm/style/acp_storage.html index 86c9298828..f064438aca 100644 --- a/phpBB/adm/style/acp_storage.html +++ b/phpBB/adm/style/acp_storage.html @@ -82,7 +82,7 @@ {% set buttons = [] %} {% for button in options.buttons %} - {% set new_button = button | merge({"name": input_name, "checked": button.value == input_value}) %} + {% set new_button = button | merge({"name": input_name, "label": lang(button.label), "checked": button.value == input_value}) %} {% set buttons = buttons | merge([new_button]) %} {% endfor %} diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml index 4ff6f96ae3..1109aa626b 100644 --- a/phpBB/config/default/container/services_storage.yml +++ b/phpBB/config/default/container/services_storage.yml @@ -4,33 +4,27 @@ services: storage.attachment: class: phpbb\storage\storage arguments: - - '@dbal.conn' - - '@cache.driver' - '@storage.adapter.factory' + - '@storage.file_tracker' - 'attachment' - - '%tables.storage%' tags: - { name: storage } storage.avatar: class: phpbb\storage\storage arguments: - - '@dbal.conn' - - '@cache.driver' - '@storage.adapter.factory' + - '@storage.file_tracker' - 'avatar' - - '%tables.storage%' tags: - { name: storage } storage.backup: class: phpbb\storage\storage arguments: - - '@dbal.conn' - - '@cache.driver' - '@storage.adapter.factory' + - '@storage.file_tracker' - 'backup' - - '%tables.storage%' tags: - { name: storage } @@ -124,3 +118,11 @@ services: - '@storage.state_helper' - '@storage.provider_collection' - '@storage.adapter_collection' + + storage.file_tracker: + class: phpbb\storage\file_tracker + arguments: + - '@dbal.conn' + - '@cache.driver' + - '%tables.storage%' + diff --git a/phpBB/includes/acp/acp_database.php b/phpBB/includes/acp/acp_database.php index 85943dc5e4..2b95ff3ad5 100644 --- a/phpBB/includes/acp/acp_database.php +++ b/phpBB/includes/acp/acp_database.php @@ -279,7 +279,7 @@ class acp_database try { - $stream = $storage->read_stream($backup_info['file_name']); + $stream = $storage->read($backup_info['file_name']); $fp = fopen($temp_file_name, 'w+b'); stream_copy_to_stream($stream, $fp); diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php index 3692dd40bd..8b8a08be80 100644 --- a/phpBB/includes/acp/acp_main.php +++ b/phpBB/includes/acp/acp_main.php @@ -496,7 +496,7 @@ class acp_main $upload_dir_size = get_formatted_filesize($config['upload_dir_size']); $storage_avatar = $phpbb_container->get('storage.avatar'); - $avatar_dir_size = get_formatted_filesize($storage_avatar->get_size()); + $avatar_dir_size = get_formatted_filesize($storage_avatar->total_size()); if ($posts_per_day > $total_posts) { diff --git a/phpBB/includes/acp/acp_storage.php b/phpBB/includes/acp/acp_storage.php index 2adf833562..35c07074b6 100644 --- a/phpBB/includes/acp/acp_storage.php +++ b/phpBB/includes/acp/acp_storage.php @@ -431,8 +431,8 @@ class acp_storage $storage_stats[] = [ 'name' => $this->lang->lang('STORAGE_' . strtoupper($storage->get_name()) . '_TITLE'), - 'files' => $storage->get_num_files(), - 'size' => get_formatted_filesize($storage->get_size()), + 'files' => $storage->total_files(), + 'size' => get_formatted_filesize($storage->total_size()), 'free_space' => $free_space, ]; } @@ -619,10 +619,18 @@ class acp_storage */ protected function validate_path(string $storage_name, array &$messages) : void { - $current_provider = $this->storage_helper->get_current_provider($storage_name); - $options = $this->storage_helper->get_provider_options($current_provider); + if ($this->request->is_set_post('submit')) + { + $provider = $this->request->variable([$storage_name, 'provider'], ''); + } + else + { + $provider = $this->storage_helper->get_current_provider($storage_name); + } - if ($this->provider_collection->get_by_class($current_provider)->get_name() == 'local' && isset($options['path'])) + $options = $this->storage_helper->get_provider_options($provider); + + if ($this->provider_collection->get_by_class($provider)->get_name() === 'local' && isset($options['path'])) { $path = $this->request->is_set_post('submit') ? $this->request->variable([$storage_name, 'path'], '') : $this->storage_helper->get_current_definition($storage_name, 'path'); diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 737332f4fa..78512d1971 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -2124,7 +2124,8 @@ function group_correct_avatar($group_id, $old_entry) try { - $storage->rename($old_filename, $new_filename); + $storage->write($new_filename, $storage->read($old_filename)); + $storage->delete($old_filename); $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_avatar = \'' . $db->sql_escape($new_entry) . "' diff --git a/phpBB/phpbb/attachment/upload.php b/phpBB/phpbb/attachment/upload.php index c0b0c490c4..ba9ffa2526 100644 --- a/phpBB/phpbb/attachment/upload.php +++ b/phpBB/phpbb/attachment/upload.php @@ -245,7 +245,7 @@ class upload // Move the thumbnail from temp folder to the storage $fp = fopen($destination, 'rb'); - $this->storage->write_stream($destination_name, $fp); + $this->storage->write($destination_name, $fp); if (is_resource($fp)) { diff --git a/phpBB/phpbb/db/migration/data/v400/storage_track.php b/phpBB/phpbb/db/migration/data/v400/storage_track.php index 7cf05503d6..d9890fd80f 100644 --- a/phpBB/phpbb/db/migration/data/v400/storage_track.php +++ b/phpBB/phpbb/db/migration/data/v400/storage_track.php @@ -15,7 +15,7 @@ namespace phpbb\db\migration\data\v400; use phpbb\db\migration\container_aware_migration; use phpbb\storage\exception\storage_exception; -use phpbb\storage\storage; +use phpbb\storage\file_tracker; class storage_track extends container_aware_migration { @@ -70,8 +70,8 @@ class storage_track extends container_aware_migration public function track_avatars() { - /** @var storage $storage */ - $storage = $this->container->get('storage.avatar'); + /** @var file_tracker $file_tracker */ + $file_tracker = $this->container->get('storage.file_tracker'); $sql = 'SELECT user_avatar FROM ' . USERS_TABLE . " @@ -95,7 +95,8 @@ class storage_track extends container_aware_migration try { - $storage->track_file($this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext); + $filename = $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $filename . '.' . $ext; + $file_tracker->track_file('avatar', $filename, filesize($this->phpbb_root_path . $this->config['storage\\avatar\\config\\path'] . '/' . $filename)); } catch (storage_exception $e) { @@ -107,8 +108,8 @@ class storage_track extends container_aware_migration public function track_attachments() { - /** @var storage $storage */ - $storage = $this->container->get('storage.attachment'); + /** @var file_tracker $file_tracker */ + $file_tracker = $this->container->get('storage.file_tracker'); $sql = 'SELECT physical_filename, thumbnail FROM ' . ATTACHMENTS_TABLE; @@ -119,7 +120,7 @@ class storage_track extends container_aware_migration { try { - $storage->track_file($row['physical_filename']); + $file_tracker->track_file('attachment', $row['physical_filename'], filesize($this->phpbb_root_path . $this->config['storage\\attachment\\config\\path'] . '/' . $row['physical_filename'])); } catch (storage_exception $e) { @@ -130,7 +131,7 @@ class storage_track extends container_aware_migration { try { - $storage->track_file('thumb_' . $row['physical_filename']); + $file_tracker->track_file('attachment', 'thumb_' . $row['physical_filename'], filesize($this->phpbb_root_path . $this->config['storage\\attachment\\config\\path'] . '/thumb_' . $row['physical_filename'])); } catch (storage_exception $e) { @@ -143,8 +144,8 @@ class storage_track extends container_aware_migration public function track_backups() { - /** @var storage $storage */ - $storage = $this->container->get('storage.backup'); + /** @var file_tracker $file_tracker */ + $file_tracker = $this->container->get('storage.file_tracker'); $sql = 'SELECT filename FROM ' . BACKUPS_TABLE; @@ -155,7 +156,7 @@ class storage_track extends container_aware_migration { try { - $storage->track_file($row['filename']); + $file_tracker->track_file('backup', $row['filename'], filesize($this->phpbb_root_path . $this->config['storage\\backup\\config\\path'] . '/' . $row['filename'])); } catch (storage_exception $e) { diff --git a/phpBB/phpbb/db/migration/data/v400/storage_track_index.php b/phpBB/phpbb/db/migration/data/v400/storage_track_index.php new file mode 100644 index 0000000000..63f57f5e00 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/storage_track_index.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\container_aware_migration; + +class storage_track_index extends container_aware_migration +{ + public static function depends_on() + { + return [ + '\phpbb\db\migration\data\v400\storage_track', + ]; + } + + public function update_schema() + { + return [ + 'add_unique_index' => [ + $this->table_prefix . 'storage' => [ + 'uidx_storage' => ['file_path', 'storage'], + ], + ] + ]; + } + + public function revert_schema() + { + return [ + 'drop_keys' => [ + $this->table_prefix . 'storage' => [ + 'uidx_storage', + ], + ], + ]; + } +} diff --git a/phpBB/phpbb/files/filespec_storage.php b/phpBB/phpbb/files/filespec_storage.php index 83678718b5..5ded1e380b 100644 --- a/phpBB/phpbb/files/filespec_storage.php +++ b/phpBB/phpbb/files/filespec_storage.php @@ -446,7 +446,7 @@ class filespec_storage { $fp = fopen($this->filename, 'rb'); - $storage->write_stream($this->destination_file, $fp); + $storage->write($this->destination_file, $fp); if (is_resource($fp)) { diff --git a/phpBB/phpbb/storage/adapter/adapter_interface.php b/phpBB/phpbb/storage/adapter/adapter_interface.php index d7fcd77bcb..db7a5475d4 100644 --- a/phpBB/phpbb/storage/adapter/adapter_interface.php +++ b/phpBB/phpbb/storage/adapter/adapter_interface.php @@ -25,32 +25,26 @@ interface adapter_interface public function configure(array $options): void; /** - * Dumps content into a file + * Reads a file as a stream * - * @param string $path - * @param string $content - * @throws storage_exception When the file cannot be written + * @param string $path File to read + * + * @return resource Returns a file pointer + * @throws storage_exception When unable to open file */ - public function put_contents(string $path, string $content): void; + public function read(string $path); /** - * Read the contents of a file + * Writes a new file using a stream * - * @param string $path The file to read + * @param string $path The target file + * @param resource $resource The resource * - * @return string Returns file contents - * @throws storage_exception When cannot read file contents + * @return int Returns the number of bytes written + * @throws storage_exception When target file exists + * When target file cannot be created */ - public function get_contents(string $path): string; - - /** - * Checks the existence of files or directories - * - * @param string $path file/directory to check - * - * @return bool Returns true if the file/directory exist, false otherwise. - */ - public function exists(string $path): bool; + public function write(string $path, $resource): int; /** * Removes files or directories @@ -61,37 +55,6 @@ interface adapter_interface */ public function delete(string $path): void; - /** - * Rename a file or a directory - * - * @param string $path_orig The original file/direcotry - * @param string $path_dest The target file/directory - * - * @throws storage_exception When file/directory cannot be renamed - */ - public function rename(string $path_orig, string $path_dest): void; - - /** - * Copies a file - * - * @param string $path_orig The original filename - * @param string $path_dest The target filename - * - * @throws storage_exception When the file cannot be copied - */ - public function copy(string $path_orig, string $path_dest): void; - - /** - * Get file size in bytes - * - * @param string $path The file - * - * @return int Size in bytes. - * - * @throws storage_exception When unable to retrieve file size - */ - public function file_size(string $path): int; - /** * Get space available in bytes * diff --git a/phpBB/phpbb/storage/adapter/local.php b/phpBB/phpbb/storage/adapter/local.php index 72d92096d5..3099df6241 100644 --- a/phpBB/phpbb/storage/adapter/local.php +++ b/phpBB/phpbb/storage/adapter/local.php @@ -13,7 +13,6 @@ namespace phpbb\storage\adapter; -use phpbb\storage\stream_interface; use phpbb\storage\exception\storage_exception; use phpbb\filesystem\exception\filesystem_exception; use phpbb\filesystem\filesystem; @@ -22,7 +21,7 @@ use phpbb\filesystem\helper as filesystem_helper; /** * Experimental */ -class local implements adapter_interface, stream_interface +class local implements adapter_interface { /** * Filesystem component @@ -47,16 +46,6 @@ class local implements adapter_interface, stream_interface */ protected $root_path; - /** - * Relative path from $phpbb_root_path to the storage folder - * Always finish with slash (/) character - * Example: - * - images/avatars/upload/ - * - * @var string path - */ - protected $path; - /** * Constructor * @@ -71,176 +60,20 @@ class local implements adapter_interface, stream_interface /** * {@inheritdoc} + * + * */ public function configure(array $options): void { - $this->path = $options['path']; - - if (substr($this->path, -1, 1) !== '/') - { - $this->path = $this->path . '/'; - } - $this->root_path = filesystem_helper::realpath($this->phpbb_root_path . $options['path']) . DIRECTORY_SEPARATOR; } /** * {@inheritdoc} */ - public function put_contents(string $path, string $content): void + public function read(string $path) { - $this->ensure_directory_exists($path); - - try - { - $this->filesystem->dump_file($this->root_path . $this->get_path($path) . $this->get_filename($path), $content); - } - catch (filesystem_exception $e) - { - throw new storage_exception('STORAGE_CANNOT_WRITE_FILE', $path, array(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function get_contents(string $path): string - { - $content = @file_get_contents($this->root_path . $this->get_path($path) . $this->get_filename($path)); - - if ($content === false) - { - throw new storage_exception('STORAGE_CANNOT_READ_FILE', $path); - } - - return $content; - } - - /** - * {@inheritdoc} - */ - public function exists(string $path): bool - { - return $this->filesystem->exists($this->root_path . $this->get_path($path) . $this->get_filename($path)); - } - - /** - * {@inheritdoc} - */ - public function delete(string $path): void - { - try - { - $this->filesystem->remove($this->root_path . $this->get_path($path) . $this->get_filename($path)); - } - catch (filesystem_exception $e) - { - throw new storage_exception('STORAGE_CANNOT_DELETE', $path, array(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function rename(string $path_orig, string $path_dest): void - { - $this->ensure_directory_exists($path_dest); - - try - { - $this->filesystem->rename($this->root_path . $this->get_path($path_orig) . $this->get_filename($path_orig), $this->root_path . $this->get_path($path_dest) . $this->get_filename($path_dest), false); - } - catch (filesystem_exception $e) - { - throw new storage_exception('STORAGE_CANNOT_RENAME', $path_orig, array(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function copy(string $path_orig, string $path_dest): void - { - $this->ensure_directory_exists($path_dest); - - try - { - $this->filesystem->copy($this->root_path . $this->get_path($path_orig) . $this->get_filename($path_orig), $this->root_path . $this->get_path($path_dest) . $this->get_filename($path_dest), false); - } - catch (filesystem_exception $e) - { - throw new storage_exception('STORAGE_CANNOT_COPY', $path_orig, array(), $e); - } - } - - /** - * Creates a directory recursively. - * - * @param string $path The directory path - * - * @throws storage_exception On any directory creation failure - */ - protected function create_dir(string $path): void - { - try - { - $this->filesystem->mkdir($this->root_path . $path); - } - catch (filesystem_exception $e) - { - throw new storage_exception('STORAGE_CANNOT_CREATE_DIR', $path, array(), $e); - } - } - - /** - * Ensures that the directory of a file exists. - * - * @param string $path The file path - * - * @throws storage_exception On any directory creation failure - */ - protected function ensure_directory_exists(string $path): void - { - $path = dirname($this->root_path . $this->get_path($path) . $this->get_filename($path)); - $path = filesystem_helper::make_path_relative($path, $this->root_path); - - if (!$this->exists($path)) - { - $this->create_dir($path); - } - } - - /** - * Get the path to the file - * - * @param string $path The file path - * @return string - */ - protected function get_path(string $path): string - { - $dirname = dirname($path); - $dirname = ($dirname != '.') ? $dirname . DIRECTORY_SEPARATOR : ''; - - return $dirname; - } - - /** - * To be used in other PR - * - * @param string $path The file path - * @return string - */ - protected function get_filename(string $path): string - { - return basename($path); - } - - /** - * {@inheritdoc} - */ - public function read_stream(string $path) - { - $stream = @fopen($this->root_path . $this->get_path($path) . $this->get_filename($path), 'rb'); + $stream = @fopen($this->root_path . $path, 'rb'); if (!$stream) { @@ -253,60 +86,52 @@ class local implements adapter_interface, stream_interface /** * {@inheritdoc} */ - public function write_stream(string $path, $resource): void + public function write(string $path, $resource): int { - $this->ensure_directory_exists($path); - - $stream = @fopen($this->root_path . $this->get_path($path) . $this->get_filename($path), 'w+b'); + $stream = @fopen($this->root_path . $path, 'w+b'); if (!$stream) { throw new storage_exception('STORAGE_CANNOT_CREATE_FILE', $path); } - if (stream_copy_to_stream($resource, $stream) === false) + if (($size = stream_copy_to_stream($resource, $stream)) === false) { fclose($stream); throw new storage_exception('STORAGE_CANNOT_COPY_RESOURCE'); } fclose($stream); - } - - /** - * {@inheritdoc} - */ - public function file_size(string $path): int - { - $size = @filesize($this->root_path . $this->get_path($path) . $this->get_filename($path)); - - if ($size === null) - { - throw new storage_exception('STORAGE_CANNOT_GET_FILESIZE'); - } return $size; } + /** + * {@inheritdoc} + */ + public function delete(string $path): void + { + try + { + $this->filesystem->remove($this->root_path . $path); + } + catch (filesystem_exception $e) + { + throw new storage_exception('STORAGE_CANNOT_DELETE', $path, array(), $e); + } + } + /** * {@inheritdoc} */ public function free_space(): float { - if (function_exists('disk_free_space')) - { - $free_space = @disk_free_space($this->root_path); - - if ($free_space === false) - { - throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); - } - } - else + if (!function_exists('disk_free_space') || ($free_space = @disk_free_space($this->root_path)) === false) { throw new storage_exception('STORAGE_CANNOT_GET_FREE_SPACE'); } return $free_space; } + } diff --git a/phpBB/phpbb/storage/adapter_factory.php b/phpBB/phpbb/storage/adapter_factory.php index 61ea879482..3d55fc7a41 100644 --- a/phpBB/phpbb/storage/adapter_factory.php +++ b/phpBB/phpbb/storage/adapter_factory.php @@ -67,20 +67,20 @@ class adapter_factory $options[$definition] = $this->config['storage\\' . $storage_name . '\\config\\' . $definition]; } - return $this->get_with_options($storage_name, $options); + return $this->get_with_options($storage_name, $provider_class, $options); } /** - * Obtains a configured adapters for a given storage with custom options + * Obtains a configured adapters with custom options * * @param string $storage_name + * @param string $provider_class * @param array $options * * @return mixed */ - public function get_with_options(string $storage_name, array $options): mixed + public function get_with_options(string $storage_name, string $provider_class, array $options): mixed { - $provider_class = $this->config['storage\\' . $storage_name . '\\provider']; $provider = $this->providers->get_by_class($provider_class); if (!$provider->is_available()) @@ -89,6 +89,7 @@ class adapter_factory } $adapter = $this->adapters->get_by_class($provider->get_adapter_class()); + $options['storage'] = $storage_name; $adapter->configure($options); return $adapter; diff --git a/phpBB/phpbb/storage/controller/attachment.php b/phpBB/phpbb/storage/controller/attachment.php index 9d84fceb45..7aed1ccdb3 100644 --- a/phpBB/phpbb/storage/controller/attachment.php +++ b/phpBB/phpbb/storage/controller/attachment.php @@ -293,11 +293,11 @@ class attachment extends controller /** * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551 */ - protected function filenameFallback($filename) + protected function filenameFallback($filename): string { - $filename = preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename); + $filename = (string) preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\\/'], '', $filename); - return (!empty($filename)) ?: 'File'; + return !empty($filename) ? $filename : 'File'; } /** @@ -305,7 +305,7 @@ class attachment extends controller */ protected function prepare(StreamedResponse $response, string $file): void { - $response->setPrivate(); // By default should be private, but make sure of it + $response->setPrivate(); // By default, should be private, but make sure of it parent::prepare($response, $file); } @@ -445,7 +445,7 @@ class attachment extends controller if (!$url) { - return ($this->config['secure_allow_empty_referer']) ? true : false; + return (bool) $this->config['secure_allow_empty_referer']; } // Split URL into domain and script part @@ -453,13 +453,13 @@ class attachment extends controller if ($url === false) { - return ($this->config['secure_allow_empty_referer']) ? true : false; + return (bool) $this->config['secure_allow_empty_referer']; } $hostname = $url['host']; unset($url); - $allowed = ($this->config['secure_allow_deny']) ? false : true; + $allowed = !$this->config['secure_allow_deny']; $iplist = array(); if (($ip_ary = @gethostbynamel($hostname)) !== false) diff --git a/phpBB/phpbb/storage/controller/avatar.php b/phpBB/phpbb/storage/controller/avatar.php index 720745e2cc..ba8a8a4e95 100644 --- a/phpBB/phpbb/storage/controller/avatar.php +++ b/phpBB/phpbb/storage/controller/avatar.php @@ -90,7 +90,7 @@ class avatar extends controller } $ext = substr(strrchr($file, '.'), 1); - $file = (int) $file; + $file = (int) $file; // This removes the timestamp leaving only the user id return $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $file . '.' . $ext; } diff --git a/phpBB/phpbb/storage/controller/controller.php b/phpBB/phpbb/storage/controller/controller.php index 94af901eed..155d3df7a6 100644 --- a/phpBB/phpbb/storage/controller/controller.php +++ b/phpBB/phpbb/storage/controller/controller.php @@ -159,7 +159,7 @@ class controller @set_time_limit(0); - $fp = $this->storage->read_stream($file); + $fp = $this->storage->read($file); // Close db connection $this->file_gc(); @@ -173,7 +173,7 @@ class controller flush(); // Terminate script to avoid the execution of terminate events - // This avoid possible errors with db connection closed + // This avoids possible errors with db connection closed exit; }); diff --git a/phpBB/phpbb/storage/exception/storage_exception.php b/phpBB/phpbb/storage/exception/storage_exception.php index 08c0cfa4ef..205cf82561 100644 --- a/phpBB/phpbb/storage/exception/storage_exception.php +++ b/phpBB/phpbb/storage/exception/storage_exception.php @@ -36,7 +36,7 @@ class storage_exception extends runtime_exception * * @return string */ - public function get_filename() + public function get_filename(): string { $parameters = $this->get_parameters(); return $parameters['filename']; diff --git a/phpBB/phpbb/storage/file_tracker.php b/phpBB/phpbb/storage/file_tracker.php new file mode 100644 index 0000000000..c858b733ac --- /dev/null +++ b/phpBB/phpbb/storage/file_tracker.php @@ -0,0 +1,198 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\storage; + +use phpbb\cache\driver\driver_interface as cache; +use phpbb\db\driver\driver_interface as db; +use phpbb\storage\exception\storage_exception; + +class file_tracker +{ + + /** + * @var db + */ + protected $db; + + /** + * Cache driver + * @var cache + */ + protected $cache; + + /** + * @var string + */ + protected $storage_table; + + /** + * Constructor + * + * @param db $db + * @param cache $cache + * @param string $storage_table + */ + public function __construct(db $db, cache $cache, string $storage_table) + { + $this->db = $db; + $this->cache = $cache; + $this->storage_table = $storage_table; + } + + /** + * Track file in database + * + * @param string $storage Storage name + * @param string $path The target file + * @param int $size Size in bytes + */ + public function track_file(string $storage, string $path, int $size): void + { + $sql_ary = array( + 'file_path' => $path, + 'storage' => $storage, + 'filesize' => $size, + ); + + $sql = 'INSERT INTO ' . $this->storage_table . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('_storage_' . $storage . '_totalsize'); + $this->cache->destroy('_storage_' . $storage . '_numfiles'); + } + + /** + * Untrack file + * + * @param string $storage Storage name + * @param string $path The target file + */ + public function untrack_file(string $storage, $path): void + { + $sql_ary = array( + 'file_path' => $path, + 'storage' => $storage, + ); + + $sql = 'DELETE FROM ' . $this->storage_table . ' + WHERE ' . $this->db->sql_build_array('DELETE', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('_storage_' . $storage . '_totalsize'); + $this->cache->destroy('_storage_' . $storage . '_numfiles'); + } + + /** + * Check if a file is tracked + * + * @param string $storage Storage name + * @param string $path The file + * + * @return bool True if file is tracked + */ + public function is_tracked(string $storage, string $path): bool + { + $sql_ary = array( + 'file_path' => $path, + 'storage' => $storage, + ); + + $sql = 'SELECT file_id FROM ' . $this->storage_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row !== false; + } + + /** + * Get file size in bytes + * + * @param string $path The file + * + * @return int Size in bytes. + * + * @throws storage_exception When unable to retrieve file size + */ + public function file_size(string $storage, string $path): int + { + $sql_ary = array( + 'file_path' => $path, + 'storage' => $storage, + ); + + $sql = 'SELECT filesize FROM ' . $this->storage_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return (int) $row['filesize']; + } + + /** + * Get number of tracked storage files for a storage + * + * @param string $storage Storage name + * + * @return int Number of files + */ + public function total_files(string $storage): int + { + $number_files = $this->cache->get('_storage_' . $storage. '_numfiles'); + + if ($number_files === false) + { + $sql = 'SELECT COUNT(file_id) AS numfiles + FROM ' . $this->storage_table . " + WHERE storage = '" . $this->db->sql_escape($storage) . "'"; + $result = $this->db->sql_query($sql); + + $number_files = $this->db->sql_fetchfield('numfiles'); + $this->cache->put('_storage_' . $storage . '_numfiles', $number_files); + + $this->db->sql_freeresult($result); + } + + return (int) $number_files; + } + + /** + * Get total storage size + * + * @param string $storage Storage name + * + * @return float Size in bytes + */ + public function total_size(string $storage): float + { + $total_size = $this->cache->get('_storage_' . $storage . '_totalsize'); + + if ($total_size === false) + { + $sql = 'SELECT SUM(filesize) AS totalsize + FROM ' . $this->storage_table . " + WHERE storage = '" . $this->db->sql_escape($storage) . "'"; + $result = $this->db->sql_query($sql); + + $total_size = $this->db->sql_fetchfield('totalsize'); + $this->cache->put('_storage_' . $storage . '_totalsize', $total_size); + + $this->db->sql_freeresult($result); + } + + return (float) $total_size; + } +} diff --git a/phpBB/phpbb/storage/helper.php b/phpBB/phpbb/storage/helper.php index 66921b60a7..66db2f4e8d 100644 --- a/phpBB/phpbb/storage/helper.php +++ b/phpBB/phpbb/storage/helper.php @@ -129,7 +129,7 @@ class helper $options[$definition] = $this->state_helper->new_definition_value($storage_name, $definition); } - $adapters[$storage_name] = $this->adapter_factory->get_with_options($storage_name, $options); + $adapters[$storage_name] = $this->adapter_factory->get_with_options($storage_name, $provider_class, $options); } return $adapters[$storage_name]; @@ -193,8 +193,8 @@ class helper $current_adapter = $this->get_current_adapter($storage_name); $new_adapter = $this->get_new_adapter($storage_name); - $stream = $current_adapter->read_stream($file); - $new_adapter->write_stream($file, $stream); + $stream = $current_adapter->read($file); + $new_adapter->write($file, $stream); if (is_resource($stream)) { diff --git a/phpBB/phpbb/storage/provider/local.php b/phpBB/phpbb/storage/provider/local.php index c6338e0c31..67a1789434 100644 --- a/phpBB/phpbb/storage/provider/local.php +++ b/phpBB/phpbb/storage/provider/local.php @@ -18,7 +18,7 @@ class local implements provider_interface /** * {@inheritdoc} */ - public function get_name() + public function get_name(): string { return 'local'; } @@ -34,7 +34,7 @@ class local implements provider_interface /** * {@inheritdoc} */ - public function get_options() + public function get_options(): array { return [ 'path' => [ @@ -47,7 +47,7 @@ class local implements provider_interface /** * {@inheritdoc} */ - public function is_available() + public function is_available(): bool { return true; } diff --git a/phpBB/phpbb/storage/provider/provider_interface.php b/phpBB/phpbb/storage/provider/provider_interface.php index 484b660aa8..aa01a51e37 100644 --- a/phpBB/phpbb/storage/provider/provider_interface.php +++ b/phpBB/phpbb/storage/provider/provider_interface.php @@ -20,7 +20,7 @@ interface provider_interface * * @return string */ - public function get_name(); + public function get_name(): string; /** * Gets adapter class @@ -34,12 +34,12 @@ interface provider_interface * * @return array Configuration keys */ - public function get_options(); + public function get_options(): array; /** * Return true if the adapter is available * * @return bool */ - public function is_available(); + public function is_available(): bool; } diff --git a/phpBB/phpbb/storage/storage.php b/phpBB/phpbb/storage/storage.php index fa1ca0d204..b07ea62441 100644 --- a/phpBB/phpbb/storage/storage.php +++ b/phpBB/phpbb/storage/storage.php @@ -13,8 +13,6 @@ namespace phpbb\storage; -use phpbb\cache\driver\driver_interface as cache; -use phpbb\db\driver\driver_interface as db; use phpbb\storage\adapter\adapter_interface; use phpbb\storage\exception\storage_exception; @@ -28,48 +26,33 @@ class storage */ protected $adapter; - /** - * @var db - */ - protected $db; - - /** - * Cache driver - * @var cache - */ - protected $cache; - /** * @var adapter_factory */ protected $factory; + /** + * @var file_tracker + */ + protected $file_tracker; + /** * @var string */ protected $storage_name; - /** - * @var string - */ - protected $storage_table; - /** * Constructor * - * @param db $db - * @param cache $cache * @param adapter_factory $factory + * @param file_tracker $file_tracker * @param string $storage_name - * @param string $storage_table */ - public function __construct(db $db, cache $cache, adapter_factory $factory, string $storage_name, string $storage_table) + public function __construct(adapter_factory $factory, file_tracker $file_tracker, string $storage_name) { - $this->db = $db; - $this->cache = $cache; $this->factory = $factory; + $this->file_tracker = $file_tracker; $this->storage_name = $storage_name; - $this->storage_table = $storage_table; } /** @@ -98,57 +81,48 @@ class storage } /** - * Dumps content into a file + * Reads a file as a stream * - * @param string $path The file to be written to. - * @param string $content The data to write into the file. - * - * @throws storage_exception When the file already exists - * When the file cannot be written - */ - public function put_contents(string $path, string $content): void - { - if ($this->exists($path)) - { - throw new storage_exception('STORAGE_FILE_EXISTS', $path); - } - - $this->get_adapter()->put_contents($path, $content); - $this->track_file($path); - } - - /** - * Read the contents of a file - * - * @param string $path The file to read - * - * @return string Returns file contents + * @param string $path File to read * + * @return resource Returns a file pointer * @throws storage_exception When the file doesn't exist - * When cannot read file contents + * When unable to open file * */ - public function get_contents(string $path): string + public function read(string $path) { if (!$this->exists($path)) { throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); } - return $this->get_adapter()->get_contents($path); + return $this->get_adapter()->read($path); } /** - * Checks the existence of files or directories + * Writes a new file using a stream * - * @param string $path file/directory to check - * @param bool $full_check check in the filesystem too + * @param string $path The target file + * @param resource $resource The resource * - * @return bool Returns true if the file/directory exist, false otherwise + * @throws storage_exception When the file exist + * When target file cannot be created */ - public function exists(string $path, bool $full_check = false): bool + public function write(string $path, $resource): void { - return ($this->is_tracked($path) && (!$full_check || $this->get_adapter()->exists($path))); + if ($this->exists($path)) + { + throw new storage_exception('STORAGE_FILE_EXISTS', $path); + } + + if (!is_resource($resource)) + { + throw new storage_exception('STORAGE_INVALID_RESOURCE'); + } + + $size = $this->get_adapter()->write($path, $resource); + $this->file_tracker->track_file($this->storage_name, $path, $size); } /** @@ -167,231 +141,19 @@ class storage } $this->get_adapter()->delete($path); - $this->untrack_file($path); + $this->file_tracker->untrack_file($this->get_name(), $path); } /** - * Rename a file or a directory + * Checks the existence of files or directories * - * @param string $path_orig The original file/direcotry - * @param string $path_dest The target file/directory + * @param string $path file/directory to check * - * @throws storage_exception When the file doesn't exist - * When target exists - * When file/directory cannot be renamed + * @return bool Returns true if the file/directory exist, false otherwise */ - public function rename(string $path_orig, string $path_dest): void + public function exists(string $path): bool { - if (!$this->exists($path_orig)) - { - throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); - } - - if ($this->exists($path_dest)) - { - throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); - } - - $this->get_adapter()->rename($path_orig, $path_dest); - $this->track_rename($path_orig, $path_dest); - } - - /** - * Copies a file - * - * @param string $path_orig The original filename - * @param string $path_dest The target filename - * - * @throws storage_exception When the file doesn't exist - * When target exists - * When the file cannot be copied - */ - public function copy(string $path_orig, string $path_dest): void - { - if (!$this->exists($path_orig)) - { - throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); - } - - if ($this->exists($path_dest)) - { - throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); - } - - $this->get_adapter()->copy($path_orig, $path_dest); - $this->track_file($path_dest); - } - - /** - * Reads a file as a stream - * - * @param string $path File to read - * - * @return resource Returns a file pointer - * @throws storage_exception When the file doesn't exist - * When unable to open file - * - */ - public function read_stream(string $path) - { - if (!$this->exists($path)) - { - throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); - } - - $stream = null; - $adapter = $this->get_adapter(); - - if ($adapter instanceof stream_interface) - { - $stream = $adapter->read_stream($path); - } - else - { - // Simulate the stream - $stream = fopen('php://temp', 'w+b'); - fwrite($stream, $adapter->get_contents($path)); - rewind($stream); - } - - return $stream; - } - - /** - * Writes a new file using a stream - * - * @param string $path The target file - * @param resource $resource The resource - * - * @throws storage_exception When the file exist - * When target file cannot be created - */ - public function write_stream(string $path, $resource): void - { - if ($this->exists($path)) - { - throw new storage_exception('STORAGE_FILE_EXISTS', $path); - } - - if (!is_resource($resource)) - { - throw new storage_exception('STORAGE_INVALID_RESOURCE'); - } - - $adapter = $this->get_adapter(); - - if ($adapter instanceof stream_interface) - { - $adapter->write_stream($path, $resource); - $this->track_file($path); - } - else - { - // Simulate the stream - $adapter->put_contents($path, stream_get_contents($resource)); - } - } - - /** - * Track file in database - * - * @param string $path The target file - * @param bool $update Update file size when already tracked - */ - public function track_file(string $path, bool $update = false): void - { - if (!$this->get_adapter()->exists($path)) - { - throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); - } - - $sql_ary = array( - 'file_path' => $path, - 'storage' => $this->get_name(), - ); - - // Get file, if exist update filesize, if not add new record - $sql = 'SELECT * FROM ' . $this->storage_table . ' - WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - if (!$row) - { - $sql_ary['filesize'] = $this->get_adapter()->file_size($path); - - $sql = 'INSERT INTO ' . $this->storage_table . $this->db->sql_build_array('INSERT', $sql_ary); - $this->db->sql_query($sql); - } - else if ($update) - { - $sql = 'UPDATE ' . $this->storage_table . ' - SET filesize = ' . $this->get_adapter()->file_size($path) . ' - WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); - $this->db->sql_query($sql); - } - - $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize'); - $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles'); - } - - /** - * Untrack file - * - * @param string $path The target file - */ - public function untrack_file($path) - { - $sql_ary = array( - 'file_path' => $path, - 'storage' => $this->get_name(), - ); - - $sql = 'DELETE FROM ' . $this->storage_table . ' - WHERE ' . $this->db->sql_build_array('DELETE', $sql_ary); - $this->db->sql_query($sql); - - $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize'); - $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles'); - } - - /** - * Check if a file is tracked - * - * @param string $path The file - * - * @return bool True if file is tracked - */ - public function is_tracked(string $path): bool - { - $sql_ary = array( - 'file_path' => $path, - 'storage' => $this->get_name(), - ); - - $sql = 'SELECT file_id FROM ' . $this->storage_table . ' - WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - return $row !== false; - } - - /** - * Rename tracked file - * - * @param string $path_orig The original file/direcotry - * @param string $path_dest The target file/directory - */ - protected function track_rename(string $path_orig, string $path_dest): void - { - $sql = 'UPDATE ' . $this->storage_table . " - SET file_path = '" . $this->db->sql_escape($path_dest) . "' - WHERE file_path = '" . $this->db->sql_escape($path_orig) . "' - AND storage = '" . $this->db->sql_escape($this->get_name()) . "'"; - $this->db->sql_query($sql); + return $this->file_tracker->is_tracked($this->get_name(), $path); } /** @@ -400,73 +162,30 @@ class storage * @param string $path The file * * @return int Size in bytes. - * - * @throws storage_exception When unable to retrieve file size */ public function file_size(string $path): int { - $sql_ary = array( - 'file_path' => $path, - 'storage' => $this->get_name(), - ); + return $this->file_tracker->file_size($this->get_name(), $path); + } - $sql = 'SELECT filesize FROM ' . $this->storage_table . ' - WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); - $result = $this->db->sql_query($sql); - $row = $this->db->sql_fetchrow($result); - $this->db->sql_freeresult($result); - - return $row !== false && !empty($row['filesize']) ? $row['filesize'] : $this->get_adapter()->file_size($path); + /** + * Return the number of files stored in this storage + * + * @return int Number of files. + */ + public function total_files(): int + { + return $this->file_tracker->total_files($this->get_name()); } /** * Get total storage size * - * @return int Size in bytes + * @return float Size in bytes */ - public function get_size(): int + public function total_size(): float { - $total_size = $this->cache->get('_storage_' . $this->get_name() . '_totalsize'); - - if ($total_size === false) - { - $sql = 'SELECT SUM(filesize) AS totalsize - FROM ' . $this->storage_table . " - WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'"; - $result = $this->db->sql_query($sql); - - $total_size = (int) $this->db->sql_fetchfield('totalsize'); - $this->cache->put('_storage_' . $this->get_name() . '_totalsize', $total_size); - - $this->db->sql_freeresult($result); - } - - return (int) $total_size; - } - - /** - * Get number of storage files - * - * @return int Number of files - */ - public function get_num_files(): int - { - $number_files = $this->cache->get('_storage_' . $this->get_name() . '_numfiles'); - - if ($number_files === false) - { - $sql = 'SELECT COUNT(file_id) AS numfiles - FROM ' . $this->storage_table . " - WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'"; - $result = $this->db->sql_query($sql); - - $number_files = (int) $this->db->sql_fetchfield('numfiles'); - $this->cache->put('_storage_' . $this->get_name() . '_numfiles', $number_files); - - $this->db->sql_freeresult($result); - } - - return (int) $number_files; + return $this->file_tracker->total_size($this->get_name()); } /** diff --git a/phpBB/phpbb/storage/stream_interface.php b/phpBB/phpbb/storage/stream_interface.php deleted file mode 100644 index 424ffcb95c..0000000000 --- a/phpBB/phpbb/storage/stream_interface.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license GNU General Public License, version 2 (GPL-2.0) - * - * For full copyright and license information, please see - * the docs/CREDITS.txt file. - * - */ - -namespace phpbb\storage; - -use phpbb\storage\exception\storage_exception; - -interface stream_interface -{ - /** - * Reads a file as a stream - * - * @param string $path File to read - * - * @return resource Returns a file pointer - * @throws storage_exception When unable to open file - */ - public function read_stream(string $path); - - /** - * Writes a new file using a stream - * - * @param string $path The target file - * @param resource $resource The resource - * - * @return void - * @throws storage_exception When target file exists - * When target file cannot be created - */ - public function write_stream(string $path, $resource): void; -} diff --git a/tests/storage/adapter/local_test.php b/tests/storage/adapter/local_test.php index 2fee54b2f5..01a7884596 100644 --- a/tests/storage/adapter/local_test.php +++ b/tests/storage/adapter/local_test.php @@ -22,51 +22,6 @@ class phpbb_storage_adapter_local_test extends phpbb_local_test_case $this->adapter->configure(['path' => 'test_path']); } - public function test_put_contents(): void - { - // When - $this->adapter->put_contents('file.txt', 'abc'); - - // Then - $this->assertFileExists($this->path . 'file.txt'); - $this->assertFileContains($this->path . 'file.txt', 'abc'); - - // Clean test - unlink($this->path . 'file.txt'); - } - - public function test_get_contents(): void - { - // Given - file_put_contents($this->path . 'file.txt', 'abc'); - - // When - $content = $this->adapter->get_contents('file.txt'); - - // Then - $this->assertEquals('abc', $content); - - // Clean test - unlink($this->path . 'file.txt'); - } - - public function test_exists(): void - { - // Given - touch($this->path . 'file.txt'); - - // When - $existent_file = $this->adapter->exists('file.txt'); - $non_existent_file = $this->adapter->exists('noexist.txt'); - - // Then - $this->assertTrue($existent_file); - $this->assertFalse($non_existent_file); - - // Clean test - unlink($this->path . 'file.txt'); - } - public function test_delete_file(): void { // Given @@ -80,48 +35,13 @@ class phpbb_storage_adapter_local_test extends phpbb_local_test_case $this->assertFileDoesNotExist($this->path . 'file.txt'); } - public function test_rename(): void - { - // Given - touch($this->path . 'file.txt'); - $this->assertFileExists($this->path . 'file.txt'); - $this->assertFileDoesNotExist($this->path . 'file2.txt'); - - // When - $this->adapter->rename('file.txt', 'file2.txt'); - - // Then - $this->assertFileDoesNotExist($this->path . 'file.txt'); - $this->assertFileExists($this->path . 'file2.txt'); - - // Clean test - unlink($this->path . 'file2.txt'); - } - - public function test_copy(): void + public function test_read() { // Given file_put_contents($this->path . 'file.txt', 'abc'); // When - $this->adapter->copy('file.txt', 'file2.txt'); - - // Then - $this->assertFileContains($this->path . 'file.txt', 'abc'); - $this->assertFileContains($this->path . 'file2.txt', 'abc'); - - // Clean test - unlink($this->path . 'file.txt'); - unlink($this->path . 'file2.txt'); - } - - public function test_read_stream() - { - // Given - file_put_contents($this->path . 'file.txt', 'abc'); - - // When - $stream = $this->adapter->read_stream('file.txt'); + $stream = $this->adapter->read('file.txt'); // Then $this->assertIsResource($stream); @@ -132,14 +52,14 @@ class phpbb_storage_adapter_local_test extends phpbb_local_test_case unlink($this->path . 'file.txt'); } - public function test_write_stream() + public function test_write() { // Given file_put_contents($this->path . 'file.txt', 'abc'); $stream = fopen($this->path . 'file.txt', 'rb'); // When - $this->adapter->write_stream('file2.txt', $stream); + $this->adapter->write('file2.txt', $stream); fclose($stream); // Then