diff --git a/phpBB/config/default/container/services_avatar.yml b/phpBB/config/default/container/services_avatar.yml
index 6cc38516ae..93a8cd0b62 100644
--- a/phpBB/config/default/container/services_avatar.yml
+++ b/phpBB/config/default/container/services_avatar.yml
@@ -61,11 +61,11 @@ services:
- '@config'
- '%core.root_path%'
- '%core.php_ext%'
- - '@filesystem'
+ - '@storage.avatar'
- '@path_helper'
- '@dispatcher'
- '@files.factory'
- - '@cache.driver'
+ - '@php_ini'
calls:
- [set_name, [avatar.driver.upload]]
tags:
diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml
index abf51d5f97..32b74687c9 100644
--- a/phpBB/config/default/container/services_storage.yml
+++ b/phpBB/config/default/container/services_storage.yml
@@ -1,4 +1,14 @@
services:
+
+# Storages
+ storage.avatar:
+ class: phpbb\storage\storage
+ arguments:
+ - '@storage.adapter.factory'
+ - 'avatar'
+ tags:
+ - { name: storage }
+
# Factory
storage.adapter.factory:
class: phpbb\storage\adapter_factory
@@ -28,6 +38,8 @@ services:
shared: false
arguments:
- '@filesystem'
+ - '@upload_imagesize'
+ - '@mimetype.guesser'
- '%core.root_path%'
tags:
- { name: storage.adapter }
diff --git a/phpBB/develop/adjust_avatars.php b/phpBB/develop/adjust_avatars.php
index dc4ae88f37..a6b326109d 100644
--- a/phpBB/develop/adjust_avatars.php
+++ b/phpBB/develop/adjust_avatars.php
@@ -19,7 +19,7 @@ $auth->acl($user->data);
$user->setup();
$echos = 0;
-
+
if (!isset($config['avatar_salt']))
{
$cache->purge();
@@ -30,6 +30,11 @@ if (!isset($config['avatar_salt']))
die('database not up to date');
}
+if (!isset($config['storage\\avatar\\config\\path']) || $config['storage\\avatar\\config\\path'] !== 'phpbb\\storage\\provider\\local')
+{
+ die('use local provider');
+}
+
// let's start with the users using a group_avatar.
$sql = 'SELECT group_id, group_avatar
FROM ' . GROUPS_TABLE . '
@@ -46,16 +51,16 @@ while ($row = $db->sql_fetchrow($result))
{
$new_avatar_name = adjust_avatar($row['group_avatar'], 'g' . $row['group_id']);
$group_avatars[] = $new_avatar_name;
-
+
// failure is probably due to the avatar name already being adjusted
if ($new_avatar_name !== false)
{
$sql = 'UPDATE ' . USERS_TABLE . "
SET user_avatar = '" . $db->sql_escape($new_avatar_name) . "'
- WHERE user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
+ WHERE user_avatar = '" . $db->sql_escape($row['group_avatar']) . "'
AND user_avatar_type = " . AVATAR_UPLOAD;
$db->sql_query($sql);
-
+
$sql = 'UPDATE ' . GROUPS_TABLE . "
SET group_avatar = '" . $db->sql_escape($new_avatar_name) . "'
WHERE group_id = {$row['group_id']}";
@@ -80,8 +85,8 @@ while ($row = $db->sql_fetchrow($result))
$db->sql_freeresult($result);
$sql = 'SELECT user_id, username, user_avatar, user_avatar_type
- FROM ' . USERS_TABLE . '
- WHERE user_avatar_type = ' . AVATAR_UPLOAD . '
+ FROM ' . USERS_TABLE . '
+ WHERE user_avatar_type = ' . AVATAR_UPLOAD . '
AND ' . $db->sql_in_set('user_avatar', $group_avatars, true, true);
$result = $db->sql_query($sql);
@@ -108,7 +113,7 @@ while ($row = $db->sql_fetchrow($result))
$db->sql_query($sql);
echo '
Failed updating user ' . $row['user_id'] . "\n";
}
-
+
if ($echos > 200)
{
echo '
' . "\n";
@@ -131,8 +136,8 @@ $db->sql_close();
function adjust_avatar($old_name, $midfix)
{
global $config, $phpbb_root_path;
-
- $avatar_path = $phpbb_root_path . $config['avatar_path'];
+
+ $avatar_path = $phpbb_root_path . $config['storage\\avatar\\config\\path'];
$extension = strtolower(substr(strrchr($old_name, '.'), 1));
$new_name = $config['avatar_salt'] . '_' . $midfix . '.' . $extension;
diff --git a/phpBB/docs/coding-guidelines.html b/phpBB/docs/coding-guidelines.html
index 3e6919c88a..97feea71ec 100644
--- a/phpBB/docs/coding-guidelines.html
+++ b/phpBB/docs/coding-guidelines.html
@@ -264,7 +264,6 @@ PHPBB_QA (Set board to QA-Mode, which means the updater also c
{T_SUPER_TEMPLATE_PATH} - styles/xxx/template
{T_IMAGES_PATH} - images/
{T_SMILIES_PATH} - $config['smilies_path']/
- {T_AVATAR_PATH} - $config['avatar_path']/
{T_AVATAR_GALLERY_PATH} - $config['avatar_gallery_path']/
{T_ICONS_PATH} - $config['icons_path']/
{T_RANKS_PATH} - $config['ranks_path']/
diff --git a/phpBB/includes/acp/acp_main.php b/phpBB/includes/acp/acp_main.php
index 4efa8c70b3..86ce84a4a1 100644
--- a/phpBB/includes/acp/acp_main.php
+++ b/phpBB/includes/acp/acp_main.php
@@ -502,26 +502,8 @@ class acp_main
$upload_dir_size = get_formatted_filesize($config['upload_dir_size']);
- $avatar_dir_size = 0;
-
- if ($avatar_dir = @opendir($phpbb_root_path . $config['avatar_path']))
- {
- while (($file = readdir($avatar_dir)) !== false)
- {
- if ($file[0] != '.' && $file != 'CVS' && strpos($file, 'index.') === false)
- {
- $avatar_dir_size += filesize($phpbb_root_path . $config['avatar_path'] . '/' . $file);
- }
- }
- closedir($avatar_dir);
-
- $avatar_dir_size = get_formatted_filesize($avatar_dir_size);
- }
- else
- {
- // Couldn't open Avatar dir.
- $avatar_dir_size = $user->lang['NOT_AVAILABLE'];
- }
+ // Couldn't open Avatar dir.
+ $avatar_dir_size = $user->lang['NOT_AVAILABLE'];
if ($posts_per_day > $total_posts)
{
diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php
index 5bf86276c8..9f7d4f02e2 100644
--- a/phpBB/includes/functions.php
+++ b/phpBB/includes/functions.php
@@ -4434,7 +4434,6 @@ function page_header($page_title = '', $display_online_list = false, $item_id =
'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
'T_IMAGES_PATH' => "{$web_path}images/",
'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/",
- 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/",
'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/",
'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/",
'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/",
@@ -4452,7 +4451,6 @@ function page_header($page_title = '', $display_online_list = false, $item_id =
'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']),
'T_IMAGES' => 'images',
'T_SMILIES' => $config['smilies_path'],
- 'T_AVATAR' => $config['avatar_path'],
'T_AVATAR_GALLERY' => $config['avatar_gallery_path'],
'T_ICONS' => $config['icons_path'],
'T_RANKS' => $config['ranks_path'],
diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php
index c79a84f8d4..b3bde79339 100644
--- a/phpBB/includes/functions_acp.php
+++ b/phpBB/includes/functions_acp.php
@@ -88,7 +88,6 @@ function adm_page_header($page_title)
'T_IMAGES_PATH' => "{$phpbb_root_path}images/",
'T_SMILIES_PATH' => "{$phpbb_root_path}{$config['smilies_path']}/",
- 'T_AVATAR_PATH' => "{$phpbb_root_path}{$config['avatar_path']}/",
'T_AVATAR_GALLERY_PATH' => "{$phpbb_root_path}{$config['avatar_gallery_path']}/",
'T_ICONS_PATH' => "{$phpbb_root_path}{$config['icons_path']}/",
'T_RANKS_PATH' => "{$phpbb_root_path}{$config['ranks_path']}/",
diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php
index cee7c39035..332a9c600f 100644
--- a/phpBB/includes/functions_download.php
+++ b/phpBB/includes/functions_download.php
@@ -25,30 +25,27 @@ if (!defined('IN_PHPBB'))
*/
function send_avatar_to_browser($file, $browser)
{
- global $config, $phpbb_root_path;
+ global $config, $phpbb_container;
+
+ $storage = $phpbb_container->get('storage.avatar');
$prefix = $config['avatar_salt'] . '_';
- $image_dir = $config['avatar_path'];
+ $file_path = $prefix . $file;
- // Adjust image_dir path (no trailing slash)
- if (substr($image_dir, -1, 1) == '/' || substr($image_dir, -1, 1) == '\\')
+ if ($storage->exists($file_path) && !headers_sent())
{
- $image_dir = substr($image_dir, 0, -1) . '/';
- }
- $image_dir = str_replace(array('../', '..\\', './', '.\\'), '', $image_dir);
+ $file_info = $storage->file_info($file_path);
- if ($image_dir && ($image_dir[0] == '/' || $image_dir[0] == '\\'))
- {
- $image_dir = '';
- }
- $file_path = $phpbb_root_path . $image_dir . '/' . $prefix . $file;
-
- if ((@file_exists($file_path) && @is_readable($file_path)) && !headers_sent())
- {
header('Cache-Control: public');
- $image_data = @getimagesize($file_path);
- header('Content-Type: ' . image_type_to_mime_type($image_data[2]));
+ try
+ {
+ header('Content-Type: ' . $file_info->mimetype);
+ }
+ catch (\phpbb\storage\exception\exception $e)
+ {
+ // Just don't send this header
+ }
if ((strpos(strtolower($browser), 'msie') !== false) && !phpbb_is_greater_ie_version($browser, 7))
{
@@ -69,24 +66,26 @@ function send_avatar_to_browser($file, $browser)
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
}
- $size = @filesize($file_path);
- if ($size)
+ try
{
- header("Content-Length: $size");
+ header('Content-Length: ' . $file_info->size);
+ }
+ catch (\phpbb\storage\exception\exception $e)
+ {
+ // Just don't send this header
}
- if (@readfile($file_path) == false)
+ try
{
- $fp = @fopen($file_path, 'rb');
-
- if ($fp !== false)
- {
- while (!feof($fp))
- {
- echo fread($fp, 8192);
- }
- fclose($fp);
- }
+ $fp = $storage->read_stream($file_path);
+ $output = fopen('php://output', 'w+b');
+ stream_copy_to_stream($fp, $output);
+ fclose($fp);
+ fclose($output);
+ }
+ catch (\Exception $e)
+ {
+ // Send nothing
}
flush();
diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php
index 3a65a8820a..3042a5c290 100644
--- a/phpBB/includes/functions_user.php
+++ b/phpBB/includes/functions_user.php
@@ -2166,7 +2166,9 @@ function phpbb_style_is_active($style_id)
*/
function avatar_delete($mode, $row, $clean_db = false)
{
- global $phpbb_root_path, $config;
+ global $config, $phpbb_container;
+
+ $storage = $phpbb_container->get('storage.avatar');
// Check if the users avatar is actually *not* a group avatar
if ($mode == 'user')
@@ -2183,11 +2185,16 @@ function avatar_delete($mode, $row, $clean_db = false)
}
$filename = get_avatar_filename($row[$mode . '_avatar']);
- if (file_exists($phpbb_root_path . $config['avatar_path'] . '/' . $filename))
+ try
{
- @unlink($phpbb_root_path . $config['avatar_path'] . '/' . $filename);
+ $storage->delete($filename);
+
return true;
}
+ catch (\phpbb\storage\exception\exception $e)
+ {
+ // Fail is covered by return statement below
+ }
return false;
}
@@ -2507,7 +2514,9 @@ function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow
*/
function group_correct_avatar($group_id, $old_entry)
{
- global $config, $db, $phpbb_root_path;
+ global $config, $db, $phpbb_container;
+
+ $storage = $phpbb_container->get('storage.avatar');
$group_id = (int) $group_id;
$ext = substr(strrchr($old_entry, '.'), 1);
@@ -2515,14 +2524,19 @@ function group_correct_avatar($group_id, $old_entry)
$new_filename = $config['avatar_salt'] . "_g$group_id.$ext";
$new_entry = 'g' . $group_id . '_' . substr(time(), -5) . ".$ext";
- $avatar_path = $phpbb_root_path . $config['avatar_path'];
- if (@rename($avatar_path . '/'. $old_filename, $avatar_path . '/' . $new_filename))
+ try
{
+ $this->storage->rename($old_filename, $new_filename);
+
$sql = 'UPDATE ' . GROUPS_TABLE . '
SET group_avatar = \'' . $db->sql_escape($new_entry) . "'
WHERE group_id = $group_id";
$db->sql_query($sql);
}
+ catch (\phpbb\storage\exception\exception $e)
+ {
+ // If rename fail, dont execute the query
+ }
}
diff --git a/phpBB/install/convert/convertor.php b/phpBB/install/convert/convertor.php
index 5118651b71..619fc73275 100644
--- a/phpBB/install/convert/convertor.php
+++ b/phpBB/install/convert/convertor.php
@@ -281,7 +281,7 @@ class convertor
$bad_folders = array();
$local_paths = array(
- 'avatar_path' => path($config['avatar_path']),
+ 'avatar_path' => path($config['storage\\avatar\\config\\path']),
'avatar_gallery_path' => path($config['avatar_gallery_path']),
'icons_path' => path($config['icons_path']),
'ranks_path' => path($config['ranks_path']),
diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql
index ef3472a89a..fbe36bee05 100644
--- a/phpBB/install/schemas/schema_data.sql
+++ b/phpBB/install/schemas/schema_data.sql
@@ -55,7 +55,6 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_max_height'
INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_max_width', '90');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_min_height', '20');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_min_width', '20');
-INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_path', 'images/avatars/upload');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_salt', 'phpbb_avatar');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_contact', 'contact@yourdomain.tld');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_contact_name', '');
@@ -288,6 +287,8 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_json
INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_vendor_dir', 'vendor-ext/');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_enable_on_install', '0');
INSERT INTO phpbb_config (config_name, config_value) VALUES ('exts_composer_purge_on_remove', '1');
+INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\avatar\provider', 'phpbb\storage\provider\local');
+INSERT INTO phpbb_config (config_name, config_value) VALUES ('storage\avatar\config\path', 'images/avatars/upload');
INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1);
INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1);
diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php
index d765a27871..621164c765 100644
--- a/phpBB/phpbb/avatar/driver/upload.php
+++ b/phpBB/phpbb/avatar/driver/upload.php
@@ -13,15 +13,18 @@
namespace phpbb\avatar\driver;
+use bantu\IniGetWrapper\IniGetWrapper;
+use \phpbb\storage\exception\exception as storage_exception;
+
/**
* Handles avatars uploaded to the board
*/
class upload extends \phpbb\avatar\driver\driver
{
/**
- * @var \phpbb\filesystem\filesystem_interface
+ * @var \phpbb\storage\storage
*/
- protected $filesystem;
+ protected $storage;
/**
* @var \phpbb\event\dispatcher_interface
@@ -33,28 +36,33 @@ class upload extends \phpbb\avatar\driver\driver
*/
protected $files_factory;
+ /**
+ * @var IniGetWrapper
+ */
+ protected $php_ini;
+
/**
* Construct a driver object
*
* @param \phpbb\config\config $config phpBB configuration
* @param string $phpbb_root_path Path to the phpBB root
* @param string $php_ext PHP file extension
- * @param \phpbb\filesystem\filesystem_interface $filesystem phpBB filesystem helper
+ * @param \phpbb\storage\storage phpBB avatar storage
* @param \phpbb\path_helper $path_helper phpBB path helper
* @param \phpbb\event\dispatcher_interface $dispatcher phpBB Event dispatcher object
* @param \phpbb\files\factory $files_factory File classes factory
- * @param \phpbb\cache\driver\driver_interface $cache Cache driver
+ * @param IniGetWrapper $php_ini ini_get() wrapper
*/
- public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\filesystem\filesystem_interface $filesystem, \phpbb\path_helper $path_helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\files\factory $files_factory, \phpbb\cache\driver\driver_interface $cache = null)
+ public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\storage\storage $storage, \phpbb\path_helper $path_helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\files\factory $files_factory, IniGetWrapper $php_ini)
{
$this->config = $config;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
- $this->filesystem = $filesystem;
+ $this->storage = $storage;
$this->path_helper = $path_helper;
$this->dispatcher = $dispatcher;
$this->files_factory = $files_factory;
- $this->cache = $cache;
+ $this->php_ini = $php_ini;
}
/**
@@ -116,7 +124,7 @@ class upload extends \phpbb\avatar\driver\driver
if (!empty($upload_file['name']))
{
- $file = $upload->handle_upload('files.types.form', 'avatar_upload_file');
+ $file = $upload->handle_upload('files.types.form_storage', 'avatar_upload_file');
}
else if (!empty($this->config['allow_avatar_remote_upload']) && !empty($url))
{
@@ -156,7 +164,7 @@ class upload extends \phpbb\avatar\driver\driver
return false;
}
- $file = $upload->handle_upload('files.types.remote', $url);
+ $file = $upload->handle_upload('files.types.remote_storage', $url);
}
else
{
@@ -169,26 +177,11 @@ class upload extends \phpbb\avatar\driver\driver
// If there was an error during upload, then abort operation
if (count($file->error))
{
- $file->remove();
+ $file->remove($this->storage);
$error = $file->error;
return false;
}
- // Calculate new destination
- $destination = $this->config['avatar_path'];
-
- // Adjust destination path (no trailing slash)
- if (substr($destination, -1, 1) == '/' || substr($destination, -1, 1) == '\\')
- {
- $destination = substr($destination, 0, -1);
- }
-
- $destination = str_replace(array('../', '..\\', './', '.\\'), '', $destination);
- if ($destination && ($destination[0] == '/' || $destination[0] == "\\"))
- {
- $destination = '';
- }
-
$filedata = array(
'filename' => $file->get('filename'),
'filesize' => $file->get('filesize'),
@@ -203,7 +196,6 @@ class upload extends \phpbb\avatar\driver\driver
*
* @event core.avatar_driver_upload_move_file_before
* @var array filedata Array containing uploaded file data
- * @var string destination Destination directory where the file is going to be moved
* @var string prefix Prefix for the avatar filename
* @var array row Array with avatar row data
* @var array error Array of errors, if filled in by this event file will not be moved
@@ -212,7 +204,6 @@ class upload extends \phpbb\avatar\driver\driver
*/
$vars = array(
'filedata',
- 'destination',
'prefix',
'row',
'error',
@@ -224,14 +215,14 @@ class upload extends \phpbb\avatar\driver\driver
if (!count($error))
{
// Move file and overwrite any existing image
- $file->move_file($destination, true);
+ $file->move_file($this->storage, true);
}
// If there was an error during move, then clean up leftovers
$error = array_merge($error, $file->error);
if (count($error))
{
- $file->remove();
+ $file->remove($this->storage);
return false;
}
@@ -257,7 +248,6 @@ class upload extends \phpbb\avatar\driver\driver
return array(
'allow_avatar_remote_upload'=> array('lang' => 'ALLOW_REMOTE_UPLOAD', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true),
'avatar_filesize' => array('lang' => 'MAX_FILESIZE', 'validate' => 'int:0', 'type' => 'number:0', 'explain' => true, 'append' => ' ' . $user->lang['BYTES']),
- 'avatar_path' => array('lang' => 'AVATAR_STORAGE_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true),
);
}
@@ -268,37 +258,35 @@ class upload extends \phpbb\avatar\driver\driver
{
$error = array();
- $destination = $this->config['avatar_path'];
$prefix = $this->config['avatar_salt'] . '_';
$ext = substr(strrchr($row['avatar'], '.'), 1);
- $filename = $this->phpbb_root_path . $destination . '/' . $prefix . $row['id'] . '.' . $ext;
+ $filename = $prefix . $row['id'] . '.' . $ext;
/**
* Before deleting an existing avatar
*
* @event core.avatar_driver_upload_delete_before
- * @var string destination Destination directory where the file is going to be deleted
* @var string prefix Prefix for the avatar filename
* @var array row Array with avatar row data
* @var array error Array of errors, if filled in by this event file will not be deleted
* @since 3.1.6-RC1
+ * @changed 3.3.0-a1 Remove destination
*/
$vars = array(
- 'destination',
'prefix',
'row',
'error',
);
extract($this->dispatcher->trigger_event('core.avatar_driver_upload_delete_before', compact($vars)));
- if (!count($error) && $this->filesystem->exists($filename))
+ if (!count($error) && $this->storage->exists($filename))
{
try
{
- $this->filesystem->remove($filename);
+ $this->storage->delete($filename);
return true;
}
- catch (\phpbb\filesystem\exception\filesystem_exception $e)
+ catch (storage_exception $e)
{
// Fail is covered by return statement below
}
@@ -316,12 +304,12 @@ class upload extends \phpbb\avatar\driver\driver
}
/**
- * Check if user is able to upload an avatar
+ * Check if user is able to upload an avatar to a temporary folder
*
* @return bool True if user can upload, false if not
*/
protected function can_upload()
{
- return ($this->filesystem->exists($this->phpbb_root_path . $this->config['avatar_path']) && $this->filesystem->is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on'));
+ return $this->php_ini->getBool('file_uploads');
}
}
diff --git a/phpBB/phpbb/db/migration/data/v330/storage_avatar.php b/phpBB/phpbb/db/migration/data/v330/storage_avatar.php
new file mode 100644
index 0000000000..077904daa6
--- /dev/null
+++ b/phpBB/phpbb/db/migration/data/v330/storage_avatar.php
@@ -0,0 +1,26 @@
+
+* @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\v330;
+
+class storage_avatar extends \phpbb\db\migration\migration
+{
+ public function update_data()
+ {
+ return array(
+ array('config.add', array('storage\\avatar\\provider', \phpbb\storage\provider\local::class)),
+ array('config.add', array('storage\\avatar\\config\\path', $this->config['avatar_path'])),
+ array('config.remove', array('avatar_path')),
+ );
+ }
+}
diff --git a/phpBB/phpbb/files/types/form.php b/phpBB/phpbb/files/types/form.php
index 2c3beb6e02..a915476191 100644
--- a/phpBB/phpbb/files/types/form.php
+++ b/phpBB/phpbb/files/types/form.php
@@ -25,21 +25,12 @@ class form extends base
/** @var factory Files factory */
protected $factory;
- /** @var language */
- protected $language;
-
- /** @var IniGetWrapper */
- protected $php_ini;
-
/** @var plupload */
protected $plupload;
/** @var request_interface */
protected $request;
- /** @var \phpbb\files\upload */
- protected $upload;
-
/**
* Construct a form upload type
*
diff --git a/phpBB/phpbb/files/types/form_storage.php b/phpBB/phpbb/files/types/form_storage.php
new file mode 100644
index 0000000000..29bef283bd
--- /dev/null
+++ b/phpBB/phpbb/files/types/form_storage.php
@@ -0,0 +1,128 @@
+
+ * @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\files\types;
+
+use bantu\IniGetWrapper\IniGetWrapper;
+use phpbb\files\factory;
+use phpbb\files\filespec;
+use phpbb\language\language;
+use phpbb\plupload\plupload;
+use phpbb\request\request_interface;
+
+class form_storage extends base
+{
+ /** @var factory Files factory */
+ protected $factory;
+
+ /** @var plupload */
+ protected $plupload;
+
+ /** @var request_interface */
+ protected $request;
+
+ /**
+ * Construct a form upload type
+ *
+ * @param factory $factory Files factory
+ * @param language $language Language class
+ * @param IniGetWrapper $php_ini ini_get() wrapper
+ * @param plupload $plupload Plupload
+ * @param request_interface $request Request object
+ */
+ public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, plupload $plupload, request_interface $request)
+ {
+ $this->factory = $factory;
+ $this->language = $language;
+ $this->php_ini = $php_ini;
+ $this->plupload = $plupload;
+ $this->request = $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function upload()
+ {
+ $args = func_get_args();
+ return $this->form_upload($args[0]);
+ }
+
+ /**
+ * Form upload method
+ * Upload file from users harddisk
+ *
+ * @param string $form_name Form name assigned to the file input field (if it is an array, the key has to be specified)
+ *
+ * @return filespec $file Object "filespec" is returned, all further operations can be done with this object
+ */
+ protected function form_upload($form_name)
+ {
+ $upload = $this->request->file($form_name);
+ unset($upload['local_mode']);
+
+ $result = $this->plupload->handle_upload($form_name);
+ if (is_array($result))
+ {
+ $upload = array_merge($upload, $result);
+ }
+
+ /** @var filespec $file */
+ $file = $this->factory->get('filespec_storage')
+ ->set_upload_ary($upload)
+ ->set_upload_namespace($this->upload);
+
+ if ($file->init_error())
+ {
+ $file->error[] = '';
+ return $file;
+ }
+
+ // Error array filled?
+ if (isset($upload['error']))
+ {
+ $error = $this->upload->assign_internal_error($upload['error']);
+
+ if ($error !== false)
+ {
+ $file->error[] = $error;
+ return $file;
+ }
+ }
+
+ // Check if empty file got uploaded (not catched by is_uploaded_file)
+ if (isset($upload['size']) && $upload['size'] == 0)
+ {
+ $file->error[] = $this->language->lang($this->upload->error_prefix . 'EMPTY_FILEUPLOAD');
+ return $file;
+ }
+
+ // PHP Upload file size check
+ $file = $this->check_upload_size($file);
+ if (sizeof($file->error))
+ {
+ return $file;
+ }
+
+ // Not correctly uploaded
+ if (!$file->is_uploaded())
+ {
+ $file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED');
+ return $file;
+ }
+
+ $this->upload->common_checks($file);
+
+ return $file;
+ }
+}
diff --git a/phpBB/phpbb/files/types/local.php b/phpBB/phpbb/files/types/local.php
index 4dfe4f7506..67948ea6df 100644
--- a/phpBB/phpbb/files/types/local.php
+++ b/phpBB/phpbb/files/types/local.php
@@ -24,18 +24,9 @@ class local extends base
/** @var factory Files factory */
protected $factory;
- /** @var language */
- protected $language;
-
- /** @var IniGetWrapper */
- protected $php_ini;
-
/** @var request_interface */
protected $request;
- /** @var \phpbb\files\upload */
- protected $upload;
-
/**
* Construct a form upload type
*
diff --git a/phpBB/phpbb/files/types/remote.php b/phpBB/phpbb/files/types/remote.php
index 1fdba0ca32..e64e360b6a 100644
--- a/phpBB/phpbb/files/types/remote.php
+++ b/phpBB/phpbb/files/types/remote.php
@@ -28,18 +28,9 @@ class remote extends base
/** @var factory Files factory */
protected $factory;
- /** @var language */
- protected $language;
-
- /** @var IniGetWrapper */
- protected $php_ini;
-
/** @var request_interface */
protected $request;
- /** @var \phpbb\files\upload */
- protected $upload;
-
/** @var string phpBB root path */
protected $phpbb_root_path;
diff --git a/phpBB/phpbb/files/types/remote_storage.php b/phpBB/phpbb/files/types/remote_storage.php
new file mode 100644
index 0000000000..3e6953703e
--- /dev/null
+++ b/phpBB/phpbb/files/types/remote_storage.php
@@ -0,0 +1,197 @@
+
+ * @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\files\types;
+
+use bantu\IniGetWrapper\IniGetWrapper;
+use phpbb\config\config;
+use phpbb\files\factory;
+use phpbb\files\filespec;
+use phpbb\language\language;
+use phpbb\request\request_interface;
+
+class remote_storage extends base
+{
+ /** @var config phpBB config */
+ protected $config;
+
+ /** @var factory Files factory */
+ protected $factory;
+
+ /** @var request_interface */
+ protected $request;
+
+ /** @var string phpBB root path */
+ protected $phpbb_root_path;
+
+ /**
+ * Construct a form upload type
+ *
+ * @param config $config phpBB config
+ * @param factory $factory Files factory
+ * @param language $language Language class
+ * @param IniGetWrapper $php_ini ini_get() wrapper
+ * @param request_interface $request Request object
+ * @param string $phpbb_root_path phpBB root path
+ */
+ public function __construct(config $config, factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request, $phpbb_root_path)
+ {
+ $this->config = $config;
+ $this->factory = $factory;
+ $this->language = $language;
+ $this->php_ini = $php_ini;
+ $this->request = $request;
+ $this->phpbb_root_path = $phpbb_root_path;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function upload()
+ {
+ $args = func_get_args();
+ return $this->remote_upload($args[0]);
+ }
+
+ /**
+ * Remote upload method
+ * Uploads file from given url
+ *
+ * @param string $upload_url URL pointing to file to upload, for example http://www.foobar.com/example.gif
+ * @return filespec $file Object "filespec" is returned, all further operations can be done with this object
+ */
+ protected function remote_upload($upload_url)
+ {
+ $upload_ary = array();
+ $upload_ary['local_mode'] = true;
+
+ if (!preg_match('#^(https?://).*?\.(' . implode('|', $this->upload->allowed_extensions) . ')$#i', $upload_url, $match))
+ {
+ return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'URL_INVALID'));
+ }
+
+ $url = parse_url($upload_url);
+
+ $upload_ary['type'] = 'application/octet-stream';
+
+ $url['path'] = explode('.', $url['path']);
+ $ext = array_pop($url['path']);
+
+ $url['path'] = implode('', $url['path']);
+ $upload_ary['name'] = utf8_basename($url['path']) . (($ext) ? '.' . $ext : '');
+
+ $remote_max_filesize = $this->get_max_file_size();
+
+ $guzzle_options = [
+ 'timeout' => $this->upload->upload_timeout,
+ 'connect_timeout' => $this->upload->upload_timeout,
+ 'verify' => !empty($this->config['remote_upload_verify']) ? (bool) $this->config['remote_upload_verify'] : false,
+ ];
+ $client = new \GuzzleHttp\Client($guzzle_options);
+
+ try
+ {
+ $response = $client->get($upload_url, $guzzle_options);
+ }
+ catch (\GuzzleHttp\Exception\ClientException $clientException)
+ {
+ return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'URL_NOT_FOUND');
+ }
+ catch (\GuzzleHttp\Exception\RequestException $requestException)
+ {
+ if (strpos($requestException->getMessage(), 'cURL error 28') !== false || preg_match('/408|504/', $requestException->getCode()))
+ {
+ return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'REMOTE_UPLOAD_TIMEOUT');
+ }
+ else
+ {
+ return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'));
+ }
+ }
+ catch (\Exception $e)
+ {
+ return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'));
+ }
+
+ $content_length = $response->getBody()->getSize();
+ if ($remote_max_filesize && $content_length > $remote_max_filesize)
+ {
+ $max_filesize = get_formatted_filesize($remote_max_filesize, false);
+
+ return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']));
+ }
+
+ if ($content_length === 0)
+ {
+ return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'EMPTY_REMOTE_DATA');
+ }
+
+ $data = $response->getBody();
+
+ $filename = tempnam(sys_get_temp_dir(), unique_id() . '-');
+
+ if (!($fp = @fopen($filename, 'wb')))
+ {
+ return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'NOT_UPLOADED');
+ }
+
+ $upload_ary['size'] = fwrite($fp, $data);
+ fclose($fp);
+ unset($data);
+
+ $upload_ary['tmp_name'] = $filename;
+
+ /** @var filespec $file */
+ $file = $this->factory->get('filespec_storage')
+ ->set_upload_ary($upload_ary)
+ ->set_upload_namespace($this->upload);
+ $this->upload->common_checks($file);
+
+ return $file;
+ }
+
+ /**
+ * Get maximum file size for remote uploads
+ *
+ * @return int Maximum file size
+ */
+ protected function get_max_file_size()
+ {
+ $max_file_size = $this->upload->max_filesize;
+ if (!$max_file_size)
+ {
+ $max_file_size = $this->php_ini->getString('upload_max_filesize');
+
+ if (!empty($max_file_size))
+ {
+ $unit = strtolower(substr($max_file_size, -1, 1));
+ $max_file_size = (int) $max_file_size;
+
+ switch ($unit)
+ {
+ case 'g':
+ $max_file_size *= 1024;
+ // no break
+ case 'm':
+ $max_file_size *= 1024;
+ // no break
+ case 'k':
+ $max_file_size *= 1024;
+ // no break
+ }
+ }
+ }
+
+ return $max_file_size;
+ }
+}
diff --git a/phpBB/phpbb/storage/adapter/local.php b/phpBB/phpbb/storage/adapter/local.php
index de63d0f6a5..1ef2516f4f 100644
--- a/phpBB/phpbb/storage/adapter/local.php
+++ b/phpBB/phpbb/storage/adapter/local.php
@@ -18,6 +18,8 @@ use phpbb\storage\exception\exception;
use phpbb\filesystem\exception\filesystem_exception;
use phpbb\filesystem\filesystem;
use phpbb\filesystem\helper as filesystem_helper;
+use phpbb\mimetype\guesser;
+use FastImageSize\FastImageSize;
/**
* @internal Experimental
@@ -31,6 +33,20 @@ class local implements adapter_interface, stream_interface
*/
protected $filesystem;
+ /**
+ * FastImageSize
+ *
+ * @var \FastImageSize\FastImageSize
+ */
+ protected $imagesize;
+
+ /**
+ * Mimetype Guesser component
+ *
+ * @var \phpbb\mimetype\guesser
+ */
+ protected $mimetype_guesser;
+
/**
* @var string path
*/
@@ -44,9 +60,11 @@ class local implements adapter_interface, stream_interface
/**
* Constructor
*/
- public function __construct(filesystem $filesystem, $phpbb_root_path)
+ public function __construct(filesystem $filesystem, FastImageSize $imagesize, guesser $mimetype_guesser, $phpbb_root_path)
{
$this->filesystem = $filesystem;
+ $this->imagesize = $imagesize;
+ $this->mimetype_guesser = $mimetype_guesser;
$this->phpbb_root_path = $phpbb_root_path;
}
@@ -235,4 +253,82 @@ class local implements adapter_interface, stream_interface
throw new exception('STORAGE_CANNOT_COPY_RESOURCE');
}
}
+
+ /**
+ * Get file size.
+ *
+ * @param string $path The file
+ *
+ * @throws \phpbb\storage\exception\exception When cannot get size
+ *
+ * @return array Properties
+ */
+ public function file_size($path)
+ {
+ $size = filesize($this->root_path . $path);
+
+ if ($size === null)
+ {
+ throw new exception('STORAGE_CANNOT_GET_FILESIZE');
+ }
+
+ return ['size' => $size];
+ }
+
+ /**
+ * Get file mimetype.
+ *
+ * @param string $path The file
+ *
+ * @return array Properties
+ */
+ public function file_mimetype($path)
+ {
+ return ['mimetype' => $this->mimetype_guesser->guess($this->root_path . $path)];
+ }
+
+ /**
+ * Get image dimensions.
+ *
+ * @param string $path The file
+ *
+ * @return array Properties
+ */
+ protected function image_dimensions($path)
+ {
+ $size = $this->imagesize->getImageSize($this->root_path . $path);
+
+ // For not supported types like swf
+ if ($size === false)
+ {
+ $imsize = getimagesize($this->root_path . $path);
+ $size = ['width' => $imsize[0], 'height' => $imsize[1]];
+ }
+
+ return ['image_width' => $size['width'], 'image_height' => $size['height']];
+ }
+
+ /**
+ * Get image width.
+ *
+ * @param string $path The file
+ *
+ * @return array Properties
+ */
+ public function file_image_width($path)
+ {
+ return $this->image_dimensions($path);
+ }
+
+ /**
+ * Get image height.
+ *
+ * @param string $path The file
+ *
+ * @return array Properties
+ */
+ public function file_image_height($path)
+ {
+ return $this->image_dimensions($path);
+ }
}
diff --git a/phpBB/phpbb/storage/file_info.php b/phpBB/phpbb/storage/file_info.php
new file mode 100644
index 0000000000..ae2bd5169d
--- /dev/null
+++ b/phpBB/phpbb/storage/file_info.php
@@ -0,0 +1,85 @@
+
+ * @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\exception;
+use phpbb\storage\adapter\adapter_interface;
+
+class file_info
+{
+ /**
+ * @var \phpbb\storage\adapter\adapter_interface
+ */
+ protected $adapter;
+
+ /**
+ * Path of the file
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Stores the properties of $path file, so dont have to be consulted multiple times.
+ * For example, when you need the width of an image, using getimagesize() you get
+ * both dimensions, so you store both here, and when you get the height, you dont have
+ * to call getimagesize() again.
+ *
+ * @var array
+ */
+ protected $properties;
+
+ /**
+ * Constructor
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $adapter
+ * @param string $path
+ */
+ public function __construct(adapter_interface $adapter, $path)
+ {
+ $this->adapter = $adapter;
+ $this->path = $path;
+ $this->properties = [];
+ }
+
+ /**
+ * Load propertys lazily.
+ *
+ * @param string name The property name.
+ *
+ * @return string Returns the property value
+ */
+ public function get($name)
+ {
+ if (!isset($this->properties[$name]))
+ {
+ if (!method_exists($this->adapter, 'file_' . $name))
+ {
+ throw new exception('STORAGE_METHOD_NOT_IMPLEMENTED');
+ }
+
+ $this->properties = array_merge($this->properties, call_user_func([$this->adapter, 'file_' . $name], $this->path));
+ }
+
+ return $this->properties[$name];
+ }
+
+ /**
+ * Alias of \phpbb\storage\file_info->get()
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+}
diff --git a/phpBB/phpbb/storage/storage.php b/phpBB/phpbb/storage/storage.php
index a2eb89ecb3..089ccce737 100644
--- a/phpBB/phpbb/storage/storage.php
+++ b/phpBB/phpbb/storage/storage.php
@@ -192,4 +192,18 @@ class storage
$adapter->put_contents($path, stream_get_contents($resource));
}
}
+
+ /**
+ * Get file info.
+ *
+ * @param string $path The file
+ *
+ * @throws \phpbb\storage\exception\not_implemented When the adapter doesnt implement the method
+ *
+ * @return \phpbb\storage\file_info Returns file_info object
+ */
+ public function file_info($path)
+ {
+ return new file_info($this->adapter, $path);
+ }
}
diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php
index 762fd776e3..2866a1673d 100644
--- a/tests/avatar/manager_test.php
+++ b/tests/avatar/manager_test.php
@@ -36,6 +36,13 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case
->will($this->returnArgument(0));
$filesystem = new \phpbb\filesystem\filesystem();
+ $adapter = new \phpbb\storage\adapter\local($filesystem, new \FastImageSize\FastImageSize(), new \phpbb\mimetype\guesser(array(new \phpbb\mimetype\extension_guesser)), $phpbb_root_path);
+ $adapter->configure(['path' => 'images/avatars/upload']);
+ $adapter_factory_mock = $this->createMock('\phpbb\storage\adapter_factory');
+ $adapter_factory_mock->expects($this->any())
+ ->method('get')
+ ->willReturn($adapter);
+ $storage = new \phpbb\storage\storage($adapter_factory_mock, '');
// Prepare dependencies for avatar manager and driver
$this->config = new \phpbb\config\config(array());
@@ -82,6 +89,8 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case
$files_factory = new \phpbb\files\factory($phpbb_container);
+ $php_ini = new \bantu\IniGetWrapper\IniGetWrapper;
+
foreach ($this->avatar_drivers() as $driver)
{
if ($driver !== 'upload')
@@ -95,7 +104,7 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case
{
$cur_avatar = $this->getMockBuilder('\phpbb\avatar\driver\\' . $driver)
->setMethods(array('get_name'))
- ->setConstructorArgs(array($this->config, $phpbb_root_path, $phpEx, $filesystem, $path_helper, $dispatcher, $files_factory, $cache))
+ ->setConstructorArgs(array($this->config, $phpbb_root_path, $phpEx, $storage, $path_helper, $dispatcher, $files_factory, $php_ini))
->getMock();
}
$cur_avatar->expects($this->any())
diff --git a/tests/storage/adapter/local_test.php b/tests/storage/adapter/local_test.php
index 6d3ba10bca..b478ce4009 100644
--- a/tests/storage/adapter/local_test.php
+++ b/tests/storage/adapter/local_test.php
@@ -24,7 +24,7 @@
$filesystem = new \phpbb\filesystem\filesystem();
$phpbb_root_path = getcwd() . DIRECTORY_SEPARATOR;
- $this->adapter = new \phpbb\storage\adapter\local($filesystem, $phpbb_root_path);
+ $this->adapter = new \phpbb\storage\adapter\local($filesystem, new \FastImageSize\FastImageSize(), new \phpbb\mimetype\guesser(array(new \phpbb\mimetype\extension_guesser)), $phpbb_root_path);
$this->adapter->configure(['path' => 'test_path']);
$this->path = $phpbb_root_path . 'test_path/';