Augmentation vers version 3.3.0

This commit is contained in:
Gauvain Boiché
2020-03-31 15:31:03 +02:00
parent d926806907
commit a1864c0414
2618 changed files with 406015 additions and 31377 deletions

View File

@@ -0,0 +1,285 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\language\language;
use phpbb\request\request_interface;
use phpbb\request\type_cast_helper;
use phpbb\user;
/**
* Apache authentication provider for phpBB3
*/
class apache extends base
{
/** @var config phpBB config */
protected $config;
/** @var driver_interface Database object */
protected $db;
/** @var language Language object */
protected $language;
/** @var request_interface Request object */
protected $request;
/** @var user User object */
protected $user;
/** @var string Relative path to phpBB root */
protected $phpbb_root_path;
/** @var string PHP file extension */
protected $php_ext;
/**
* Apache Authentication Constructor
*
* @param config $config Config object
* @param driver_interface $db Database object
* @param language $language Language object
* @param request_interface $request Request object
* @param user $user User object
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct(config $config, driver_interface $db, language $language, request_interface $request, user $user, $phpbb_root_path, $php_ext)
{
$this->config = $config;
$this->db = $db;
$this->language = $language;
$this->request = $request;
$this->user = $user;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
}
/**
* {@inheritdoc}
*/
public function init()
{
if (!$this->request->is_set('PHP_AUTH_USER', request_interface::SERVER) || $this->user->data['username'] !== htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')))
{
return $this->language->lang('APACHE_SETUP_BEFORE_USE');
}
return false;
}
/**
* {@inheritdoc}
*/
public function login($username, $password)
{
// do not allow empty password
if (!$password)
{
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'NO_PASSWORD_SUPPLIED',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$this->request->is_set('PHP_AUTH_USER', request_interface::SERVER))
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER'));
$php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW'));
if (!empty($php_auth_user) && !empty($php_auth_pw))
{
if ($php_auth_user !== $username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type
FROM ' . USERS_TABLE . "
WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if ($row)
{
// User inactive...
if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE)
{
return array(
'status' => LOGIN_ERROR_ACTIVE,
'error_msg' => 'ACTIVE_ERROR',
'user_row' => $row,
);
}
// Successful login...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
// this is the user's first login so create an empty profile
return array(
'status' => LOGIN_SUCCESS_CREATE_PROFILE,
'error_msg' => false,
'user_row' => $this->user_row($php_auth_user),
);
}
// Not logged into apache
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE',
'user_row' => array('user_id' => ANONYMOUS),
);
}
/**
* {@inheritdoc}
*/
public function autologin()
{
if (!$this->request->is_set('PHP_AUTH_USER', request_interface::SERVER))
{
return array();
}
$php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER'));
$php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW'));
if (!empty($php_auth_user) && !empty($php_auth_pw))
{
$type_cast_helper = new type_cast_helper();
$type_cast_helper->set_var($php_auth_user, $php_auth_user, 'string', true);
$sql = 'SELECT *
FROM ' . USERS_TABLE . "
WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if ($row)
{
return ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) ? array() : $row;
}
if (!function_exists('user_add'))
{
include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
}
// create the user if he does not exist yet
user_add($this->user_row($php_auth_user));
$sql = 'SELECT *
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($php_auth_user)) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if ($row)
{
return $row;
}
}
return array();
}
/**
* This function generates an array which can be passed to the user_add
* function in order to create a user
*
* @param string $username The username of the new user.
*
* @return array Contains data that can be passed directly to
* the user_add function.
*/
private function user_row($username)
{
// first retrieve default group id
$sql = 'SELECT group_id
FROM ' . GROUPS_TABLE . "
WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "'
AND group_type = " . GROUP_SPECIAL;
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$row)
{
trigger_error('NO_GROUP');
}
// generate user account data
return array(
'username' => $username,
'user_password' => '',
'user_email' => '',
'group_id' => (int) $row['group_id'],
'user_type' => USER_NORMAL,
'user_ip' => $this->user->ip,
'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0,
);
}
/**
* {@inheritdoc}
*/
public function validate_session($user)
{
// Check if PHP_AUTH_USER is set and handle this case
if ($this->request->is_set('PHP_AUTH_USER', request_interface::SERVER))
{
$php_auth_user = $this->request->server('PHP_AUTH_USER');
return ($php_auth_user === $user['username']) ? true : false;
}
// PHP_AUTH_USER is not set. A valid session is now determined by the user type (anonymous/bot or not)
if ($user['user_type'] == USER_IGNORE)
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider;
/**
* Base authentication provider class that all other providers should implement
*/
abstract class base implements provider_interface
{
/**
* {@inheritdoc}
*/
public function init()
{
return;
}
/**
* {@inheritdoc}
*/
public function autologin()
{
return;
}
/**
* {@inheritdoc}
*/
public function acp()
{
return;
}
/**
* {@inheritdoc}
*/
public function get_acp_template($new_config)
{
return;
}
/**
* {@inheritdoc}
*/
public function get_login_data()
{
return;
}
/**
* {@inheritdoc}
*/
public function get_auth_link_data($user_id = 0)
{
return;
}
/**
* {@inheritdoc}
*/
public function logout($data, $new_session)
{
return;
}
/**
* {@inheritdoc}
*/
public function validate_session($user)
{
return;
}
/**
* {@inheritdoc}
*/
public function login_link_has_necessary_data($login_link_data)
{
return;
}
/**
* {@inheritdoc}
*/
public function link_account(array $link_data)
{
return;
}
/**
* {@inheritdoc}
*/
public function unlink_account(array $link_data)
{
return;
}
}

View File

@@ -0,0 +1,259 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider;
use phpbb\captcha\factory;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\passwords\manager;
use phpbb\request\request_interface;
use phpbb\user;
/**
* Database authentication provider for phpBB3
* This is for authentication via the integrated user table
*/
class db extends base
{
/** @var factory CAPTCHA factory */
protected $captcha_factory;
/** @var config phpBB config */
protected $config;
/** @var driver_interface DBAL driver instance */
protected $db;
/** @var request_interface Request object */
protected $request;
/** @var user User object */
protected $user;
/** @var string phpBB root path */
protected $phpbb_root_path;
/** @var string PHP file extension */
protected $php_ext;
/**
* phpBB passwords manager
*
* @var manager
*/
protected $passwords_manager;
/**
* Database Authentication Constructor
*
* @param factory $captcha_factory
* @param config $config
* @param driver_interface $db
* @param manager $passwords_manager
* @param request_interface $request
* @param user $user
* @param string $phpbb_root_path
* @param string $php_ext
*/
public function __construct(factory $captcha_factory, config $config, driver_interface $db, manager $passwords_manager, request_interface $request, user $user, $phpbb_root_path, $php_ext)
{
$this->captcha_factory = $captcha_factory;
$this->config = $config;
$this->db = $db;
$this->passwords_manager = $passwords_manager;
$this->request = $request;
$this->user = $user;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
}
/**
* {@inheritdoc}
*/
public function login($username, $password)
{
// Auth plugins get the password untrimmed.
// For compatibility we trim() here.
$password = trim($password);
// do not allow empty password
if (!$password)
{
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'NO_PASSWORD_SUPPLIED',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$username_clean = utf8_clean_string($username);
$sql = 'SELECT *
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (($this->user->ip && !$this->config['ip_login_limit_use_forwarded']) ||
($this->user->forwarded_for && $this->config['ip_login_limit_use_forwarded']))
{
$sql = 'SELECT COUNT(*) AS attempts
FROM ' . LOGIN_ATTEMPT_TABLE . '
WHERE attempt_time > ' . (time() - (int) $this->config['ip_login_limit_time']);
if ($this->config['ip_login_limit_use_forwarded'])
{
$sql .= " AND attempt_forwarded_for = '" . $this->db->sql_escape($this->user->forwarded_for) . "'";
}
else
{
$sql .= " AND attempt_ip = '" . $this->db->sql_escape($this->user->ip) . "' ";
}
$result = $this->db->sql_query($sql);
$attempts = (int) $this->db->sql_fetchfield('attempts');
$this->db->sql_freeresult($result);
$attempt_data = array(
'attempt_ip' => $this->user->ip,
'attempt_browser' => trim(substr($this->user->browser, 0, 149)),
'attempt_forwarded_for' => $this->user->forwarded_for,
'attempt_time' => time(),
'user_id' => ($row) ? (int) $row['user_id'] : 0,
'username' => $username,
'username_clean' => $username_clean,
);
$sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data);
$this->db->sql_query($sql);
}
else
{
$attempts = 0;
}
if (!$row)
{
if ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max'])
{
return array(
'status' => LOGIN_ERROR_ATTEMPTS,
'error_msg' => 'LOGIN_ERROR_ATTEMPTS',
'user_row' => array('user_id' => ANONYMOUS),
);
}
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$show_captcha = ($this->config['max_login_attempts'] && $row['user_login_attempts'] >= $this->config['max_login_attempts']) ||
($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']);
// If there are too many login attempts, we need to check for a confirm image
// Every auth module is able to define what to do by itself...
if ($show_captcha)
{
$captcha = $this->captcha_factory->get_instance($this->config['captcha_plugin']);
$captcha->init(CONFIRM_LOGIN);
$vc_response = $captcha->validate($row);
if ($vc_response)
{
return array(
'status' => LOGIN_ERROR_ATTEMPTS,
'error_msg' => 'LOGIN_ERROR_ATTEMPTS',
'user_row' => $row,
);
}
else
{
$captcha->reset();
}
}
// Check password ...
if ($this->passwords_manager->check($password, $row['user_password'], $row))
{
// Check for old password hash...
if ($this->passwords_manager->convert_flag || strlen($row['user_password']) == 32)
{
$hash = $this->passwords_manager->hash($password);
// Update the password in the users table to the new format
$sql = 'UPDATE ' . USERS_TABLE . "
SET user_password = '" . $this->db->sql_escape($hash) . "'
WHERE user_id = {$row['user_id']}";
$this->db->sql_query($sql);
$row['user_password'] = $hash;
}
$sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . '
WHERE user_id = ' . $row['user_id'];
$this->db->sql_query($sql);
if ($row['user_login_attempts'] != 0)
{
// Successful, reset login attempts (the user passed all stages)
$sql = 'UPDATE ' . USERS_TABLE . '
SET user_login_attempts = 0
WHERE user_id = ' . $row['user_id'];
$this->db->sql_query($sql);
}
// User inactive...
if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE)
{
return array(
'status' => LOGIN_ERROR_ACTIVE,
'error_msg' => 'ACTIVE_ERROR',
'user_row' => $row,
);
}
// Successful login... set user_login_attempts to zero...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
// Password incorrect - increase login attempts
$sql = 'UPDATE ' . USERS_TABLE . '
SET user_login_attempts = user_login_attempts + 1
WHERE user_id = ' . (int) $row['user_id'] . '
AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX;
$this->db->sql_query($sql);
// Give status about wrong password...
return array(
'status' => ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD,
'error_msg' => 'LOGIN_ERROR_PASSWORD',
'user_row' => $row,
);
}
}

View File

@@ -0,0 +1,359 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\language\language;
use phpbb\user;
/**
* Database authentication provider for phpBB3
* This is for authentication via the integrated user table
*/
class ldap extends base
{
/** @var config phpBB config */
protected $config;
/** @var driver_interface DBAL driver interface */
protected $db;
/** @var language phpBB language class */
protected $language;
/** @var user phpBB user */
protected $user;
/**
* LDAP Authentication Constructor
*
* @param config $config Config object
* @param driver_interface $db DBAL driver interface
* @param language $language Language object
* @param user $user User object
*/
public function __construct(config $config, driver_interface $db, language $language, user $user)
{
$this->config = $config;
$this->db = $db;
$this->language = $language;
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public function init()
{
if (!@extension_loaded('ldap'))
{
return $this->language->lang('LDAP_NO_LDAP_EXTENSION');
}
$this->config['ldap_port'] = (int) $this->config['ldap_port'];
if ($this->config['ldap_port'])
{
$ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']);
}
else
{
$ldap = @ldap_connect($this->config['ldap_server']);
}
if (!$ldap)
{
return $this->language->lang('LDAP_NO_SERVER_CONNECTION');
}
@ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
@ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
if ($this->config['ldap_user'] || $this->config['ldap_password'])
{
if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password'])))
{
return $this->language->lang('LDAP_INCORRECT_USER_PASSWORD');
}
}
// ldap_connect only checks whether the specified server is valid, so the connection might still fail
$search = @ldap_search(
$ldap,
htmlspecialchars_decode($this->config['ldap_base_dn']),
$this->ldap_user_filter($this->user->data['username']),
(empty($this->config['ldap_email'])) ?
array(htmlspecialchars_decode($this->config['ldap_uid'])) :
array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])),
0,
1
);
if ($search === false)
{
return $this->language->lang('LDAP_SEARCH_FAILED');
}
$result = @ldap_get_entries($ldap, $search);
@ldap_close($ldap);
if (!is_array($result) || count($result) < 2)
{
return $this->language->lang('LDAP_NO_IDENTITY', $this->user->data['username']);
}
if (!empty($this->config['ldap_email']) && !isset($result[0][htmlspecialchars_decode($this->config['ldap_email'])]))
{
return $this->language->lang('LDAP_NO_EMAIL');
}
return false;
}
/**
* {@inheritdoc}
*/
public function login($username, $password)
{
// do not allow empty password
if (!$password)
{
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'NO_PASSWORD_SUPPLIED',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!$username)
{
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
if (!@extension_loaded('ldap'))
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LDAP_NO_LDAP_EXTENSION',
'user_row' => array('user_id' => ANONYMOUS),
);
}
$this->config['ldap_port'] = (int) $this->config['ldap_port'];
if ($this->config['ldap_port'])
{
$ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']);
}
else
{
$ldap = @ldap_connect($this->config['ldap_server']);
}
if (!$ldap)
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LDAP_NO_SERVER_CONNECTION',
'user_row' => array('user_id' => ANONYMOUS),
);
}
@ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
@ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
if ($this->config['ldap_user'] || $this->config['ldap_password'])
{
if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password'])))
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LDAP_NO_SERVER_CONNECTION',
'user_row' => array('user_id' => ANONYMOUS),
);
}
}
$search = @ldap_search(
$ldap,
htmlspecialchars_decode($this->config['ldap_base_dn']),
$this->ldap_user_filter($username),
(empty($this->config['ldap_email'])) ?
array(htmlspecialchars_decode($this->config['ldap_uid'])) :
array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])),
0,
1
);
$ldap_result = @ldap_get_entries($ldap, $search);
if (is_array($ldap_result) && count($ldap_result) > 1)
{
if (@ldap_bind($ldap, $ldap_result[0]['dn'], htmlspecialchars_decode($password)))
{
@ldap_close($ldap);
$sql ='SELECT user_id, username, user_password, user_passchg, user_email, user_type
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if ($row)
{
unset($ldap_result);
// User inactive...
if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE)
{
return array(
'status' => LOGIN_ERROR_ACTIVE,
'error_msg' => 'ACTIVE_ERROR',
'user_row' => $row,
);
}
// Successful login... set user_login_attempts to zero...
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
else
{
// retrieve default group id
$sql = 'SELECT group_id
FROM ' . GROUPS_TABLE . "
WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "'
AND group_type = " . GROUP_SPECIAL;
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$row)
{
trigger_error('NO_GROUP');
}
// generate user account data
$ldap_user_row = array(
'username' => $username,
'user_password' => '',
'user_email' => (!empty($this->config['ldap_email'])) ? utf8_htmlspecialchars($ldap_result[0][htmlspecialchars_decode($this->config['ldap_email'])][0]) : '',
'group_id' => (int) $row['group_id'],
'user_type' => USER_NORMAL,
'user_ip' => $this->user->ip,
'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0,
);
unset($ldap_result);
// this is the user's first login so create an empty profile
return array(
'status' => LOGIN_SUCCESS_CREATE_PROFILE,
'error_msg' => false,
'user_row' => $ldap_user_row,
);
}
}
else
{
unset($ldap_result);
@ldap_close($ldap);
// Give status about wrong password...
return array(
'status' => LOGIN_ERROR_PASSWORD,
'error_msg' => 'LOGIN_ERROR_PASSWORD',
'user_row' => array('user_id' => ANONYMOUS),
);
}
}
@ldap_close($ldap);
return array(
'status' => LOGIN_ERROR_USERNAME,
'error_msg' => 'LOGIN_ERROR_USERNAME',
'user_row' => array('user_id' => ANONYMOUS),
);
}
/**
* {@inheritdoc}
*/
public function acp()
{
// These are fields required in the config table
return array(
'ldap_server', 'ldap_port', 'ldap_base_dn', 'ldap_uid', 'ldap_user_filter', 'ldap_email', 'ldap_user', 'ldap_password',
);
}
/**
* {@inheritdoc}
*/
public function get_acp_template($new_config)
{
return array(
'TEMPLATE_FILE' => 'auth_provider_ldap.html',
'TEMPLATE_VARS' => array(
'AUTH_LDAP_BASE_DN' => $new_config['ldap_base_dn'],
'AUTH_LDAP_EMAIL' => $new_config['ldap_email'],
'AUTH_LDAP_PASSORD' => $new_config['ldap_password'] !== '' ? '********' : '',
'AUTH_LDAP_PORT' => $new_config['ldap_port'],
'AUTH_LDAP_SERVER' => $new_config['ldap_server'],
'AUTH_LDAP_UID' => $new_config['ldap_uid'],
'AUTH_LDAP_USER' => $new_config['ldap_user'],
'AUTH_LDAP_USER_FILTER' => $new_config['ldap_user_filter'],
),
);
}
/**
* Generates a filter string for ldap_search to find a user
*
* @param $username string Username identifying the searched user
*
* @return string A filter string for ldap_search
*/
private function ldap_user_filter($username)
{
$filter = '(' . $this->config['ldap_uid'] . '=' . $this->ldap_escape(htmlspecialchars_decode($username)) . ')';
if ($this->config['ldap_user_filter'])
{
$_filter = ($this->config['ldap_user_filter'][0] == '(' && substr($this->config['ldap_user_filter'], -1) == ')') ? $this->config['ldap_user_filter'] : "({$this->config['ldap_user_filter']})";
$filter = "(&{$filter}{$_filter})";
}
return $filter;
}
/**
* Escapes an LDAP AttributeValue
*
* @param string $string The string to be escaped
* @return string The escaped string
*/
private function ldap_escape($string)
{
return str_replace(array('*', '\\', '(', ')'), array('\\*', '\\\\', '\\(', '\\)'), $string);
}
}

View File

@@ -0,0 +1,859 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth;
use OAuth\Common\Http\Exception\TokenResponseException;
use OAuth\ServiceFactory;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Service\ServiceInterface;
use OAuth\OAuth1\Service\AbstractService as OAuth1Service;
use OAuth\OAuth2\Service\AbstractService as OAuth2Service;
use phpbb\auth\provider\base;
use phpbb\auth\provider\db;
use phpbb\auth\provider\oauth\service\exception;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use phpbb\di\service_collection;
use phpbb\event\dispatcher;
use phpbb\language\language;
use phpbb\request\request_interface;
use phpbb\user;
/**
* OAuth authentication provider for phpBB3
*/
class oauth extends base
{
/** @var config */
protected $config;
/** @var driver_interface */
protected $db;
/** @var db */
protected $db_auth;
/** @var dispatcher */
protected $dispatcher;
/** @var language */
protected $language;
/** @var request_interface */
protected $request;
/** @var service_collection */
protected $service_providers;
/** @var user */
protected $user;
/** @var string OAuth table: token storage */
protected $oauth_token_table;
/** @var string OAuth table: state */
protected $oauth_state_table;
/** @var string OAuth table: account association */
protected $oauth_account_table;
/** @var string Users table */
protected $users_table;
/** @var string phpBB root path */
protected $root_path;
/** @var string php File extension */
protected $php_ext;
/**
* Constructor.
*
* @param config $config Config object
* @param driver_interface $db Database object
* @param db $db_auth DB auth provider
* @param dispatcher $dispatcher Event dispatcher object
* @param language $language Language object
* @param request_interface $request Request object
* @param service_collection $service_providers OAuth providers service collection
* @param user $user User object
* @param string $oauth_token_table OAuth table: token storage
* @param string $oauth_state_table OAuth table: state
* @param string $oauth_account_table OAuth table: account association
* @param string $users_table User table
* @param string $root_path phpBB root path
* @param string $php_ext php File extension
*/
public function __construct(
config $config,
driver_interface $db,
db $db_auth,
dispatcher $dispatcher,
language $language,
request_interface $request,
service_collection $service_providers,
user $user,
$oauth_token_table,
$oauth_state_table,
$oauth_account_table,
$users_table,
$root_path,
$php_ext
)
{
$this->config = $config;
$this->db = $db;
$this->db_auth = $db_auth;
$this->dispatcher = $dispatcher;
$this->language = $language;
$this->service_providers = $service_providers;
$this->request = $request;
$this->user = $user;
$this->oauth_token_table = $oauth_token_table;
$this->oauth_state_table = $oauth_state_table;
$this->oauth_account_table = $oauth_account_table;
$this->users_table = $users_table;
$this->root_path = $root_path;
$this->php_ext = $php_ext;
}
/**
* {@inheritdoc}
*/
public function init()
{
// This does not test whether or not the key and secret provided are valid.
foreach ($this->service_providers as $service_provider)
{
$credentials = $service_provider->get_service_credentials();
if (($credentials['key'] && !$credentials['secret']) || (!$credentials['key'] && $credentials['secret']))
{
return $this->language->lang('AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING');
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function login($username, $password)
{
// Temporary workaround for only having one authentication provider available
if (!$this->request->is_set('oauth_service'))
{
return $this->db_auth->login($username, $password);
}
// Request the name of the OAuth service
$provider = $this->request->variable('oauth_service', '', false);
$service_name = $this->get_service_name($provider);
if ($provider === '' || !array_key_exists($service_name, $this->service_providers))
{
return [
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST',
'user_row' => ['user_id' => ANONYMOUS],
];
}
// Get the service credentials for the given service
$storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table);
$query = 'mode=login&login=external&oauth_service=' . $provider;
try
{
/** @var OAuth1Service|OAuth2Service $service */
$service = $this->get_service($provider, $storage, $query);
}
catch (\Exception $e)
{
return [
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => $e->getMessage(),
'user_row' => ['user_id' => ANONYMOUS],
];
}
if ($this->is_set_code($service))
{
$this->service_providers[$service_name]->set_external_service_provider($service);
try
{
$unique_id = $this->service_providers[$service_name]->perform_auth_login();
}
catch (exception $e)
{
return [
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => $e->getMessage(),
'user_row' => ['user_id' => ANONYMOUS],
];
}
/**
* Check to see if this provider is already associated with an account.
*
* Enforcing a data type to make sure it are strings and not integers,
* so values are quoted in the SQL WHERE statement.
*/
$data = [
'provider' => (string) utf8_strtolower($provider),
'oauth_provider_id' => (string) $unique_id
];
$sql = 'SELECT user_id
FROM ' . $this->oauth_account_table . '
WHERE ' . $this->db->sql_build_array('SELECT', $data);
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$redirect_data = array(
'auth_provider' => 'oauth',
'login_link_oauth_service' => $provider,
);
/**
* Event is triggered before check if provider is already associated with an account
*
* @event core.oauth_login_after_check_if_provider_id_has_match
* @var array row User row
* @var array data Provider data
* @var array redirect_data Data to be appended to the redirect url
* @var ServiceInterface service OAuth service
* @since 3.2.3-RC1
* @changed 3.2.6-RC1 Added redirect_data
*/
$vars = [
'row',
'data',
'redirect_data',
'service',
];
extract($this->dispatcher->trigger_event('core.oauth_login_after_check_if_provider_id_has_match', compact($vars)));
if (!$row)
{
// The user does not yet exist, ask to link or create profile
return [
'status' => LOGIN_SUCCESS_LINK_PROFILE,
'error_msg' => 'LOGIN_OAUTH_ACCOUNT_NOT_LINKED',
'user_row' => [],
'redirect_data' => $redirect_data,
];
}
// Retrieve the user's account
$sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_ip, user_type, user_login_attempts
FROM ' . $this->users_table . '
WHERE user_id = ' . (int) $row['user_id'];
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$row)
{
return [
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY',
'user_row' => ['user_id' => ANONYMOUS],
];
}
/**
* Check if the user is banned.
* The fourth parameter (return) has to be true, otherwise the OAuth login is still called and
* an uncaught exception is thrown as there is no token stored in the database.
*/
$ban = $this->user->check_ban($row['user_id'], $row['user_ip'], $row['user_email'], true);
if (!empty($ban))
{
$till_date = !empty($ban['ban_end']) ? $this->user->format_date($ban['ban_end']) : '';
$message = !empty($ban['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM';
$contact_link = phpbb_get_board_contact_link($this->config, $this->root_path, $this->php_ext);
$message = $this->language->lang($message, $till_date, '<a href="' . $contact_link . '">', '</a>');
$message .= !empty($ban['ban_give_reason']) ? '<br /><br />' . $this->language->lang('BOARD_BAN_REASON', $ban['ban_give_reason']) : '';
$message .= !empty($ban['ban_triggered_by']) ? '<br /><br /><em>' . $this->language->lang('BAN_TRIGGERED_BY_' . utf8_strtoupper($ban['ban_triggered_by'])) . '</em>' : '';
return [
'status' => LOGIN_BREAK,
'error_msg' => $message,
'user_row' => $row,
];
}
// Update token storage to store the user_id
$storage->set_user_id($row['user_id']);
/**
* Event is triggered after user is successfully logged in via OAuth.
*
* @event core.auth_oauth_login_after
* @var array row User row
* @since 3.1.11-RC1
*/
$vars = [
'row',
];
extract($this->dispatcher->trigger_event('core.auth_oauth_login_after', compact($vars)));
// The user is now authenticated and can be logged in
return [
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
];
}
else
{
return $this->set_redirect($service);
}
}
/**
* {@inheritdoc}
*/
public function get_login_data()
{
$login_data = [
'TEMPLATE_FILE' => 'login_body_oauth.html',
'BLOCK_VAR_NAME' => 'oauth',
'BLOCK_VARS' => [],
];
foreach ($this->service_providers as $service_name => $service_provider)
{
// Only include data if the credentials are set
$credentials = $service_provider->get_service_credentials();
if ($credentials['key'] && $credentials['secret'])
{
$provider = $this->get_provider($service_name);
$redirect_url = generate_board_url() . '/ucp.' . $this->php_ext . '?mode=login&login=external&oauth_service=' . $provider;
$login_data['BLOCK_VARS'][$service_name] = [
'REDIRECT_URL' => redirect($redirect_url, true),
'SERVICE_NAME' => $this->get_provider_title($provider),
];
}
}
return $login_data;
}
/**
* {@inheritdoc}
*/
public function acp()
{
$ret = [];
foreach ($this->service_providers as $service_name => $service_provider)
{
$provider = $this->get_provider($service_name);
$provider = utf8_strtolower($provider);
$ret[] = 'auth_oauth_' . $provider . '_key';
$ret[] = 'auth_oauth_' . $provider . '_secret';
}
return $ret;
}
/**
* {@inheritdoc}
*/
public function get_acp_template($new_config)
{
$ret = [
'BLOCK_VAR_NAME' => 'oauth_services',
'BLOCK_VARS' => [],
'TEMPLATE_FILE' => 'auth_provider_oauth.html',
'TEMPLATE_VARS' => [],
];
foreach ($this->service_providers as $service_name => $service_provider)
{
$provider = $this->get_provider($service_name);
$ret['BLOCK_VARS'][$provider] = [
'NAME' => $provider,
'ACTUAL_NAME' => $this->get_provider_title($provider),
'KEY' => $new_config['auth_oauth_' . utf8_strtolower($provider) . '_key'],
'SECRET' => $new_config['auth_oauth_' . utf8_strtolower($provider) . '_secret'],
];
}
return $ret;
}
/**
* {@inheritdoc}
*/
public function login_link_has_necessary_data($login_link_data)
{
if (empty($login_link_data))
{
return 'LOGIN_LINK_NO_DATA_PROVIDED';
}
if (!array_key_exists('oauth_service', $login_link_data) || !$login_link_data['oauth_service'] ||
!array_key_exists('link_method', $login_link_data) || !$login_link_data['link_method'])
{
return 'LOGIN_LINK_MISSING_DATA';
}
return null;
}
/**
* {@inheritdoc}
*/
public function link_account(array $link_data)
{
// Check for a valid link method (auth_link or login_link)
if (!array_key_exists('link_method', $link_data) ||
!in_array($link_data['link_method'], ['auth_link', 'login_link']))
{
return 'LOGIN_LINK_MISSING_DATA';
}
// We must have an oauth_service listed, check for it two ways
if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service'])
{
$link_data['oauth_service'] = $this->request->variable('oauth_service', '');
if (!$link_data['oauth_service'])
{
return 'LOGIN_LINK_MISSING_DATA';
}
}
$service_name = $this->get_service_name($link_data['oauth_service']);
if (!array_key_exists($service_name, $this->service_providers))
{
return 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST';
}
switch ($link_data['link_method'])
{
case 'auth_link':
return $this->link_account_auth_link($link_data, $service_name);
case 'login_link':
return $this->link_account_login_link($link_data, $service_name);
default:
return 'LOGIN_LINK_MISSING_DATA';
}
}
/**
* {@inheritdoc}
*/
public function logout($data, $new_session)
{
// Clear all tokens belonging to the user
$storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table);
$storage->clearAllTokens();
return;
}
/**
* {@inheritdoc}
*/
public function get_auth_link_data($user_id = 0)
{
$user_ids = [];
$block_vars = [];
$sql = 'SELECT oauth_provider_id, provider
FROM ' . $this->oauth_account_table . '
WHERE user_id = ' . ($user_id > 0 ? (int) $user_id : (int) $this->user->data['user_id']);
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$user_ids[$row['provider']] = $row['oauth_provider_id'];
}
$this->db->sql_freeresult($result);
foreach ($this->service_providers as $service_name => $service_provider)
{
// Only include data if the credentials are set
$credentials = $service_provider->get_service_credentials();
if ($credentials['key'] && $credentials['secret'])
{
$provider = $this->get_provider($service_name);
$block_vars[$service_name] = [
'SERVICE_NAME' => $this->get_provider_title($provider),
'UNIQUE_ID' => isset($user_ids[$provider]) ? $user_ids[$provider] : null,
'HIDDEN_FIELDS' => [
'link' => !isset($user_ids[$provider]),
'oauth_service' => $provider,
],
];
}
}
return [
'BLOCK_VAR_NAME' => 'oauth',
'BLOCK_VARS' => $block_vars,
'TEMPLATE_FILE' => 'ucp_auth_link_oauth.html',
];
}
/**
* {@inheritdoc}
*/
public function unlink_account(array $link_data)
{
if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service'])
{
return 'LOGIN_LINK_MISSING_DATA';
}
// Remove user specified in $link_data if possible
$user_id = isset($link_data['user_id']) ? $link_data['user_id'] : $this->user->data['user_id'];
// Remove the link
$sql = 'DELETE FROM ' . $this->oauth_account_table . "
WHERE provider = '" . $this->db->sql_escape($link_data['oauth_service']) . "'
AND user_id = " . (int) $user_id;
$this->db->sql_query($sql);
$service_name = $this->get_service_name($link_data['oauth_service']);
// Clear all tokens belonging to the user on this service
$storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table);
$storage->clearToken($service_name);
return false;
}
/**
* Performs the account linking for login_link.
*
* @param array $link_data The same variable given to
* {@see \phpbb\auth\provider\provider_interface::link_account}
* @param string $service_name The name of the service being used in linking.
* @return string|false Returns a language key (string) if an error is encountered,
* or false on success.
*/
protected function link_account_login_link(array $link_data, $service_name)
{
$storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table);
// Check for an access token, they should have one
if (!$storage->has_access_token_by_session($service_name))
{
return 'LOGIN_LINK_ERROR_OAUTH_NO_ACCESS_TOKEN';
}
// Prepare for an authentication request
$query = 'mode=login_link&login_link_oauth_service=' . $link_data['oauth_service'];
try
{
$service = $this->get_service($link_data['oauth_service'], $storage, $query);
}
catch (\Exception $e)
{
return $e->getMessage();
}
$this->service_providers[$service_name]->set_external_service_provider($service);
try
{
// The user has already authenticated successfully, request to authenticate again
$unique_id = $this->service_providers[$service_name]->perform_token_auth();
}
catch (exception $e)
{
return $e->getMessage();
}
// Insert into table, they will be able to log in after this
$data = [
'user_id' => $link_data['user_id'],
'provider' => utf8_strtolower($link_data['oauth_service']),
'oauth_provider_id' => $unique_id,
];
$this->link_account_perform_link($data);
// Update token storage to store the user_id
$storage->set_user_id($link_data['user_id']);
return false;
}
/**
* Performs the account linking for auth_link.
*
* @param array $link_data The same variable given to
* {@see \phpbb\auth\provider\provider_interface::link_account}
* @param string $service_name The name of the service being used in linking.
* @return string|false Returns a language constant (string) if an error is encountered,
* or false on success.
*/
protected function link_account_auth_link(array $link_data, $service_name)
{
$storage = new token_storage($this->db, $this->user, $this->oauth_token_table, $this->oauth_state_table);
$query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . $link_data['oauth_service'];
try
{
/** @var OAuth1Service|OAuth2Service $service */
$service = $this->get_service($link_data['oauth_service'], $storage, $query);
}
catch (\Exception $e)
{
return $e->getMessage();
}
if ($this->is_set_code($service))
{
$this->service_providers[$service_name]->set_external_service_provider($service);
try
{
$unique_id = $this->service_providers[$service_name]->perform_auth_login();
}
catch (exception $e)
{
return $e->getMessage();
}
// Insert into table, they will be able to log in after this
$data = [
'user_id' => $this->user->data['user_id'],
'provider' => utf8_strtolower($link_data['oauth_service']),
'oauth_provider_id' => $unique_id,
];
$this->link_account_perform_link($data);
return false;
}
else
{
return $this->set_redirect($service);
}
}
/**
* Performs the query that inserts an account link
*
* @param array $data This array is passed to db->sql_build_array
*/
protected function link_account_perform_link(array $data)
{
// Check if the external account is already associated with other user
$sql = 'SELECT user_id
FROM ' . $this->oauth_account_table . "
WHERE provider = '" . $this->db->sql_escape($data['provider']) . "'
AND oauth_provider_id = '" . $this->db->sql_escape($data['oauth_provider_id']) . "'";
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if ($row)
{
trigger_error('AUTH_PROVIDER_OAUTH_ERROR_ALREADY_LINKED');
}
// Link account
$sql = 'INSERT INTO ' . $this->oauth_account_table . ' ' . $this->db->sql_build_array('INSERT', $data);
$this->db->sql_query($sql);
/**
* Event is triggered after user links account.
*
* @event core.auth_oauth_link_after
* @var array data User row
* @since 3.1.11-RC1
*/
$vars = [
'data',
];
extract($this->dispatcher->trigger_event('core.auth_oauth_link_after', compact($vars)));
}
/**
* Returns a new service object.
*
* @param string $provider The name of the provider
* @param token_storage $storage Token storage object
* @param string $query The query string used for the redirect uri
* @return ServiceInterface
* @throws exception When OAuth service was not created
*/
protected function get_service($provider, token_storage $storage, $query)
{
$service_name = $this->get_service_name($provider);
/** @see \phpbb\auth\provider\oauth\service\service_interface::get_service_credentials */
$service_credentials = $this->service_providers[$service_name]->get_service_credentials();
/** @see \phpbb\auth\provider\oauth\service\service_interface::get_auth_scope */
$scopes = $this->service_providers[$service_name]->get_auth_scope();
$callback = generate_board_url() . "/ucp.{$this->php_ext}?{$query}";
// Setup the credentials for the requests
$credentials = new Credentials(
$service_credentials['key'],
$service_credentials['secret'],
$callback
);
$service_factory = new ServiceFactory;
// Allow providers to register a custom class or override the provider name
if ($class = $this->service_providers[$service_name]->get_external_service_class())
{
if (class_exists($class))
{
try
{
$service_factory->registerService($provider, $class);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
}
else
{
$provider = $class;
}
}
$service = $service_factory->createService($provider, $credentials, $storage, $scopes);
if (!$service)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED');
}
return $service;
}
/**
* Returns the service name for an OAuth provider name.
*
* @param string $provider The OAuth provider name
* @return string The service name
*/
protected function get_service_name($provider)
{
if (strpos($provider, 'auth.provider.oauth.service.') !== 0)
{
$provider = 'auth.provider.oauth.service.' . utf8_strtolower($provider);
}
return $provider;
}
/**
* Returns the OAuth provider name from a service name.
*
* @param string $service_name The service name
* @return string The OAuth provider name
*/
protected function get_provider($service_name)
{
return str_replace('auth.provider.oauth.service.', '', $service_name);
}
/**
* Returns the localized title for the OAuth provider.
*
* @param string $provider The OAuth provider name
* @return string The OAuth provider title
*/
protected function get_provider_title($provider)
{
return $this->language->lang('AUTH_PROVIDER_OAUTH_SERVICE_' . utf8_strtoupper($provider));
}
/**
* Returns whether or not the authorization code is set.
*
* @param OAuth1Service|OAuth2Service $service The external OAuth service
* @return bool Whether or not the authorization code is set in the URL
* for the respective OAuth service's version
*/
protected function is_set_code($service)
{
switch ($service::OAUTH_VERSION)
{
case 1:
return $this->request->is_set('oauth_token', request_interface::GET);
case 2:
return $this->request->is_set('code', request_interface::GET);
default:
return false;
}
}
/**
* Sets a redirect to the authorization uri.
*
* @param OAuth1Service|OAuth2Service $service The external OAuth service
* @return array|false Array if an error occurred,
* false on success
*/
protected function set_redirect($service)
{
$parameters = [];
if ($service::OAUTH_VERSION === 1)
{
try
{
$token = $service->requestRequestToken();
$parameters = ['oauth_token' => $token->getRequestToken()];
}
catch (TokenResponseException $e)
{
return [
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => $e->getMessage(),
'user_row' => ['user_id' => ANONYMOUS],
];
}
}
redirect($service->getAuthorizationUri($parameters), false, true);
return false;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* Base OAuth abstract class that all OAuth services should implement
*/
abstract class base implements service_interface
{
/**
* External OAuth service provider
*
* @var \OAuth\Common\Service\ServiceInterface
*/
protected $service_provider;
/**
* {@inheritdoc}
*/
public function get_auth_scope()
{
return [];
}
/**
* {@inheritdoc}
*/
public function get_external_service_class()
{
return '';
}
/**
* {@inheritdoc}
*/
public function get_external_service_provider()
{
return $this->service_provider;
}
/**
* {@inheritdoc}
*/
public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider)
{
$this->service_provider = $service_provider;
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* Bitly OAuth service
*/
class bitly extends base
{
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\request\request_interface */
protected $request;
/**
* Constructor.
*
* @param \phpbb\config\config $config Config object
* @param \phpbb\request\request_interface $request Request object
*/
public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request)
{
$this->config = $config;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public function get_service_credentials()
{
return [
'key' => $this->config['auth_oauth_bitly_key'],
'secret' => $this->config['auth_oauth_bitly_secret'],
];
}
/**
* {@inheritdoc}
*/
public function perform_auth_login()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// This was a callback request, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
}
catch (\OAuth\Common\Http\Exception\TokenResponseException $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('user/info'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier returned from bitly
return $result['data']['login'];
}
/**
* {@inheritdoc}
*/
public function perform_token_auth()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('user/info'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier
return $result['data']['login'];
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* Facebook OAuth service
*/
class facebook extends base
{
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\request\request_interface */
protected $request;
/**
* Constructor.
*
* @param \phpbb\config\config $config Config object
* @param \phpbb\request\request_interface $request Request object
*/
public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request)
{
$this->config = $config;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public function get_service_credentials()
{
return [
'key' => $this->config['auth_oauth_facebook_key'],
'secret' => $this->config['auth_oauth_facebook_secret'],
];
}
/**
* {@inheritdoc}
*/
public function perform_auth_login()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// This was a callback request, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
}
catch (\OAuth\Common\Http\Exception\TokenResponseException $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('/me'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier
return $result['id'];
}
/**
* {@inheritdoc}
*/
public function perform_token_auth()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('/me'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier
return $result['id'];
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* Google OAuth service
*/
class google extends base
{
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\request\request_interface */
protected $request;
/**
* Constructor.
*
* @param \phpbb\config\config $config Config object
* @param \phpbb\request\request_interface $request Request object
*/
public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request)
{
$this->config = $config;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public function get_auth_scope()
{
return [
'userinfo_email',
'userinfo_profile',
];
}
/**
* {@inheritdoc}
*/
public function get_service_credentials()
{
return [
'key' => $this->config['auth_oauth_google_key'],
'secret' => $this->config['auth_oauth_google_secret'],
];
}
/**
* {@inheritdoc}
*/
public function perform_auth_login()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// This was a callback request, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
}
catch (\OAuth\Common\Http\Exception\TokenResponseException $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier
return $result['id'];
}
/**
* {@inheritdoc}
*/
public function perform_token_auth()
{
if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
try
{
// Send a request with it
$result = (array) json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
}
catch (\OAuth\Common\Exception\Exception $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Return the unique identifier
return $result['id'];
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* OAuth service interface
*/
interface service_interface
{
/**
* Returns an array of the scopes necessary for auth
*
* @return array An array of the required scopes
*/
public function get_auth_scope();
/**
* Returns an array containing the service credentials belonging to requested
* service.
*
* @return array An array containing the 'key' and the 'secret' of the
* service in the form:
* array(
* 'key' => string
* 'secret' => string
* )
*/
public function get_service_credentials();
/**
* Returns the results of the authentication in json format
*
* @throws \phpbb\auth\provider\oauth\service\exception
* @return string The unique identifier returned by the service provider
* that is used to authenticate the user with phpBB.
*/
public function perform_auth_login();
/**
* Returns the results of the authentication in json format
* Use this function when the user already has an access token
*
* @throws \phpbb\auth\provider\oauth\service\exception
* @return string The unique identifier returned by the service provider
* that is used to authenticate the user with phpBB.
*/
public function perform_token_auth();
/**
* Returns the class of external library service provider that has to be used.
*
* @return string If the string is a class, it will register the provided string as a class,
* which later will be generated as the OAuth external service provider.
* If the string is not a class, it will use this string,
* trying to generate a service for the version 2 and 1 respectively:
* \OAuth\OAuth2\Service\<string>
* If the string is empty, it will default to OAuth's standard service classes,
* trying to generate a service for the version 2 and 1 respectively:
* \OAuth\OAuth2\Service\Facebook
*/
public function get_external_service_class();
/**
* Returns the external library service provider once it has been set
*/
public function get_external_service_provider();
/**
* Sets the external library service provider
*
* @param \OAuth\Common\Service\ServiceInterface $service_provider
*/
public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider);
}

View File

@@ -0,0 +1,111 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth\service;
/**
* Twitter OAuth service
*/
class twitter extends base
{
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\request\request_interface */
protected $request;
/**
* Constructor.
*
* @param \phpbb\config\config $config Config object
* @param \phpbb\request\request_interface $request Request object
*/
public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request)
{
$this->config = $config;
$this->request = $request;
}
/**
* {@inheritdoc}
*/
public function get_service_credentials()
{
return [
'key' => $this->config['auth_oauth_twitter_key'],
'secret' => $this->config['auth_oauth_twitter_secret'],
];
}
/**
* {@inheritdoc}
*/
public function perform_auth_login()
{
if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
$storage = $this->service_provider->getStorage();
try
{
/** @var \OAuth\OAuth1\Token\TokenInterface $token */
$token = $storage->retrieveAccessToken('Twitter');
}
catch (\OAuth\Common\Storage\Exception\TokenNotFoundException $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
$secret = $token->getRequestTokenSecret();
try
{
// This was a callback request, get the token
$this->service_provider->requestAccessToken(
$this->request->variable('oauth_token', ''),
$this->request->variable('oauth_verifier', ''),
$secret
);
}
catch (\OAuth\Common\Http\Exception\TokenResponseException $e)
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_REQUEST');
}
// Send a request with it
$result = (array) json_decode($this->service_provider->request('account/verify_credentials.json'), true);
// Return the unique identifier
return $result['id'];
}
/**
* {@inheritdoc}
*/
public function perform_token_auth()
{
if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter))
{
throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
// Send a request with it
$result = (array) json_decode($this->service_provider->request('account/verify_credentials.json'), true);
// Return the unique identifier
return $result['id'];
}
}

View File

@@ -0,0 +1,620 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider\oauth;
use OAuth\OAuth1\Token\StdOAuth1Token;
use OAuth\Common\Token\TokenInterface;
use OAuth\Common\Storage\TokenStorageInterface;
use OAuth\Common\Storage\Exception\TokenNotFoundException;
use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException;
/**
* OAuth storage wrapper for phpBB's cache
*/
class token_storage implements TokenStorageInterface
{
/** @var \phpbb\db\driver\driver_interface */
protected $db;
/** @var \phpbb\user */
protected $user;
/** @var string OAuth table: token storage */
protected $oauth_token_table;
/** @var string OAuth table: state */
protected $oauth_state_table;
/** @var TokenInterface OAuth token */
protected $cachedToken;
/** @var string OAuth state */
protected $cachedState;
/**
* Constructor.
*
* @param \phpbb\db\driver\driver_interface $db Database object
* @param \phpbb\user $user User object
* @param string $oauth_token_table OAuth table: token storage
* @param string $oauth_state_table OAuth table: state
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $oauth_token_table, $oauth_state_table)
{
$this->db = $db;
$this->user = $user;
$this->oauth_token_table = $oauth_token_table;
$this->oauth_state_table = $oauth_state_table;
}
/**
* {@inheritdoc}
*/
public function retrieveAccessToken($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedToken instanceof TokenInterface)
{
return $this->cachedToken;
}
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$data['session_id'] = $this->user->data['session_id'];
}
return $this->_retrieve_access_token($data);
}
/**
* {@inheritdoc}
*/
public function storeAccessToken($service, TokenInterface $token)
{
$service = $this->get_service_name_for_db($service);
$this->cachedToken = $token;
$data = [
'oauth_token' => $this->json_encode_token($token),
];
$sql = 'UPDATE ' . $this->oauth_token_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . '
WHERE user_id = ' . (int) $this->user->data['user_id'] . "
AND provider = '" . $this->db->sql_escape($service) . "'";
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
}
$this->db->sql_query($sql);
if (!$this->db->sql_affectedrows())
{
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
'oauth_token' => $this->json_encode_token($token),
'session_id' => $this->user->data['session_id'],
];
$sql = 'INSERT INTO ' . $this->oauth_token_table . $this->db->sql_build_array('INSERT', $data);
$this->db->sql_query($sql);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function hasAccessToken($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedToken)
{
return true;
}
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$data['session_id'] = $this->user->data['session_id'];
}
return $this->has_access_token($data);
}
/**
* {@inheritdoc}
*/
public function clearToken($service)
{
$service = $this->get_service_name_for_db($service);
$this->cachedToken = null;
$sql = 'DELETE FROM ' . $this->oauth_token_table . '
WHERE user_id = ' . (int) $this->user->data['user_id'] . "
AND provider = '" . $this->db->sql_escape($service) . "'";
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
}
$this->db->sql_query($sql);
return $this;
}
/**
* {@inheritdoc}
*/
public function clearAllTokens()
{
$this->cachedToken = null;
$sql = 'DELETE FROM ' . $this->oauth_token_table . '
WHERE user_id = ' . (int) $this->user->data['user_id'];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
}
$this->db->sql_query($sql);
return $this;
}
/**
* {@inheritdoc}
*/
public function storeAuthorizationState($service, $state)
{
$service = $this->get_service_name_for_db($service);
$this->cachedState = $state;
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
'oauth_state' => $state,
'session_id' => $this->user->data['session_id'],
];
$sql = 'INSERT INTO ' . $this->oauth_state_table . ' ' . $this->db->sql_build_array('INSERT', $data);
$this->db->sql_query($sql);
return $this;
}
/**
* {@inheritdoc}
*/
public function hasAuthorizationState($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return true;
}
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$data['session_id'] = $this->user->data['session_id'];
}
return (bool) $this->get_state_row($data);
}
/**
* {@inheritdoc}
*/
public function retrieveAuthorizationState($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return $this->cachedState;
}
$data = [
'user_id' => (int) $this->user->data['user_id'],
'provider' => $service,
];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$data['session_id'] = $this->user->data['session_id'];
}
return $this->get_state_row($data);
}
/**
* {@inheritdoc}
*/
public function clearAuthorizationState($service)
{
$service = $this->get_service_name_for_db($service);
$this->cachedState = null;
$sql = 'DELETE FROM ' . $this->oauth_state_table . '
WHERE user_id = ' . (int) $this->user->data['user_id'] . "
AND provider = '" . $this->db->sql_escape($service) . "'";
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
}
$this->db->sql_query($sql);
return $this;
}
/**
* {@inheritdoc}
*/
public function clearAllAuthorizationStates()
{
$this->cachedState = null;
$sql = 'DELETE FROM ' . $this->oauth_state_table . '
WHERE user_id = ' . (int) $this->user->data['user_id'];
if ((int) $this->user->data['user_id'] === ANONYMOUS)
{
$sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
}
$this->db->sql_query($sql);
return $this;
}
/**
* Updates the user_id field in the database associated with the token.
*
* @param int $user_id The user identifier
* @return void
*/
public function set_user_id($user_id)
{
if (!$this->cachedToken)
{
return;
}
$data = [
'user_id' => (int) $user_id,
];
$sql = 'UPDATE ' . $this->oauth_token_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . '
WHERE user_id = ' . (int) $this->user->data['user_id'] . "
AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'";
$this->db->sql_query($sql);
}
/**
* Checks to see if an access token exists solely by the session_id of the user.
*
* @param string $service The OAuth service name
* @return bool true if the user's access token exists,
* false if the user's access token does not exist
*/
public function has_access_token_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedToken)
{
return true;
}
$data = [
'session_id' => $this->user->data['session_id'],
'provider' => $service,
];
return $this->has_access_token($data);
}
/**
* Checks to see if a state exists solely by the session_id of the user.
*
* @param string $service The OAuth service name
* @return bool true if the user's state exists,
* false if the user's state does not exist
*/
public function has_state_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return true;
}
$data = [
'session_id' => $this->user->data['session_id'],
'provider' => $service,
];
return (bool) $this->get_state_row($data);
}
/**
* A helper function that performs the query for has access token functions.
*
* @param array $data The SQL WHERE data
* @return bool true if the user's access token exists,
* false if the user's access token does not exist
*/
protected function has_access_token($data)
{
return (bool) $this->get_access_token_row($data);
}
/**
* A helper function that performs the query for retrieving access token functions by session.
* Also checks if the token is a valid token.
*
* @param string $service The OAuth service provider name
* @return TokenInterface
* @throws TokenNotFoundException
*/
public function retrieve_access_token_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedToken instanceof TokenInterface)
{
return $this->cachedToken;
}
$data = [
'session_id' => $this->user->data['session_id'],
'provider' => $service,
];
return $this->_retrieve_access_token($data);
}
/**
* A helper function that performs the query for retrieving state functions by session.
*
* @param string $service The OAuth service provider name
* @return string The OAuth state
* @throws AuthorizationStateNotFoundException
*/
public function retrieve_state_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return $this->cachedState;
}
$data = [
'session_id' => $this->user->data['session_id'],
'provider' => $service,
];
return $this->_retrieve_state($data);
}
/**
* A helper function that performs the query for retrieve access token functions.
* Also checks if the token is a valid token.
*
* @param array $data The SQL WHERE data
* @return TokenInterface
* @throws TokenNotFoundException
*/
protected function _retrieve_access_token($data)
{
$row = $this->get_access_token_row($data);
if (!$row)
{
throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED');
}
$token = $this->json_decode_token($row['oauth_token']);
// Ensure that the token was serialized/unserialized correctly
if (!($token instanceof TokenInterface))
{
$this->clearToken($data['provider']);
throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED');
}
$this->cachedToken = $token;
return $token;
}
/**
* A helper function that performs the query for retrieve state functions.
*
* @param array $data The SQL WHERE data
* @return string The OAuth state
* @throws AuthorizationStateNotFoundException
*/
protected function _retrieve_state($data)
{
$row = $this->get_state_row($data);
if (!$row)
{
throw new AuthorizationStateNotFoundException();
}
$this->cachedState = $row['oauth_state'];
return $this->cachedState;
}
/**
* A helper function that performs the query for retrieving an access token.
*
* @param array $data The SQL WHERE data
* @return array|false array with the OAuth token row,
* false if the token does not exist
*/
protected function get_access_token_row($data)
{
$sql = 'SELECT oauth_token
FROM ' . $this->oauth_token_table . '
WHERE ' . $this->db->sql_build_array('SELECT', $data);
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
return $row;
}
/**
* A helper function that performs the query for retrieving a state.
*
* @param array $data The SQL WHERE data
* @return array|false array with the OAuth state row,
* false if the state does not exist
*/
protected function get_state_row($data)
{
$sql = 'SELECT oauth_state
FROM ' . $this->oauth_state_table . '
WHERE ' . $this->db->sql_build_array('SELECT', $data);
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
return $row;
}
/**
* A helper function that JSON encodes a TokenInterface's data.
*
* @param TokenInterface $token
* @return string The json encoded TokenInterface's data
*/
public function json_encode_token(TokenInterface $token)
{
$members = [
'accessToken' => $token->getAccessToken(),
'endOfLife' => $token->getEndOfLife(),
'extraParams' => $token->getExtraParams(),
'refreshToken' => $token->getRefreshToken(),
'token_class' => get_class($token),
];
// Handle additional data needed for OAuth1 tokens
if ($token instanceof StdOAuth1Token)
{
$members['requestToken'] = $token->getRequestToken();
$members['requestTokenSecret'] = $token->getRequestTokenSecret();
$members['accessTokenSecret'] = $token->getAccessTokenSecret();
}
return json_encode($members);
}
/**
* A helper function that JSON decodes a data string and creates a TokenInterface.
*
* @param string $json The json encoded TokenInterface's data
* @return TokenInterface
* @throws TokenNotFoundException
*/
public function json_decode_token($json)
{
$token_data = json_decode($json, true);
if ($token_data === null)
{
throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED');
}
$token_class = $token_data['token_class'];
$access_token = $token_data['accessToken'];
$refresh_token = $token_data['refreshToken'];
$endOfLife = $token_data['endOfLife'];
$extra_params = $token_data['extraParams'];
/**
* Create the token
* @var TokenInterface $token
*/
$token = new $token_class($access_token, $refresh_token, TokenInterface::EOL_NEVER_EXPIRES, $extra_params);
$token->setEndOfLife($endOfLife);
// Handle OAuth 1.0 specific elements
if ($token instanceof StdOAuth1Token)
{
$token->setRequestToken($token_data['requestToken']);
$token->setRequestTokenSecret($token_data['requestTokenSecret']);
$token->setAccessTokenSecret($token_data['accessTokenSecret']);
}
return $token;
}
/**
* Returns the service name as it must be stored in the database.
*
* @param string $provider The OAuth provider name
* @return string The OAuth service name
*/
protected function get_service_name_for_db($provider)
{
// Enforce the naming convention for oauth services
if (strpos($provider, 'auth.provider.oauth.service.') !== 0)
{
$provider = 'auth.provider.oauth.service.' . strtolower($provider);
}
return $provider;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\auth\provider;
/**
* The interface authentication provider classes have to implement.
*/
interface provider_interface
{
/**
* Checks whether the user is currently identified to the authentication
* provider.
* Called in acp_board while setting authentication plugins.
* Changing to an authentication provider will not be permitted in acp_board
* if there is an error.
*
* @return boolean|string False if the user is identified, otherwise an
* error message, or null if not implemented.
*/
public function init();
/**
* Performs login.
*
* @param string $username The name of the user being authenticated.
* @param string $password The password of the user.
* @return array An associative array of the format:
* array(
* 'status' => status constant
* 'error_msg' => string
* 'user_row' => array
* )
* A fourth key of the array may be present:
* 'redirect_data' This key is only used when 'status' is
* equal to LOGIN_SUCCESS_LINK_PROFILE and its value is an
* associative array that is turned into GET variables on
* the redirect url.
*/
public function login($username, $password);
/**
* Autologin function
*
* @return array|null containing the user row, empty if no auto login
* should take place, or null if not implemented.
*/
public function autologin();
/**
* This function is used to output any required fields in the authentication
* admin panel. It also defines any required configuration table fields.
*
* @return array|null Returns null if not implemented or an array of the
* configuration fields of the provider.
*/
public function acp();
/**
* This function updates the template with variables related to the acp
* options with whatever configuration values are passed to it as an array.
* It then returns the name of the acp file related to this authentication
* provider.
*
* @param \phpbb\config\config $new_config Contains the new configuration values
* that have been set in acp_board.
* @return array|null Returns null if not implemented or an array with
* the template file name and an array of the vars
* that the template needs that must conform to the
* following example:
* array(
* 'TEMPLATE_FILE' => string,
* 'TEMPLATE_VARS' => array(...),
* )
* An optional third element may be added to this
* array: 'BLOCK_VAR_NAME'. If this is present,
* then its value should be a string that is used
* to designate the name of the loop used in the
* ACP template file. When this is present, an
* additional key named 'BLOCK_VARS' is required.
* This must be an array containing at least one
* array of variables that will be assigned during
* the loop in the template. An example of this is
* presented below:
* array(
* 'BLOCK_VAR_NAME' => string,
* 'BLOCK_VARS' => array(
* 'KEY IS UNIMPORTANT' => array(...),
* ),
* 'TEMPLATE_FILE' => string,
* 'TEMPLATE_VARS' => array(...),
* )
*/
public function get_acp_template($new_config);
/**
* Returns an array of data necessary to build custom elements on the login
* form.
*
* @return array|null If this function is not implemented on an auth
* provider then it returns null. If it is implemented
* it will return an array of up to four elements of
* which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is
* present then 'BLOCK_VARS' must also be present in
* the array. The fourth element 'VARS' is also
* optional. The array, with all four elements present
* looks like the following:
* array(
* 'TEMPLATE_FILE' => string,
* 'BLOCK_VAR_NAME' => string,
* 'BLOCK_VARS' => array(...),
* 'VARS' => array(...),
* )
*/
public function get_login_data();
/**
* Performs additional actions during logout.
*
* @param array $data An array corresponding to
* \phpbb\session::data
* @param boolean $new_session True for a new session, false for no new
* session.
*/
public function logout($data, $new_session);
/**
* The session validation function checks whether the user is still logged
* into phpBB.
*
* @param array $user
* @return boolean true if the given user is authenticated, false if the
* session should be closed, or null if not implemented.
*/
public function validate_session($user);
/**
* Checks to see if $login_link_data contains all information except for the
* user_id of an account needed to successfully link an external account to
* a forum account.
*
* @param array $login_link_data Any data needed to link a phpBB account to
* an external account.
* @return string|null Returns a string with a language constant if there
* is data missing or null if there is no error.
*/
public function login_link_has_necessary_data($login_link_data);
/**
* Links an external account to a phpBB account.
*
* @param array $link_data Any data needed to link a phpBB account to
* an external account.
*/
public function link_account(array $link_data);
/**
* Returns an array of data necessary to build the ucp_auth_link page
*
* @param int $user_id User ID for whom the data should be retrieved.
* defaults to 0, which is not a valid ID. The method
* should fall back to the current user's ID in this
* case.
* @return array|null If this function is not implemented on an auth
* provider then it returns null. If it is implemented
* it will return an array of up to four elements of
* which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is
* present then 'BLOCK_VARS' must also be present in
* the array. The fourth element 'VARS' is also
* optional. The array, with all four elements present
* looks like the following:
* array(
* 'TEMPLATE_FILE' => string,
* 'BLOCK_VAR_NAME' => string,
* 'BLOCK_VARS' => array(...),
* 'VARS' => array(...),
* )
*/
public function get_auth_link_data($user_id = 0);
/**
* Unlinks an external account from a phpBB account.
*
* @param array $link_data Any data needed to unlink a phpBB account
* from a phpbb account.
*/
public function unlink_account(array $link_data);
}

View File

@@ -0,0 +1,236 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\avatar\driver;
/**
* Handles avatars hosted remotely
*/
class remote extends \phpbb\avatar\driver\driver
{
/**
* {@inheritdoc}
*/
public function get_data($row)
{
return array(
'src' => $row['avatar'],
'width' => $row['avatar_width'],
'height' => $row['avatar_height'],
);
}
/**
* {@inheritdoc}
*/
public function prepare_form($request, $template, $user, $row, &$error)
{
$template->assign_vars(array(
'AVATAR_REMOTE_WIDTH' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_remote_width', ''),
'AVATAR_REMOTE_HEIGHT' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_remote_width', ''),
'AVATAR_REMOTE_URL' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar']) ? $row['avatar'] : '',
));
return true;
}
/**
* {@inheritdoc}
*/
public function process_form($request, $template, $user, $row, &$error)
{
global $phpbb_dispatcher;
$url = $request->variable('avatar_remote_url', '');
$width = $request->variable('avatar_remote_width', 0);
$height = $request->variable('avatar_remote_height', 0);
if (empty($url))
{
return false;
}
if (!preg_match('#^(http|https|ftp)://#i', $url))
{
$url = 'http://' . $url;
}
if (!function_exists('validate_data'))
{
require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
}
$validate_array = validate_data(
array(
'url' => $url,
),
array(
'url' => array('string', true, 5, 255),
)
);
$error = array_merge($error, $validate_array);
if (!empty($error))
{
return false;
}
/**
* Event to make custom validation of avatar upload
*
* @event core.ucp_profile_avatar_upload_validation
* @var string url Image url
* @var string width Image width
* @var string height Image height
* @var array error Error message array
* @since 3.2.9-RC1
*/
$vars = array('url', 'width', 'height', 'error');
extract($phpbb_dispatcher->trigger_event('core.ucp_profile_avatar_upload_validation', compact($vars)));
if (!empty($error))
{
return false;
}
// Check if this url looks alright
// Do not allow specifying the port (see RFC 3986) or IP addresses
if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url) ||
preg_match('@^(http|https|ftp)://[^/:?#]+:[0-9]+[/:?#]@i', $url) ||
preg_match('#^(http|https|ftp)://(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])#i', $url) ||
preg_match('#^(http|https|ftp)://(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))#i', $url))
{
$error[] = 'AVATAR_URL_INVALID';
return false;
}
// Get image dimensions
if (($width <= 0 || $height <= 0) && (($image_data = $this->imagesize->getImageSize($url)) === false))
{
$error[] = 'UNABLE_GET_IMAGE_SIZE';
return false;
}
if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['height'] <= 0))
{
$error[] = 'AVATAR_NO_SIZE';
return false;
}
$width = ($width && $height) ? $width : $image_data['width'];
$height = ($width && $height) ? $height : $image_data['height'];
if ($width <= 0 || $height <= 0)
{
$error[] = 'AVATAR_NO_SIZE';
return false;
}
$types = \phpbb\files\upload::image_types();
$extension = strtolower(\phpbb\files\filespec::get_extension($url));
// Check if this is actually an image
if ($file_stream = @fopen($url, 'r'))
{
// Timeout after 1 second
stream_set_timeout($file_stream, 1);
// read some data to ensure headers are present
fread($file_stream, 1024);
$meta = stream_get_meta_data($file_stream);
if (isset($meta['wrapper_data']['headers']) && is_array($meta['wrapper_data']['headers']))
{
$headers = $meta['wrapper_data']['headers'];
}
else if (isset($meta['wrapper_data']) && is_array($meta['wrapper_data']))
{
$headers = $meta['wrapper_data'];
}
else
{
$headers = array();
}
foreach ($headers as $header)
{
$header = preg_split('/ /', $header, 2);
if (strtr(strtolower(trim($header[0], ':')), '_', '-') === 'content-type')
{
if (strpos($header[1], 'image/') !== 0)
{
$error[] = 'AVATAR_URL_INVALID';
fclose($file_stream);
return false;
}
else
{
fclose($file_stream);
break;
}
}
}
}
else
{
$error[] = 'AVATAR_URL_INVALID';
return false;
}
if (!empty($image_data) && (!isset($types[$image_data['type']]) || !in_array($extension, $types[$image_data['type']])))
{
if (!isset($types[$image_data['type']]))
{
$error[] = 'UNABLE_GET_IMAGE_SIZE';
}
else
{
$error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data['type']][0], $extension);
}
return false;
}
if ($this->config['avatar_max_width'] || $this->config['avatar_max_height'])
{
if ($width > $this->config['avatar_max_width'] || $height > $this->config['avatar_max_height'])
{
$error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height);
return false;
}
}
if ($this->config['avatar_min_width'] || $this->config['avatar_min_height'])
{
if ($width < $this->config['avatar_min_width'] || $height < $this->config['avatar_min_height'])
{
$error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height);
return false;
}
}
return array(
'avatar' => $url,
'avatar_width' => $width,
'avatar_height' => $height,
);
}
/**
* {@inheritdoc}
*/
public function get_template_name()
{
return 'ucp_avatar_options_remote.html';
}
}

View File

@@ -0,0 +1,332 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\avatar\driver;
/**
* Handles avatars uploaded to the board
*/
class upload extends \phpbb\avatar\driver\driver
{
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* @var \phpbb\files\factory
*/
protected $files_factory;
/**
* 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\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
*/
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)
{
$this->config = $config;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->filesystem = $filesystem;
$this->path_helper = $path_helper;
$this->dispatcher = $dispatcher;
$this->files_factory = $files_factory;
$this->cache = $cache;
}
/**
* {@inheritdoc}
*/
public function get_data($row)
{
$root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path();
return array(
'src' => $root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'],
'width' => $row['avatar_width'],
'height' => $row['avatar_height'],
);
}
/**
* {@inheritdoc}
*/
public function prepare_form($request, $template, $user, $row, &$error)
{
if (!$this->can_upload())
{
return false;
}
$template->assign_vars(array(
'S_UPLOAD_AVATAR_URL' => ($this->config['allow_avatar_remote_upload']) ? true : false,
'AVATAR_UPLOAD_SIZE' => $this->config['avatar_filesize'],
'AVATAR_ALLOWED_EXTENSIONS' => implode(',', preg_replace('/^/', '.', $this->allowed_extensions)),
));
return true;
}
/**
* {@inheritdoc}
*/
public function process_form($request, $template, $user, $row, &$error)
{
if (!$this->can_upload())
{
return false;
}
/** @var \phpbb\files\upload $upload */
$upload = $this->files_factory->get('upload')
->set_error_prefix('AVATAR_')
->set_allowed_extensions($this->allowed_extensions)
->set_max_filesize($this->config['avatar_filesize'])
->set_allowed_dimensions(
$this->config['avatar_min_width'],
$this->config['avatar_min_height'],
$this->config['avatar_max_width'],
$this->config['avatar_max_height'])
->set_disallowed_content((isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false));
$url = $request->variable('avatar_upload_url', '');
$upload_file = $request->file('avatar_upload_file');
if (!empty($upload_file['name']))
{
$file = $upload->handle_upload('files.types.form', 'avatar_upload_file');
}
else if (!empty($this->config['allow_avatar_remote_upload']) && !empty($url))
{
if (!preg_match('#^(http|https|ftp)://#i', $url))
{
$url = 'http://' . $url;
}
if (!function_exists('validate_data'))
{
require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
}
$validate_array = validate_data(
array(
'url' => $url,
),
array(
'url' => array('string', true, 5, 255),
)
);
$error = array_merge($error, $validate_array);
if (!empty($error))
{
return false;
}
// Do not allow specifying the port (see RFC 3986) or IP addresses
// remote_upload() will do its own check for allowed filetypes
if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url) ||
preg_match('@^(http|https|ftp)://[^/:?#]+:[0-9]+[/:?#]@i', $url) ||
preg_match('#^(http|https|ftp)://(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])#i', $url) ||
preg_match('#^(http|https|ftp)://(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))#i', $url))
{
$error[] = 'AVATAR_URL_INVALID';
return false;
}
$file = $upload->handle_upload('files.types.remote', $url);
}
else
{
return false;
}
$prefix = $this->config['avatar_salt'] . '_';
$file->clean_filename('avatar', $prefix, $row['id']);
// If there was an error during upload, then abort operation
if (count($file->error))
{
$file->remove();
$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'),
'mimetype' => $file->get('mimetype'),
'extension' => $file->get('extension'),
'physical_filename' => $file->get('realname'),
'real_filename' => $file->get('uploadname'),
);
/**
* Before moving new file in place (and eventually overwriting the existing avatar with the newly uploaded avatar)
*
* @event core.avatar_driver_upload_move_file_before
* @var array filedata Array containing uploaded file data
* @var \phpbb\files\filespec file Instance of filespec class
* @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
* @since 3.1.6-RC1
* @changed 3.1.9-RC1 Added filedata
* @changed 3.2.3-RC1 Added file
*/
$vars = array(
'filedata',
'file',
'destination',
'prefix',
'row',
'error',
);
extract($this->dispatcher->trigger_event('core.avatar_driver_upload_move_file_before', compact($vars)));
unset($filedata);
if (!count($error))
{
// Move file and overwrite any existing image
$file->move_file($destination, true);
}
// If there was an error during move, then clean up leftovers
$error = array_merge($error, $file->error);
if (count($error))
{
$file->remove();
return false;
}
// Delete current avatar if not overwritten
$ext = substr(strrchr($row['avatar'], '.'), 1);
if ($ext && $ext !== $file->get('extension'))
{
$this->delete($row);
}
return array(
'avatar' => $row['id'] . '_' . time() . '.' . $file->get('extension'),
'avatar_width' => $file->get('width'),
'avatar_height' => $file->get('height'),
);
}
/**
* {@inheritdoc}
*/
public function prepare_form_acp($user)
{
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),
);
}
/**
* {@inheritdoc}
*/
public function delete($row)
{
$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;
/**
* 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
*/
$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))
{
try
{
$this->filesystem->remove($filename);
return true;
}
catch (\phpbb\filesystem\exception\filesystem_exception $e)
{
// Fail is covered by return statement below
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function get_template_name()
{
return 'ucp_avatar_options_upload.html';
}
/**
* Check if user is able to upload an avatar
*
* @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'));
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cache\driver;
if (!defined('PHPBB_ACM_MEMCACHED_PORT'))
{
define('PHPBB_ACM_MEMCACHED_PORT', 11211);
}
if (!defined('PHPBB_ACM_MEMCACHED_COMPRESS'))
{
define('PHPBB_ACM_MEMCACHED_COMPRESS', true);
}
if (!defined('PHPBB_ACM_MEMCACHED_HOST'))
{
define('PHPBB_ACM_MEMCACHED_HOST', 'localhost');
}
if (!defined('PHPBB_ACM_MEMCACHED'))
{
//can define multiple servers with host1/port1,host2/port2 format
define('PHPBB_ACM_MEMCACHED', PHPBB_ACM_MEMCACHED_HOST . '/' . PHPBB_ACM_MEMCACHED_PORT);
}
/**
* ACM for Memcached
*/
class memcached extends \phpbb\cache\driver\memory
{
/** @var string Extension to use */
protected $extension = 'memcached';
/** @var \Memcached Memcached class */
protected $memcached;
/** @var int Flags */
protected $flags = 0;
/**
* Memcached constructor
*
* @param string $memcached_servers Memcached servers string (optional)
*/
public function __construct($memcached_servers = '')
{
// Call the parent constructor
parent::__construct();
$memcached_servers = $memcached_servers ?: PHPBB_ACM_MEMCACHED;
$this->memcached = new \Memcached();
$this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
// Memcached defaults to using compression, disable if we don't want
// to use it
if (!PHPBB_ACM_MEMCACHED_COMPRESS)
{
$this->memcached->setOption(\Memcached::OPT_COMPRESSION, false);
}
$server_list = [];
foreach (explode(',', $memcached_servers) as $u)
{
if (preg_match('#(.*)/(\d+)#', $u, $parts))
{
$server_list[] = [trim($parts[1]), (int) trim($parts[2])];
}
}
$this->memcached->addServers($server_list);
if (empty($server_list) || empty($this->memcached->getStats()))
{
trigger_error('Could not connect to memcached server(s).');
}
}
/**
* {@inheritDoc}
*/
public function unload()
{
parent::unload();
unset($this->memcached);
}
/**
* {@inheritDoc}
*/
public function purge()
{
$this->memcached->flush();
parent::purge();
}
/**
* Fetch an item from the cache
*
* @param string $var Cache key
*
* @return mixed Cached data
*/
protected function _read($var)
{
return $this->memcached->get($this->key_prefix . $var);
}
/**
* Store data in the cache
*
* @param string $var Cache key
* @param mixed $data Data to store
* @param int $ttl Time-to-live of cached data
* @return bool True if the operation succeeded
*/
protected function _write($var, $data, $ttl = 2592000)
{
if (!$this->memcached->replace($this->key_prefix . $var, $data, $ttl))
{
return $this->memcached->set($this->key_prefix . $var, $data, $ttl);
}
return true;
}
/**
* Remove an item from the cache
*
* @param string $var Cache key
* @return bool True if the operation succeeded
*/
protected function _delete($var)
{
return $this->memcached->delete($this->key_prefix . $var);
}
}

View File

@@ -0,0 +1,282 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cache\driver;
/**
* ACM Abstract Memory Class
*/
abstract class memory extends \phpbb\cache\driver\base
{
var $key_prefix;
/**
* Set cache path
*/
function __construct()
{
global $dbname, $table_prefix, $phpbb_container;
$this->cache_dir = $phpbb_container->getParameter('core.cache_dir');
$this->key_prefix = substr(md5($dbname . $table_prefix), 0, 8) . '_';
if (!isset($this->extension) || !extension_loaded($this->extension))
{
global $acm_type;
trigger_error("Could not find required extension [{$this->extension}] for the ACM module $acm_type.", E_USER_ERROR);
}
if (isset($this->function) && !function_exists($this->function))
{
global $acm_type;
trigger_error("The required function [{$this->function}] is not available for the ACM module $acm_type.", E_USER_ERROR);
}
}
/**
* {@inheritDoc}
*/
function load()
{
// grab the global cache
$data = $this->_read('global');
if ($data !== false)
{
$this->vars = $data;
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
function save()
{
if (!$this->is_modified)
{
return;
}
$this->_write('global', $this->vars, 2592000);
$this->is_modified = false;
}
/**
* {@inheritDoc}
*/
function tidy()
{
global $config;
// cache has auto GC, no need to have any code here :)
$config->set('cache_last_gc', time(), false);
}
/**
* {@inheritDoc}
*/
function get($var_name)
{
if ($var_name[0] == '_')
{
if (!$this->_exists($var_name))
{
return false;
}
return $this->_read($var_name);
}
else
{
return ($this->_exists($var_name)) ? $this->vars[$var_name] : false;
}
}
/**
* {@inheritDoc}
*/
function put($var_name, $var, $ttl = 2592000)
{
if ($var_name[0] == '_')
{
$this->_write($var_name, $var, $ttl);
}
else
{
$this->vars[$var_name] = $var;
$this->is_modified = true;
}
}
/**
* {@inheritDoc}
*/
function destroy($var_name, $table = '')
{
if ($var_name == 'sql' && !empty($table))
{
if (!is_array($table))
{
$table = array($table);
}
foreach ($table as $table_name)
{
// gives us the md5s that we want
$temp = $this->_read('sql_' . $table_name);
if ($temp === false)
{
continue;
}
// delete each query ref
foreach ($temp as $md5_id => $void)
{
$this->_delete('sql_' . $md5_id);
}
// delete the table ref
$this->_delete('sql_' . $table_name);
}
return;
}
if (!$this->_exists($var_name))
{
return;
}
if ($var_name[0] == '_')
{
$this->_delete($var_name);
}
else if (isset($this->vars[$var_name]))
{
$this->is_modified = true;
unset($this->vars[$var_name]);
// We save here to let the following cache hits succeed
$this->save();
}
}
/**
* {@inheritDoc}
*/
function _exists($var_name)
{
if ($var_name[0] == '_')
{
return $this->_isset($var_name);
}
else
{
if (!count($this->vars))
{
$this->load();
}
return isset($this->vars[$var_name]);
}
}
/**
* {@inheritDoc}
*/
function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl)
{
// Remove extra spaces and tabs
$query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
$query_id = md5($query);
// determine which tables this query belongs to
// Some queries use backticks, namely the get_database_size() query
// don't check for conformity, the SQL would error and not reach here.
if (!preg_match_all('/(?:FROM \\(?(`?\\w+`?(?: \\w+)?(?:, ?`?\\w+`?(?: \\w+)?)*)\\)?)|(?:JOIN (`?\\w+`?(?: \\w+)?))/', $query, $regs, PREG_SET_ORDER))
{
// Bail out if the match fails.
return $query_result;
}
$tables = array();
foreach ($regs as $match)
{
if ($match[0][0] == 'F')
{
$tables = array_merge($tables, array_map('trim', explode(',', $match[1])));
}
else
{
$tables[] = $match[2];
}
}
foreach ($tables as $table_name)
{
// Remove backticks
$table_name = ($table_name[0] == '`') ? substr($table_name, 1, -1) : $table_name;
if (($pos = strpos($table_name, ' ')) !== false)
{
$table_name = substr($table_name, 0, $pos);
}
$temp = $this->_read('sql_' . $table_name);
if ($temp === false)
{
$temp = array();
}
$temp[$query_id] = true;
// This must never expire
$this->_write('sql_' . $table_name, $temp, 0);
}
// store them in the right place
$this->sql_rowset[$query_id] = array();
$this->sql_row_pointer[$query_id] = 0;
while ($row = $db->sql_fetchrow($query_result))
{
$this->sql_rowset[$query_id][] = $row;
}
$db->sql_freeresult($query_result);
$this->_write('sql_' . $query_id, $this->sql_rowset[$query_id], $ttl);
return $query_id;
}
/**
* Check if a cache var exists
*
* @access protected
* @param string $var Cache key
* @return bool True if it exists, otherwise false
*/
function _isset($var)
{
// Most caches don't need to check
return true;
}
}

View File

@@ -0,0 +1,386 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\captcha;
/**
* Main non-gd captcha class
* @ignore
*/
class non_gd
{
var $filtered_pngs;
var $width = 320;
var $height = 50;
/**
* Define filtered pngs on init
*/
function __construct()
{
// If we can we will generate a single filtered png, we avoid nastiness via emulation of some Zlib stuff
$this->define_filtered_pngs();
}
/**
* Create the image containing $code with a seed of $seed
*/
function execute($code, $seed)
{
$img_height = $this->height - 10;
$img_width = 0;
mt_srand($seed);
$char_widths = $hold_chars = array();
$code_len = strlen($code);
for ($i = 0; $i < $code_len; $i++)
{
$char = $code[$i];
$width = mt_rand(0, 4);
$raw_width = $this->filtered_pngs[$char]['width'];
$char_widths[$i] = $width;
$img_width += $raw_width - $width;
// Split the char into chunks of $raw_width + 1 length
if (empty($hold_chars[$char]))
{
$hold_chars[$char] = str_split(base64_decode($this->filtered_pngs[$char]['data']), $raw_width + 1);
}
}
$offset_x = mt_rand(0, $this->width - $img_width);
$offset_y = mt_rand(0, $this->height - $img_height);
$image = '';
for ($i = 0; $i < $this->height; $i++)
{
$image .= chr(0);
if ($i > $offset_y && $i < $offset_y + $img_height)
{
for ($j = 0; $j < $offset_x; $j++)
{
$image .= chr(mt_rand(140, 255));
}
for ($j = 0; $j < $code_len; $j++)
{
$image .= $this->randomise(substr($hold_chars[$code[$j]][$i - $offset_y - 1], 1), $char_widths[$j]);
}
for ($j = $offset_x + $img_width; $j < $this->width; $j++)
{
$image .= chr(mt_rand(140, 255));
}
}
else
{
for ($j = 0; $j < $this->width; $j++)
{
$image .= chr(mt_rand(140, 255));
}
}
}
unset($hold_chars);
$image = $this->create_png($image, $this->width, $this->height);
// Output image
header('Content-Type: image/png');
header('Cache-control: no-cache, no-store');
echo $image;
exit;
}
/**
* This is designed to randomise the pixels of the image data within
* certain limits so as to keep it readable. It also varies the image
* width a little
*/
function randomise($scanline, $width)
{
$new_line = '';
$end = strlen($scanline) - ceil($width/2);
for ($i = (int) floor($width / 2); $i < $end; $i++)
{
$pixel = ord($scanline[$i]);
if ($pixel < 190)
{
$new_line .= chr(mt_rand(0, 205));
}
else if ($pixel > 190)
{
$new_line .= chr(mt_rand(145, 255));
}
else
{
$new_line .= $scanline[$i];
}
}
return $new_line;
}
/**
* This creates a chunk of the given type, with the given data
* of the given length adding the relevant crc
*/
function png_chunk($length, $type, $data)
{
$raw = $type . $data;
return pack('N', $length) . $raw . pack('N', crc32($raw));
}
/**
* Creates greyscale 8bit png - The PNG spec can be found at
* http://www.libpng.org/pub/png/spec/PNG-Contents.html we use
* png because it's a fully recognised open standard and supported
* by practically all modern browsers and OSs
*/
function create_png($raw_image, $width, $height)
{
// SIG
$image = pack('C8', 137, 80, 78, 71, 13, 10, 26, 10);
// IHDR
$raw = pack('N2', $width, $height);
$raw .= pack('C5', 8, 0, 0, 0, 0);
$image .= $this->png_chunk(13, 'IHDR', $raw);
// IDAT
if (@extension_loaded('zlib'))
{
$raw_image = gzcompress($raw_image);
$length = strlen($raw_image);
}
else
{
// The total length of this image, uncompressed, is just a calculation of pixels
$length = ($width + 1) * $height;
// Adler-32 hash generation
// Note: The hash is _backwards_ so we must reverse it
if (@extension_loaded('hash'))
{
$adler_hash = strrev(hash('adler32', $raw_image, true));
}
else if (@extension_loaded('mhash'))
{
$adler_hash = strrev(mhash(MHASH_ADLER32, $raw_image));
}
else
{
// Optimized Adler-32 loop ported from the GNU Classpath project
$temp_length = $length;
$s1 = 1;
$s2 = $index = 0;
while ($temp_length > 0)
{
// We can defer the modulo operation:
// s1 maximally grows from 65521 to 65521 + 255 * 3800
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
$substract_value = ($temp_length < 3800) ? $temp_length : 3800;
$temp_length -= $substract_value;
while (--$substract_value >= 0)
{
$s1 += ord($raw_image[$index]);
$s2 += $s1;
$index++;
}
$s1 %= 65521;
$s2 %= 65521;
}
$adler_hash = pack('N', ($s2 << 16) | $s1);
}
// This is the same thing as gzcompress($raw_image, 0) but does not need zlib
$raw_image = pack('C3v2', 0x78, 0x01, 0x01, $length, ~$length) . $raw_image . $adler_hash;
// The Zlib header + Adler hash make us add on 11
$length += 11;
}
// IDAT
$image .= $this->png_chunk($length, 'IDAT', $raw_image);
// IEND
$image .= $this->png_chunk(0, 'IEND', '');
return $image;
}
/**
* png image data
* Each 'data' element is base64_encoded uncompressed IDAT
*/
function define_filtered_pngs()
{
$this->filtered_pngs = array(
'0' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////olFAkBAAAGDyA4P///M31/////////////wD////////////////0dAgAAAAAAAAAAAAEcPipFGHn////////////AP//////////////6DAAAAAAAAAAAAAAAAAALSEAN+T///////////8A//////////////xAAAAAAAAAAAAAAAAAAAAAACPA/////////////wD/////////////oAAAAAAAAAAAAAAAAAAAAAAAev//////////////AP////////////8oAAAAAAAAPNj/zDAAAAAAAABD//////////////8A////////////1AAAAAAAABjw////5BAAAAAAAADo/////////////wD///////////+QAAAAAAAAbP//////QgAAAAAAAKj/////////////AP///////////1wAAAAAAACs/////8AXAAAAAAAAcP////////////8A////////////OAAAAAAAAND////dNwAAAAAAAABI/////////////wD///////////8gAAAAAAAA4P//7koACwAAAAAAACT/////////////AP///////////wgAAAAAAAD///VqAwaPAAAAAAAAEP////////////8A////////////AAAAAAAAAP/8kQYDavUAAAAAAAAA/////////////wD///////////8AAAAAAAAA/6kNAEru/wAAAAAAAAD/////////////AP///////////wAAAAAAAADAIwA33f//AAAAAAAAAP////////////8A////////////FAAAAAAAADYAI8D///8AAAAAAAAQ/////////////wD///////////8kAAAAAAAAAA2p////5AAAAAAAACD/////////////AP///////////0gAAAAAAAAFkfz////UAAAAAAAAQP////////////8A////////////cAAAAAAAAET1/////7AAAAAAAABo/////////////wD///////////+oAAAAAAAAXfX/////sAAAAAAAAGj/////////////AAAAALgAAAAAAAAwAAAAAAAAAAAAAAD////////////oAAAAAAAACOT////oEAAAAAAAAOD/////////////AP////////////8+AAAAAAAAKMz/zDQAAAAAAAA0//////////////8A////////////7jgAAAAAAAAAAAAAAAAAAAAAAKT//////////////wD///////////VqAwIAAAAAAAAAAAAAAAAAAAA8////////////////AP//////////rQcDaVEAAAAAAAAAAAAAAAAAKOj///////////////8A///////////nblnu/IAIAAAAAAAAAAAAAFzw/////////////////wD////////////79////+iITCAAAAAgSITg////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////w==',
'width' => 40
),
'1' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////8BAAAAAAAP//////////////////AP////////////////////////9sAAAAAAAA//////////////////8A////////////////////////pAAAAAAAAAD//////////////////wD//////////////////////6wEAAAAAAAAAP//////////////////AP////////////////////h4AAAAAAAAAAAA//////////////////8A//////////////////ygJAAAAAAAAAAAAAD//////////////////wD//////////////9x8HAAAAAAAAAAAAAAAAP//////////////////AP//////////////AAAAAAAAAAAAAAAAAAAA//////////////////8A//////////////8AAAAAAAAAAAAAAAAAAAD//////////////////wD//////////////wAAAAAAAAR4AAAAAAAAAP//////////////////AP//////////////AAAAAAA4zP8AAAAAAAAA//////////////////8A//////////////8AAAA4sP///wAAAAAAAAD//////////////////wD//////////////yR80P//////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'2' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////okFAkCAAABCBIfNT///////////////////8A///////////////8hAgAAAAAAAAAAAAAAFTo/////////////////wD//////////////1QAAAAAAAAAAAAAAAAAACjo////////////////AP////////////+MAAAAAAAAAAAAAAAAAAAAADj///////////////8A////////////9BAAAAAAAAAAAAAAAAAAAAAAALD//////////////wD///////////+gAAAAAAAAAHjs+KwMAAAAAAAAVP//////////////AP///////////1gAAAAAAABM/////6QAAAAAAAAU//////////////8A////////////KAAAAAAAALj/////+AAAAAAAAAD//////////////wD///////////+MfGBMOCAI8P/////wAAAAAAAACP//////////////AP///////////////////////////5wAAAAAAAAw//////////////8A///////////////////////////oFAAAAAAAAHz//////////////wD/////////////////////////6CgAAAAAAAAE3P//////////////AP///////////////////////9ggAAAAAAAAAHT///////////////8A//////////////////////+0DAAAAAAAAAA8+P///////////////wD/////////////////////gAAAAAAAAAAAKOj/////////////////AP//////////////////9FAAAAAAAAAAADzw//////////////////8A/////////////////+g4AAAAAAAAAABk/P///////////////////wD////////////////oKAAAAAAAAAAMqP//////////////////////AP//////////////6CgAAAAAAAAAMNz///////////////////////8A//////////////g4AAAAAAAAAFT0/////////////////////////wD/////////////bAAAAAAAAABU/P//////////////////////////AP///////////8wAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A////////////SAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////9AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////xAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'3' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////8sGg0FAAAACA4cLz8////////////////////AP//////////////rBgAAAAAAAAAAAAAACTA//////////////////8A/////////////3QAAAAAAAAAAAAAAAAAAASs/////////////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAjc////////////////AP//////////6AwAAAAAAAAAAAAAAAAAAAAAAGT///////////////8A//////////94AAAAAAAABJDw/8g4AAAAAAAAHP///////////////wD//////////yAAAAAAAACE/////9gAAAAAAAAA////////////////AP///////////NSwiGQ4FOT//////AAAAAAAABD///////////////8A//////////////////////////+YAAAAAAAAVP///////////////wD//////////////////////P/ggAQAAAAAAATM////////////////AP////////////////////9gAAAAAAAAAAAElP////////////////8A/////////////////////0AAAAAAAAAAHLj//////////////////wD/////////////////////OAAAAAAAAAAwkPj/////////////////AP////////////////////8gAAAAAAAAAAAAINj///////////////8A/////////////////////xAAAAAAAAAAAAAAIPD//////////////wD/////////////////////uOz/4HgEAAAAAAAAhP//////////////AP///////////////////////////3wAAAAAAAAw//////////////8A////////////////////////////6AAAAAAAAAj//////////////wD/////////////////////////////AAAAAAAAAP//////////////AP//////////tJh8YEQoDNz//////+AAAAAAAAAY//////////////8A//////////88AAAAAAAAaP//////dAAAAAAAAEz//////////////wD//////////6QAAAAAAAAAdOD/5HQAAAAAAAAApP//////////////AP///////////CgAAAAAAAAAAAAAAAAAAAAAACD4//////////////8A////////////yAQAAAAAAAAAAAAAAAAAAAAEuP///////////////wD/////////////rAQAAAAAAAAAAAAAAAAABJD/////////////////AP//////////////zDQAAAAAAAAAAAAAACTA//////////////////8A/////////////////8BwOCAAAAAUNGi0/P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'4' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////////////nAAAAAAAAAD///////////////8A/////////////////////////8AEAAAAAAAAAP///////////////wD////////////////////////gGAAAAAAAAAAA////////////////AP//////////////////////9DAAAAAAAAAAAAD///////////////8A//////////////////////9UAAAAAAAAAAAAAP///////////////wD/////////////////////hAAAAAAAAAAAAAAA////////////////AP///////////////////7QAAAAAAAAAAAAAAAD///////////////8A///////////////////UDAAAAAAUAAAAAAAAAP///////////////wD/////////////////7CQAAAAABMAAAAAAAAAA////////////////AP////////////////xEAAAAAACU/wAAAAAAAAD///////////////8A////////////////cAAAAAAAZP//AAAAAAAAAP///////////////wD//////////////6AAAAAAADz8//8AAAAAAAAA////////////////AP/////////////IBAAAAAAc6P///wAAAAAAAAD///////////////8A////////////5BgAAAAADMz/////AAAAAAAAAP///////////////wD///////////g0AAAAAACk//////8AAAAAAAAA////////////////AP//////////XAAAAAAAfP///////wAAAAAAAAD///////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP///////////////////////////wAAAAAAAAD///////////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'5' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////8AAAAAAAAAAAAAAAAAAAAAAA//////////////8A///////////////MAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////6wAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////iAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////////9kAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////0QAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////IAAAAAAAYP////////////////////////////8A//////////////wAAAAAAAB8/////////////////////////////wD/////////////3AAAAAAAAIj/////////////////////////////AP////////////+4AAAAAAAAoLRYHAAEKGTE//////////////////8A/////////////5QAAAAAAAAQAAAAAAAAAABY9P///////////////wD/////////////dAAAAAAAAAAAAAAAAAAAAAA89P//////////////AP////////////9QAAAAAAAAAAAAAAAAAAAAAABg//////////////8A/////////////zAAAAAAAAAAAAAAAAAAAAAAAADQ/////////////wD/////////////IAAAAAAAAGjY/+h4BAAAAAAAAGz/////////////AP//////////////9NS0lHSc//////90AAAAAAAALP////////////8A/////////////////////////////9QAAAAAAAAE/////////////wD//////////////////////////////wAAAAAAAAD/////////////AP/////////////////////////////8AAAAAAAAEP////////////8A////////////pIRwWEAgDOD//////8wAAAAAAAA8/////////////wD///////////9EAAAAAAAAaP//////ZAAAAAAAAHz/////////////AP///////////6QAAAAAAAAAaOD/4GQAAAAAAAAE4P////////////8A/////////////CQAAAAAAAAAAAAAAAAAAAAAAGD//////////////wD/////////////yAQAAAAAAAAAAAAAAAAAAAAc7P//////////////AP//////////////rAwAAAAAAAAAAAAAAAAAGNj///////////////8A////////////////0EAAAAAAAAAAAAAAAFTo/////////////////wD//////////////////8h4QCAAAAAcQHzU////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'6' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////+0ZCwMAAAUNGjI////////////////////AP/////////////////EMAAAAAAAAAAAAABM6P////////////////8A////////////////lAQAAAAAAAAAAAAAAAAo6P///////////////wD//////////////6wAAAAAAAAAAAAAAAAAAABI////////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAACw//////////////8A/////////////3AAAAAAAAAoxP/YPAAAAAAAAEj//////////////wD////////////4EAAAAAAACOD////YDCBAVGiAoP//////////////AP///////////7gAAAAAAABY//////////////////////////////8A////////////eAAAAAAAAJT//////////////////////////////wD///////////9MAAAAAAAAvP/IXBgABCx03P//////////////////AP///////////ygAAAAAAADcdAAAAAAAAAAEiP////////////////8A////////////FAAAAAAAAFAAAAAAAAAAAAAAcP///////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAlP//////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAQ8P////////////8A////////////AAAAAAAAAABAyP/kZAAAAAAAAACQ/////////////wD///////////8MAAAAAAAALPj/////WAAAAAAAAET/////////////AP///////////yQAAAAAAACY///////MAAAAAAAAFP////////////8A////////////SAAAAAAAAMD///////wAAAAAAAAA/////////////wD///////////9wAAAAAAAAvP///////wAAAAAAAAD/////////////AP///////////7QAAAAAAACI///////UAAAAAAAAJP////////////8A////////////+AwAAAAAACDw/////2wAAAAAAABY/////////////wD/////////////cAAAAAAAADC8/Ox4AAAAAAAAAKj/////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAAAk/P////////////8A//////////////+oAAAAAAAAAAAAAAAAAAAABLj//////////////wD///////////////+QAAAAAAAAAAAAAAAAAACQ////////////////AP////////////////+0JAAAAAAAAAAAAAAkuP////////////////8A///////////////////8sGg0FAAADCxgqPz//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'7' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAABP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAy4/////////////wD//////////////////////////+QUAAAAAAAEuP//////////////AP/////////////////////////8QAAAAAAAAKT///////////////8A/////////////////////////4wAAAAAAAB0/////////////////wD////////////////////////cCAAAAAAANPz/////////////////AP///////////////////////0QAAAAAAATY//////////////////8A//////////////////////+0AAAAAAAAeP///////////////////wD//////////////////////CQAAAAAABTw////////////////////AP////////////////////+gAAAAAAAAkP////////////////////8A/////////////////////ywAAAAAABDw/////////////////////wD///////////////////+4AAAAAAAAbP//////////////////////AP///////////////////1wAAAAAAADQ//////////////////////8A///////////////////4DAAAAAAAMP///////////////////////wD//////////////////7QAAAAAAAB8////////////////////////AP//////////////////aAAAAAAAAMj///////////////////////8A//////////////////8oAAAAAAAM/P///////////////////////wD/////////////////8AAAAAAAAET/////////////////////////AP////////////////+0AAAAAAAAcP////////////////////////8A/////////////////4wAAAAAAACY/////////////////////////wD/////////////////WAAAAAAAAMD/////////////////////////AP////////////////80AAAAAAAA4P////////////////////////8A/////////////////xAAAAAAAAD4/////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'8' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////////IdDQUAAAEIEiA1P//////////////////AP/////////////////gRAAAAAAAAAAAAAAAROD///////////////8A////////////////0BgAAAAAAAAAAAAAAAAAEMj//////////////wD///////////////AcAAAAAAAAAAAAAAAAAAAAHPD/////////////AP//////////////hAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A//////////////8sAAAAAAAAKMz/zCgAAAAAAAAs/////////////wD//////////////wAAAAAAAADM////zAAAAAAAAAD/////////////AP//////////////BAAAAAAAAP//////AAAAAAAABP////////////8A//////////////8sAAAAAAAAzP///9QAAAAAAAAw/////////////wD//////////////3wAAAAAAAAoyP/YNAAAAAAAAIT/////////////AP//////////////7BgAAAAAAAAAAAAAAAAAAAAc8P////////////8A////////////////xBgAAAAAAAAAAAAAAAAAGNj//////////////wD/////////////////tAQAAAAAAAAAAAAAAACo////////////////AP///////////////HAAAAAAAAAAAAAAAAAAAAB8//////////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB8/////////////wD/////////////wAAAAAAAAABk4P/UWAAAAAAAAATQ////////////AP////////////9UAAAAAAAAaP//////XAAAAAAAAGT///////////8A/////////////xgAAAAAAADg///////cAAAAAAAAJP///////////wD/////////////AAAAAAAAAP////////8AAAAAAAAA////////////AP////////////8AAAAAAAAA4P//////3AAAAAAAAAT///////////8A/////////////ygAAAAAAABg//////9cAAAAAAAALP///////////wD/////////////ZAAAAAAAAABY1P/cXAAAAAAAAABw////////////AP/////////////QAAAAAAAAAAAAAAAAAAAAAAAABNz///////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB0/////////////wD///////////////Q8AAAAAAAAAAAAAAAAAAAAUPz/////////////AP////////////////x4CAAAAAAAAAAAAAAAEIT8//////////////8A///////////////////smFQwGAAAABg0ZKT0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'9' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////ysYCwMAAAUNGiw/P//////////////////AP////////////////+4JAAAAAAAAAAAAAAkuP////////////////8A////////////////lAQAAAAAAAAAAAAAAAAAkP///////////////wD//////////////8AEAAAAAAAAAAAAAAAAAAAAqP//////////////AP/////////////8JAAAAAAAAAAAAAAAAAAAAAAQ7P////////////8A/////////////6wAAAAAAAAAfOz8vCwAAAAAAABw/////////////wD/////////////WAAAAAAAAHD/////7BgAAAAAAAz4////////////AP////////////8kAAAAAAAA1P//////hAAAAAAAALT///////////8A/////////////wAAAAAAAAD///////+4AAAAAAAAcP///////////wD/////////////AAAAAAAAAPz//////8AAAAAAAABI////////////AP////////////8UAAAAAAAAzP//////lAAAAAAAACT///////////8A/////////////0QAAAAAAABY//////gsAAAAAAAADP///////////wD/////////////kAAAAAAAAABw5P/IPAAAAAAAAAAA////////////AP/////////////wEAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////////+UAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////9wAAAAAAAAAAAAAFAAAAAAAAAU////////////AP////////////////+IBAAAAAAAAABw3AAAAAAAACj///////////8A///////////////////cdCwEABhcxP+8AAAAAAAATP///////////wD//////////////////////////////5AAAAAAAAB4////////////AP//////////////////////////////UAAAAAAAALj///////////8A//////////////+kgGxUQCAM2P///+AIAAAAAAAQ+P///////////wD//////////////0gAAAAAAAA42P/EKAAAAAAAAHD/////////////AP//////////////sAAAAAAAAAAAAAAAAAAAAAAQ6P////////////8A////////////////TAAAAAAAAAAAAAAAAAAAAKz//////////////wD////////////////oKAAAAAAAAAAAAAAAAASU////////////////AP/////////////////sUAAAAAAAAAAAAAAwxP////////////////8A////////////////////yHA0FAAADCxktP///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'A' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////+QAAAAAAAAAAAAAAOT/////////////////AP//////////////////kAAAAAAAAAAAAAAAkP////////////////8A//////////////////88AAAAAAAAAAAAAAA8/////////////////wD/////////////////5AAAAAAAAAAAAAAAAADk////////////////AP////////////////+QAAAAAAAAAAAAAAAAAJD///////////////8A/////////////////zwAAAAAAAAAAAAAAAAAPP///////////////wD////////////////kAAAAAAAAAAgAAAAAAAAA5P//////////////AP///////////////5AAAAAAAAAAgAAAAAAAAACQ//////////////8A////////////////PAAAAAAAAAz8HAAAAAAAADz//////////////wD//////////////+QAAAAAAAAAWP9kAAAAAAAAANz/////////////AP//////////////kAAAAAAAAACk/7wAAAAAAAAAhP////////////8A//////////////88AAAAAAAABOz//BQAAAAAAAAw/////////////wD/////////////4AAAAAAAAAA8////ZAAAAAAAAADc////////////AP////////////+EAAAAAAAAAIj///+8AAAAAAAAAIT///////////8A/////////////zAAAAAAAAAA2P////wQAAAAAAAAMP///////////wD////////////cAAAAAAAAACT//////1wAAAAAAAAA3P//////////AP///////////4QAAAAAAAAAAAAAAAAAAAAAAAAAAACE//////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAAAAAADD//////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANz/////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhP////////8A//////////8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw/////////wD/////////3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADc////////AP////////+EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIT///////8A/////////zAAAAAAAAAAhP///////////2QAAAAAAAAAMP///////wD////////cAAAAAAAAAADM////////////vAAAAAAAAAAA3P//////AP///////4QAAAAAAAAAHP/////////////4DAAAAAAAAACE//////8A////////MAAAAAAAAABk//////////////9cAAAAAAAAADD//////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'B' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAEDh83P///////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAEhP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAeP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAABY////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAABT///////////8A//////////8AAAAAAAAAAP/////4zEwAAAAAAAAAAP///////////wD//////////wAAAAAAAAAA////////7AAAAAAAAAAQ////////////AP//////////AAAAAAAAAAD////////sAAAAAAAAAEj///////////8A//////////8AAAAAAAAAAP/////4zEQAAAAAAAAAtP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAFz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAiA/P////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAIjPj//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAGKz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJT///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAABNz//////////wD//////////wAAAAAAAAAA///////sqCAAAAAAAAAAbP//////////AP//////////AAAAAAAAAAD/////////yAAAAAAAAAAs//////////8A//////////8AAAAAAAAAAP//////////AAAAAAAAAAT//////////wD//////////wAAAAAAAAAA/////////7wAAAAAAAAAAP//////////AP//////////AAAAAAAAAAD//////+ikGAAAAAAAAAAY//////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFT//////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsP//////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAADj///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAc6P///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAATOj/////////////AP//////////AAAAAAAAAAAAAAAAAAAEIEBkkNj///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'C' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////5JRULBAAAAgkTIDQ//////////////////8A////////////////1FAAAAAAAAAAAAAAAABAyP///////////////wD//////////////4gEAAAAAAAAAAAAAAAAAAAElP//////////////AP////////////9wAAAAAAAAAAAAAAAAAAAAAAAAlP////////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAEyP///////////wD//////////9wIAAAAAAAAAAAAAAAAAAAAAAAAAAAw////////////AP//////////WAAAAAAAAAAAWMz/8JwQAAAAAAAAAACw//////////8A/////////+wEAAAAAAAAAID//////9QMAAAAAAAAAET//////////wD/////////nAAAAAAAAAAo/P///////3wAAAAABDBspP//////////AP////////9gAAAAAAAAAIz/////////3BxQjMT0//////////////8A/////////zQAAAAAAAAAzP///////////////////////////////wD/////////GAAAAAAAAADo////////////////////////////////AP////////8AAAAAAAAAAP////////////////////////////////8A/////////wAAAAAAAAAA/////////////////////////////////wD/////////AAAAAAAAAAD/////////////////////////////////AP////////8cAAAAAAAAAOj///////////////////////////////8A/////////zgAAAAAAAAA0P/////////kIGio7P///////////////wD/////////bAAAAAAAAACg/////////5wAAAAAMHS49P//////////AP////////+oAAAAAAAAAEz/////////PAAAAAAAAAAc//////////8A//////////QIAAAAAAAAALz//////6QAAAAAAAAAAGT//////////wD//////////3AAAAAAAAAADIzo/+SEBAAAAAAAAAAAyP//////////AP//////////7BAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////rAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD/////////////fAAAAAAAAAAAAAAAAAAAAAAAAJz/////////////AP//////////////iAQAAAAAAAAAAAAAAAAAAASY//////////////8A////////////////yEAAAAAAAAAAAAAAAAA8yP///////////////wD//////////////////9yIUCwQAAAAIEB4yP//////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'D' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAADChQkOT/////////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAABGjw//////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAACDY/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAABjk////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKj//////////wD///////////8AAAAAAAAAAP///+isSAAAAAAAAAAANP//////////AP///////////wAAAAAAAAAA////////hAAAAAAAAAAA2P////////8A////////////AAAAAAAAAAD/////////MAAAAAAAAACQ/////////wD///////////8AAAAAAAAAAP////////+MAAAAAAAAAFj/////////AP///////////wAAAAAAAAAA/////////8gAAAAAAAAAMP////////8A////////////AAAAAAAAAAD/////////5AAAAAAAAAAY/////////wD///////////8AAAAAAAAAAP//////////AAAAAAAAAAD/////////AP///////////wAAAAAAAAAA//////////8AAAAAAAAAAP////////8A////////////AAAAAAAAAAD//////////wAAAAAAAAAA/////////wD///////////8AAAAAAAAAAP/////////wAAAAAAAAABD/////////AP///////////wAAAAAAAAAA/////////9QAAAAAAAAAJP////////8A////////////AAAAAAAAAAD/////////qAAAAAAAAABI/////////wD///////////8AAAAAAAAAAP////////9QAAAAAAAAAHj/////////AP///////////wAAAAAAAAAA////////uAAAAAAAAAAAvP////////8A////////////AAAAAAAAAAD////w0HwEAAAAAAAAACT8/////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAADz8//////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAY6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAKNz/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAACHT0//////////////8A////////////AAAAAAAAAAAAAAAAABg4bKj0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'E' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'F' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'G' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////MB8TCgQAAAACCA4YJzs////////////////AP///////////////JQcAAAAAAAAAAAAAAAAAAhw8P////////////8A/////////////9gwAAAAAAAAAAAAAAAAAAAAAAAk2P///////////wD////////////EDAAAAAAAAAAAAAAAAAAAAAAAAAAc7P//////////AP//////////2AwAAAAAAAAAAAAAAAAAAAAAAAAAAABY//////////8A//////////wwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQ/////////wD/////////kAAAAAAAAAAAEHzQ/P/gmCAAAAAAAAAAAFz/////////AP////////wcAAAAAAAAACjg////////8CwAAAAAAAAgWP////////8A////////vAAAAAAAAAAI2P//////////yBRAcJjI8P///////////wD///////94AAAAAAAAAGD/////////////////////////////////AP///////0AAAAAAAAAAsP////////////////////////////////8A////////IAAAAAAAAADc/////////////////////////////////wD///////8AAAAAAAAAAP///////wAAAAAAAAAAAAAAAAD/////////AP///////wAAAAAAAAAA////////AAAAAAAAAAAAAAAAAP////////8A////////AAAAAAAAAAD///////8AAAAAAAAAAAAAAAAA/////////wD///////8gAAAAAAAAAOD//////wAAAAAAAAAAAAAAAAD/////////AP///////0AAAAAAAAAAtP//////AAAAAAAAAAAAAAAAAP////////8A////////cAAAAAAAAABw//////8AAAAAAAAAAAAAAAAA/////////wD///////+8AAAAAAAAABDs////////////AAAAAAAAAAD/////////AP////////wYAAAAAAAAADz0//////////AAAAAAAAAAAP////////8A/////////5AAAAAAAAAAACCY4P//3KhcCAAAAAAAAAAA/////////wD/////////+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////AP//////////xAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIP////////8A////////////rAQAAAAAAAAAAAAAAAAAAAAAAAAAAGTw/////////wD/////////////vBQAAAAAAAAAAAAAAAAAAAAAADjI////////////AP//////////////8HAQAAAAAAAAAAAAAAAAAEiw//////////////8A//////////////////iwcEAgBAAABCA4aKDk/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'H' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'I' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'J' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAj//////////////wD//////////+zMrIxwUDAQ//////wAAAAAAAAAIP//////////////AP//////////DAAAAAAAAADo////2AAAAAAAAAA0//////////////8A//////////8wAAAAAAAAAKj///+YAAAAAAAAAFj//////////////wD//////////2gAAAAAAAAAIND/yBgAAAAAAAAAkP//////////////AP//////////vAAAAAAAAAAAAAAAAAAAAAAAAADc//////////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAUP///////////////wD////////////EBAAAAAAAAAAAAAAAAAAAABjk////////////////AP////////////+sBAAAAAAAAAAAAAAAAAAY2P////////////////8A///////////////EMAAAAAAAAAAAAAAAVOj//////////////////wD/////////////////vHBAIAAAABg8fNT/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'K' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////8AAAAAAAAAAP//////////wAQAAAAAAAAAAABw////////AP///////wAAAAAAAAAA/////////9AMAAAAAAAAAAAAcP////////8A////////AAAAAAAAAAD////////cGAAAAAAAAAAAAHD//////////wD///////8AAAAAAAAAAP//////6CgAAAAAAAAAAABs////////////AP///////wAAAAAAAAAA//////Q0AAAAAAAAAAAAVPz///////////8A////////AAAAAAAAAAD////8RAAAAAAAAAAAAFT8/////////////wD///////8AAAAAAAAAAP///1gAAAAAAAAAAABU/P//////////////AP///////wAAAAAAAAAA//9wAAAAAAAAAAAASPz///////////////8A////////AAAAAAAAAAD/jAAAAAAAAAAAADz0/////////////////wD///////8AAAAAAAAAAKQAAAAAAAAAAAA89P//////////////////AP///////wAAAAAAAAAABAAAAAAAAAAAFPT///////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAApP///////////////////wD///////8AAAAAAAAAAAAAAAAAAAAAAAAU8P//////////////////AP///////wAAAAAAAAAAAAAAAAAAAAAAAABk//////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAAAADE/////////////////wD///////8AAAAAAAAAAAAAAAAoEAAAAAAAACz8////////////////AP///////wAAAAAAAAAAAAAAGNiAAAAAAAAAAIj///////////////8A////////AAAAAAAAAAAAABjY//gYAAAAAAAACOD//////////////wD///////8AAAAAAAAAAAAY2P///5wAAAAAAAAASP//////////////AP///////wAAAAAAAAAAGNj//////CgAAAAAAAAAqP////////////8A////////AAAAAAAAAADI////////sAAAAAAAAAAc8P///////////wD///////8AAAAAAAAAAP//////////QAAAAAAAAABs////////////AP///////wAAAAAAAAAA///////////IAAAAAAAAAATI//////////8A////////AAAAAAAAAAD///////////9YAAAAAAAAADD8/////////wD///////8AAAAAAAAAAP///////////9wEAAAAAAAAAJD/////////AP///////wAAAAAAAAAA/////////////3AAAAAAAAAADOT///////8A////////AAAAAAAAAAD/////////////7BAAAAAAAAAAUP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'L' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'M' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////8AAAAAAAAAAAAAAHz//////3wAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAATP//////UAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAc//////8cAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAADw////8AAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAAALz////AAAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAAkP///5AAAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAABc////ZAAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAoAAAAADD///8wAAAAACQAAAAAAAAA////////AP//////AAAAAAAAAFwAAAAABPz//AgAAAAAXAAAAAAAAAD///////8A//////8AAAAAAAAAkAAAAAAA0P/UAAAAAACQAAAAAAAAAP///////wD//////wAAAAAAAADMAAAAAACg/6gAAAAAAMQAAAAAAAAA////////AP//////AAAAAAAAAPgEAAAAAHD/dAAAAAAE+AAAAAAAAAD///////8A//////8AAAAAAAAA/zQAAAAAQP9IAAAAADD/AAAAAAAAAP///////wD//////wAAAAAAAAD/bAAAAAAQ/xQAAAAAaP8AAAAAAAAA////////AP//////AAAAAAAAAP+gAAAAAADQAAAAAACc/wAAAAAAAAD///////8A//////8AAAAAAAAA/9QAAAAAAGgAAAAAAND/AAAAAAAAAP///////wD//////wAAAAAAAAD//wwAAAAAFAAAAAAM/P8AAAAAAAAA////////AP//////AAAAAAAAAP//RAAAAAAAAAAAADz//wAAAAAAAAD///////8A//////8AAAAAAAAA//94AAAAAAAAAAAAcP//AAAAAAAAAP///////wD//////wAAAAAAAAD//7AAAAAAAAAAAACo//8AAAAAAAAA////////AP//////AAAAAAAAAP//5AAAAAAAAAAAANz//wAAAAAAAAD///////8A//////8AAAAAAAAA////HAAAAAAAAAAQ////AAAAAAAAAP///////wD//////wAAAAAAAAD///9QAAAAAAAAAEz///8AAAAAAAAA////////AP//////AAAAAAAAAP///4gAAAAAAAAAfP///wAAAAAAAAD///////8A//////8AAAAAAAAA////vAAAAAAAAACw////AAAAAAAAAP///////wD//////wAAAAAAAAD////wAAAAAAAAAOz///8AAAAAAAAA////////AP//////AAAAAAAAAP////8sAAAAAAAc/////wAAAAAAAAD///////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'N' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAALD/////////////AAAAAAAAAP//////////AP////////8AAAAAAAAAFOj///////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAASP///////////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAkP//////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAI1P////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAw+P///////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAABw////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAC8//////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAABzs/////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAFD/////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAJz///8AAAAAAAAA//////////8A/////////wAAAAAAAAAUAAAAAAAADNz//wAAAAAAAAD//////////wD/////////AAAAAAAAALQAAAAAAAAANPz/AAAAAAAAAP//////////AP////////8AAAAAAAAA/2wAAAAAAAAAfP8AAAAAAAAA//////////8A/////////wAAAAAAAAD/+CwAAAAAAAAExAAAAAAAAAD//////////wD/////////AAAAAAAAAP//0AQAAAAAAAAgAAAAAAAAAP//////////AP////////8AAAAAAAAA////jAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////RAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP/////kFAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA//////+sAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD///////9kAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP////////QkAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA/////////8wEAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD//////////4QAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP///////////DwAAAAAAAAAAP//////////AP////////8AAAAAAAAA////////////4BAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////////////qAAAAAAAAAD//////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'O' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////0qGw4HAAAABw4aKT0/////////////////wD////////////////wcAwAAAAAAAAAAAAAAAho6P//////////////AP//////////////uBQAAAAAAAAAAAAAAAAAAAAMoP////////////8A/////////////6AEAAAAAAAAAAAAAAAAAAAAAAAAkP///////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP//////////8BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAM5P////////8A//////////9wAAAAAAAAAAAsrPD/7KQsAAAAAAAAAABg/////////wD/////////+BAAAAAAAAAAUPj///////hQAAAAAAAAAAjs////////AP////////+sAAAAAAAAABDw//////////AYAAAAAAAAAKD///////8A/////////2wAAAAAAAAAdP///////////3wAAAAAAAAAYP///////wD/////////OAAAAAAAAAC4////////////xAAAAAAAAAAw////////AP////////8cAAAAAAAAAOD////////////oAAAAAAAAABT///////8A/////////wAAAAAAAAAA//////////////8AAAAAAAAAAP///////wD/////////AAAAAAAAAAD//////////////wAAAAAAAAAA////////AP////////8AAAAAAAAAAP/////////////8AAAAAAAAAAD///////8A/////////xwAAAAAAAAA5P///////////+AAAAAAAAAAHP///////wD/////////NAAAAAAAAAC8////////////uAAAAAAAAAA4////////AP////////9oAAAAAAAAAHj///////////98AAAAAAAAAGT///////8A/////////6gAAAAAAAAAGPD/////////+BgAAAAAAAAApP///////wD/////////9AwAAAAAAAAAUPz///////xcAAAAAAAAAAjs////////AP//////////cAAAAAAAAAAALKjs//CwOAAAAAAAAAAAYP////////8A///////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzk/////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP////////////+QAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A//////////////+sEAAAAAAAAAAAAAAAAAAAAAyg/////////////wD////////////////oZAgAAAAAAAAAAAAAAARg4P//////////////AP//////////////////9KhsOCAAAAAUMFyc7P////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'P' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////wAAAAAAAAAAAAAAAAAACCxguP////////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAOOD//////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAGOD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAARP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAABo////////////AP///////////wAAAAAAAAAA////6JwMAAAAAAAAADD///////////8A////////////AAAAAAAAAAD//////6AAAAAAAAAADP///////////wD///////////8AAAAAAAAAAP//////9AAAAAAAAAAA////////////AP///////////wAAAAAAAAAA///////0AAAAAAAAAAD///////////8A////////////AAAAAAAAAAD//////5gAAAAAAAAAHP///////////wD///////////8AAAAAAAAAAP///9iICAAAAAAAAABI////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAIT/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAABU/P////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAIhPz//////////////wD///////////8AAAAAAAAAAAAAAAAABCRMkOz/////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'Q' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////SoaDQcAAAAHDhoqPT///////////////////8A//////////////BwDAAAAAAAAAAAAAAACHDo/////////////////wD///////////+4FAAAAAAAAAAAAAAAAAAAABCo////////////////AP//////////nAQAAAAAAAAAAAAAAAAAAAAAAACQ//////////////8A/////////7gEAAAAAAAAAAAAAAAAAAAAAAAAAACg/////////////wD////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzo////////////AP///////3AAAAAAAAAAACyo8P/sqCwAAAAAAAAAAGT///////////8A///////4EAAAAAAAAABM+P///////FQAAAAAAAAACPT//////////wD//////7AAAAAAAAAAFPD/////////9BgAAAAAAAAApP//////////AP//////bAAAAAAAAAB4////////////fAAAAAAAAABk//////////8A//////84AAAAAAAAALz///////////+8AAAAAAAAADT//////////wD//////xwAAAAAAAAA6P///////////+QAAAAAAAAAHP//////////AP//////AAAAAAAAAAD//////////////wAAAAAAAAAA//////////8A//////8AAAAAAAAAAP//////////////AAAAAAAAAAD//////////wD//////wAAAAAAAAAA/P////////////8AAAAAAAAAAP//////////AP//////GAAAAAAAAADg////////////4AAAAAAAAAAc//////////8A//////84AAAAAAAAALT////MJHTo//+8AAAAAAAAADT//////////wD//////2wAAAAAAAAAdP///2AAABCg/3wAAAAAAAAAZP//////////AP//////rAAAAAAAAAAY9P/sCAAAAABMGAAAAAAAAACk//////////8A///////4EAAAAAAAAABU/P+0OAAAAAAAAAAAAAAACPT//////////wD///////94AAAAAAAAAAA4sPD/gAAAAAAAAAAAAABk////////////AP////////AcAAAAAAAAAAAAAAAAAAAAAAAAAAAADOT///////////8A/////////7wEAAAAAAAAAAAAAAAAAAAAAAAAAACQ/////////////wD//////////6wEAAAAAAAAAAAAAAAAAAAAAAAAABSs////////////AP///////////7gUAAAAAAAAAAAAAAAAAAAAAAAAAABAwP////////8A//////////////BwDAAAAAAAAAAAAAAABAgAAAAAAAA8/////////wD////////////////0qGg0GAAAABgwXJjkxBgAAAAAALD/////////AP//////////////////////////////////5DQAAAAk/P////////8A////////////////////////////////////+GwAAJD//////////wD//////////////////////////////////////8A49P//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'R' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////wAAAAAAAAAAAAAAAAAAAAQgOGSk+P///////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAcuP//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAEsP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ6P///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///////////8A/////////wAAAAAAAAAA///////svDgAAAAAAAAACP///////////wD/////////AAAAAAAAAAD/////////7AAAAAAAAAAA////////////AP////////8AAAAAAAAAAP/////////cAAAAAAAAABD///////////8A/////////wAAAAAAAAAA//////DQoCQAAAAAAAAAQP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACU////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIPj///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAzU/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAA02P//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAxctPz///////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAEDY/////////////////wD/////////AAAAAAAAAAD/9LAsAAAAAAAAAAzc////////////////AP////////8AAAAAAAAAAP///+wkAAAAAAAAADD8//////////////8A/////////wAAAAAAAAAA/////8QAAAAAAAAAAJD//////////////wD/////////AAAAAAAAAAD//////1QAAAAAAAAAFPD/////////////AP////////8AAAAAAAAAAP//////3AQAAAAAAAAAgP////////////8A/////////wAAAAAAAAAA////////aAAAAAAAAAAM6P///////////wD/////////AAAAAAAAAAD////////oCAAAAAAAAABs////////////AP////////8AAAAAAAAAAP////////+AAAAAAAAAAATc//////////8A/////////wAAAAAAAAAA//////////AUAAAAAAAAAFj//////////wD/////////AAAAAAAAAAD//////////5AAAAAAAAAAAND/////////AP////////8AAAAAAAAAAP//////////+CQAAAAAAAAAQP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'S' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////8vHBEIAgAAAQgQHC8/P////////////////8A////////////////pCQAAAAAAAAAAAAAAAAcoP///////////////wD//////////////FwAAAAAAAAAAAAAAAAAAAAAXP//////////////AP////////////9oAAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A////////////zAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////9cAAAAAAAAAAAAAAAAAAAAAAAAAACA////////////AP///////////xgAAAAAAAAAUOD/8KwkAAAAAAAAADj///////////8A////////////AAAAAAAAAAD0/////8wABCAgICxASP///////////wD///////////8MAAAAAAAAAMz/////////////////////////////AP///////////0AAAAAAAAAACFiQxPT///////////////////////8A////////////oAAAAAAAAAAAAAAAADBwtPT//////////////////wD////////////8QAAAAAAAAAAAAAAAAAAACFTA////////////////AP/////////////oOAAAAAAAAAAAAAAAAAAAAABM6P////////////8A///////////////4fAgAAAAAAAAAAAAAAAAAAAAY2P///////////wD/////////////////7IwwAAAAAAAAAAAAAAAAAAAo+P//////////AP/////////////////////koGw0BAAAAAAAAAAAAACU//////////8A///////////////////////////4uFgAAAAAAAAAADz//////////wD//////////2BgSEA0IBwA6P///////5QAAAAAAAAADP//////////AP//////////JAAAAAAAAACc/////////AAAAAAAAAAA//////////8A//////////9YAAAAAAAAACDo///////AAAAAAAAAABT//////////wD//////////6QAAAAAAAAAACCk7P/snBQAAAAAAAAAUP//////////AP//////////+BAAAAAAAAAAAAAAAAAAAAAAAAAAAACs//////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAAOP///////////wD////////////8RAAAAAAAAAAAAAAAAAAAAAAAABjc////////////AP/////////////0PAAAAAAAAAAAAAAAAAAAAAAg2P////////////8A///////////////8hBQAAAAAAAAAAAAAAAAMdPT//////////////wD/////////////////+LRwSCAMAAAAHDhoqPT/////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'T' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'U' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////JAAAAAAAAADk/////////+gAAAAAAAAAHP//////////AP////////9MAAAAAAAAAJz/////////nAAAAAAAAABE//////////8A/////////4gAAAAAAAAAHOj//////+ggAAAAAAAAAHz//////////wD/////////0AAAAAAAAAAAIJzs/+ykIAAAAAAAAAAA0P//////////AP//////////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A///////////IBAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAAAAJj/////////////AP////////////+UBAAAAAAAAAAAAAAAAAAAAASU//////////////8A///////////////IPAAAAAAAAAAAAAAAAAAwyP///////////////wD/////////////////0IxYOCAIAAAEIEiAyP//////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'V' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////zAAAAAAAAAAYP//////////////ZAAAAAAAAAAw////////AP//////kAAAAAAAAAAU/P////////////8UAAAAAAAAAJD///////8A///////oBAAAAAAAAADE////////////xAAAAAAAAAAE7P///////wD///////9MAAAAAAAAAHD///////////94AAAAAAAAAEz/////////AP///////6gAAAAAAAAAJP///////////yQAAAAAAAAArP////////8A////////+BAAAAAAAAAA1P/////////YAAAAAAAAABT4/////////wD/////////aAAAAAAAAACE/////////4QAAAAAAAAAbP//////////AP/////////EAAAAAAAAADT/////////OAAAAAAAAADM//////////8A//////////8kAAAAAAAAAOT//////+QAAAAAAAAAKP///////////wD//////////4QAAAAAAAAAmP//////nAAAAAAAAACI////////////AP//////////5AAAAAAAAABE//////9EAAAAAAAABOT///////////8A////////////QAAAAAAAAAT0////9AgAAAAAAABI/////////////wD///////////+gAAAAAAAAAKT///+kAAAAAAAAAKj/////////////AP////////////QIAAAAAAAAXP///1wAAAAAAAAM+P////////////8A/////////////1wAAAAAAAAM+P/8DAAAAAAAAGT//////////////wD/////////////vAAAAAAAAAC8/7wAAAAAAAAAxP//////////////AP//////////////HAAAAAAAAGj/aAAAAAAAACT///////////////8A//////////////94AAAAAAAAHP8cAAAAAAAAhP///////////////wD//////////////9gAAAAAAAAAkAAAAAAAAADk////////////////AP///////////////zgAAAAAAAAQAAAAAAAAQP////////////////8A////////////////lAAAAAAAAAAAAAAAAACg/////////////////wD////////////////sCAAAAAAAAAAAAAAADPT/////////////////AP////////////////9QAAAAAAAAAAAAAABg//////////////////8A/////////////////7AAAAAAAAAAAAAAAMD//////////////////wD//////////////////BQAAAAAAAAAAAAc////////////////////AP//////////////////cAAAAAAAAAAAAHz///////////////////8A///////////////////MAAAAAAAAAAAA3P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'W' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//8cAAAAAAAAALz/////4AAAAAAAAAAA6P////+8AAAAAAAAABz//wD//1QAAAAAAAAAjP////+gAAAAAAAAAACo/////4wAAAAAAAAAUP//AP//jAAAAAAAAABU/////2AAAAAAAAAAAGj/////VAAAAAAAAACM//8A///EAAAAAAAAACT/////IAAAAAAAAAAAKP////8kAAAAAAAAAMT//wD///gEAAAAAAAAAPD//+AAAAAAAAAAAAAA6P//8AAAAAAAAAAE9P//AP///zAAAAAAAAAAvP//oAAAAAAAAAAAAACo//+8AAAAAAAAADD///8A////bAAAAAAAAACM//9gAAAAAAAAAAAAAGT//4wAAAAAAAAAaP///wD///+kAAAAAAAAAFT//yAAAAAAAAAAAAAAIP//VAAAAAAAAACc////AP///9gAAAAAAAAAJP/gAAAAAAAAAAAAAAAA4P8kAAAAAAAAANT///8A/////xAAAAAAAAAA8KAAAAAAAAAAAAAAAACg8AAAAAAAAAAQ/////wD/////TAAAAAAAAAC8YAAAAAAAAAAAAAAAAGC8AAAAAAAAAET/////AP////+AAAAAAAAAAIwgAAAAAAAAAAAAAAAAIIwAAAAAAAAAfP////8A/////7gAAAAAAAAANAAAAAAAACwwAAAAAAAANAAAAAAAAACw/////wD/////8AAAAAAAAAAAAAAAAAAAdHgAAAAAAAAAAAAAAAAAAOz/////AP//////KAAAAAAAAAAAAAAAAAC4vAAAAAAAAAAAAAAAAAAg//////8A//////9gAAAAAAAAAAAAAAAACPj4CAAAAAAAAAAAAAAAAFj//////wD//////5QAAAAAAAAAAAAAAABE//9IAAAAAAAAAAAAAAAAkP//////AP//////0AAAAAAAAAAAAAAAAIj//4wAAAAAAAAAAAAAAADI//////8A///////8DAAAAAAAAAAAAAAAzP//1AAAAAAAAAAAAAAABPj//////wD///////88AAAAAAAAAAAAABT/////GAAAAAAAAAAAAAA0////////AP///////3QAAAAAAAAAAAAAWP////9gAAAAAAAAAAAAAHD///////8A////////sAAAAAAAAAAAAACg/////6QAAAAAAAAAAAAApP///////wD////////kAAAAAAAAAAAAAOT/////6AAAAAAAAAAAAADc////////AP////////8cAAAAAAAAAAAo////////MAAAAAAAAAAAEP////////8A/////////1QAAAAAAAAAAHD///////94AAAAAAAAAABM/////////wD/////////jAAAAAAAAAAAtP///////7wAAAAAAAAAAID/////////AP/////////EAAAAAAAAAAT0////////+AgAAAAAAAAAuP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'X' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////9UAAAAAAAAAKz///////////+sAAAAAAAAAFD/////////AP///////+QQAAAAAAAAFOT/////////8BwAAAAAAAAM5P////////8A/////////5gAAAAAAAAATP////////9kAAAAAAAAAJD//////////wD//////////0AAAAAAAAAAoP//////wAAAAAAAAAA0/P//////////AP//////////2AgAAAAAAAAQ4P////gkAAAAAAAABMz///////////8A////////////iAAAAAAAAABA////dAAAAAAAAABw/////////////wD////////////8MAAAAAAAAACU/9AEAAAAAAAAHPD/////////////AP/////////////IBAAAAAAAAAzYMAAAAAAAAACs//////////////8A//////////////90AAAAAAAAABAAAAAAAAAATP///////////////wD///////////////QgAAAAAAAAAAAAAAAAAAzg////////////////AP///////////////7wAAAAAAAAAAAAAAAAAjP////////////////8A/////////////////2AAAAAAAAAAAAAAADD8/////////////////wD/////////////////7BQAAAAAAAAAAAAEyP//////////////////AP/////////////////gDAAAAAAAAAAAAAjY//////////////////8A/////////////////0AAAAAAAAAAAAAAADj8/////////////////wD///////////////+UAAAAAAAAAAAAAAAAAJD/////////////////AP//////////////4AwAAAAAAAAAAAAAAAAADOD///////////////8A//////////////9AAAAAAAAAAAAAAAAAAAAAQP///////////////wD/////////////nAAAAAAAAAAAWAAAAAAAAAAAlP//////////////AP///////////+QQAAAAAAAAAGD/YAAAAAAAAAAM4P////////////8A////////////TAAAAAAAAAAs9P/0LAAAAAAAAABM/////////////wD//////////6AAAAAAAAAADNT////UDAAAAAAAAACg////////////AP/////////kEAAAAAAAAACg//////+gAAAAAAAAABDk//////////8A/////////0wAAAAAAAAAYP////////9gAAAAAAAAAEz//////////wD///////+oAAAAAAAAACz0//////////QsAAAAAAAAAKT/////////AP//////7BQAAAAAAAAM1P///////////9QMAAAAAAAAFOz///////8A//////9UAAAAAAAAAKD//////////////6AAAAAAAAAAVP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'Y' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////1QAAAAAAAAAAGj//////////2gAAAAAAAAAAFT///////8A////////5BAAAAAAAAAAAMT////////EAAAAAAAAAAAQ5P///////wD/////////mAAAAAAAAAAAKPj/////+CgAAAAAAAAAAJj/////////AP//////////PAAAAAAAAAAAgP////+AAAAAAAAAAAA8//////////8A///////////YCAAAAAAAAAAE2P//2AQAAAAAAAAACNj//////////wD///////////+AAAAAAAAAAAA4//84AAAAAAAAAACA////////////AP////////////woAAAAAAAAAACUlAAAAAAAAAAAKPz///////////8A/////////////8gAAAAAAAAAABAQAAAAAAAAAADI/////////////wD//////////////2wAAAAAAAAAAAAAAAAAAAAAbP//////////////AP//////////////8BwAAAAAAAAAAAAAAAAAABzw//////////////8A////////////////tAAAAAAAAAAAAAAAAAAAtP///////////////wD/////////////////VAAAAAAAAAAAAAAAAFT/////////////////AP/////////////////oEAAAAAAAAAAAAAAQ6P////////////////8A//////////////////+cAAAAAAAAAAAAAJz//////////////////wD///////////////////9AAAAAAAAAAABA////////////////////AP///////////////////9gAAAAAAAAAANj///////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
'Z' => array(
'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAQ//////////////8A/////////////////////////1AAAAAAAAAABLz//////////////wD///////////////////////98AAAAAAAAAACY////////////////AP//////////////////////pAAAAAAAAAAAaP////////////////8A/////////////////////8QIAAAAAAAAAET8/////////////////wD////////////////////gGAAAAAAAAAAo9P//////////////////AP//////////////////9CwAAAAAAAAAFNz///////////////////8A//////////////////xMAAAAAAAAAATA/////////////////////wD/////////////////eAAAAAAAAAAAnP//////////////////////AP///////////////5wAAAAAAAAAAHT///////////////////////8A///////////////ABAAAAAAAAABM/P///////////////////////wD/////////////3BQAAAAAAAAALPT/////////////////////////AP////////////QoAAAAAAAAABjg//////////////////////////8A///////////8SAAAAAAAAAAExP///////////////////////////wD//////////2wAAAAAAAAAAKD/////////////////////////////AP////////+YAAAAAAAAAAB8//////////////////////////////8A/////////wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=',
'width' => 40
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
/**
* The class loader resolves class names to file system paths and loads them if
* necessary.
*
* Classes have to be of the form phpbb_(dir_)*(classpart_)*, so directory names
* must never contain underscores. Example: phpbb_dir_subdir_class_name is a
* valid class name, while phpbb_dir_sub_dir_class_name is not.
*
* If every part of the class name is a directory, the last directory name is
* also used as the filename, e.g. phpbb_dir would resolve to dir/dir.php.
*/
class class_loader
{
private $namespace;
private $path;
private $php_ext;
private $cache;
/**
* A map of looked up class names to paths relative to $this->path.
* This map is stored in cache and looked up if the cache is available.
*
* @var array
*/
private $cached_paths = array();
/**
* Creates a new \phpbb\class_loader, which loads files with the given
* file extension from the given path.
*
* @param string $namespace Required namespace for files to be loaded
* @param string $path Directory to load files from
* @param string $php_ext The file extension for PHP files
* @param \phpbb\cache\driver\driver_interface $cache An implementation of the phpBB cache interface.
*/
public function __construct($namespace, $path, $php_ext = 'php', \phpbb\cache\driver\driver_interface $cache = null)
{
if ($namespace[0] !== '\\')
{
$namespace = '\\' . $namespace;
}
$this->namespace = $namespace;
$this->path = $path;
$this->php_ext = $php_ext;
$this->set_cache($cache);
}
/**
* Provide the class loader with a cache to store paths. If set to null, the
* the class loader will resolve paths by checking for the existence of every
* directory in the class name every time.
*
* @param \phpbb\cache\driver\driver_interface $cache An implementation of the phpBB cache interface.
*/
public function set_cache(\phpbb\cache\driver\driver_interface $cache = null)
{
if ($cache)
{
$this->cached_paths = $cache->get('class_loader_' . str_replace('\\', '__', $this->namespace));
if ($this->cached_paths === false)
{
$this->cached_paths = array();
}
}
$this->cache = $cache;
}
/**
* Registers the class loader as an autoloader using SPL.
*/
public function register()
{
spl_autoload_register(array($this, 'load_class'));
}
/**
* Removes the class loader from the SPL autoloader stack.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'load_class'));
}
/**
* Resolves a phpBB class name to a relative path which can be included.
*
* @param string $class The class name to resolve, must be in the
* namespace the loader was constructed with.
* Has to begin with \
* @return string|bool A relative path to the file containing the
* class or false if looking it up failed.
*/
public function resolve_path($class)
{
if (isset($this->cached_paths[$class]))
{
return $this->path . $this->cached_paths[$class] . '.' . $this->php_ext;
}
if (!preg_match('/^' . preg_quote($this->namespace, '/') . '[a-zA-Z0-9_\\\\]+$/', $class))
{
return false;
}
$relative_path = str_replace('\\', '/', substr($class, strlen($this->namespace)));
if (!file_exists($this->path . $relative_path . '.' . $this->php_ext))
{
return false;
}
if ($this->cache)
{
$this->cached_paths[$class] = $relative_path;
$this->cache->put('class_loader_' . str_replace('\\', '__', $this->namespace), $this->cached_paths);
}
return $this->path . $relative_path . '.' . $this->php_ext;
}
/**
* Resolves a class name to a path and then includes it.
*
* @param string $class The class name which is being loaded.
*/
public function load_class($class)
{
// In general $class is not supposed to contain a leading backslash,
// but sometimes it does. See tickets PHP-50731 and HHVM-1840.
if ($class[0] !== '\\')
{
$class = '\\' . $class;
}
if (substr($class, 0, strlen($this->namespace)) === $this->namespace)
{
$path = $this->resolve_path($class);
if ($path)
{
require $path;
}
}
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\config;
/**
* Configuration container class
*/
class config implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
* The configuration data
* @var array(string => string)
*/
protected $config;
/**
* Creates a configuration container with a default set of values
*
* @param array(string => string) $config The configuration data.
*/
public function __construct(array $config)
{
$this->config = $config;
}
/**
* Retrieves an ArrayIterator over the configuration values.
*
* @return \ArrayIterator An iterator over all config data
*/
public function getIterator()
{
return new \ArrayIterator($this->config);
}
/**
* Checks if the specified config value exists.
*
* @param string $key The configuration option's name.
* @return bool Whether the configuration option exists.
*/
public function offsetExists($key)
{
return isset($this->config[$key]);
}
/**
* Retrieves a configuration value.
*
* @param string $key The configuration option's name.
* @return string The configuration value
*/
public function offsetGet($key)
{
return (isset($this->config[$key])) ? $this->config[$key] : '';
}
/**
* Temporarily overwrites the value of a configuration variable.
*
* The configuration change will not persist. It will be lost
* after the request.
*
* @param string $key The configuration option's name.
* @param string $value The temporary value.
*/
public function offsetSet($key, $value)
{
$this->config[$key] = $value;
}
/**
* Called when deleting a configuration value directly, triggers an error.
*
* @param string $key The configuration option's name.
*/
public function offsetUnset($key)
{
trigger_error('Config values have to be deleted explicitly with the \phpbb\config\config::delete($key) method.', E_USER_ERROR);
}
/**
* Retrieves the number of configuration options currently set.
*
* @return int Number of config options
*/
public function count()
{
return count($this->config);
}
/**
* Removes a configuration option
*
* @param String $key The configuration option's name
* @param bool $use_cache Whether this variable should be cached or if it
* changes too frequently to be efficiently cached
* @return null
*/
public function delete($key, $use_cache = true)
{
unset($this->config[$key]);
}
/**
* Sets a configuration option's value
*
* @param string $key The configuration option's name
* @param string $value New configuration value
* @param bool $use_cache Whether this variable should be cached or if it
* changes too frequently to be efficiently cached.
*/
public function set($key, $value, $use_cache = true)
{
$this->config[$key] = $value;
}
/**
* Sets a configuration option's value only if the old_value matches the
* current configuration value or the configuration value does not exist yet.
*
* @param string $key The configuration option's name
* @param string $old_value Current configuration value
* @param string $new_value New configuration value
* @param bool $use_cache Whether this variable should be cached or if it
* changes too frequently to be efficiently cached.
* @return bool True if the value was changed, false otherwise.
*/
public function set_atomic($key, $old_value, $new_value, $use_cache = true)
{
if (!isset($this->config[$key]) || $this->config[$key] == $old_value)
{
$this->config[$key] = $new_value;
return true;
}
return false;
}
/**
* Checks configuration option's value only if the new_value matches the
* current configuration value and the configuration value does exist.Called
* only after set_atomic has been called.
*
* @param string $key The configuration option's name
* @param string $new_value New configuration value
* @throws \phpbb\exception\http_exception when config value is set and not equal to new_value.
* @return bool True if the value was changed, false otherwise.
*/
public function ensure_lock($key, $new_value)
{
if (isset($this->config[$key]) && $this->config[$key] == $new_value)
{
return true;
}
throw new \phpbb\exception\http_exception(500, 'Failure while aqcuiring locks.');
}
/**
* Increments an integer configuration value.
*
* @param string $key The configuration option's name
* @param int $increment Amount to increment by
* @param bool $use_cache Whether this variable should be cached or if it
* changes too frequently to be efficiently cached.
*/
function increment($key, $increment, $use_cache = true)
{
if (!isset($this->config[$key]))
{
$this->config[$key] = 0;
}
$this->config[$key] += $increment;
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
class config_php_file
{
/** @var string phpBB Root Path */
protected $phpbb_root_path;
/** @var string php file extension */
protected $php_ext;
/**
* Indicates whether the php config file has been loaded.
*
* @var bool
*/
protected $config_loaded = false;
/**
* The content of the php config file
*
* @var array
*/
protected $config_data = array();
/**
* The path to the config file. (Default: $phpbb_root_path . 'config.' . $php_ext)
*
* @var string
*/
protected $config_file;
private $defined_vars;
/**
* Constructor
*
* @param string $phpbb_root_path phpBB Root Path
* @param string $php_ext php file extension
*/
function __construct($phpbb_root_path, $php_ext)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->config_file = $this->phpbb_root_path . 'config.' . $this->php_ext;
}
/**
* Set the path to the config file.
*
* @param string $config_file
*/
public function set_config_file($config_file)
{
$this->config_file = $config_file;
$this->config_loaded = false;
}
/**
* Returns an associative array containing the variables defined by the config file.
*
* @return array Return the content of the config file or an empty array if the file does not exists.
*/
public function get_all()
{
$this->load_config_file();
return $this->config_data;
}
/**
* Return the value of a variable defined into the config.php file or null if the variable does not exist.
*
* @param string $variable The name of the variable
* @return mixed Value of the variable or null if the variable is not defined.
*/
public function get($variable)
{
$this->load_config_file();
return isset($this->config_data[$variable]) ? $this->config_data[$variable] : null;
}
/**
* Load the config file and store the information.
*
* @return null
*/
protected function load_config_file()
{
if (!$this->config_loaded && file_exists($this->config_file))
{
$this->defined_vars = get_defined_vars();
require($this->config_file);
$this->config_data = array_diff_key(get_defined_vars(), $this->defined_vars);
$this->config_loaded = true;
}
}
/**
* Convert either 3.0 dbms or 3.1 db driver class name to 3.1 db driver class name.
*
* If $dbms is a valid 3.1 db driver class name, returns it unchanged.
* Otherwise prepends phpbb\db\driver\ to the dbms to convert a 3.0 dbms
* to 3.1 db driver class name.
*
* @param string $dbms dbms parameter
* @return string driver class
* @throws \RuntimeException
*/
public function convert_30_dbms_to_31($dbms)
{
// Note: this check is done first because mysqli extension
// supplies a mysqli class, and class_exists($dbms) would return
// true for mysqli class.
// However, per the docblock any valid 3.1 driver name should be
// recognized by this function, and have priority over 3.0 dbms.
if (strpos($dbms, 'phpbb\db\driver') === false && class_exists('phpbb\db\driver\\' . $dbms))
{
return 'phpbb\db\driver\\' . $dbms;
}
if (class_exists($dbms))
{
// Additionally we could check that $dbms extends phpbb\db\driver\driver.
// http://php.net/manual/en/class.reflectionclass.php
// Beware of possible performance issues:
// http://stackoverflow.com/questions/294582/php-5-reflection-api-performance
// We could check for interface implementation in all paths or
// only when we do not prepend phpbb\db\driver\.
/*
$reflection = new \ReflectionClass($dbms);
if ($reflection->isSubclassOf('phpbb\db\driver\driver'))
{
return $dbms;
}
*/
return $dbms;
}
// Force use of mysqli when specifying mysql
if (preg_match('/(phpbb\\\db\\\driver\\\)?mysql$/i', $dbms))
{
return 'phpbb\db\driver\mysqli';
}
throw new \RuntimeException("You have specified an invalid dbms driver: $dbms");
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\console\command\cron;
use phpbb\exception\runtime_exception;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
class run extends \phpbb\console\command\command
{
/** @var \phpbb\cron\manager */
protected $cron_manager;
/** @var \phpbb\lock\db */
protected $lock_db;
/**
* Construct method
*
* @param \phpbb\user $user The user object (used to get language information)
* @param \phpbb\cron\manager $cron_manager The cron manager containing
* the cron tasks to be executed.
* @param \phpbb\lock\db $lock_db The lock for accessing database.
*/
public function __construct(\phpbb\user $user, \phpbb\cron\manager $cron_manager, \phpbb\lock\db $lock_db)
{
$this->cron_manager = $cron_manager;
$this->lock_db = $lock_db;
parent::__construct($user);
}
/**
* Sets the command name and description
*
* @return null
*/
protected function configure()
{
$this
->setName('cron:run')
->setDescription($this->user->lang('CLI_DESCRIPTION_CRON_RUN'))
->setHelp($this->user->lang('CLI_HELP_CRON_RUN'))
->addArgument('name', InputArgument::OPTIONAL, $this->user->lang('CLI_DESCRIPTION_CRON_RUN_ARGUMENT_1'))
;
}
/**
* Executes the command cron:run.
*
* Tries to acquire the cron lock, then if no argument has been given runs all ready cron tasks.
* If the cron lock can not be obtained, an error message is printed
* and the exit status is set to 1.
* If the verbose option is specified, each start of a task is printed.
* Otherwise there is no output.
* If an argument is given to the command, only the task whose name matches the
* argument will be started. If verbose option is specified,
* an info message containing the name of the task is printed.
* If no task matches the argument given, an error message is printed
* and the exit status is set to 2.
*
* @param InputInterface $input The input stream used to get the argument and verboe option.
* @param OutputInterface $output The output stream, used for printing verbose-mode and error information.
*
* @return int 0 if all is ok, 1 if a lock error occurred and 2 if no task matching the argument was found.
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($this->lock_db->acquire())
{
$task_name = $input->getArgument('name');
if ($task_name)
{
$exit_status = $this->run_one($input, $output, $task_name);
}
else
{
$exit_status = $this->run_all($input, $output);
}
$this->lock_db->release();
return $exit_status;
}
else
{
throw new runtime_exception('CRON_LOCK_ERROR', array(), null, 1);
}
}
/**
* Executes all ready cron tasks.
*
* If verbose mode is set, an info message will be printed if there is no task to
* be run, or else for each starting task.
*
* @see execute
* @param InputInterface $input The input stream used to get the argument and verbose option.
* @param OutputInterface $output The output stream, used for printing verbose-mode and error information.
* @return int 0
*/
protected function run_all(InputInterface $input, OutputInterface $output)
{
$run_tasks = $this->cron_manager->find_all_ready_tasks();
if ($run_tasks)
{
foreach ($run_tasks as $task)
{
if ($input->getOption('verbose'))
{
$output->writeln('<info>' . $this->user->lang('RUNNING_TASK', $task->get_name()) . '</info>');
}
$task->run();
}
}
else
{
if ($input->getOption('verbose'))
{
$output->writeln('<info>' . $this->user->lang('CRON_NO_TASK') . '</info>');
}
}
return 0;
}
/**
* Executes a given cron task, if it is ready.
*
* If there is a task whose name matches $task_name, it is run and 0 is returned.
* and if verbose mode is set, print an info message with the name of the task.
* If there is no task matching $task_name, the function prints an error message
* and returns with status 2.
*
* @see execute
*
* @param InputInterface $input The input stream used to get the argument and verbose option.
* @param OutputInterface $output The output stream, used for printing verbose-mode and error information.
* @param string $task_name The name of the task that should be run.
*
* @return int 0 if all is well, 2 if no task matches $task_name.
*/
protected function run_one(InputInterface $input, OutputInterface $output, $task_name)
{
$task = $this->cron_manager->find_task($task_name);
if ($task)
{
if ($input->getOption('verbose'))
{
$output->writeln('<info>' . $this->user->lang('RUNNING_TASK', $task_name) . '</info>');
}
$task->run();
return 0;
}
else
{
throw new runtime_exception('CRON_NO_SUCH_TASK', array( $task_name), null, 2);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\console\command\extension;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class enable extends command
{
protected function configure()
{
$this
->setName('extension:enable')
->setDescription($this->user->lang('CLI_DESCRIPTION_ENABLE_EXTENSION'))
->addArgument(
'extension-name',
InputArgument::REQUIRED,
$this->user->lang('CLI_EXTENSION_NAME')
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('extension-name');
if (!$this->manager->is_available($name))
{
$io->error($this->user->lang('CLI_EXTENSION_NOT_EXIST', $name));
return 1;
}
$extension = $this->manager->get_extension($name);
if (($enableable = $extension->is_enableable()) !== true)
{
$message = !empty($enableable) ? $enableable : $this->user->lang('CLI_EXTENSION_NOT_ENABLEABLE', $name);
$message = is_array($message) ? implode(PHP_EOL, $message) : $message;
$io->error($message);
return 1;
}
if ($this->manager->is_enabled($name))
{
$io->error($this->user->lang('CLI_EXTENSION_ENABLED', $name));
return 1;
}
$this->manager->enable($name);
$this->manager->load_extensions();
if ($this->manager->is_enabled($name))
{
$this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_ENABLE', time(), array($name));
$io->success($this->user->lang('CLI_EXTENSION_ENABLE_SUCCESS', $name));
return 0;
}
else
{
$io->error($this->user->lang('CLI_EXTENSION_ENABLE_FAILURE', $name));
return 1;
}
}
}

View File

@@ -0,0 +1,332 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\console\command\update;
use phpbb\config\config;
use phpbb\exception\exception_interface;
use phpbb\language\language;
use phpbb\user;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ContainerInterface;
class check extends \phpbb\console\command\command
{
/** @var \phpbb\config\config */
protected $config;
/** @var \Symfony\Component\DependencyInjection\ContainerBuilder */
protected $phpbb_container;
/**
* @var language
*/
private $language;
/**
* Construct method
*/
public function __construct(user $user, config $config, ContainerInterface $phpbb_container, language $language)
{
$this->config = $config;
$this->phpbb_container = $phpbb_container;
$this->language = $language;
$this->language->add_lang(array('acp/common', 'acp/extensions'));
parent::__construct($user);
}
/**
* Configures the service.
*
* Sets the name and description of the command.
*
* @return null
*/
protected function configure()
{
$this
->setName('update:check')
->setDescription($this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK'))
->addArgument('ext-name', InputArgument::OPTIONAL, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_ARGUMENT_1'))
->addOption('stability', null, InputOption::VALUE_REQUIRED, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_OPTION_STABILITY'))
->addOption('cache', 'c', InputOption::VALUE_NONE, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_OPTION_CACHE'))
;
}
/**
* Executes the command.
*
* Checks if an update is available.
* If at least one is available, a message is printed and if verbose mode is set the list of possible updates is printed.
* If their is none, nothing is printed unless verbose mode is set.
*
* @param InputInterface $input Input stream, used to get the options.
* @param OutputInterface $output Output stream, used to print messages.
* @return int 0 if the board is up to date, 1 if it is not and 2 if an error occurred.
* @throws \RuntimeException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$recheck = true;
if ($input->getOption('cache'))
{
$recheck = false;
}
$stability = null;
if ($input->getOption('stability'))
{
$stability = $input->getOption('stability');
if (!($stability == 'stable') && !($stability == 'unstable'))
{
$io->error($this->language->lang('CLI_ERROR_INVALID_STABILITY', $stability));
return 3;
}
}
$ext_name = $input->getArgument('ext-name');
if ($ext_name != null)
{
if ($ext_name == 'all')
{
return $this->check_all_ext($io, $stability, $recheck);
}
else
{
return $this->check_ext($input, $io, $stability, $recheck, $ext_name);
}
}
else
{
return $this->check_core($input, $io, $stability, $recheck);
}
}
/**
* Check if a given extension is up to date
*
* @param InputInterface $input Input stream, used to get the options.
* @param SymfonyStyle $io IO handler, for formatted and unified IO
* @param string $stability Force a given stability
* @param bool $recheck Disallow the use of the cache
* @param string $ext_name The extension name
* @return int
*/
protected function check_ext(InputInterface $input, SymfonyStyle $io, $stability, $recheck, $ext_name)
{
try
{
$ext_manager = $this->phpbb_container->get('ext.manager');
$md_manager = $ext_manager->create_extension_metadata_manager($ext_name);
$updates_available = $ext_manager->version_check($md_manager, $recheck, false, $stability);
$metadata = $md_manager->get_metadata('all');
if ($input->getOption('verbose'))
{
$io->title($md_manager->get_metadata('display-name'));
$io->note($this->language->lang('CURRENT_VERSION') . $this->language->lang('COLON') . ' ' . $metadata['version']);
}
if (!empty($updates_available))
{
if ($input->getOption('verbose'))
{
$io->caution($this->language->lang('NOT_UP_TO_DATE', $metadata['name']));
$this->display_versions($io, $updates_available);
}
return 1;
}
else
{
if ($input->getOption('verbose'))
{
$io->success($this->language->lang('UPDATE_NOT_NEEDED'));
}
return 0;
}
}
catch (\RuntimeException $e)
{
$io->error($this->language->lang('EXTENSION_NOT_INSTALLED', $ext_name));
return 1;
}
}
/**
* Check if the core is up to date
*
* @param InputInterface $input Input stream, used to get the options.
* @param SymfonyStyle $io IO handler, for formatted and unified IO
* @param string $stability Force a given stability
* @param bool $recheck Disallow the use of the cache
* @return int
*/
protected function check_core(InputInterface $input, SymfonyStyle $io, $stability, $recheck)
{
$version_helper = $this->phpbb_container->get('version_helper');
$version_helper->force_stability($stability);
$updates_available = $version_helper->get_suggested_updates($recheck);
if ($input->getOption('verbose'))
{
$io->title('phpBB core');
$io->note( $this->language->lang('CURRENT_VERSION') . $this->language->lang('COLON') . ' ' . $this->config['version']);
}
if (!empty($updates_available))
{
$io->caution($this->language->lang('UPDATE_NEEDED'));
if ($input->getOption('verbose'))
{
$this->display_versions($io, $updates_available);
}
return 1;
}
else
{
if ($input->getOption('verbose'))
{
$io->success($this->language->lang('UPDATE_NOT_NEEDED'));
}
return 0;
}
}
/**
* Check if all the available extensions are up to date
*
* @param SymfonyStyle $io IO handler, for formatted and unified IO
* @param string $stability Stability specifier string
* @param bool $recheck Disallow the use of the cache
* @return int
*/
protected function check_all_ext(SymfonyStyle $io, $stability, $recheck)
{
/** @var \phpbb\extension\manager $ext_manager */
$ext_manager = $this->phpbb_container->get('ext.manager');
$rows = [];
foreach ($ext_manager->all_available() as $ext_name => $ext_path)
{
$row = [];
$row[] = sprintf("<info>%s</info>", $ext_name);
$md_manager = $ext_manager->create_extension_metadata_manager($ext_name);
try
{
$metadata = $md_manager->get_metadata('all');
if (isset($metadata['extra']['version-check']))
{
try {
$updates_available = $ext_manager->version_check($md_manager, $recheck, false, $stability);
if (!empty($updates_available))
{
$versions = array_map(function($entry)
{
return $entry['current'];
}, $updates_available);
$row[] = sprintf("<comment>%s</comment>", $metadata['version']);
$row[] = implode(', ', $versions);
}
else
{
$row[] = sprintf("<info>%s</info>", $metadata['version']);
$row[] = '';
}
} catch (\RuntimeException $e) {
$row[] = $metadata['version'];
$row[] = '';
}
}
else
{
$row[] = $metadata['version'];
$row[] = '';
}
}
catch (exception_interface $e)
{
$exception_message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters()));
$row[] = '<error>' . $exception_message . '</error>';
}
catch (\RuntimeException $e)
{
$row[] = '<error>' . $e->getMessage() . '</error>';
}
$rows[] = $row;
}
$io->table([
$this->language->lang('EXTENSION_NAME'),
$this->language->lang('CURRENT_VERSION'),
$this->language->lang('LATEST_VERSION'),
], $rows);
return 0;
}
/**
* Display the details of the available updates
*
* @param SymfonyStyle $io IO handler, for formatted and unified IO
* @param array $updates_available The list of the available updates
*/
protected function display_versions(SymfonyStyle $io, $updates_available)
{
$io->section($this->language->lang('UPDATES_AVAILABLE'));
$rows = [];
foreach ($updates_available as $version_data)
{
$row = ['', '', ''];
$row[0] = $version_data['current'];
if (isset($version_data['announcement']))
{
$row[1] = $version_data['announcement'];
}
if (isset($version_data['download']))
{
$row[2] = $version_data['download'];
}
$rows[] = $row;
}
$io->table([
$this->language->lang('VERSION'),
$this->language->lang('ANNOUNCEMENT_TOPIC'),
$this->language->lang('DOWNLOAD_LATEST'),
], $rows);
}
}

View File

@@ -0,0 +1,334 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\console\command\user;
use phpbb\config\config;
use phpbb\console\command\command;
use phpbb\db\driver\driver_interface;
use phpbb\exception\runtime_exception;
use phpbb\language\language;
use phpbb\passwords\manager;
use phpbb\user;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
class add extends command
{
/** @var array Array of interactively acquired options */
protected $data;
/** @var driver_interface */
protected $db;
/** @var config */
protected $config;
/** @var language */
protected $language;
/** @var manager */
protected $password_manager;
/**
* phpBB root path
*
* @var string
*/
protected $phpbb_root_path;
/**
* PHP extension.
*
* @var string
*/
protected $php_ext;
/**
* Construct method
*
* @param user $user
* @param driver_interface $db
* @param config $config
* @param language $language
* @param manager $password_manager
* @param string $phpbb_root_path
* @param string $php_ext
*/
public function __construct(user $user, driver_interface $db, config $config, language $language, manager $password_manager, $phpbb_root_path, $php_ext)
{
$this->db = $db;
$this->config = $config;
$this->language = $language;
$this->password_manager = $password_manager;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->language->add_lang('ucp');
parent::__construct($user);
}
/**
* Sets the command name and description
*
* @return null
*/
protected function configure()
{
$this
->setName('user:add')
->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ADD'))
->setHelp($this->language->lang('CLI_HELP_USER_ADD'))
->addOption(
'username',
'U',
InputOption::VALUE_REQUIRED,
$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME')
)
->addOption(
'password',
'P',
InputOption::VALUE_REQUIRED,
$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD')
)
->addOption(
'email',
'E',
InputOption::VALUE_REQUIRED,
$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL')
)
->addOption(
'send-email',
null,
InputOption::VALUE_NONE,
$this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY')
)
;
}
/**
* Executes the command user:add
*
* Adds a new user to the database. If options are not provided, it will ask for the username, password and email.
* User is added to the registered user group. Language and timezone default to $config settings.
*
* @param InputInterface $input The input stream used to get the options
* @param OutputInterface $output The output stream, used to print messages
*
* @return int 0 if all is well, 1 if any errors occurred
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
try
{
$this->validate_user_data();
$group_id = $this->get_group_id();
}
catch (runtime_exception $e)
{
$io->error($e->getMessage());
return 1;
}
$user_row = array(
'username' => $this->data['username'],
'user_password' => $this->password_manager->hash($this->data['new_password']),
'user_email' => $this->data['email'],
'group_id' => $group_id,
'user_timezone' => $this->config['board_timezone'],
'user_lang' => $this->config['default_lang'],
'user_type' => USER_NORMAL,
'user_regdate' => time(),
);
$user_id = (int) user_add($user_row);
if (!$user_id)
{
$io->error($this->language->lang('AUTH_NO_PROFILE_CREATED'));
return 1;
}
if ($input->getOption('send-email') && $this->config['email_enable'])
{
$this->send_activation_email($user_id);
}
$io->success($this->language->lang('CLI_USER_ADD_SUCCESS', $this->data['username']));
return 0;
}
/**
* Interacts with the user.
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$helper = $this->getHelper('question');
$this->data = array(
'username' => $input->getOption('username'),
'new_password' => $input->getOption('password'),
'email' => $input->getOption('email'),
);
if (!$this->data['username'])
{
$question = new Question($this->ask_user('USERNAME'));
$this->data['username'] = $helper->ask($input, $output, $question);
}
if (!$this->data['new_password'])
{
$question = new Question($this->ask_user('PASSWORD'));
$question->setValidator(function ($value) use ($helper, $input, $output) {
$question = new Question($this->ask_user('CONFIRM_PASSWORD'));
$question->setHidden(true);
if ($helper->ask($input, $output, $question) != $value)
{
throw new runtime_exception($this->language->lang('NEW_PASSWORD_ERROR'));
}
return $value;
});
$question->setHidden(true);
$question->setMaxAttempts(5);
$this->data['new_password'] = $helper->ask($input, $output, $question);
}
if (!$this->data['email'])
{
$question = new Question($this->ask_user('EMAIL_ADDRESS'));
$this->data['email'] = $helper->ask($input, $output, $question);
}
}
/**
* Validate the submitted user data
*
* @throws runtime_exception if any data fails validation
* @return null
*/
protected function validate_user_data()
{
if (!function_exists('validate_data'))
{
require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext);
}
$error = validate_data($this->data, array(
'username' => array(
array('string', false, $this->config['min_name_chars'], $this->config['max_name_chars']),
array('username', '')),
'new_password' => array(
array('string', false, $this->config['min_pass_chars'], 0),
array('password')),
'email' => array(
array('string', false, 6, 60),
array('user_email')),
));
if ($error)
{
throw new runtime_exception(implode("\n", array_map(array($this->language, 'lang'), $error)));
}
}
/**
* Get the group id
*
* Go and find in the database the group_id corresponding to 'REGISTERED'
*
* @throws runtime_exception if the group id does not exist in database.
* @return null
*/
protected function get_group_id()
{
$sql = 'SELECT group_id
FROM ' . GROUPS_TABLE . "
WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "'
AND group_type = " . GROUP_SPECIAL;
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$row || !$row['group_id'])
{
throw new runtime_exception($this->language->lang('NO_GROUP'));
}
return $row['group_id'];
}
/**
* Send account activation email
*
* @param int $user_id The new user's id
* @return null
*/
protected function send_activation_email($user_id)
{
switch ($this->config['require_activation'])
{
case USER_ACTIVATION_SELF:
$email_template = 'user_welcome_inactive';
$user_actkey = gen_rand_string(mt_rand(6, 10));
break;
case USER_ACTIVATION_ADMIN:
$email_template = 'admin_welcome_inactive';
$user_actkey = gen_rand_string(mt_rand(6, 10));
break;
default:
$email_template = 'user_welcome';
$user_actkey = '';
break;
}
if (!class_exists('messenger'))
{
require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext);
}
$messenger = new \messenger(false);
$messenger->template($email_template, $this->user->lang_name);
$messenger->to($this->data['email'], $this->data['username']);
$messenger->anti_abuse_headers($this->config, $this->user);
$messenger->assign_vars(array(
'WELCOME_MSG' => htmlspecialchars_decode($this->language->lang('WELCOME_SUBJECT', $this->config['sitename'])),
'USERNAME' => htmlspecialchars_decode($this->data['username']),
'PASSWORD' => htmlspecialchars_decode($this->data['new_password']),
'U_ACTIVATE' => generate_board_url() . "/ucp.{$this->php_ext}?mode=activate&u=$user_id&k=$user_actkey")
);
$messenger->send(NOTIFY_EMAIL);
}
/**
* Helper to translate questions to the user
*
* @param string $key The language key
* @return string The language key translated with a colon and space appended
*/
protected function ask_user($key)
{
return $this->language->lang($key) . $this->language->lang('COLON') . ' ';
}
}

View File

@@ -0,0 +1,899 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
/**
* phpbb_visibility
* Handle fetching and setting the visibility for topics and posts
*/
class content_visibility
{
/**
* Database object
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* User object
* @var \phpbb\user
*/
protected $user;
/**
* Auth object
* @var \phpbb\auth\auth
*/
protected $auth;
/**
* config object
* @var \phpbb\config\config
*/
protected $config;
/**
* Event dispatcher object
* @var \phpbb\event\dispatcher_interface
*/
protected $phpbb_dispatcher;
/**
* phpBB root path
* @var string
*/
protected $phpbb_root_path;
/**
* PHP Extension
* @var string
*/
protected $php_ext;
/**
* Constructor
*
* @param \phpbb\auth\auth $auth Auth object
* @param \phpbb\config\config $config Config object
* @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object
* @param \phpbb\db\driver\driver_interface $db Database object
* @param \phpbb\user $user User object
* @param string $phpbb_root_path Root path
* @param string $php_ext PHP Extension
* @param string $forums_table Forums table name
* @param string $posts_table Posts table name
* @param string $topics_table Topics table name
* @param string $users_table Users table name
*/
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, $phpbb_root_path, $php_ext, $forums_table, $posts_table, $topics_table, $users_table)
{
$this->auth = $auth;
$this->config = $config;
$this->phpbb_dispatcher = $phpbb_dispatcher;
$this->db = $db;
$this->user = $user;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->forums_table = $forums_table;
$this->posts_table = $posts_table;
$this->topics_table = $topics_table;
$this->users_table = $users_table;
}
/**
* Can the current logged-in user soft-delete posts?
*
* @param $forum_id int Forum ID whose permissions to check
* @param $poster_id int Poster ID of the post in question
* @param $post_locked bool Is the post locked?
* @return bool
*/
public function can_soft_delete($forum_id, $poster_id, $post_locked)
{
if ($this->auth->acl_get('m_softdelete', $forum_id))
{
return true;
}
else if ($this->auth->acl_get('f_softdelete', $forum_id) && $poster_id == $this->user->data['user_id'] && !$post_locked)
{
return true;
}
return false;
}
/**
* Get the topics post count or the forums post/topic count based on permissions
*
* @param $mode string One of topic_posts, forum_posts or forum_topics
* @param $data array Array with the topic/forum data to calculate from
* @param $forum_id int The forum id is used for permission checks
* @return int Number of posts/topics the user can see in the topic/forum
*/
public function get_count($mode, $data, $forum_id)
{
if (!$this->auth->acl_get('m_approve', $forum_id))
{
return (int) $data[$mode . '_approved'];
}
return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted'];
}
/**
* Check topic/post visibility for a given forum ID
*
* Note: Read permissions are not checked.
*
* @param $mode string Either "topic" or "post"
* @param $forum_id int The forum id is used for permission checks
* @param $data array Array with item information to check visibility
* @return bool True if the item is visible, false if not
*/
public function is_visible($mode, $forum_id, $data)
{
$visibility = $data[$mode . '_visibility'];
$poster_key = ($mode === 'topic') ? 'topic_poster' : 'poster_id';
$is_visible = ($visibility == ITEM_APPROVED) ||
($this->config['display_unapproved_posts'] &&
($this->user->data['user_id'] != ANONYMOUS) &&
($visibility == ITEM_UNAPPROVED || $visibility == ITEM_REAPPROVE) &&
($this->user->data['user_id'] == $data[$poster_key])) ||
$this->auth->acl_get('m_approve', $forum_id);
/**
* Allow changing the result of calling is_visible
*
* @event core.phpbb_content_visibility_is_visible
* @var bool is_visible Default visibility condition, to be modified by extensions if needed.
* @var string mode Either "topic" or "post"
* @var int forum_id Forum id of the current item
* @var array data Array of item information
* @since 3.2.2-RC1
*/
$vars = array(
'is_visible',
'mode',
'forum_id',
'data',
);
extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_is_visible', compact($vars)));
return $is_visible;
}
/**
* Create topic/post visibility SQL for a given forum ID
*
* Note: Read permissions are not checked.
*
* @param $mode string Either "topic" or "post"
* @param $forum_id int The forum id is used for permission checks
* @param $table_alias string Table alias to prefix in SQL queries
* @return string The appropriate combination SQL logic for topic/post_visibility
*/
public function get_visibility_sql($mode, $forum_id, $table_alias = '')
{
$where_sql = '';
$get_visibility_sql_overwrite = false;
/**
* Allow changing the result of calling get_visibility_sql
*
* @event core.phpbb_content_visibility_get_visibility_sql_before
* @var string where_sql Extra visibility conditions. It must end with either an SQL "AND" or an "OR"
* @var string mode Either "topic" or "post" depending on the query this is being used in
* @var array forum_id The forum id in which the search is made.
* @var string table_alias Table alias to prefix in SQL queries
* @var mixed get_visibility_sql_overwrite If a string, forces the function to return get_forums_visibility_sql_overwrite after executing the event
* If false, get_visibility_sql continues normally
* It must be either boolean or string
* @since 3.1.4-RC1
*/
$vars = array(
'where_sql',
'mode',
'forum_id',
'table_alias',
'get_visibility_sql_overwrite',
);
extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_visibility_sql_before', compact($vars)));
if ($get_visibility_sql_overwrite !== false)
{
return $get_visibility_sql_overwrite;
}
if ($this->auth->acl_get('m_approve', $forum_id))
{
$where_sql .= '1 = 1';
}
else
{
$visibility_query = $table_alias . $mode . '_visibility = ';
$where_sql .= '(' . $visibility_query . ITEM_APPROVED . ')';
if ($this->config['display_unapproved_posts'] && ($this->user->data['user_id'] != ANONYMOUS))
{
$poster_key = ($mode === 'topic') ? 'topic_poster' : 'poster_id';
$where_sql .= ' OR ((' . $visibility_query . ITEM_UNAPPROVED . ' OR ' . $visibility_query . ITEM_REAPPROVE .')';
$where_sql .= ' AND ' . $table_alias . $poster_key . ' = ' . ((int) $this->user->data['user_id']) . ')';
}
}
return '(' . $where_sql . ')';
}
/**
* Create topic/post visibility SQL for a set of forums
*
* Note: Read permissions are not checked. Forums without read permissions
* should not be in $forum_ids
*
* @param $mode string Either "topic" or "post"
* @param $forum_ids array Array of forum ids which the posts/topics are limited to
* @param $table_alias string Table alias to prefix in SQL queries
* @return string The appropriate combination SQL logic for topic/post_visibility
*/
public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '')
{
$where_sql = '';
$approve_forums = array_keys($this->auth->acl_getf('m_approve', true));
if (!empty($forum_ids) && !empty($approve_forums))
{
$approve_forums = array_intersect($forum_ids, $approve_forums);
$forum_ids = array_diff($forum_ids, $approve_forums);
}
$get_forums_visibility_sql_overwrite = false;
/**
* Allow changing the result of calling get_forums_visibility_sql
*
* @event core.phpbb_content_visibility_get_forums_visibility_before
* @var string where_sql Extra visibility conditions. It must end with either an SQL "AND" or an "OR"
* @var string mode Either "topic" or "post" depending on the query this is being used in
* @var array forum_ids Array of forum ids which the posts/topics are limited to
* @var string table_alias Table alias to prefix in SQL queries
* @var array approve_forums Array of forums where the user has m_approve permissions
* @var mixed get_forums_visibility_sql_overwrite If a string, forces the function to return get_forums_visibility_sql_overwrite after executing the event
* If false, get_forums_visibility_sql continues normally
* It must be either boolean or string
* @since 3.1.3-RC1
*/
$vars = array(
'where_sql',
'mode',
'forum_ids',
'table_alias',
'approve_forums',
'get_forums_visibility_sql_overwrite',
);
extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_forums_visibility_before', compact($vars)));
if ($get_forums_visibility_sql_overwrite !== false)
{
return $get_forums_visibility_sql_overwrite;
}
// Moderator can view all posts/topics in the moderated forums
$where_sql .= '(' . $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums, false, true) . ' OR ';
// Normal user can view approved items only
$where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . '
AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true) . '))';
return '(' . $where_sql . ')';
}
/**
* Create topic/post visibility SQL for all forums on the board
*
* Note: Read permissions are not checked. Forums without read permissions
* should be in $exclude_forum_ids
*
* @param $mode string Either "topic" or "post"
* @param $exclude_forum_ids array Array of forum ids which are excluded
* @param $table_alias string Table alias to prefix in SQL queries
* @return string The appropriate combination SQL logic for topic/post_visibility
*/
public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '')
{
$where_sqls = array();
$approve_forums = array_diff(array_keys($this->auth->acl_getf('m_approve', true)), $exclude_forum_ids);
$visibility_sql_overwrite = null;
/**
* Allow changing the result of calling get_global_visibility_sql
*
* @event core.phpbb_content_visibility_get_global_visibility_before
* @var array where_sqls Array of extra visibility conditions. Will be joined by imploding with "OR".
* @var string mode Either "topic" or "post" depending on the query this is being used in
* @var array exclude_forum_ids Array of forum ids the current user doesn't have access to
* @var string table_alias Table alias to prefix in SQL queries
* @var array approve_forums Array of forums where the user has m_approve permissions
* @var string visibility_sql_overwrite If not empty, forces the function to return visibility_sql_overwrite after executing the event
* @since 3.1.3-RC1
*/
$vars = array(
'where_sqls',
'mode',
'exclude_forum_ids',
'table_alias',
'approve_forums',
'visibility_sql_overwrite',
);
extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_global_visibility_before', compact($vars)));
if ($visibility_sql_overwrite)
{
return $visibility_sql_overwrite;
}
// Include approved items in all forums but the excluded
$where_sqls[] = '(' . $this->db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true, true) . '
AND ' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ')';
// If user has moderator permissions, add everything in the moderated forums
if (count($approve_forums))
{
$where_sqls[] = $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums);
}
return '(' . implode(' OR ', $where_sqls) . ')';
}
/**
* Change visibility status of one post or all posts of a topic
*
* @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @param $post_id mixed Post ID or array of post IDs to act on,
* if it is empty, all posts of topic_id will be modified
* @param $topic_id int Topic where $post_id is found
* @param $forum_id int Forum where $topic_id is found
* @param $user_id int User performing the action
* @param $time int Timestamp when the action is performed
* @param $reason string Reason why the visibility was changed.
* @param $is_starter bool Is this the first post of the topic changed?
* @param $is_latest bool Is this the last post of the topic changed?
* @param $limit_visibility mixed Limit updating per topic_id to a certain visibility
* @param $limit_delete_time mixed Limit updating per topic_id to a certain deletion time
* @return array Changed post data, empty array if an error occurred.
*/
public function set_post_visibility($visibility, $post_id, $topic_id, $forum_id, $user_id, $time, $reason, $is_starter, $is_latest, $limit_visibility = false, $limit_delete_time = false)
{
if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE)))
{
return array();
}
if ($post_id)
{
if (is_array($post_id))
{
$where_sql = $this->db->sql_in_set('post_id', array_map('intval', $post_id));
}
else
{
$where_sql = 'post_id = ' . (int) $post_id;
}
$where_sql .= ' AND topic_id = ' . (int) $topic_id;
}
else
{
$where_sql = 'topic_id = ' . (int) $topic_id;
// Limit the posts to a certain visibility and deletion time
// This allows us to only restore posts, that were approved
// when the topic got soft deleted. So previous soft deleted
// and unapproved posts are still soft deleted/unapproved
if ($limit_visibility !== false)
{
$where_sql .= ' AND post_visibility = ' . (int) $limit_visibility;
}
if ($limit_delete_time !== false)
{
$where_sql .= ' AND post_delete_time = ' . (int) $limit_delete_time;
}
}
$sql = 'SELECT poster_id, post_id, post_postcount, post_visibility
FROM ' . $this->posts_table . '
WHERE ' . $where_sql;
$result = $this->db->sql_query($sql);
$post_ids = $poster_postcounts = $postcounts = $postcount_visibility = array();
while ($row = $this->db->sql_fetchrow($result))
{
$post_ids[] = (int) $row['post_id'];
if ($row['post_visibility'] != $visibility)
{
if ($row['post_postcount'] && !isset($poster_postcounts[(int) $row['poster_id']]))
{
$poster_postcounts[(int) $row['poster_id']] = 1;
}
else if ($row['post_postcount'])
{
$poster_postcounts[(int) $row['poster_id']]++;
}
if (!isset($postcount_visibility[$row['post_visibility']]))
{
$postcount_visibility[$row['post_visibility']] = 1;
}
else
{
$postcount_visibility[$row['post_visibility']]++;
}
}
}
$this->db->sql_freeresult($result);
if (empty($post_ids))
{
return array();
}
if (!function_exists('truncate_string'))
{
include($this->phpbb_root_path . 'includes/functions_content.' . $this->php_ext);
}
$data = array(
'post_visibility' => (int) $visibility,
'post_delete_user' => (int) $user_id,
'post_delete_time' => ((int) $time) ?: time(),
'post_delete_reason' => truncate_string($reason, 255, 255, false),
);
/**
* Perform actions right before the query to change post visibility
*
* @event core.set_post_visibility_before_sql
* @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @var array post_id Array containing all post IDs to be modified. If blank, all posts within the topic are modified.
* @var int topic_id Topic of the post IDs to be modified.
* @var int forum_id Forum ID that the topic_id resides in.
* @var int user_id User ID doing this action.
* @var int time Timestamp of this action.
* @var string reason Reason specified by the user for this change.
* @var bool is_starter Are we changing the topic's starter?
* @var bool is_latest Are we changing the topic's latest post?
* @var array data The data array for this action.
* @since 3.1.10-RC1
* @changed 3.2.2-RC1 Use time instead of non-existent timestamp
*/
$vars = array(
'visibility',
'post_id',
'topic_id',
'forum_id',
'user_id',
'time',
'reason',
'is_starter',
'is_latest',
'data',
);
extract($this->phpbb_dispatcher->trigger_event('core.set_post_visibility_before_sql', compact($vars)));
$sql = 'UPDATE ' . $this->posts_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . '
WHERE ' . $this->db->sql_in_set('post_id', $post_ids);
$this->db->sql_query($sql);
// Group the authors by post count, to reduce the number of queries
foreach ($poster_postcounts as $poster_id => $num_posts)
{
$postcounts[$num_posts][] = $poster_id;
}
// Update users postcounts
foreach ($postcounts as $num_posts => $poster_ids)
{
if (in_array($visibility, array(ITEM_REAPPROVE, ITEM_DELETED)))
{
$sql = 'UPDATE ' . $this->users_table . '
SET user_posts = 0
WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . '
AND user_posts < ' . $num_posts;
$this->db->sql_query($sql);
$sql = 'UPDATE ' . $this->users_table . '
SET user_posts = user_posts - ' . $num_posts . '
WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . '
AND user_posts >= ' . $num_posts;
$this->db->sql_query($sql);
}
else
{
$sql = 'UPDATE ' . $this->users_table . '
SET user_posts = user_posts + ' . $num_posts . '
WHERE ' . $this->db->sql_in_set('user_id', $poster_ids);
$this->db->sql_query($sql);
}
}
$update_topic_postcount = true;
// Sync the first/last topic information if needed
if (!$is_starter && $is_latest)
{
if (!function_exists('update_post_information'))
{
include($this->phpbb_root_path . 'includes/functions_posting.' . $this->php_ext);
}
// update_post_information can only update the last post info ...
if ($topic_id)
{
update_post_information('topic', $topic_id, false);
}
if ($forum_id)
{
update_post_information('forum', $forum_id, false);
}
}
else if ($is_starter && $topic_id)
{
if (!function_exists('sync'))
{
include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext);
}
// ... so we need to use sync, if the first post is changed.
// The forum is resynced recursive by sync() itself.
sync('topic', 'topic_id', $topic_id, true);
// sync recalculates the topic replies and forum posts by itself, so we don't do that.
$update_topic_postcount = false;
}
$topic_update_array = array();
// Update the topic's reply count and the forum's post count
if ($update_topic_postcount)
{
$field_alias = array(
ITEM_APPROVED => 'posts_approved',
ITEM_UNAPPROVED => 'posts_unapproved',
ITEM_DELETED => 'posts_softdeleted',
ITEM_REAPPROVE => 'posts_unapproved',
);
$cur_posts = array_fill_keys($field_alias, 0);
foreach ($postcount_visibility as $post_visibility => $visibility_posts)
{
$cur_posts[$field_alias[(int) $post_visibility]] += $visibility_posts;
}
$sql_ary = array();
$recipient_field = $field_alias[$visibility];
foreach ($cur_posts as $field => $count)
{
// Decrease the count for the old statuses.
if ($count && $field != $recipient_field)
{
$sql_ary[$field] = " - $count";
}
}
// Add up the count from all statuses excluding the recipient status.
$count_increase = array_sum(array_diff($cur_posts, array($recipient_field)));
if ($count_increase)
{
$sql_ary[$recipient_field] = " + $count_increase";
}
if (count($sql_ary))
{
$forum_sql = array();
foreach ($sql_ary as $field => $value_change)
{
$topic_update_array[] = 'topic_' . $field . ' = topic_' . $field . $value_change;
$forum_sql[] = 'forum_' . $field . ' = forum_' . $field . $value_change;
}
$sql = 'UPDATE ' . $this->forums_table . '
SET ' . implode(', ', $forum_sql) . '
WHERE forum_id = ' . (int) $forum_id;
$this->db->sql_query($sql);
}
}
if ($post_id)
{
$sql = 'SELECT 1 AS has_attachments
FROM ' . POSTS_TABLE . '
WHERE topic_id = ' . (int) $topic_id . '
AND post_attachment = 1
AND post_visibility = ' . ITEM_APPROVED . '
AND ' . $this->db->sql_in_set('post_id', $post_id, true);
$result = $this->db->sql_query_limit($sql, 1);
$has_attachment = (bool) $this->db->sql_fetchfield('has_attachments');
$this->db->sql_freeresult($result);
if ($has_attachment && $visibility == ITEM_APPROVED)
{
$topic_update_array[] = 'topic_attachment = 1';
}
else if (!$has_attachment && $visibility != ITEM_APPROVED)
{
$topic_update_array[] = 'topic_attachment = 0';
}
}
if (!empty($topic_update_array))
{
// Update the number for replies and posts, and update the attachments flag
$sql = 'UPDATE ' . $this->topics_table . '
SET ' . implode(', ', $topic_update_array) . '
WHERE topic_id = ' . (int) $topic_id;
$this->db->sql_query($sql);
}
/**
* Perform actions after all steps to changing post visibility
*
* @event core.set_post_visibility_after
* @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @var array post_id Array containing all post IDs to be modified. If blank, all posts within the topic are modified.
* @var int topic_id Topic of the post IDs to be modified.
* @var int forum_id Forum ID that the topic_id resides in.
* @var int user_id User ID doing this action.
* @var int time Timestamp of this action.
* @var string reason Reason specified by the user for this change.
* @var bool is_starter Are we changing the topic's starter?
* @var bool is_latest Are we changing the topic's latest post?
* @var array data The data array for this action.
* @since 3.1.10-RC1
* @changed 3.2.2-RC1 Use time instead of non-existent timestamp
*/
$vars = array(
'visibility',
'post_id',
'topic_id',
'forum_id',
'user_id',
'time',
'reason',
'is_starter',
'is_latest',
'data',
);
extract($this->phpbb_dispatcher->trigger_event('core.set_post_visibility_after', compact($vars)));
return $data;
}
/**
* Set topic visibility
*
* Allows approving (which is akin to undeleting/restore) or soft deleting an entire topic.
* Calls set_post_visibility as needed.
*
* Note: By default, when a soft deleted topic is restored. Only posts that
* were approved at the time of soft deleting, are being restored.
* Same applies to soft deleting. Only approved posts will be marked
* as soft deleted.
* If you want to update all posts, use the force option.
*
* @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @param $topic_id mixed Topic ID to act on
* @param $forum_id int Forum where $topic_id is found
* @param $user_id int User performing the action
* @param $time int Timestamp when the action is performed
* @param $reason string Reason why the visibilty was changed.
* @param $force_update_all bool Force to update all posts within the topic
* @return array Changed topic data, empty array if an error occurred.
*/
public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false)
{
if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE)))
{
return array();
}
if (!$force_update_all)
{
$sql = 'SELECT topic_visibility, topic_delete_time
FROM ' . $this->topics_table . '
WHERE topic_id = ' . (int) $topic_id;
$result = $this->db->sql_query($sql);
$original_topic_data = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!$original_topic_data)
{
// The topic does not exist...
return array();
}
}
if (!function_exists('truncate_string'))
{
include($this->phpbb_root_path . 'includes/functions_content.' . $this->php_ext);
}
// Note, we do not set a reason for the posts, just for the topic
$data = array(
'topic_visibility' => (int) $visibility,
'topic_delete_user' => (int) $user_id,
'topic_delete_time' => ((int) $time) ?: time(),
'topic_delete_reason' => truncate_string($reason, 255, 255, false),
);
/**
* Perform actions right before the query to change topic visibility
*
* @event core.set_topic_visibility_before_sql
* @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @var int topic_id Topic of the post IDs to be modified.
* @var int forum_id Forum ID that the topic_id resides in.
* @var int user_id User ID doing this action.
* @var int time Timestamp of this action.
* @var string reason Reason specified by the user for this change.
* @var bool force_update_all Force an update on all posts within the topic, regardless of their current approval state.
* @var array data The data array for this action.
* @since 3.1.10-RC1
* @changed 3.2.2-RC1 Use time instead of non-existent timestamp
*/
$vars = array(
'visibility',
'topic_id',
'forum_id',
'user_id',
'time',
'reason',
'force_update_all',
'data',
);
extract($this->phpbb_dispatcher->trigger_event('core.set_topic_visibility_before_sql', compact($vars)));
$sql = 'UPDATE ' . $this->topics_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . '
WHERE topic_id = ' . (int) $topic_id;
$this->db->sql_query($sql);
if (!$this->db->sql_affectedrows())
{
return array();
}
if (!$force_update_all && $original_topic_data['topic_delete_time'] && $original_topic_data['topic_visibility'] == ITEM_DELETED && $visibility == ITEM_APPROVED)
{
// If we're restoring a topic we only restore posts, that were soft deleted through the topic soft deletion.
$this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility'], $original_topic_data['topic_delete_time']);
}
else if (!$force_update_all && $original_topic_data['topic_visibility'] == ITEM_APPROVED && $visibility == ITEM_DELETED)
{
// If we're soft deleting a topic we only mark approved posts as soft deleted.
$this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility']);
}
else
{
$this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true);
}
/**
* Perform actions after all steps to changing topic visibility
*
* @event core.set_topic_visibility_after
* @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE}
* @var int topic_id Topic of the post IDs to be modified.
* @var int forum_id Forum ID that the topic_id resides in.
* @var int user_id User ID doing this action.
* @var int time Timestamp of this action.
* @var string reason Reason specified by the user for this change.
* @var bool force_update_all Force an update on all posts within the topic, regardless of their current approval state.
* @var array data The data array for this action.
* @since 3.1.10-RC1
* @changed 3.2.2-RC1 Use time instead of non-existent timestamp
*/
$vars = array(
'visibility',
'topic_id',
'forum_id',
'user_id',
'time',
'reason',
'force_update_all',
'data',
);
extract($this->phpbb_dispatcher->trigger_event('core.set_topic_visibility_after', compact($vars)));
return $data;
}
/**
* Add post to topic and forum statistics
*
* @param $data array Contains information from the topics table about given topic
* @param &$sql_data array Populated with the SQL changes, may be empty at call time
* @return null
*/
public function add_post_to_statistic($data, &$sql_data)
{
$sql_data[$this->topics_table] = (($sql_data[$this->topics_table]) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved + 1';
$sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved + 1';
if ($data['post_postcount'])
{
$sql_data[$this->users_table] = (($sql_data[$this->users_table]) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts + 1';
}
$this->config->increment('num_posts', 1, false);
}
/**
* Remove post from topic and forum statistics
*
* @param $data array Contains information from the topics table about given topic
* @param &$sql_data array Populated with the SQL changes, may be empty at call time
* @return null
*/
public function remove_post_from_statistic($data, &$sql_data)
{
if ($data['post_visibility'] == ITEM_APPROVED)
{
$sql_data[$this->topics_table] = ((!empty($sql_data[$this->topics_table])) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved - 1';
$sql_data[$this->forums_table] = ((!empty($sql_data[$this->forums_table])) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved - 1';
if ($data['post_postcount'])
{
$sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1';
}
$this->config->increment('num_posts', -1, false);
}
else if ($data['post_visibility'] == ITEM_UNAPPROVED || $data['post_visibility'] == ITEM_REAPPROVE)
{
$sql_data[FORUMS_TABLE] = (($sql_data[FORUMS_TABLE]) ? $sql_data[FORUMS_TABLE] . ', ' : '') . 'forum_posts_unapproved = forum_posts_unapproved - 1';
$sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_posts_unapproved = topic_posts_unapproved - 1';
}
else if ($data['post_visibility'] == ITEM_DELETED)
{
$sql_data[FORUMS_TABLE] = (($sql_data[FORUMS_TABLE]) ? $sql_data[FORUMS_TABLE] . ', ' : '') . 'forum_posts_softdeleted = forum_posts_softdeleted - 1';
$sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_posts_softdeleted = topic_posts_softdeleted - 1';
}
}
/**
* Remove topic from forum statistics
*
* @param $data array Post and topic data
* @param &$sql_data array Populated with the SQL changes, may be empty at call time
* @return null
*/
public function remove_topic_from_statistic($data, &$sql_data)
{
if ($data['topic_visibility'] == ITEM_APPROVED)
{
$sql_data[FORUMS_TABLE] .= 'forum_posts_approved = forum_posts_approved - 1, forum_topics_approved = forum_topics_approved - 1';
if ($data['post_postcount'])
{
$sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1';
}
}
else if ($data['topic_visibility'] == ITEM_UNAPPROVED || $data['post_visibility'] == ITEM_REAPPROVE)
{
$sql_data[FORUMS_TABLE] .= 'forum_posts_unapproved = forum_posts_unapproved - 1, forum_topics_unapproved = forum_topics_unapproved - 1';
}
else if ($data['topic_visibility'] == ITEM_DELETED)
{
$sql_data[FORUMS_TABLE] .= 'forum_posts_softdeleted = forum_posts_softdeleted - 1, forum_topics_softdeleted = forum_topics_softdeleted - 1';
}
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Controller helper class, contains methods that do things for controllers
*/
class helper
{
/**
* Template object
* @var \phpbb\template\template
*/
protected $template;
/**
* User object
* @var \phpbb\user
*/
protected $user;
/**
* config object
* @var \phpbb\config\config
*/
protected $config;
/* @var \phpbb\symfony_request */
protected $symfony_request;
/* @var \phpbb\request\request_interface */
protected $request;
/**
* @var \phpbb\routing\helper
*/
protected $routing_helper;
/**
* Constructor
*
* @param \phpbb\template\template $template Template object
* @param \phpbb\user $user User object
* @param \phpbb\config\config $config Config object
* @param \phpbb\symfony_request $symfony_request Symfony Request object
* @param \phpbb\request\request_interface $request phpBB request object
* @param \phpbb\routing\helper $routing_helper Helper to generate the routes
*/
public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\routing\helper $routing_helper)
{
$this->template = $template;
$this->user = $user;
$this->config = $config;
$this->symfony_request = $symfony_request;
$this->request = $request;
$this->routing_helper = $routing_helper;
}
/**
* Automate setting up the page and creating the response object.
*
* @param string $template_file The template handle to render
* @param string $page_title The title of the page to output
* @param int $status_code The status code to be sent to the page header
* @param bool $display_online_list Do we display online users list
* @param int $item_id Restrict online users to item id
* @param string $item Restrict online users to a certain session item, e.g. forum for session_forum_id
* @param bool $send_headers Whether headers should be sent by page_header(). Defaults to false for controllers.
*
* @return Response object containing rendered page
*/
public function render($template_file, $page_title = '', $status_code = 200, $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = false)
{
page_header($page_title, $display_online_list, $item_id, $item, $send_headers);
$this->template->set_filenames(array(
'body' => $template_file,
));
page_footer(true, false, false);
$headers = !empty($this->user->data['is_bot']) ? array('X-PHPBB-IS-BOT' => 'yes') : array();
return new Response($this->template->assign_display('body'), $status_code, $headers);
}
/**
* Generate a URL to a route
*
* @param string $route Name of the route to travel
* @param array $params String or array of additional url parameters
* @param bool $is_amp Is url using &amp; (true) or & (false)
* @param string|bool $session_id Possibility to use a custom session id instead of the global one
* @param bool|string $reference_type The type of reference to be generated (one of the constants)
* @return string The URL already passed through append_sid()
*/
public function route($route, array $params = array(), $is_amp = true, $session_id = false, $reference_type = UrlGeneratorInterface::ABSOLUTE_PATH)
{
return $this->routing_helper->route($route, $params, $is_amp, $session_id, $reference_type);
}
/**
* Output an error, effectively the same thing as trigger_error
*
* @param string $message The error message
* @param int $code The error code (e.g. 404, 500, 503, etc.)
* @return Response A Response instance
*
* @deprecated 3.1.3 (To be removed: 4.0.0) Use exceptions instead.
*/
public function error($message, $code = 500)
{
return $this->message($message, array(), 'INFORMATION', $code);
}
/**
* Output a message
*
* In case of an error, please throw an exception instead
*
* @param string $message The message to display (must be a language variable)
* @param array $parameters The parameters to use with the language var
* @param string $title Title for the message (must be a language variable)
* @param int $code The HTTP status code (e.g. 404, 500, 503, etc.)
* @return Response A Response instance
*/
public function message($message, array $parameters = array(), $title = 'INFORMATION', $code = 200)
{
array_unshift($parameters, $message);
$message_text = call_user_func_array(array($this->user, 'lang'), $parameters);
$message_title = $this->user->lang($title);
if ($this->request->is_ajax())
{
global $refresh_data;
return new JsonResponse(
array(
'MESSAGE_TITLE' => $message_title,
'MESSAGE_TEXT' => $message_text,
'S_USER_WARNING' => false,
'S_USER_NOTICE' => false,
'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null
),
$code
);
}
$this->template->assign_vars(array(
'MESSAGE_TEXT' => $message_text,
'MESSAGE_TITLE' => $message_title,
));
return $this->render('message_body.html', $message_title, $code);
}
/**
* Assigns automatic refresh time meta tag in template
*
* @param int $time time in seconds, when redirection should occur
* @param string $url the URL where the user should be redirected
* @return null
*/
public function assign_meta_refresh_var($time, $url)
{
$this->template->assign_vars(array(
'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />',
));
}
/**
* Return the current url
*
* @return string
*/
public function get_current_url()
{
return generate_board_url(true) . $this->request->escape($this->symfony_request->getRequestUri(), true);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cron\controller;
use Symfony\Component\HttpFoundation\Response;
/**
* Controller for running cron jobs
*/
class cron
{
/**
* Handles CRON requests
*
* @param string $cron_type
*
* @return Response
*/
public function handle($cron_type)
{
$response = new Response();
$response->headers->set('Cache-Control', 'no-cache');
$response->headers->set('Content-type', 'image/gif');
$response->headers->set('Content-length', '43');
$response->setContent(base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='));
return $response;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cron\event;
use phpbb\cron\manager;
use phpbb\lock\db;
use phpbb\request\request_interface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\PostResponseEvent;
/**
* Event listener that executes cron tasks, after the response was served
*/
class cron_runner_listener implements EventSubscriberInterface
{
/**
* @var \phpbb\lock\db
*/
private $cron_lock;
/**
* @var \phpbb\cron\manager
*/
private $cron_manager;
/**
* @var \phpbb\request\request_interface
*/
private $request;
/**
* Constructor
*
* @param db $lock
* @param manager $manager
* @param request_interface $request
*/
public function __construct(db $lock, manager $manager, request_interface $request)
{
$this->cron_lock = $lock;
$this->cron_manager = $manager;
$this->request = $request;
}
/**
* Runs the cron job after the response was sent
*
* @param PostResponseEvent $event The event
*/
public function on_kernel_terminate(PostResponseEvent $event)
{
$request = $event->getRequest();
$controller_name = $request->get('_route');
if ($controller_name !== 'phpbb_cron_run')
{
return;
}
$cron_type = $request->get('cron_type');
if ($this->cron_lock->acquire())
{
$task = $this->cron_manager->find_task($cron_type);
if ($task)
{
if ($task->is_parametrized())
{
$task->parse_parameters($this->request);
}
if ($task->is_ready())
{
$task->run();
}
$this->cron_lock->release();
}
}
}
/**
* {@inheritdoc}
*/
static public function getSubscribedEvents()
{
return array(
KernelEvents::TERMINATE => 'on_kernel_terminate',
);
}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cron;
use phpbb\cron\task\wrapper;
use phpbb\routing\helper;
/**
* Cron manager class.
*
* Finds installed cron tasks, stores task objects, provides task selection.
*/
class manager
{
/**
* @var helper
*/
protected $routing_helper;
/**
* Set of \phpbb\cron\task\wrapper objects.
* Array holding all tasks that have been found.
*
* @var array
*/
protected $tasks = array();
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $php_ext;
/**
* Constructor. Loads all available tasks.
*
* @param array|\Traversable $tasks Provides an iterable set of task names
* @param helper $routing_helper Routing helper
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct($tasks, helper $routing_helper, $phpbb_root_path, $php_ext)
{
$this->routing_helper = $routing_helper;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->load_tasks($tasks);
}
/**
* Loads tasks given by name, wraps them
* and puts them into $this->tasks.
*
* @param array|\Traversable $tasks Array of instances of \phpbb\cron\task\task
*
* @return null
*/
public function load_tasks($tasks)
{
foreach ($tasks as $task)
{
$this->tasks[] = $this->wrap_task($task);
}
}
/**
* Finds a task that is ready to run.
*
* If several tasks are ready, any one of them could be returned.
*
* If no tasks are ready, null is returned.
*
* @return \phpbb\cron\task\wrapper|null
*/
public function find_one_ready_task()
{
shuffle($this->tasks);
foreach ($this->tasks as $task)
{
if ($task->is_ready())
{
return $task;
}
}
return null;
}
/**
* Finds all tasks that are ready to run.
*
* @return array List of tasks which are ready to run (wrapped in \phpbb\cron\task\wrapper).
*/
public function find_all_ready_tasks()
{
$tasks = array();
foreach ($this->tasks as $task)
{
if ($task->is_ready())
{
$tasks[] = $task;
}
}
return $tasks;
}
/**
* Finds a task by name.
*
* If there is no task with the specified name, null is returned.
*
* Web runner uses this method to resolve names to tasks.
*
* @param string $name Name of the task to look up.
* @return \phpbb\cron\task\wrapper A wrapped task corresponding to the given name, or null.
*/
public function find_task($name)
{
foreach ($this->tasks as $task)
{
if ($task->get_name() == $name)
{
return $task;
}
}
return null;
}
/**
* Find all tasks and return them.
*
* @return array List of all tasks.
*/
public function get_tasks()
{
return $this->tasks;
}
/**
* Wraps a task inside an instance of \phpbb\cron\task\wrapper.
*
* @param \phpbb\cron\task\task $task The task.
* @return \phpbb\cron\task\wrapper The wrapped task.
*/
public function wrap_task(\phpbb\cron\task\task $task)
{
return new wrapper($task, $this->routing_helper, $this->phpbb_root_path, $this->php_ext);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cron\task\core;
/**
* Update old hashes to the current default hashing algorithm
*
* It is intended to gradually update all "old" style hashes to the
* current default hashing algorithm.
*/
class update_hashes extends \phpbb\cron\task\base
{
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\db\driver\driver_interface */
protected $db;
/** @var \phpbb\lock\db */
protected $update_lock;
/** @var \phpbb\passwords\manager */
protected $passwords_manager;
/** @var string Default hashing type */
protected $default_type;
/**
* Constructor.
*
* @param \phpbb\config\config $config
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\lock\db $update_lock
* @param \phpbb\passwords\manager $passwords_manager
* @param array $hashing_algorithms Hashing driver
* service collection
* @param array $defaults Default password types
*/
public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\lock\db $update_lock, \phpbb\passwords\manager $passwords_manager, $hashing_algorithms, $defaults)
{
$this->config = $config;
$this->db = $db;
$this->passwords_manager = $passwords_manager;
$this->update_lock = $update_lock;
foreach ($defaults as $type)
{
if ($hashing_algorithms[$type]->is_supported() && !$hashing_algorithms[$type] instanceof \phpbb\passwords\driver\base_native)
{
$this->default_type = $type;
break;
}
}
}
/**
* {@inheritdoc}
*/
public function is_runnable()
{
return !$this->config['use_system_cron'];
}
/**
* {@inheritdoc}
*/
public function should_run()
{
if (!empty($this->config['update_hashes_lock']))
{
$last_run = explode(' ', $this->config['update_hashes_lock']);
if ($last_run[0] + 60 >= time())
{
return false;
}
}
return $this->config['enable_update_hashes'] && $this->config['update_hashes_last_cron'] < (time() - 60);
}
/**
* {@inheritdoc}
*/
public function run()
{
if ($this->update_lock->acquire())
{
$sql = 'SELECT user_id, user_password
FROM ' . USERS_TABLE . '
WHERE user_password ' . $this->db->sql_like_expression('$H$' . $this->db->get_any_char()) . '
OR user_password ' . $this->db->sql_like_expression('$CP$' . $this->db->get_any_char());
$result = $this->db->sql_query_limit($sql, 20);
$affected_rows = 0;
while ($row = $this->db->sql_fetchrow($result))
{
$new_hash = $this->passwords_manager->hash($row['user_password'], array($this->default_type));
// Increase number so we know that users were selected from the database
$affected_rows++;
$sql = 'UPDATE ' . USERS_TABLE . "
SET user_password = '" . $this->db->sql_escape($new_hash) . "'
WHERE user_id = " . (int) $row['user_id'];
$this->db->sql_query($sql);
}
$this->config->set('update_hashes_last_cron', time());
$this->update_lock->release();
// Stop cron for good once all hashes are converted
if ($affected_rows === 0)
{
$this->config->set('enable_update_hashes', '0');
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\cron\task;
use phpbb\routing\helper;
/**
* Cron task wrapper class.
* Enhances cron tasks with convenience methods that work identically for all tasks.
*/
class wrapper
{
/**
* @var helper
*/
protected $routing_helper;
/**
* @var task
*/
protected $task;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $php_ext;
/**
* Constructor.
*
* Wraps a task $task, which must implement cron_task interface.
*
* @param task $task The cron task to wrap.
* @param helper $routing_helper Routing helper for route generation
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct(task $task, helper $routing_helper, $phpbb_root_path, $php_ext)
{
$this->task = $task;
$this->routing_helper = $routing_helper;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
}
/**
* Returns whether the wrapped task is parametrised.
*
* Parametrized tasks accept parameters during initialization and must
* normally be scheduled with parameters.
*
* @return bool Whether or not this task is parametrized.
*/
public function is_parametrized()
{
return $this->task instanceof parametrized;
}
/**
* Returns whether the wrapped task is ready to run.
*
* A task is ready to run when it is runnable according to current configuration
* and enough time has passed since it was last run.
*
* @return bool Whether the wrapped task is ready to run.
*/
public function is_ready()
{
return $this->task->is_runnable() && $this->task->should_run();
}
/**
* Returns a url through which this task may be invoked via web.
*
* When system cron is not in use, running a cron task is accomplished
* by outputting an image with the url returned by this function as
* source.
*
* @return string URL through which this task may be invoked.
*/
public function get_url()
{
$params['cron_type'] = $this->get_name();
if ($this->is_parametrized())
{
$params = array_merge($params, $this->task->get_parameters());
}
return $this->routing_helper->route('phpbb_cron_run', $params);
}
/**
* Forwards all other method calls to the wrapped task implementation.
*
* @return mixed
*/
public function __call($name, $args)
{
return call_user_func_array(array($this->task, $name), $args);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,467 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
interface driver_interface
{
/**
* Set value for load_time debug parameter
*
* @param bool $value
*/
public function set_debug_load_time($value);
/**
* Set value for sql_explain debug parameter
*
* @param bool $value
*/
public function set_debug_sql_explain($value);
/**
* Gets the name of the sql layer.
*
* @return string
*/
public function get_sql_layer();
/**
* Gets the name of the database.
*
* @return string
*/
public function get_db_name();
/**
* Wildcards for matching any (%) character within LIKE expressions
*
* @return string
*/
public function get_any_char();
/**
* Wildcards for matching exactly one (_) character within LIKE expressions
*
* @return string
*/
public function get_one_char();
/**
* Gets the time spent into the queries
*
* @return int
*/
public function get_sql_time();
/**
* Gets the connect ID.
*
* @return mixed
*/
public function get_db_connect_id();
/**
* Indicates if an error was triggered.
*
* @return bool
*/
public function get_sql_error_triggered();
/**
* Gets the last faulty query
*
* @return string
*/
public function get_sql_error_sql();
/**
* Indicates if we are in a transaction.
*
* @return bool
*/
public function get_transaction();
/**
* Gets the returned error.
*
* @return array
*/
public function get_sql_error_returned();
/**
* Indicates if multiple insertion can be used
*
* @return bool
*/
public function get_multi_insert();
/**
* Set if multiple insertion can be used
*
* @param bool $multi_insert
*/
public function set_multi_insert($multi_insert);
/**
* Gets the exact number of rows in a specified table.
*
* @param string $table_name Table name
* @return string Exact number of rows in $table_name.
*/
public function get_row_count($table_name);
/**
* Gets the estimated number of rows in a specified table.
*
* @param string $table_name Table name
* @return string Number of rows in $table_name.
* Prefixed with ~ if estimated (otherwise exact).
*/
public function get_estimated_row_count($table_name);
/**
* Run LOWER() on DB column of type text (i.e. neither varchar nor char).
*
* @param string $column_name The column name to use
* @return string A SQL statement like "LOWER($column_name)"
*/
public function sql_lower_text($column_name);
/**
* Display sql error page
*
* @param string $sql The SQL query causing the error
* @return mixed Returns the full error message, if $this->return_on_error
* is set, null otherwise
*/
public function sql_error($sql = '');
/**
* Returns whether results of a query need to be buffered to run a
* transaction while iterating over them.
*
* @return bool Whether buffering is required.
*/
public function sql_buffer_nested_transactions();
/**
* Run binary OR operator on DB column.
*
* @param string $column_name The column name to use
* @param int $bit The value to use for the OR operator,
* will be converted to (1 << $bit). Is used by options,
* using the number schema... 0, 1, 2...29
* @param string $compare Any custom SQL code after the check (e.g. "= 0")
* @return string A SQL statement like "$column | (1 << $bit) {$compare}"
*/
public function sql_bit_or($column_name, $bit, $compare = '');
/**
* Version information about used database
*
* @param bool $raw Only return the fetched sql_server_version
* @param bool $use_cache Is it safe to retrieve the value from the cache
* @return string sql server version
*/
public function sql_server_info($raw = false, $use_cache = true);
/**
* Return on error or display error message
*
* @param bool $fail Should we return on errors, or stop
* @return null
*/
public function sql_return_on_error($fail = false);
/**
* Build sql statement from an array
*
* @param string $query Should be on of the following strings:
* INSERT, INSERT_SELECT, UPDATE, SELECT, DELETE
* @param array $assoc_ary Array with "column => value" pairs
* @return string A SQL statement like "c1 = 'a' AND c2 = 'b'"
*/
public function sql_build_array($query, $assoc_ary = array());
/**
* Fetch all rows
*
* @param mixed $query_id Already executed query to get the rows from,
* if false, the last query will be used.
* @return mixed Nested array if the query had rows, false otherwise
*/
public function sql_fetchrowset($query_id = false);
/**
* SQL Transaction
*
* @param string $status Should be one of the following strings:
* begin, commit, rollback
* @return mixed Buffered, seekable result handle, false on error
*/
public function sql_transaction($status = 'begin');
/**
* Build a concatenated expression
*
* @param string $expr1 Base SQL expression where we append the second one
* @param string $expr2 SQL expression that is appended to the first expression
* @return string Concatenated string
*/
public function sql_concatenate($expr1, $expr2);
/**
* Build a case expression
*
* Note: The two statements action_true and action_false must have the same
* data type (int, vchar, ...) in the database!
*
* @param string $condition The condition which must be true,
* to use action_true rather then action_else
* @param string $action_true SQL expression that is used, if the condition is true
* @param mixed $action_false SQL expression that is used, if the condition is false
* @return string CASE expression including the condition and statements
*/
public function sql_case($condition, $action_true, $action_false = false);
/**
* Build sql statement from array for select and select distinct statements
*
* Possible query values: SELECT, SELECT_DISTINCT
*
* @param string $query Should be one of: SELECT, SELECT_DISTINCT
* @param array $array Array with the query data:
* SELECT A comma imploded list of columns to select
* FROM Array with "table => alias" pairs,
* (alias can also be an array)
* Optional: LEFT_JOIN Array of join entries:
* FROM Table that should be joined
* ON Condition for the join
* Optional: WHERE Where SQL statement
* Optional: GROUP_BY Group by SQL statement
* Optional: ORDER_BY Order by SQL statement
* @return string A SQL statement ready for execution
*/
public function sql_build_query($query, $array);
/**
* Fetch field
* if rownum is false, the current row is used, else it is pointing to the row (zero-based)
*
* @param string $field Name of the column
* @param mixed $rownum Row number, if false the current row will be used
* and the row curser will point to the next row
* Note: $rownum is 0 based
* @param mixed $query_id Already executed query to get the rows from,
* if false, the last query will be used.
* @return mixed String value of the field in the selected row,
* false, if the row does not exist
*/
public function sql_fetchfield($field, $rownum = false, $query_id = false);
/**
* Fetch current row
*
* @param mixed $query_id Already executed query to get the rows from,
* if false, the last query will be used.
* @return mixed Array with the current row,
* false, if the row does not exist
*/
public function sql_fetchrow($query_id = false);
/**
* Returns SQL string to cast a string expression to an int.
*
* @param string $expression An expression evaluating to string
* @return string Expression returning an int
*/
public function cast_expr_to_bigint($expression);
/**
* Get last inserted id after insert statement
*
* @return string Autoincrement value of the last inserted row
*/
public function sql_nextid();
/**
* Add to query count
*
* @param bool $cached Is this query cached?
* @return null
*/
public function sql_add_num_queries($cached = false);
/**
* Build LIMIT query
*
* @param string $query The SQL query to execute
* @param int $total The number of rows to select
* @param int $offset
* @param int $cache_ttl Either 0 to avoid caching or
* the time in seconds which the result shall be kept in cache
* @return mixed Buffered, seekable result handle, false on error
*/
public function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0);
/**
* Base query method
*
* @param string $query The SQL query to execute
* @param int $cache_ttl Either 0 to avoid caching or
* the time in seconds which the result shall be kept in cache
* @return mixed Buffered, seekable result handle, false on error
*/
public function sql_query($query = '', $cache_ttl = 0);
/**
* Returns SQL string to cast an integer expression to a string.
*
* @param string $expression An expression evaluating to int
* @return string Expression returning a string
*/
public function cast_expr_to_string($expression);
/**
* Connect to server
*
* @param string $sqlserver Address of the database server
* @param string $sqluser User name of the SQL user
* @param string $sqlpassword Password of the SQL user
* @param string $database Name of the database
* @param mixed $port Port of the database server
* @param bool $persistency
* @param bool $new_link Should a new connection be established
* @return mixed Connection ID on success, string error message otherwise
*/
public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false);
/**
* Run binary AND operator on DB column.
* Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}"
*
* @param string $column_name The column name to use
* @param int $bit The value to use for the AND operator,
* will be converted to (1 << $bit). Is used by
* options, using the number schema: 0, 1, 2...29
* @param string $compare Any custom SQL code after the check (for example "= 0")
* @return string A SQL statement like: "{$column} & (1 << {$bit}) {$compare}"
*/
public function sql_bit_and($column_name, $bit, $compare = '');
/**
* Free sql result
*
* @param mixed $query_id Already executed query result,
* if false, the last query will be used.
* @return null
*/
public function sql_freeresult($query_id = false);
/**
* Return number of sql queries and cached sql queries used
*
* @param bool $cached Should we return the number of cached or normal queries?
* @return int Number of queries that have been executed
*/
public function sql_num_queries($cached = false);
/**
* Run more than one insert statement.
*
* @param string $table Table name to run the statements on
* @param array $sql_ary Multi-dimensional array holding the statement data
* @return bool false if no statements were executed.
*/
public function sql_multi_insert($table, $sql_ary);
/**
* Return number of affected rows
*
* @return mixed Number of the affected rows by the last query
* false if no query has been run before
*/
public function sql_affectedrows();
/**
* DBAL garbage collection, close SQL connection
*
* @return mixed False if no connection was opened before,
* Server response otherwise
*/
public function sql_close();
/**
* Seek to given row number
*
* @param mixed $rownum Row number the curser should point to
* Note: $rownum is 0 based
* @param mixed $query_id ID of the query to set the row cursor on
* if false, the last query will be used.
* $query_id will then be set correctly
* @return bool False if something went wrong
*/
public function sql_rowseek($rownum, &$query_id);
/**
* Escape string used in sql query
*
* @param string $msg String to be escaped
* @return string Escaped version of $msg
*/
public function sql_escape($msg);
/**
* Correctly adjust LIKE expression for special characters
* Some DBMS are handling them in a different way
*
* @param string $expression The expression to use. Every wildcard is
* escaped, except $this->any_char and $this->one_char
* @return string A SQL statement like: "LIKE 'bertie_%'"
*/
public function sql_like_expression($expression);
/**
* Correctly adjust NOT LIKE expression for special characters
* Some DBMS are handling them in a different way
*
* @param string $expression The expression to use. Every wildcard is
* escaped, except $this->any_char and $this->one_char
* @return string A SQL statement like: "NOT LIKE 'bertie_%'"
*/
public function sql_not_like_expression($expression);
/**
* Explain queries
*
* @param string $mode Available modes: display, start, stop,
* add_select_row, fromcache, record_fromcache
* @param string $query The Query that should be explained
* @return mixed Either a full HTML page, boolean or null
*/
public function sql_report($mode, $query = '');
/**
* Build IN or NOT IN sql comparison string, uses <> or = on single element
* arrays to improve comparison speed
*
* @param string $field Name of the sql column that shall be compared
* @param array $array Array of values that are (not) allowed
* @param bool $negate true for NOT IN (), false for IN ()
* @param bool $allow_empty_set If true, allow $array to be empty,
* this function will return 1=1 or 1=0 then.
* @return string A SQL statement like: "IN (1, 2, 3, 4)" or "= 1"
*/
public function sql_in_set($field, $array, $negate = false, $allow_empty_set = false);
}

View File

@@ -0,0 +1,459 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
use \Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Database Abstraction Layer
*/
class factory implements driver_interface
{
/**
* @var driver_interface
*/
protected $driver = null;
/**
* @var ContainerInterface
*/
protected $container;
/**
* Constructor.
*
* @param ContainerInterface $container A ContainerInterface instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Return the current driver (and retrieved it from the container if necessary)
*
* @return driver_interface
*/
protected function get_driver()
{
if ($this->driver === null)
{
$this->driver = $this->container->get('dbal.conn.driver');
}
return $this->driver;
}
/**
* Set the current driver
*
* @param driver_interface $driver
*/
public function set_driver(driver_interface $driver)
{
$this->driver = $driver;
}
/**
* {@inheritdoc}
*/
public function set_debug_load_time($value)
{
$this->get_driver()->set_debug_load_time($value);
}
/**
* {@inheritdoc}
*/
public function set_debug_sql_explain($value)
{
$this->get_driver()->set_debug_sql_explain($value);
}
/**
* {@inheritdoc}
*/
public function get_sql_layer()
{
return $this->get_driver()->get_sql_layer();
}
/**
* {@inheritdoc}
*/
public function get_db_name()
{
return $this->get_driver()->get_db_name();
}
/**
* {@inheritdoc}
*/
public function get_any_char()
{
return $this->get_driver()->get_any_char();
}
/**
* {@inheritdoc}
*/
public function get_one_char()
{
return $this->get_driver()->get_one_char();
}
/**
* {@inheritdoc}
*/
public function get_db_connect_id()
{
return $this->get_driver()->get_db_connect_id();
}
/**
* {@inheritdoc}
*/
public function get_sql_error_triggered()
{
return $this->get_driver()->get_sql_error_triggered();
}
/**
* {@inheritdoc}
*/
public function get_sql_error_sql()
{
return $this->get_driver()->get_sql_error_sql();
}
/**
* {@inheritdoc}
*/
public function get_transaction()
{
return $this->get_driver()->get_transaction();
}
/**
* {@inheritdoc}
*/
public function get_sql_time()
{
return $this->get_driver()->get_sql_time();
}
/**
* {@inheritdoc}
*/
public function get_sql_error_returned()
{
return $this->get_driver()->get_sql_error_returned();
}
/**
* {@inheritdoc}
*/
public function get_multi_insert()
{
return $this->get_driver()->get_multi_insert();
}
/**
* {@inheritdoc}
*/
public function set_multi_insert($multi_insert)
{
$this->get_driver()->set_multi_insert($multi_insert);
}
/**
* {@inheritdoc}
*/
public function get_row_count($table_name)
{
return $this->get_driver()->get_row_count($table_name);
}
/**
* {@inheritdoc}
*/
public function get_estimated_row_count($table_name)
{
return $this->get_driver()->get_estimated_row_count($table_name);
}
/**
* {@inheritdoc}
*/
public function sql_lower_text($column_name)
{
return $this->get_driver()->sql_lower_text($column_name);
}
/**
* {@inheritdoc}
*/
public function sql_error($sql = '')
{
return $this->get_driver()->sql_error($sql);
}
/**
* {@inheritdoc}
*/
public function sql_buffer_nested_transactions()
{
return $this->get_driver()->sql_buffer_nested_transactions();
}
/**
* {@inheritdoc}
*/
public function sql_bit_or($column_name, $bit, $compare = '')
{
return $this->get_driver()->sql_bit_or($column_name, $bit, $compare);
}
/**
* {@inheritdoc}
*/
public function sql_server_info($raw = false, $use_cache = true)
{
return $this->get_driver()->sql_server_info($raw, $use_cache);
}
/**
* {@inheritdoc}
*/
public function sql_return_on_error($fail = false)
{
return $this->get_driver()->sql_return_on_error($fail);
}
/**
* {@inheritdoc}
*/
public function sql_build_array($query, $assoc_ary = array())
{
return $this->get_driver()->sql_build_array($query, $assoc_ary);
}
/**
* {@inheritdoc}
*/
public function sql_fetchrowset($query_id = false)
{
return $this->get_driver()->sql_fetchrowset($query_id);
}
/**
* {@inheritdoc}
*/
public function sql_transaction($status = 'begin')
{
return $this->get_driver()->sql_transaction($status);
}
/**
* {@inheritdoc}
*/
public function sql_concatenate($expr1, $expr2)
{
return $this->get_driver()->sql_concatenate($expr1, $expr2);
}
/**
* {@inheritdoc}
*/
public function sql_case($condition, $action_true, $action_false = false)
{
return $this->get_driver()->sql_case($condition, $action_true, $action_false);
}
/**
* {@inheritdoc}
*/
public function sql_build_query($query, $array)
{
return $this->get_driver()->sql_build_query($query, $array);
}
/**
* {@inheritdoc}
*/
public function sql_fetchfield($field, $rownum = false, $query_id = false)
{
return $this->get_driver()->sql_fetchfield($field, $rownum, $query_id);
}
/**
* {@inheritdoc}
*/
public function sql_fetchrow($query_id = false)
{
return $this->get_driver()->sql_fetchrow($query_id);
}
/**
* {@inheritdoc}
*/
public function cast_expr_to_bigint($expression)
{
return $this->get_driver()->cast_expr_to_bigint($expression);
}
/**
* {@inheritdoc}
*/
public function sql_nextid()
{
return $this->get_driver()->sql_nextid();
}
/**
* {@inheritdoc}
*/
public function sql_add_num_queries($cached = false)
{
return $this->get_driver()->sql_add_num_queries($cached);
}
/**
* {@inheritdoc}
*/
public function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
return $this->get_driver()->sql_query_limit($query, $total, $offset, $cache_ttl);
}
/**
* {@inheritdoc}
*/
public function sql_query($query = '', $cache_ttl = 0)
{
return $this->get_driver()->sql_query($query, $cache_ttl);
}
/**
* {@inheritdoc}
*/
public function cast_expr_to_string($expression)
{
return $this->get_driver()->cast_expr_to_string($expression);
}
/**
* {@inheritdoc}
*/
public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
throw new \Exception('Disabled method.');
}
/**
* {@inheritdoc}
*/
public function sql_bit_and($column_name, $bit, $compare = '')
{
return $this->get_driver()->sql_bit_and($column_name, $bit, $compare);
}
/**
* {@inheritdoc}
*/
public function sql_freeresult($query_id = false)
{
return $this->get_driver()->sql_freeresult($query_id);
}
/**
* {@inheritdoc}
*/
public function sql_num_queries($cached = false)
{
return $this->get_driver()->sql_num_queries($cached);
}
/**
* {@inheritdoc}
*/
public function sql_multi_insert($table, $sql_ary)
{
return $this->get_driver()->sql_multi_insert($table, $sql_ary);
}
/**
* {@inheritdoc}
*/
public function sql_affectedrows()
{
return $this->get_driver()->sql_affectedrows();
}
/**
* {@inheritdoc}
*/
public function sql_close()
{
return $this->get_driver()->sql_close();
}
/**
* {@inheritdoc}
*/
public function sql_rowseek($rownum, &$query_id)
{
return $this->get_driver()->sql_rowseek($rownum, $query_id);
}
/**
* {@inheritdoc}
*/
public function sql_escape($msg)
{
return $this->get_driver()->sql_escape($msg);
}
/**
* {@inheritdoc}
*/
public function sql_like_expression($expression)
{
return $this->get_driver()->sql_like_expression($expression);
}
/**
* {@inheritdoc}
*/
public function sql_not_like_expression($expression)
{
return $this->get_driver()->sql_not_like_expression($expression);
}
/**
* {@inheritdoc}
*/
public function sql_report($mode, $query = '')
{
return $this->get_driver()->sql_report($mode, $query);
}
/**
* {@inheritdoc}
*/
public function sql_in_set($field, $array, $negate = false, $allow_empty_set = false)
{
return $this->get_driver()->sql_in_set($field, $array, $negate, $allow_empty_set);
}
}

View File

@@ -0,0 +1,384 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
/**
* Unified ODBC functions
* Unified ODBC functions support any database having ODBC driver, for example Adabas D, IBM DB2, iODBC, Solid, Sybase SQL Anywhere...
* Here we only support MSSQL Server 2000+ because of the provided schema
*
* @note number of bytes returned for returning data depends on odbc.defaultlrl php.ini setting.
* If it is limited to 4K for example only 4K of data is returned max, resulting in incomplete theme data for example.
* @note odbc.defaultbinmode may affect UTF8 characters
*/
class mssql_odbc extends \phpbb\db\driver\mssql_base
{
var $last_query_text = '';
var $connect_error = '';
/**
* {@inheritDoc}
*/
function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
$this->persistency = $persistency;
$this->user = $sqluser;
$this->dbname = $database;
$port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':';
$this->server = $sqlserver . (($port) ? $port_delimiter . $port : '');
$max_size = @ini_get('odbc.defaultlrl');
if (!empty($max_size))
{
$unit = strtolower(substr($max_size, -1, 1));
$max_size = (int) $max_size;
if ($unit == 'k')
{
$max_size = floor($max_size / 1024);
}
else if ($unit == 'g')
{
$max_size *= 1024;
}
else if (is_numeric($unit))
{
$max_size = floor((int) ($max_size . $unit) / 1048576);
}
$max_size = max(8, $max_size) . 'M';
@ini_set('odbc.defaultlrl', $max_size);
}
if ($this->persistency)
{
if (!function_exists('odbc_pconnect'))
{
$this->connect_error = 'odbc_pconnect function does not exist, is odbc extension installed?';
return $this->sql_error('');
}
$this->db_connect_id = @odbc_pconnect($this->server, $this->user, $sqlpassword);
}
else
{
if (!function_exists('odbc_connect'))
{
$this->connect_error = 'odbc_connect function does not exist, is odbc extension installed?';
return $this->sql_error('');
}
$this->db_connect_id = @odbc_connect($this->server, $this->user, $sqlpassword);
}
return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
}
/**
* {@inheritDoc}
*/
function sql_server_info($raw = false, $use_cache = true)
{
global $cache;
if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false)
{
$result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')");
$row = false;
if ($result_id)
{
$row = odbc_fetch_array($result_id);
odbc_free_result($result_id);
}
$this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0;
if (!empty($cache) && $use_cache)
{
$cache->put('mssqlodbc_version', $this->sql_server_version);
}
}
if ($raw)
{
return $this->sql_server_version;
}
return ($this->sql_server_version) ? 'MSSQL (ODBC)<br />' . $this->sql_server_version : 'MSSQL (ODBC)';
}
/**
* SQL Transaction
* @access private
*/
function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION');
break;
case 'commit':
return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION');
break;
case 'rollback':
return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION');
break;
}
return true;
}
/**
* {@inheritDoc}
*/
function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->last_query_text = $query;
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false)
{
$this->sql_error($query);
}
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* Build LIMIT query
*/
function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
$this->query_result = false;
// Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows)
if ($total)
{
// We need to grab the total number of rows + the offset number of rows to get the correct result
if (strpos($query, 'SELECT DISTINCT') === 0)
{
$query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15);
}
else
{
$query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6);
}
}
$result = $this->sql_query($query, $cache_ttl);
// Seek by $offset rows
if ($offset)
{
$this->sql_rowseek($offset, $result);
}
return $result;
}
/**
* {@inheritDoc}
*/
function sql_affectedrows()
{
return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false;
}
/**
* {@inheritDoc}
*/
function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
return ($query_id) ? odbc_fetch_array($query_id) : false;
}
/**
* {@inheritDoc}
*/
function sql_nextid()
{
$result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY');
if ($result_id)
{
if (odbc_fetch_array($result_id))
{
$id = odbc_result($result_id, 1);
odbc_free_result($result_id);
return $id;
}
odbc_free_result($result_id);
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if (isset($this->open_queries[(int) $query_id]))
{
unset($this->open_queries[(int) $query_id]);
return odbc_free_result($query_id);
}
return false;
}
/**
* return sql error array
* @access private
*/
function _sql_error()
{
if (function_exists('odbc_errormsg'))
{
$error = array(
'message' => @odbc_errormsg(),
'code' => @odbc_error(),
);
}
else
{
$error = array(
'message' => $this->connect_error,
'code' => '',
);
}
return $error;
}
/**
* Close sql connection
* @access private
*/
function _sql_close()
{
return @odbc_close($this->db_connect_id);
}
/**
* Build db-specific report
* @access private
*/
function _sql_report($mode, $query = '')
{
switch ($mode)
{
case 'start':
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = @odbc_exec($this->db_connect_id, $query);
if ($result)
{
while ($void = odbc_fetch_array($result))
{
// Take the time spent on parsing rows into account
}
odbc_free_result($result);
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
}

View File

@@ -0,0 +1,450 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
/**
* This is the MS SQL Server Native database abstraction layer.
* PHP mssql native driver required.
* @author Chris Pucci
*
*/
namespace phpbb\db\driver;
class mssqlnative extends \phpbb\db\driver\mssql_base
{
var $m_insert_id = null;
var $last_query_text = '';
var $query_options = array();
var $connect_error = '';
/**
* {@inheritDoc}
*/
function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
// Test for driver support, to avoid suppressed fatal error
if (!function_exists('sqlsrv_connect'))
{
$this->connect_error = 'Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx';
return $this->sql_error('');
}
//set up connection variables
$this->persistency = $persistency;
$this->user = $sqluser;
$this->dbname = $database;
$port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':';
$this->server = $sqlserver . (($port) ? $port_delimiter . $port : '');
//connect to database
$this->db_connect_id = sqlsrv_connect($this->server, array(
'Database' => $this->dbname,
'UID' => $this->user,
'PWD' => $sqlpassword,
'CharacterSet' => 'UTF-8'
));
return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
}
/**
* {@inheritDoc}
*/
function sql_server_info($raw = false, $use_cache = true)
{
global $cache;
if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false)
{
$arr_server_info = sqlsrv_server_info($this->db_connect_id);
$this->sql_server_version = $arr_server_info['SQLServerVersion'];
if (!empty($cache) && $use_cache)
{
$cache->put('mssql_version', $this->sql_server_version);
}
}
if ($raw)
{
return $this->sql_server_version;
}
return ($this->sql_server_version) ? 'MSSQL<br />' . $this->sql_server_version : 'MSSQL';
}
/**
* {@inheritDoc}
*/
function sql_buffer_nested_transactions()
{
return true;
}
/**
* SQL Transaction
* @access private
*/
function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return sqlsrv_begin_transaction($this->db_connect_id);
break;
case 'commit':
return sqlsrv_commit($this->db_connect_id);
break;
case 'rollback':
return sqlsrv_rollback($this->db_connect_id);
break;
}
return true;
}
/**
* {@inheritDoc}
*/
function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->last_query_text = $query;
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false)
{
$this->sql_error($query);
}
// reset options for next query
$this->query_options = array();
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* Build LIMIT query
*/
function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
$this->query_result = false;
// total == 0 means all results - not zero results
if ($offset == 0 && $total !== 0)
{
if (strpos($query, "SELECT") === false)
{
$query = "TOP {$total} " . $query;
}
else
{
$query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query);
}
}
else if ($offset > 0)
{
$query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query);
$query = 'SELECT *
FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3
FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3';
if ($total > 0)
{
$query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total);
}
else
{
$query .= ' WHERE line3 > ' . $offset;
}
}
$result = $this->sql_query($query, $cache_ttl);
return $result;
}
/**
* {@inheritDoc}
*/
function sql_affectedrows()
{
return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->query_result) : false;
}
/**
* {@inheritDoc}
*/
function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
if (!$query_id)
{
return false;
}
$row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC);
if ($row)
{
foreach ($row as $key => $value)
{
$row[$key] = ($value === ' ' || $value === null) ? '' : $value;
}
// remove helper values from LIMIT queries
if (isset($row['line2']))
{
unset($row['line2'], $row['line3']);
}
}
return ($row !== null) ? $row : false;
}
/**
* {@inheritDoc}
*/
function sql_nextid()
{
$result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY');
if ($result_id)
{
$row = sqlsrv_fetch_array($result_id);
$id = $row[0];
sqlsrv_free_stmt($result_id);
return $id;
}
else
{
return false;
}
}
/**
* {@inheritDoc}
*/
function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if (isset($this->open_queries[(int) $query_id]))
{
unset($this->open_queries[(int) $query_id]);
return sqlsrv_free_stmt($query_id);
}
return false;
}
/**
* return sql error array
* @access private
*/
function _sql_error()
{
if (function_exists('sqlsrv_errors'))
{
$errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS);
$error_message = '';
$code = 0;
if ($errors != null)
{
foreach ($errors as $error)
{
$error_message .= "SQLSTATE: " . $error['SQLSTATE'] . "\n";
$error_message .= "code: " . $error['code'] . "\n";
$code = $error['code'];
$error_message .= "message: " . $error['message'] . "\n";
}
$this->last_error_result = $error_message;
$error = $this->last_error_result;
}
else
{
$error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array();
}
$error = array(
'message' => $error,
'code' => $code,
);
}
else
{
$error = array(
'message' => $this->connect_error,
'code' => '',
);
}
return $error;
}
/**
* Close sql connection
* @access private
*/
function _sql_close()
{
return @sqlsrv_close($this->db_connect_id);
}
/**
* Build db-specific report
* @access private
*/
function _sql_report($mode, $query = '')
{
switch ($mode)
{
case 'start':
$html_table = false;
@sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;');
if ($result = @sqlsrv_query($this->db_connect_id, $query))
{
sqlsrv_next_result($result);
while ($row = sqlsrv_fetch_array($result))
{
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
sqlsrv_free_stmt($result);
}
@sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;');
if ($html_table)
{
$this->html_hold .= '</table>';
}
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = @sqlsrv_query($this->db_connect_id, $query);
if ($result)
{
while ($void = sqlsrv_fetch_array($result))
{
// Take the time spent on parsing rows into account
}
sqlsrv_free_stmt($result);
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
/**
* Utility method used to retrieve number of rows
* Emulates mysql_num_rows
* Used in acp_database.php -> write_data_mssqlnative()
* Requires a static or keyset cursor to be definde via
* mssqlnative_set_query_options()
*/
function mssqlnative_num_rows($res)
{
if ($res !== false)
{
return sqlsrv_num_rows($res);
}
else
{
return false;
}
}
/**
* Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter.
*/
function mssqlnative_set_query_options($options)
{
$this->query_options = $options;
}
}

View File

@@ -0,0 +1,492 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
/**
* MySQLi Database Abstraction Layer
* mysqli-extension has to be compiled with:
* MySQL 4.1+ or MySQL 5.0+
*/
class mysqli extends \phpbb\db\driver\mysql_base
{
var $multi_insert = true;
var $connect_error = '';
/**
* {@inheritDoc}
*/
function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
if (!function_exists('mysqli_connect'))
{
$this->connect_error = 'mysqli_connect function does not exist, is mysqli extension installed?';
return $this->sql_error('');
}
$this->persistency = $persistency;
$this->user = $sqluser;
// If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix
$this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver;
$this->dbname = $database;
$port = (!$port) ? null : $port;
// If port is set and it is not numeric, most likely mysqli socket is set.
// Try to map it to the $socket parameter.
$socket = null;
if ($port)
{
if (is_numeric($port))
{
$port = (int) $port;
}
else
{
$socket = $port;
$port = null;
}
}
$this->db_connect_id = mysqli_init();
if (!@mysqli_real_connect($this->db_connect_id, $this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket, MYSQLI_CLIENT_FOUND_ROWS))
{
$this->db_connect_id = '';
}
if ($this->db_connect_id && $this->dbname != '')
{
// Disable loading local files on client side
@mysqli_options($this->db_connect_id, MYSQLI_OPT_LOCAL_INFILE, false);
@mysqli_query($this->db_connect_id, "SET NAMES 'utf8'");
// enforce strict mode on databases that support it
if (version_compare($this->sql_server_info(true), '5.0.2', '>='))
{
$result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode');
if ($result)
{
$row = mysqli_fetch_assoc($result);
mysqli_free_result($result);
$modes = array_map('trim', explode(',', $row['sql_mode']));
}
else
{
$modes = array();
}
// TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES
if (!in_array('TRADITIONAL', $modes))
{
if (!in_array('STRICT_ALL_TABLES', $modes))
{
$modes[] = 'STRICT_ALL_TABLES';
}
if (!in_array('STRICT_TRANS_TABLES', $modes))
{
$modes[] = 'STRICT_TRANS_TABLES';
}
}
$mode = implode(',', $modes);
@mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'");
}
return $this->db_connect_id;
}
return $this->sql_error('');
}
/**
* {@inheritDoc}
*/
function sql_server_info($raw = false, $use_cache = true)
{
global $cache;
if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false)
{
$result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version');
if ($result)
{
$row = mysqli_fetch_assoc($result);
mysqli_free_result($result);
$this->sql_server_version = $row['version'];
if (!empty($cache) && $use_cache)
{
$cache->put('mysqli_version', $this->sql_server_version);
}
}
}
return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version;
}
/**
* SQL Transaction
* @access private
*/
function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return @mysqli_autocommit($this->db_connect_id, false);
break;
case 'commit':
$result = @mysqli_commit($this->db_connect_id);
@mysqli_autocommit($this->db_connect_id, true);
return $result;
break;
case 'rollback':
$result = @mysqli_rollback($this->db_connect_id);
@mysqli_autocommit($this->db_connect_id, true);
return $result;
break;
}
return true;
}
/**
* {@inheritDoc}
*/
function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false)
{
$this->sql_error($query);
}
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* {@inheritDoc}
*/
function sql_affectedrows()
{
return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false;
}
/**
* {@inheritDoc}
*/
function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
if ($query_id)
{
$result = mysqli_fetch_assoc($query_id);
return $result !== null ? $result : false;
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_rowseek($rownum, &$query_id)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false;
}
/**
* {@inheritDoc}
*/
function sql_nextid()
{
return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false;
}
/**
* {@inheritDoc}
*/
function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if (!$query_id)
{
return false;
}
if ($query_id === true)
{
return true;
}
return mysqli_free_result($query_id);
}
/**
* {@inheritDoc}
*/
function sql_escape($msg)
{
return @mysqli_real_escape_string($this->db_connect_id, $msg);
}
/**
* return sql error array
* @access private
*/
function _sql_error()
{
if ($this->db_connect_id)
{
$error = array(
'message' => @mysqli_error($this->db_connect_id),
'code' => @mysqli_errno($this->db_connect_id)
);
}
else if (function_exists('mysqli_connect_error'))
{
$error = array(
'message' => @mysqli_connect_error(),
'code' => @mysqli_connect_errno(),
);
}
else
{
$error = array(
'message' => $this->connect_error,
'code' => '',
);
}
return $error;
}
/**
* Close sql connection
* @access private
*/
function _sql_close()
{
return @mysqli_close($this->db_connect_id);
}
/**
* Build db-specific report
* @access private
*/
function _sql_report($mode, $query = '')
{
static $test_prof;
// current detection method, might just switch to see the existence of INFORMATION_SCHEMA.PROFILING
if ($test_prof === null)
{
$test_prof = false;
if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false)
{
$ver = mysqli_get_server_version($this->db_connect_id);
if ($ver >= 50037 && $ver < 50100)
{
$test_prof = true;
}
}
}
switch ($mode)
{
case 'start':
$explain_query = $query;
if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
if (preg_match('/^SELECT/', $explain_query))
{
$html_table = false;
// begin profiling
if ($test_prof)
{
@mysqli_query($this->db_connect_id, 'SET profiling = 1;');
}
if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query"))
{
while ($row = mysqli_fetch_assoc($result))
{
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
mysqli_free_result($result);
}
if ($html_table)
{
$this->html_hold .= '</table>';
}
if ($test_prof)
{
$html_table = false;
// get the last profile
if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;'))
{
$this->html_hold .= '<br />';
while ($row = mysqli_fetch_assoc($result))
{
// make <unknown> HTML safe
if (!empty($row['Source_function']))
{
$row['Source_function'] = str_replace(array('<', '>'), array('&lt;', '&gt;'), $row['Source_function']);
}
// remove unsupported features
foreach ($row as $key => $val)
{
if ($val === null)
{
unset($row[$key]);
}
}
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
mysqli_free_result($result);
}
if ($html_table)
{
$this->html_hold .= '</table>';
}
@mysqli_query($this->db_connect_id, 'SET profiling = 0;');
}
}
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = @mysqli_query($this->db_connect_id, $query);
if ($result)
{
while ($void = mysqli_fetch_assoc($result))
{
// Take the time spent on parsing rows into account
}
mysqli_free_result($result);
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
}

View File

@@ -0,0 +1,821 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
/**
* Oracle Database Abstraction Layer
*/
class oracle extends \phpbb\db\driver\driver
{
var $last_query_text = '';
var $connect_error = '';
/**
* {@inheritDoc}
*/
function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
$this->persistency = $persistency;
$this->user = $sqluser;
$this->server = $sqlserver . (($port) ? ':' . $port : '');
$this->dbname = $database;
$connect = $database;
// support for "easy connect naming"
if ($sqlserver !== '' && $sqlserver !== '/')
{
if (substr($sqlserver, -1, 1) == '/')
{
$sqlserver == substr($sqlserver, 0, -1);
}
$connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database;
}
if ($new_link)
{
if (!function_exists('ocinlogon'))
{
$this->connect_error = 'ocinlogon function does not exist, is oci extension installed?';
return $this->sql_error('');
}
$this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8');
}
else if ($this->persistency)
{
if (!function_exists('ociplogon'))
{
$this->connect_error = 'ociplogon function does not exist, is oci extension installed?';
return $this->sql_error('');
}
$this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8');
}
else
{
if (!function_exists('ocilogon'))
{
$this->connect_error = 'ocilogon function does not exist, is oci extension installed?';
return $this->sql_error('');
}
$this->db_connect_id = @ocilogon($this->user, $sqlpassword, $connect, 'UTF8');
}
return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error('');
}
/**
* {@inheritDoc}
*/
function sql_server_info($raw = false, $use_cache = true)
{
/**
* force $use_cache false. I didn't research why the caching code below is commented out
* but I assume its because the Oracle extension provides a direct method to access it
* without a query.
*/
/*
global $cache;
if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false)
{
$result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\'');
@ociexecute($result, OCI_DEFAULT);
@ocicommit($this->db_connect_id);
$row = array();
@ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS);
@ocifreestatement($result);
$this->sql_server_version = trim($row['BANNER']);
$cache->put('oracle_version', $this->sql_server_version);
}
*/
$this->sql_server_version = @ociserverversion($this->db_connect_id);
return $this->sql_server_version;
}
/**
* SQL Transaction
* @access private
*/
function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return true;
break;
case 'commit':
return @ocicommit($this->db_connect_id);
break;
case 'rollback':
return @ocirollback($this->db_connect_id);
break;
}
return true;
}
/**
* Oracle specific code to handle the fact that it does not compare columns properly
* @access private
*/
function _rewrite_col_compare($args)
{
if (count($args) == 4)
{
if ($args[2] == '=')
{
return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))';
}
else if ($args[2] == '<>')
{
// really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P
return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))';
}
}
else
{
return $this->_rewrite_where($args[0]);
}
}
/**
* Oracle specific code to handle it's lack of sanity
* @access private
*/
function _rewrite_where($where_clause)
{
preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER);
$out = '';
foreach ($result as $val)
{
if (!isset($val[5]))
{
if ($val[4] !== "''")
{
$out .= $val[0];
}
else
{
$out .= ' ' . $val[1] . ' ' . $val[2];
if ($val[3] == '=')
{
$out .= ' is NULL';
}
else if ($val[3] == '<>')
{
$out .= ' is NOT NULL';
}
}
}
else
{
$in_clause = array();
$sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1);
$extra = false;
preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER);
$i = 0;
foreach ($sub_vals[0] as $sub_val)
{
// two things:
// 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison
// 2) This fixes the 1000 list limit that Oracle has (ORA-01795)
if ($sub_val !== "''")
{
$in_clause[(int) $i++/1000][] = $sub_val;
}
else
{
$extra = true;
}
}
if (!$extra && $i < 1000)
{
$out .= $val[0];
}
else
{
$out .= ' ' . $val[1] . '(';
$in_array = array();
// constuct each IN() clause
foreach ($in_clause as $in_values)
{
$in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')';
}
// Join the IN() clauses against a few ORs (IN is just a nicer OR anyway)
$out .= implode(' OR ', $in_array);
// handle the empty string case
if ($extra)
{
$out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL';
}
$out .= ')';
unset($in_array, $in_clause);
}
}
}
return $out;
}
/**
* {@inheritDoc}
*/
function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->last_query_text = $query;
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
$in_transaction = false;
if (!$this->transaction)
{
$this->sql_transaction('begin');
}
else
{
$in_transaction = true;
}
$array = array();
// We overcome Oracle's 4000 char limit by binding vars
if (strlen($query) > 4000)
{
if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs))
{
if (strlen($regs[3]) > 4000)
{
$cols = explode(', ', $regs[2]);
preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER);
/* The code inside this comment block breaks clob handling, but does allow the
database restore script to work. If you want to allow no posts longer than 4KB
and/or need the db restore script, uncomment this.
if (count($cols) !== count($vals))
{
// Try to replace some common data we know is from our restore script or from other sources
$regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]);
$_vals = explode(', ', $regs[3]);
$vals = array();
$is_in_val = false;
$i = 0;
$string = '';
foreach ($_vals as $value)
{
if (strpos($value, "'") === false && !$is_in_val)
{
$vals[$i++] = $value;
continue;
}
if (substr($value, -1) === "'")
{
$vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value;
$string = '';
$is_in_val = false;
if ($vals[$i][0] !== "'")
{
$vals[$i] = "''" . $vals[$i];
}
$i++;
continue;
}
else
{
$string .= (($is_in_val) ? ', ' : '') . $value;
$is_in_val = true;
}
}
if ($string)
{
// New value if cols != value
$vals[(count($cols) !== count($vals)) ? $i : $i - 1] .= $string;
}
$vals = array(0 => $vals);
}
*/
$inserts = $vals[0];
unset($vals);
foreach ($inserts as $key => $value)
{
if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2
{
$inserts[$key] = ':' . strtoupper($cols[$key]);
$array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1));
}
}
$query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')';
}
}
else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER))
{
if (strlen($data[0][2]) > 4000)
{
$update = $data[0][1];
$where = $data[0][3];
preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER);
unset($data);
$cols = array();
foreach ($temp as $value)
{
if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2
{
$cols[] = $value[1] . '=:' . strtoupper($value[1]);
$array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1));
}
else
{
$cols[] = $value[1] . '=' . $value[2];
}
}
$query = $update . implode(', ', $cols) . ' ' . $where;
unset($cols);
}
}
}
switch (substr($query, 0, 6))
{
case 'DELETE':
if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs))
{
$query = $regs[1] . $this->_rewrite_where($regs[2]);
unset($regs);
}
break;
case 'UPDATE':
if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs))
{
$query = $regs[1] . $this->_rewrite_where($regs[2]);
unset($regs);
}
break;
case 'SELECT':
$query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query);
break;
}
$this->query_result = @ociparse($this->db_connect_id, $query);
foreach ($array as $key => $value)
{
@ocibindbyname($this->query_result, $key, $array[$key], -1);
}
$success = @ociexecute($this->query_result, OCI_DEFAULT);
if (!$success)
{
$this->sql_error($query);
$this->query_result = false;
}
else
{
if (!$in_transaction)
{
$this->sql_transaction('commit');
}
}
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* Build LIMIT query
*/
function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
$this->query_result = false;
$query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset;
return $this->sql_query($query, $cache_ttl);
}
/**
* {@inheritDoc}
*/
function sql_affectedrows()
{
return ($this->query_result) ? @ocirowcount($this->query_result) : false;
}
/**
* {@inheritDoc}
*/
function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
if ($query_id)
{
$row = array();
$result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS);
if (!$result || !$row)
{
return false;
}
$result_row = array();
foreach ($row as $key => $value)
{
// Oracle treats empty strings as null
if (is_null($value))
{
$value = '';
}
// OCI->CLOB?
if (is_object($value))
{
$value = $value->load();
}
$result_row[strtolower($key)] = $value;
}
return $result_row;
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_rowseek($rownum, &$query_id)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
if (!$query_id)
{
return false;
}
// Reset internal pointer
@ociexecute($query_id, OCI_DEFAULT);
// We do not fetch the row for rownum == 0 because then the next resultset would be the second row
for ($i = 0; $i < $rownum; $i++)
{
if (!$this->sql_fetchrow($query_id))
{
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
function sql_nextid()
{
$query_id = $this->query_result;
if ($query_id !== false && $this->last_query_text != '')
{
if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename))
{
$query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL';
$stmt = @ociparse($this->db_connect_id, $query);
if ($stmt)
{
$success = @ociexecute($stmt, OCI_DEFAULT);
if ($success)
{
$temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS);
ocifreestatement($stmt);
if ($temp_result)
{
return $temp_array['CURRVAL'];
}
else
{
return false;
}
}
}
}
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if (isset($this->open_queries[(int) $query_id]))
{
unset($this->open_queries[(int) $query_id]);
return ocifreestatement($query_id);
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_escape($msg)
{
return str_replace(array("'", "\0"), array("''", ''), $msg);
}
/**
* Build LIKE expression
* @access private
*/
function _sql_like_expression($expression)
{
return $expression . " ESCAPE '\\'";
}
/**
* Build NOT LIKE expression
* @access private
*/
function _sql_not_like_expression($expression)
{
return $expression . " ESCAPE '\\'";
}
function _sql_custom_build($stage, $data)
{
return $data;
}
function _sql_bit_and($column_name, $bit, $compare = '')
{
return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
}
function _sql_bit_or($column_name, $bit, $compare = '')
{
return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : '');
}
/**
* return sql error array
* @access private
*/
function _sql_error()
{
if (function_exists('ocierror'))
{
$error = @ocierror();
$error = (!$error) ? @ocierror($this->query_result) : $error;
$error = (!$error) ? @ocierror($this->db_connect_id) : $error;
if ($error)
{
$this->last_error_result = $error;
}
else
{
$error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array();
}
}
else
{
$error = array(
'message' => $this->connect_error,
'code' => '',
);
}
return $error;
}
/**
* Close sql connection
* @access private
*/
function _sql_close()
{
return @ocilogoff($this->db_connect_id);
}
/**
* Build db-specific report
* @access private
*/
function _sql_report($mode, $query = '')
{
switch ($mode)
{
case 'start':
$html_table = false;
// Grab a plan table, any will do
$sql = "SELECT table_name
FROM USER_TABLES
WHERE table_name LIKE '%PLAN_TABLE%'";
$stmt = ociparse($this->db_connect_id, $sql);
ociexecute($stmt);
$result = array();
if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS))
{
$table = $result['TABLE_NAME'];
// This is the statement_id that will allow us to track the plan
$statement_id = substr(md5($query), 0, 30);
// Remove any stale plans
$stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'");
ociexecute($stmt2);
ocifreestatement($stmt2);
// Explain the plan
$sql = "EXPLAIN PLAN
SET STATEMENT_ID = '$statement_id'
FOR $query";
$stmt2 = ociparse($this->db_connect_id, $sql);
ociexecute($stmt2);
ocifreestatement($stmt2);
// Get the data from the plan
$sql = "SELECT operation, options, object_name, object_type, cardinality, cost
FROM plan_table
START WITH id = 0 AND statement_id = '$statement_id'
CONNECT BY PRIOR id = parent_id
AND statement_id = '$statement_id'";
$stmt2 = ociparse($this->db_connect_id, $sql);
ociexecute($stmt2);
$row = array();
while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS))
{
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
ocifreestatement($stmt2);
// Remove the plan we just made, we delete them on request anyway
$stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'");
ociexecute($stmt2);
ocifreestatement($stmt2);
}
ocifreestatement($stmt);
if ($html_table)
{
$this->html_hold .= '</table>';
}
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = @ociparse($this->db_connect_id, $query);
if ($result)
{
$success = @ociexecute($result, OCI_DEFAULT);
if ($success)
{
$row = array();
while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS))
{
// Take the time spent on parsing rows into account
}
@ocifreestatement($result);
}
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
}

View File

@@ -0,0 +1,500 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
/**
* PostgreSQL Database Abstraction Layer
* Minimum Requirement is Version 8.3+
*/
class postgres extends \phpbb\db\driver\driver
{
var $multi_insert = true;
var $last_query_text = '';
var $connect_error = '';
/**
* {@inheritDoc}
*/
function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
$connect_string = '';
if ($sqluser)
{
$connect_string .= "user=$sqluser ";
}
if ($sqlpassword)
{
$connect_string .= "password=$sqlpassword ";
}
if ($sqlserver)
{
// $sqlserver can carry a port separated by : for compatibility reasons
// If $sqlserver has more than one : it's probably an IPv6 address.
// In this case we only allow passing a port via the $port variable.
if (substr_count($sqlserver, ':') === 1)
{
list($sqlserver, $port) = explode(':', $sqlserver);
}
if ($sqlserver !== 'localhost')
{
$connect_string .= "host=$sqlserver ";
}
if ($port)
{
$connect_string .= "port=$port ";
}
}
$schema = '';
if ($database)
{
$this->dbname = $database;
if (strpos($database, '.') !== false)
{
list($database, $schema) = explode('.', $database);
}
$connect_string .= "dbname=$database";
}
$this->persistency = $persistency;
if ($this->persistency)
{
if (!function_exists('pg_pconnect'))
{
$this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?';
return $this->sql_error('');
}
$collector = new \phpbb\error_collector;
$collector->install();
$this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW);
}
else
{
if (!function_exists('pg_connect'))
{
$this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?';
return $this->sql_error('');
}
$collector = new \phpbb\error_collector;
$collector->install();
$this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW);
}
$collector->uninstall();
if ($this->db_connect_id)
{
if ($schema !== '')
{
@pg_query($this->db_connect_id, 'SET search_path TO ' . $schema);
}
return $this->db_connect_id;
}
$this->connect_error = $collector->format_errors();
return $this->sql_error('');
}
/**
* {@inheritDoc}
*/
function sql_server_info($raw = false, $use_cache = true)
{
global $cache;
if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false)
{
$query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version');
if ($query_id)
{
$row = pg_fetch_assoc($query_id, null);
pg_free_result($query_id);
$this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0;
if (!empty($cache) && $use_cache)
{
$cache->put('pgsql_version', $this->sql_server_version);
}
}
}
return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version;
}
/**
* SQL Transaction
* @access private
*/
function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return @pg_query($this->db_connect_id, 'BEGIN');
break;
case 'commit':
return @pg_query($this->db_connect_id, 'COMMIT');
break;
case 'rollback':
return @pg_query($this->db_connect_id, 'ROLLBACK');
break;
}
return true;
}
/**
* {@inheritDoc}
*/
function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->last_query_text = $query;
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false)
{
$this->sql_error($query);
}
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
else if (strpos($query, 'SELECT') === 0)
{
$this->open_queries[(int) $this->query_result] = $this->query_result;
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* Build db-specific query data
* @access private
*/
function _sql_custom_build($stage, $data)
{
return $data;
}
/**
* Build LIMIT query
*/
function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
$this->query_result = false;
// if $total is set to 0 we do not want to limit the number of rows
if ($total == 0)
{
$total = 'ALL';
}
$query .= "\n LIMIT $total OFFSET $offset";
return $this->sql_query($query, $cache_ttl);
}
/**
* {@inheritDoc}
*/
function sql_affectedrows()
{
return ($this->query_result) ? @pg_affected_rows($this->query_result) : false;
}
/**
* {@inheritDoc}
*/
function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
return ($query_id) ? pg_fetch_assoc($query_id, null) : false;
}
/**
* {@inheritDoc}
*/
function sql_rowseek($rownum, &$query_id)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && $cache->sql_exists($query_id))
{
return $cache->sql_rowseek($rownum, $query_id);
}
return ($query_id) ? @pg_result_seek($query_id, $rownum) : false;
}
/**
* {@inheritDoc}
*/
function sql_nextid()
{
$query_id = $this->query_result;
if ($query_id !== false && $this->last_query_text != '')
{
if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename))
{
$query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value";
$temp_q_id = @pg_query($this->db_connect_id, $query);
if (!$temp_q_id)
{
return false;
}
$temp_result = pg_fetch_assoc($temp_q_id, null);
pg_free_result($query_id);
return ($temp_result) ? $temp_result['last_value'] : false;
}
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if (isset($this->open_queries[(int) $query_id]))
{
unset($this->open_queries[(int) $query_id]);
return pg_free_result($query_id);
}
return false;
}
/**
* {@inheritDoc}
*/
function sql_escape($msg)
{
return @pg_escape_string($msg);
}
/**
* Build LIKE expression
* @access private
*/
function _sql_like_expression($expression)
{
return $expression;
}
/**
* Build NOT LIKE expression
* @access private
*/
function _sql_not_like_expression($expression)
{
return $expression;
}
/**
* {@inheritDoc}
*/
function cast_expr_to_bigint($expression)
{
return 'CAST(' . $expression . ' as DECIMAL(255, 0))';
}
/**
* {@inheritDoc}
*/
function cast_expr_to_string($expression)
{
return 'CAST(' . $expression . ' as VARCHAR(255))';
}
/**
* return sql error array
* @access private
*/
function _sql_error()
{
// pg_last_error only works when there is an established connection.
// Connection errors have to be tracked by us manually.
if ($this->db_connect_id)
{
$message = @pg_last_error($this->db_connect_id);
}
else
{
$message = $this->connect_error;
}
return array(
'message' => $message,
'code' => ''
);
}
/**
* Close sql connection
* @access private
*/
function _sql_close()
{
return @pg_close($this->db_connect_id);
}
/**
* Build db-specific report
* @access private
*/
function _sql_report($mode, $query = '')
{
switch ($mode)
{
case 'start':
$explain_query = $query;
if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
if (preg_match('/^SELECT/', $explain_query))
{
$html_table = false;
if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query"))
{
while ($row = pg_fetch_assoc($result, null))
{
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
pg_free_result($result);
}
if ($html_table)
{
$this->html_hold .= '</table>';
}
}
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = @pg_query($this->db_connect_id, $query);
if ($result)
{
while ($void = pg_fetch_assoc($result, null))
{
// Take the time spent on parsing rows into account
}
pg_free_result($result);
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
}

View File

@@ -0,0 +1,430 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\driver;
/**
* SQLite3 Database Abstraction Layer
* Minimum Requirement: 3.6.15+
*/
class sqlite3 extends \phpbb\db\driver\driver
{
/**
* @var string Stores errors during connection setup in case the driver is not available
*/
protected $connect_error = '';
/**
* @var \SQLite3 The SQLite3 database object to operate against
*/
protected $dbo = null;
/**
* {@inheritDoc}
*/
public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false)
{
$this->persistency = false;
$this->user = $sqluser;
$this->server = $sqlserver . (($port) ? ':' . $port : '');
$this->dbname = $database;
if (!class_exists('SQLite3', false))
{
$this->connect_error = 'SQLite3 not found, is the extension installed?';
return $this->sql_error('');
}
try
{
$this->dbo = new \SQLite3($this->server, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
$this->dbo->busyTimeout(60000);
$this->db_connect_id = true;
}
catch (\Exception $e)
{
$this->connect_error = $e->getMessage();
return array('message' => $this->connect_error);
}
return true;
}
/**
* {@inheritDoc}
*/
public function sql_server_info($raw = false, $use_cache = true)
{
global $cache;
if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false)
{
$version = \SQLite3::version();
$this->sql_server_version = $version['versionString'];
if (!empty($cache) && $use_cache)
{
$cache->put('sqlite_version', $this->sql_server_version);
}
}
return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version;
}
/**
* SQL Transaction
*
* @param string $status Should be one of the following strings:
* begin, commit, rollback
* @return bool Success/failure of the transaction query
*/
protected function _sql_transaction($status = 'begin')
{
switch ($status)
{
case 'begin':
return $this->dbo->exec('BEGIN IMMEDIATE');
break;
case 'commit':
return $this->dbo->exec('COMMIT');
break;
case 'rollback':
return @$this->dbo->exec('ROLLBACK');
break;
}
return true;
}
/**
* {@inheritDoc}
*/
public function sql_query($query = '', $cache_ttl = 0)
{
if ($query != '')
{
global $cache;
if ($this->debug_sql_explain)
{
$this->sql_report('start', $query);
}
else if ($this->debug_load_time)
{
$this->curtime = microtime(true);
}
$this->last_query_text = $query;
$this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false;
$this->sql_add_num_queries($this->query_result);
if ($this->query_result === false)
{
if ($this->transaction === true && strpos($query, 'INSERT') === 0)
{
$query = preg_replace('/^INSERT INTO/', 'INSERT OR ROLLBACK INTO', $query);
}
if (($this->query_result = @$this->dbo->query($query)) === false)
{
// Try to recover a lost database connection
if ($this->dbo && !@$this->dbo->lastErrorMsg())
{
if ($this->sql_connect($this->server, $this->user, '', $this->dbname))
{
$this->query_result = @$this->dbo->query($query);
}
}
if ($this->query_result === false)
{
$this->sql_error($query);
}
}
if ($this->debug_sql_explain)
{
$this->sql_report('stop', $query);
}
else if ($this->debug_load_time)
{
$this->sql_time += microtime(true) - $this->curtime;
}
if (!$this->query_result)
{
return false;
}
if ($cache && $cache_ttl)
{
$this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl);
}
}
else if ($this->debug_sql_explain)
{
$this->sql_report('fromcache', $query);
}
}
else
{
return false;
}
return $this->query_result;
}
/**
* Build LIMIT query
*
* @param string $query The SQL query to execute
* @param int $total The number of rows to select
* @param int $offset
* @param int $cache_ttl Either 0 to avoid caching or
* the time in seconds which the result shall be kept in cache
* @return mixed Buffered, seekable result handle, false on error
*/
protected function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0)
{
$this->query_result = false;
// if $total is set to 0 we do not want to limit the number of rows
if ($total == 0)
{
$total = -1;
}
$query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total);
return $this->sql_query($query, $cache_ttl);
}
/**
* {@inheritDoc}
*/
public function sql_affectedrows()
{
return ($this->db_connect_id) ? $this->dbo->changes() : false;
}
/**
* {@inheritDoc}
*/
public function sql_fetchrow($query_id = false)
{
global $cache;
if ($query_id === false)
{
/** @var \SQLite3Result $query_id */
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_fetchrow($query_id);
}
return is_object($query_id) ? @$query_id->fetchArray(SQLITE3_ASSOC) : false;
}
/**
* {@inheritDoc}
*/
public function sql_nextid()
{
return ($this->db_connect_id) ? $this->dbo->lastInsertRowID() : false;
}
/**
* {@inheritDoc}
*/
public function sql_freeresult($query_id = false)
{
global $cache;
if ($query_id === false)
{
$query_id = $this->query_result;
}
if ($cache && !is_object($query_id) && $cache->sql_exists($query_id))
{
return $cache->sql_freeresult($query_id);
}
if ($query_id)
{
return @$query_id->finalize();
}
}
/**
* {@inheritDoc}
*/
public function sql_escape($msg)
{
return \SQLite3::escapeString($msg);
}
/**
* {@inheritDoc}
*
* For SQLite an underscore is an unknown character.
*/
public function sql_like_expression($expression)
{
// Unlike LIKE, GLOB is unfortunately case sensitive.
// We only catch * and ? here, not the character map possible on file globbing.
$expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression);
$expression = str_replace(array('?', '*'), array("\?", "\*"), $expression);
$expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression);
return 'GLOB \'' . $this->sql_escape($expression) . '\'';
}
/**
* {@inheritDoc}
*
* For SQLite an underscore is an unknown character.
*/
public function sql_not_like_expression($expression)
{
// Unlike NOT LIKE, NOT GLOB is unfortunately case sensitive
// We only catch * and ? here, not the character map possible on file globbing.
$expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression);
$expression = str_replace(array('?', '*'), array("\?", "\*"), $expression);
$expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression);
return 'NOT GLOB \'' . $this->sql_escape($expression) . '\'';
}
/**
* return sql error array
*
* @return array
*/
protected function _sql_error()
{
if (class_exists('SQLite3', false) && isset($this->dbo))
{
$error = array(
'message' => $this->dbo->lastErrorMsg(),
'code' => $this->dbo->lastErrorCode(),
);
}
else
{
$error = array(
'message' => $this->connect_error,
'code' => '',
);
}
return $error;
}
/**
* Build db-specific query data
*
* @param string $stage Available stages: FROM, WHERE
* @param mixed $data A string containing the CROSS JOIN query or an array of WHERE clauses
*
* @return string The db-specific query fragment
*/
protected function _sql_custom_build($stage, $data)
{
return $data;
}
/**
* Close sql connection
*
* @return bool False if failure
*/
protected function _sql_close()
{
return $this->dbo->close();
}
/**
* Build db-specific report
*
* @param string $mode Available modes: display, start, stop,
* add_select_row, fromcache, record_fromcache
* @param string $query The Query that should be explained
* @return mixed Either a full HTML page, boolean or null
*/
protected function _sql_report($mode, $query = '')
{
switch ($mode)
{
case 'start':
$explain_query = $query;
if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m))
{
$explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2];
}
if (preg_match('/^SELECT/', $explain_query))
{
$html_table = false;
if ($result = $this->dbo->query("EXPLAIN QUERY PLAN $explain_query"))
{
while ($row = $result->fetchArray(SQLITE3_ASSOC))
{
$html_table = $this->sql_report('add_select_row', $query, $html_table, $row);
}
}
if ($html_table)
{
$this->html_hold .= '</table>';
}
}
break;
case 'fromcache':
$endtime = explode(' ', microtime());
$endtime = $endtime[0] + $endtime[1];
$result = $this->dbo->query($query);
if ($result)
{
while ($void = $result->fetchArray(SQLITE3_ASSOC))
{
// Take the time spent on parsing rows into account
}
}
$splittime = explode(' ', microtime());
$splittime = $splittime[0] + $splittime[1];
$this->sql_report('record_fromcache', $query, $endtime, $splittime);
break;
}
}
}

View File

@@ -0,0 +1,301 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\extractor;
use phpbb\db\extractor\exception\extractor_not_initialized_exception;
class mysql_extractor extends base_extractor
{
/**
* {@inheritdoc}
*/
public function write_start($table_prefix)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$sql_data = "#\n";
$sql_data .= "# phpBB Backup Script\n";
$sql_data .= "# Dump of tables for $table_prefix\n";
$sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n";
$sql_data .= "#\n";
$this->flush($sql_data);
}
/**
* {@inheritdoc}
*/
public function write_table($table_name)
{
static $new_extract;
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
if ($new_extract === null)
{
if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>='))
{
$new_extract = true;
}
else
{
$new_extract = false;
}
}
if ($new_extract)
{
$this->new_write_table($table_name);
}
else
{
$this->old_write_table($table_name);
}
}
/**
* {@inheritdoc}
*/
public function write_data($table_name)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$this->write_data_mysqli($table_name);
}
/**
* Extracts data from database table (for MySQLi driver)
*
* @param string $table_name name of the database table
* @return null
* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor()
*/
protected function write_data_mysqli($table_name)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$sql = "SELECT *
FROM $table_name";
$result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT);
if ($result != false)
{
$fields_cnt = mysqli_num_fields($result);
// Get field information
$field = mysqli_fetch_fields($result);
$field_set = array();
for ($j = 0; $j < $fields_cnt; $j++)
{
$field_set[] = $field[$j]->name;
}
$search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"');
$replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"');
$fields = implode(', ', $field_set);
$sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES ';
$first_set = true;
$query_len = 0;
$max_len = get_usable_memory();
while ($row = mysqli_fetch_row($result))
{
$values = array();
if ($first_set)
{
$query = $sql_data . '(';
}
else
{
$query .= ',(';
}
for ($j = 0; $j < $fields_cnt; $j++)
{
if (!isset($row[$j]) || is_null($row[$j]))
{
$values[$j] = 'NULL';
}
else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024))
{
$values[$j] = $row[$j];
}
else
{
$values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'";
}
}
$query .= implode(', ', $values) . ')';
$query_len += strlen($query);
if ($query_len > $max_len)
{
$this->flush($query . ";\n\n");
$query = '';
$query_len = 0;
$first_set = true;
}
else
{
$first_set = false;
}
}
mysqli_free_result($result);
// check to make sure we have nothing left to flush
if (!$first_set && $query)
{
$this->flush($query . ";\n\n");
}
}
}
/**
* Extracts database table structure (for MySQLi or MySQL 3.23.20+)
*
* @param string $table_name name of the database table
* @return null
* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor()
*/
protected function new_write_table($table_name)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$sql = 'SHOW CREATE TABLE ' . $table_name;
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$sql_data = '# Table: ' . $table_name . "\n";
$sql_data .= "DROP TABLE IF EXISTS $table_name;\n";
$this->flush($sql_data . $row['Create Table'] . ";\n\n");
$this->db->sql_freeresult($result);
}
/**
* Extracts database table structure (for MySQL versions older than 3.23.20)
*
* @param string $table_name name of the database table
* @return null
* @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor()
*/
protected function old_write_table($table_name)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$sql_data = '# Table: ' . $table_name . "\n";
$sql_data .= "DROP TABLE IF EXISTS $table_name;\n";
$sql_data .= "CREATE TABLE $table_name(\n";
$rows = array();
$sql = "SHOW FIELDS
FROM $table_name";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$line = ' ' . $row['Field'] . ' ' . $row['Type'];
if (!is_null($row['Default']))
{
$line .= " DEFAULT '{$row['Default']}'";
}
if ($row['Null'] != 'YES')
{
$line .= ' NOT NULL';
}
if ($row['Extra'] != '')
{
$line .= ' ' . $row['Extra'];
}
$rows[] = $line;
}
$this->db->sql_freeresult($result);
$sql = "SHOW KEYS
FROM $table_name";
$result = $this->db->sql_query($sql);
$index = array();
while ($row = $this->db->sql_fetchrow($result))
{
$kname = $row['Key_name'];
if ($kname != 'PRIMARY')
{
if ($row['Non_unique'] == 0)
{
$kname = "UNIQUE|$kname";
}
}
if ($row['Sub_part'])
{
$row['Column_name'] .= '(' . $row['Sub_part'] . ')';
}
$index[$kname][] = $row['Column_name'];
}
$this->db->sql_freeresult($result);
foreach ($index as $key => $columns)
{
$line = ' ';
if ($key == 'PRIMARY')
{
$line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')';
}
else if (strpos($key, 'UNIQUE') === 0)
{
$line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')';
}
else if (strpos($key, 'FULLTEXT') === 0)
{
$line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')';
}
else
{
$line .= "KEY $key (" . implode(', ', $columns) . ')';
}
$rows[] = $line;
}
$sql_data .= implode(",\n", $rows);
$sql_data .= "\n);\n\n";
$this->flush($sql_data);
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v30x;
class release_3_0_4_rc1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return phpbb_version_compare($this->config['version'], '3.0.4-RC1', '>=');
}
static public function depends_on()
{
return array('\phpbb\db\migration\data\v30x\release_3_0_3');
}
public function update_schema()
{
return array(
'add_columns' => array(
$this->table_prefix . 'profile_fields' => array(
'field_show_profile' => array('BOOL', 0),
),
),
'change_columns' => array(
$this->table_prefix . 'styles' => array(
'style_id' => array('UINT', NULL, 'auto_increment'),
'template_id' => array('UINT', 0),
'theme_id' => array('UINT', 0),
'imageset_id' => array('UINT', 0),
),
$this->table_prefix . 'styles_imageset' => array(
'imageset_id' => array('UINT', NULL, 'auto_increment'),
),
$this->table_prefix . 'styles_imageset_data' => array(
'image_id' => array('UINT', NULL, 'auto_increment'),
'imageset_id' => array('UINT', 0),
),
$this->table_prefix . 'styles_theme' => array(
'theme_id' => array('UINT', NULL, 'auto_increment'),
),
$this->table_prefix . 'styles_template' => array(
'template_id' => array('UINT', NULL, 'auto_increment'),
),
$this->table_prefix . 'styles_template_data' => array(
'template_id' => array('UINT', 0),
),
$this->table_prefix . 'forums' => array(
'forum_style' => array('UINT', 0),
),
$this->table_prefix . 'users' => array(
'user_style' => array('UINT', 0),
),
),
);
}
public function revert_schema()
{
return array(
'drop_columns' => array(
$this->table_prefix . 'profile_fields' => array(
'field_show_profile',
),
),
);
}
public function update_data()
{
return array(
array('custom', array(array(&$this, 'update_custom_profile_fields'))),
array('config.update', array('version', '3.0.4-RC1')),
);
}
public function update_custom_profile_fields()
{
// Update the Custom Profile Fields based on previous settings to the new \format
$sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide
FROM ' . PROFILE_FIELDS_TABLE;
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$sql_ary = array(
'field_required' => 0,
'field_show_on_reg' => 0,
'field_hide' => 0,
'field_show_profile'=> 0,
);
if ($row['field_required'])
{
$sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
}
else if ($row['field_show_on_reg'])
{
$sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
}
else if ($row['field_hide'])
{
// Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module
$sql_ary['field_hide'] = 1;
}
else
{
// equivalent to "none", which is the "Display in user control panel" option
$sql_ary['field_show_profile'] = 1;
}
$this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary);
}
$this->db->sql_freeresult($result);
}
}

View File

@@ -0,0 +1,211 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v310;
class softdelete_p1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return $this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_visibility');
}
static public function depends_on()
{
return array('\phpbb\db\migration\data\v310\dev');
}
public function update_schema()
{
return array(
'add_columns' => array(
$this->table_prefix . 'forums' => array(
'forum_posts_approved' => array('UINT', 0),
'forum_posts_unapproved' => array('UINT', 0),
'forum_posts_softdeleted' => array('UINT', 0),
'forum_topics_approved' => array('UINT', 0),
'forum_topics_unapproved' => array('UINT', 0),
'forum_topics_softdeleted' => array('UINT', 0),
),
$this->table_prefix . 'posts' => array(
'post_visibility' => array('TINT:3', 0),
'post_delete_time' => array('TIMESTAMP', 0),
'post_delete_reason' => array('STEXT_UNI', ''),
'post_delete_user' => array('UINT', 0),
),
$this->table_prefix . 'topics' => array(
'topic_visibility' => array('TINT:3', 0),
'topic_delete_time' => array('TIMESTAMP', 0),
'topic_delete_reason' => array('STEXT_UNI', ''),
'topic_delete_user' => array('UINT', 0),
'topic_posts_approved' => array('UINT', 0),
'topic_posts_unapproved' => array('UINT', 0),
'topic_posts_softdeleted' => array('UINT', 0),
),
),
'add_index' => array(
$this->table_prefix . 'posts' => array(
'post_visibility' => array('post_visibility'),
),
$this->table_prefix . 'topics' => array(
'topic_visibility' => array('topic_visibility'),
'forum_vis_last' => array('forum_id', 'topic_visibility', 'topic_last_post_id'),
),
),
);
}
public function revert_schema()
{
return array(
'drop_columns' => array(
$this->table_prefix . 'forums' => array(
'forum_posts_approved',
'forum_posts_unapproved',
'forum_posts_softdeleted',
'forum_topics_approved',
'forum_topics_unapproved',
'forum_topics_softdeleted',
),
$this->table_prefix . 'posts' => array(
'post_visibility',
'post_delete_time',
'post_delete_reason',
'post_delete_user',
),
$this->table_prefix . 'topics' => array(
'topic_visibility',
'topic_delete_time',
'topic_delete_reason',
'topic_delete_user',
'topic_posts_approved',
'topic_posts_unapproved',
'topic_posts_softdeleted',
),
),
'drop_keys' => array(
$this->table_prefix . 'posts' => array('post_visibility'),
$this->table_prefix . 'topics' => array('topic_visibility', 'forum_vis_last'),
),
);
}
public function update_data()
{
return array(
array('custom', array(array($this, 'update_post_visibility'))),
array('custom', array(array($this, 'update_topic_visibility'))),
array('custom', array(array($this, 'update_topics_post_counts'))),
array('custom', array(array($this, 'update_forums_topic_and_post_counts'))),
array('permission.add', array('f_softdelete', false)),
array('permission.add', array('m_softdelete', false)),
);
}
public function update_post_visibility()
{
$sql = 'UPDATE ' . $this->table_prefix . 'posts
SET post_visibility = post_approved';
$this->sql_query($sql);
}
public function update_topic_visibility()
{
$sql = 'UPDATE ' . $this->table_prefix . 'topics
SET topic_visibility = topic_approved';
$this->sql_query($sql);
}
public function update_topics_post_counts()
{
/*
* Using sql_case here to avoid "BIGINT UNSIGNED value is out of range" errors.
* As we update all topics in 2 queries, one broken topic would stop the conversion
* for all topics and the suppressed error will cause the admin to not even notice it.
*/
$sql = 'UPDATE ' . $this->table_prefix . 'topics
SET topic_posts_approved = topic_replies + 1,
topic_posts_unapproved = ' . $this->db->sql_case('topic_replies_real > topic_replies', 'topic_replies_real - topic_replies', '0') . '
WHERE topic_visibility = ' . ITEM_APPROVED;
$this->sql_query($sql);
$sql = 'UPDATE ' . $this->table_prefix . 'topics
SET topic_posts_approved = 0,
topic_posts_unapproved = (' . $this->db->sql_case('topic_replies_real > topic_replies', 'topic_replies_real - topic_replies', '0') . ') + 1
WHERE topic_visibility = ' . ITEM_UNAPPROVED;
$this->sql_query($sql);
}
public function update_forums_topic_and_post_counts($start)
{
$start = (int) $start;
$limit = 10;
$converted_forums = 0;
if (!$start)
{
// Preserve the forum_posts value for link forums as it represents redirects.
$sql = 'UPDATE ' . $this->table_prefix . 'forums
SET forum_posts_approved = forum_posts
WHERE forum_type = ' . FORUM_LINK;
$this->db->sql_query($sql);
}
$sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS sum_topics, SUM(topic_posts_approved) AS sum_posts_approved, SUM(topic_posts_unapproved) AS sum_posts_unapproved
FROM ' . $this->table_prefix . 'topics
GROUP BY forum_id, topic_visibility
ORDER BY forum_id, topic_visibility';
$result = $this->db->sql_query_limit($sql, $limit, $start);
$update_forums = array();
while ($row = $this->db->sql_fetchrow($result))
{
$converted_forums++;
$forum_id = (int) $row['forum_id'];
if (!isset($update_forums[$forum_id]))
{
$update_forums[$forum_id] = array(
'forum_posts_approved' => 0,
'forum_posts_unapproved' => 0,
'forum_topics_approved' => 0,
'forum_topics_unapproved' => 0,
);
}
$update_forums[$forum_id]['forum_posts_approved'] += (int) $row['sum_posts_approved'];
$update_forums[$forum_id]['forum_posts_unapproved'] += (int) $row['sum_posts_unapproved'];
$update_forums[$forum_id][(($row['topic_visibility'] == ITEM_APPROVED) ? 'forum_topics_approved' : 'forum_topics_unapproved')] += (int) $row['sum_topics'];
}
$this->db->sql_freeresult($result);
foreach ($update_forums as $forum_id => $forum_data)
{
$sql = 'UPDATE ' . FORUMS_TABLE . '
SET ' . $this->db->sql_build_array('UPDATE', $forum_data) . '
WHERE forum_id = ' . $forum_id;
$this->sql_query($sql);
}
if ($converted_forums < $limit)
{
// There are no more topics, we are done
return;
}
// There are still more topics to query, return the next start value
return $start + $limit;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class timezone_p3 extends \phpbb\db\migration\migration
{
static public function depends_on()
{
return array('\phpbb\db\migration\data\v310\timezone');
}
public function update_data()
{
return array(
array('config.remove', array('board_dst')),
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class user_emoji_permission extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
$sql = 'SELECT auth_option_id
FROM ' . ACL_OPTIONS_TABLE . "
WHERE auth_option = 'u_emoji'";
$result = $this->db->sql_query($sql);
$auth_option_id = $this->db->sql_fetchfield('auth_option_id');
$this->db->sql_freeresult($result);
return $auth_option_id !== false;
}
static public function depends_on()
{
return [
'\phpbb\db\migration\data\v32x\v329rc1',
];
}
public function update_data()
{
return [
['permission.add', ['u_emoji']],
['permission.permission_set', ['REGISTERED', 'u_emoji', 'group']],
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class v328 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return phpbb_version_compare($this->config['version'], '3.2.8', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\v328rc1',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.2.8')),
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class v328rc1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return phpbb_version_compare($this->config['version'], '3.2.8-RC1', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\timezone_p3',
'\phpbb\db\migration\data\v32x\v327',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.2.8-RC1')),
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class v329 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return phpbb_version_compare($this->config['version'], '3.2.9', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\v329rc1',
'\phpbb\db\migration\data\v32x\user_emoji_permission',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.2.9')),
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\v32x;
class v329rc1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return phpbb_version_compare($this->config['version'], '3.2.9-RC1', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\v328',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.2.9-RC1')),
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 add_display_unapproved_posts_config extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return $this->config->offsetExists('display_unapproved_posts');
}
public static function depends_on()
{
return ['\phpbb\db\migration\data\v330\dev',];
}
public function update_data()
{
return [
['config.add', ['display_unapproved_posts', 1]],
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 dev extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return version_compare($this->config['version'], '3.3.0-dev', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\v327',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.3.0-dev')),
);
}
}

View File

@@ -0,0 +1,49 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 forums_legend_limit extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return $this->db_tools->sql_column_exists($this->table_prefix . 'forums', 'display_subforum_limit');
}
static public function depends_on()
{
return ['\phpbb\db\migration\data\v330\v330b1'];
}
public function update_schema()
{
return [
'add_columns' => [
$this->table_prefix . 'forums' => [
'display_subforum_limit' => ['BOOL', 0, 'after' => 'display_subforum_list'],
],
],
];
}
public function revert_schema()
{
return [
'drop_columns' => [
$this->table_prefix . 'forums' => [
'display_subforum_limit',
],
],
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 jquery_update extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return $this->config['load_jquery_url'] === '//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js';
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v330\dev',
);
}
public function update_data()
{
return array(
array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js')),
);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 remove_attachment_flash extends \phpbb\db\migration\migration
{
// Following constants were deprecated in 3.3
// and moved from constants.php to compatibility_globals.php,
// thus define them as class constants
const ATTACHMENT_CATEGORY_FLASH = 5;
protected $cat_id = array(
self::ATTACHMENT_CATEGORY_FLASH,
);
public static function depends_on()
{
return ['\phpbb\db\migration\data\v330\dev',];
}
public function update_data()
{
return array(
array('custom', array(array($this, 'remove_flash_group'))),
);
}
public function remove_flash_group()
{
// select group ids of outdated media
$sql = 'SELECT group_id
FROM ' . EXTENSION_GROUPS_TABLE . '
WHERE ' . $this->db->sql_in_set('cat_id', $this->cat_id);
$result = $this->db->sql_query($sql);
$group_ids = array();
while ($group_id = (int) $this->db->sql_fetchfield('group_id'))
{
$group_ids[] = $group_id;
}
$this->db->sql_freeresult($result);
// nothing to do, admin has removed all the outdated media extension groups
if (empty($group_ids))
{
return true;
}
// get the group id of downloadable files
$sql = 'SELECT group_id
FROM ' . EXTENSION_GROUPS_TABLE . "
WHERE group_name = 'DOWNLOADABLE_FILES'";
$result = $this->db->sql_query($sql);
$download_id = (int) $this->db->sql_fetchfield('group_id');
$this->db->sql_freeresult($result);
if (empty($download_id))
{
$sql = 'UPDATE ' . EXTENSIONS_TABLE . '
SET group_id = 0
WHERE ' . $this->db->sql_in_set('group_id', $group_ids);
}
else
{
// move outdated media extensions to downloadable files
$sql = 'UPDATE ' . EXTENSIONS_TABLE . "
SET group_id = $download_id" . '
WHERE ' . $this->db->sql_in_set('group_id', $group_ids);
}
$this->db->sql_query($sql);
// delete the now empty, outdated media extension groups
$sql = 'DELETE FROM ' . EXTENSION_GROUPS_TABLE . '
WHERE ' . $this->db->sql_in_set('group_id', $group_ids);
$this->db->sql_query($sql);
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 remove_email_hash extends \phpbb\db\migration\migration
{
static public function depends_on()
{
return ['\phpbb\db\migration\data\v30x\release_3_0_0'];
}
public function update_schema()
{
return [
'add_index' => [
$this->table_prefix . 'users' => [
'user_email' => ['user_email'],
],
],
'drop_keys' => [
$this->table_prefix . 'users' => [
'user_email_hash',
],
],
'drop_columns' => [
$this->table_prefix . 'users' => ['user_email_hash'],
],
];
}
public function revert_schema()
{
return [
'add_columns' => [
$this->table_prefix . 'users' => [
'user_email_hash' => ['BINT', 0],
],
],
'add_index' => [
$this->table_prefix . 'users' => [
'user_email_hash',
],
],
'drop_keys' => [
$this->table_prefix . 'users' => [
'user_email' => ['user_email'],
],
],
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 remove_max_pass_chars extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return !$this->config->offsetExists('max_pass_chars');
}
public static function depends_on()
{
return [
'\phpbb\db\migration\data\v330\dev',
];
}
public function update_data()
{
return [
['config.remove', ['max_pass_chars']],
];
}
public function revert_data()
{
return [
['config.add', ['max_pass_chars', 100]],
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 reset_password extends \phpbb\db\migration\migration
{
static public function depends_on()
{
return [
'\phpbb\db\migration\data\v330\dev',
];
}
public function update_schema()
{
return [
'add_columns' => [
$this->table_prefix . 'users' => [
'reset_token' => ['VCHAR:64', '', 'after' => 'user_actkey'],
'reset_token_expiration' => ['TIMESTAMP', 0, 'after' => 'reset_token'],
],
],
];
}
public function revert_schema()
{
return [
'drop_columns' => [
$this->table_prefix . 'users' => [
'reset_token',
'reset_token_expiration',
],
],
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 v330 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return version_compare($this->config['version'], '3.3.0', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v32x\v329',
'\phpbb\db\migration\data\v330\v330rc1',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.3.0')),
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 v330b1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return version_compare($this->config['version'], '3.3.0-b1', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v330\jquery_update',
'\phpbb\db\migration\data\v330\reset_password',
'\phpbb\db\migration\data\v330\remove_attachment_flash',
'\phpbb\db\migration\data\v330\remove_max_pass_chars',
'\phpbb\db\migration\data\v32x\v328',
'\phpbb\db\migration\data\v330\dev',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.3.0-b1')),
);
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 v330b2 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return version_compare($this->config['version'], '3.3.0-b2', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v330\add_display_unapproved_posts_config',
'\phpbb\db\migration\data\v330\forums_legend_limit',
'\phpbb\db\migration\data\v330\remove_email_hash',
'\phpbb\db\migration\data\v330\v330b1',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.3.0-b2')),
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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 v330rc1 extends \phpbb\db\migration\migration
{
public function effectively_installed()
{
return version_compare($this->config['version'], '3.3.0-RC1', '>=');
}
static public function depends_on()
{
return array(
'\phpbb\db\migration\data\v330\v330b2',
);
}
public function update_data()
{
return array(
array('config.update', array('version', '3.3.0-RC1')),
);
}
}

View File

@@ -0,0 +1,556 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\tool;
use phpbb\module\exception\module_exception;
/**
* Migration module management tool
*/
class module implements \phpbb\db\migration\tool\tool_interface
{
/** @var \phpbb\cache\service */
protected $cache;
/** @var \phpbb\db\driver\driver_interface */
protected $db;
/** @var \phpbb\user */
protected $user;
/** @var \phpbb\module\module_manager */
protected $module_manager;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $php_ext;
/** @var string */
protected $modules_table;
/** @var array */
protected $module_categories = array();
/**
* Constructor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\cache\service $cache
* @param \phpbb\user $user
* @param \phpbb\module\module_manager $module_manager
* @param string $phpbb_root_path
* @param string $php_ext
* @param string $modules_table
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, \phpbb\module\module_manager $module_manager, $phpbb_root_path, $php_ext, $modules_table)
{
$this->db = $db;
$this->cache = $cache;
$this->user = $user;
$this->module_manager = $module_manager;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->modules_table = $modules_table;
}
/**
* {@inheritdoc}
*/
public function get_name()
{
return 'module';
}
/**
* Module Exists
*
* Check if a module exists
*
* @param string $class The module class(acp|mcp|ucp)
* @param int|string|bool $parent The parent module_id|module_langname (0 for no parent).
* Use false to ignore the parent check and check class wide.
* @param int|string $module The module_id|module_langname you would like to
* check for to see if it exists
* @param bool $lazy Checks lazily if the module exists. Returns true if it exists in at
* least one given parent.
* @return bool true if module exists in *all* given parents, false if not in any given parent;
* true if ignoring parent check and module exists class wide, false if not found at all.
*/
public function exists($class, $parent, $module, $lazy = false)
{
// the main root directory should return true
if (!$module)
{
return true;
}
$parent_sqls = [];
if ($parent !== false)
{
$parents = $this->get_parent_module_id($parent, $module, false);
if ($parents === false)
{
return false;
}
foreach ((array) $parents as $parent_id)
{
$parent_sqls[] = 'AND parent_id = ' . (int) $parent_id;
}
}
else
{
$parent_sqls[] = '';
}
foreach ($parent_sqls as $parent_sql)
{
$sql = 'SELECT module_id
FROM ' . $this->modules_table . "
WHERE module_class = '" . $this->db->sql_escape($class) . "'
$parent_sql
AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'");
$result = $this->db->sql_query($sql);
$module_id = $this->db->sql_fetchfield('module_id');
$this->db->sql_freeresult($result);
if (!$lazy && !$module_id)
{
return false;
}
if ($lazy && $module_id)
{
return true;
}
}
// Returns true, if modules exist in all parents and false otherwise
return !$lazy;
}
/**
* Module Add
*
* Add a new module
*
* @param string $class The module class(acp|mcp|ucp)
* @param int|string $parent The parent module_id|module_langname (0 for no parent)
* @param array $data an array of the data on the new \module.
* This can be setup in two different ways.
* 1. The "manual" way. For inserting a category or one at a time.
* It will be merged with the base array shown a bit below,
* but at the least requires 'module_langname' to be sent, and,
* if you want to create a module (instead of just a category) you must
* send module_basename and module_mode.
* array(
* 'module_enabled' => 1,
* 'module_display' => 1,
* 'module_basename' => '',
* 'module_class' => $class,
* 'parent_id' => (int) $parent,
* 'module_langname' => '',
* 'module_mode' => '',
* 'module_auth' => '',
* )
* 2. The "automatic" way. For inserting multiple at a time based on the
* specs in the info file for the module(s). For this to work the
* modules must be correctly setup in the info file.
* An example follows (this would insert the settings, log, and flag
* modes from the includes/acp/info/acp_asacp.php file):
* array(
* 'module_basename' => 'asacp',
* 'modes' => array('settings', 'log', 'flag'),
* )
* Optionally you may not send 'modes' and it will insert all of the
* modules in that info file.
* path, specify that here
* @return null
* @throws \phpbb\db\migration\exception
*/
public function add($class, $parent = 0, $data = array())
{
global $user, $phpbb_log;
// allow sending the name as a string in $data to create a category
if (!is_array($data))
{
$data = array('module_langname' => $data);
}
$parents = (array) $this->get_parent_module_id($parent, $data);
if (!isset($data['module_langname']))
{
// The "automatic" way
$basename = (isset($data['module_basename'])) ? $data['module_basename'] : '';
$module = $this->get_module_info($class, $basename);
foreach ($module['modes'] as $mode => $module_info)
{
if (!isset($data['modes']) || in_array($mode, $data['modes']))
{
$new_module = array(
'module_basename' => $basename,
'module_langname' => $module_info['title'],
'module_mode' => $mode,
'module_auth' => $module_info['auth'],
'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true,
'before' => (isset($module_info['before'])) ? $module_info['before'] : false,
'after' => (isset($module_info['after'])) ? $module_info['after'] : false,
);
// Run the "manual" way with the data we've collected.
foreach ($parents as $parent)
{
$this->add($class, $parent, $new_module);
}
}
}
return;
}
foreach ($parents as $parent)
{
$data['parent_id'] = $parent;
// The "manual" way
if (!$this->exists($class, false, $parent))
{
throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST', $parent);
}
if ($this->exists($class, $parent, $data['module_langname']))
{
throw new \phpbb\db\migration\exception('MODULE_EXISTS', $data['module_langname']);
}
$module_data = array(
'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1,
'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1,
'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '',
'module_class' => $class,
'parent_id' => (int) $parent,
'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '',
'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '',
'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '',
);
try
{
$this->module_manager->update_module_data($module_data);
// Success
$module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']);
$phpbb_log->add('admin', (isset($user->data['user_id'])) ? $user->data['user_id'] : ANONYMOUS, $user->ip, 'LOG_MODULE_ADD', false, array($module_log_name));
// Move the module if requested above/below an existing one
if (isset($data['before']) && $data['before'])
{
$before_mode = $before_langname = '';
if (is_array($data['before']))
{
// Restore legacy-legacy behaviour from phpBB 3.0
list($before_mode, $before_langname) = $data['before'];
}
else
{
// Legacy behaviour from phpBB 3.1+
$before_langname = $data['before'];
}
$sql = 'SELECT left_id
FROM ' . $this->modules_table . "
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND parent_id = " . (int) $parent . "
AND module_langname = '" . $this->db->sql_escape($before_langname) . "'"
. (($before_mode) ? " AND module_mode = '" . $this->db->sql_escape($before_mode) . "'" : '');
$result = $this->db->sql_query($sql);
$to_left = (int) $this->db->sql_fetchfield('left_id');
$this->db->sql_freeresult($result);
$sql = 'UPDATE ' . $this->modules_table . "
SET left_id = left_id + 2, right_id = right_id + 2
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND left_id >= $to_left
AND left_id < {$module_data['left_id']}";
$this->db->sql_query($sql);
$sql = 'UPDATE ' . $this->modules_table . "
SET left_id = $to_left, right_id = " . ($to_left + 1) . "
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND module_id = {$module_data['module_id']}";
$this->db->sql_query($sql);
}
else if (isset($data['after']) && $data['after'])
{
$after_mode = $after_langname = '';
if (is_array($data['after']))
{
// Restore legacy-legacy behaviour from phpBB 3.0
list($after_mode, $after_langname) = $data['after'];
}
else
{
// Legacy behaviour from phpBB 3.1+
$after_langname = $data['after'];
}
$sql = 'SELECT right_id
FROM ' . $this->modules_table . "
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND parent_id = " . (int) $parent . "
AND module_langname = '" . $this->db->sql_escape($after_langname) . "'"
. (($after_mode) ? " AND module_mode = '" . $this->db->sql_escape($after_mode) . "'" : '');
$result = $this->db->sql_query($sql);
$to_right = (int) $this->db->sql_fetchfield('right_id');
$this->db->sql_freeresult($result);
$sql = 'UPDATE ' . $this->modules_table . "
SET left_id = left_id + 2, right_id = right_id + 2
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND left_id >= $to_right
AND left_id < {$module_data['left_id']}";
$this->db->sql_query($sql);
$sql = 'UPDATE ' . $this->modules_table . '
SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . "
WHERE module_class = '" . $this->db->sql_escape($class) . "'
AND module_id = {$module_data['module_id']}";
$this->db->sql_query($sql);
}
}
catch (module_exception $e)
{
// Error
throw new \phpbb\db\migration\exception('MODULE_ERROR', $e->getMessage());
}
}
// Clear the Modules Cache
$this->module_manager->remove_cache_file($class);
}
/**
* Module Remove
*
* Remove a module
*
* @param string $class The module class(acp|mcp|ucp)
* @param int|string|bool $parent The parent module_id|module_langname(0 for no parent).
* Use false to ignore the parent check and check class wide.
* @param int|string $module The module id|module_langname
* specify that here
* @return null
* @throws \phpbb\db\migration\exception
*/
public function remove($class, $parent = 0, $module = '')
{
// Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto
if (is_array($module))
{
if (isset($module['module_langname']))
{
// Manual Method
return $this->remove($class, $parent, $module['module_langname']);
}
// Failed.
if (!isset($module['module_basename']))
{
throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST');
}
// Automatic method
$basename = $module['module_basename'];
$module_info = $this->get_module_info($class, $basename);
foreach ($module_info['modes'] as $mode => $info)
{
if (!isset($module['modes']) || in_array($mode, $module['modes']))
{
$this->remove($class, $parent, $info['title']);
}
}
}
else
{
if (!$this->exists($class, $parent, $module, true))
{
return;
}
$parent_sql = '';
if ($parent !== false)
{
$parents = (array) $this->get_parent_module_id($parent, $module);
$parent_sql = 'AND ' . $this->db->sql_in_set('parent_id', $parents);
}
$module_ids = array();
if (!is_numeric($module))
{
$sql = 'SELECT module_id
FROM ' . $this->modules_table . "
WHERE module_langname = '" . $this->db->sql_escape($module) . "'
AND module_class = '" . $this->db->sql_escape($class) . "'
$parent_sql";
$result = $this->db->sql_query($sql);
while ($module_id = $this->db->sql_fetchfield('module_id'))
{
$module_ids[] = (int) $module_id;
}
$this->db->sql_freeresult($result);
}
else
{
$module_ids[] = (int) $module;
}
foreach ($module_ids as $module_id)
{
$this->module_manager->delete_module($module_id, $class);
}
$this->module_manager->remove_cache_file($class);
}
}
/**
* {@inheritdoc}
*/
public function reverse()
{
$arguments = func_get_args();
$original_call = array_shift($arguments);
$call = false;
switch ($original_call)
{
case 'add':
$call = 'remove';
break;
case 'remove':
$call = 'add';
break;
case 'reverse':
// Reversing a reverse is just the call itself
$call = array_shift($arguments);
break;
}
if ($call)
{
return call_user_func_array(array(&$this, $call), $arguments);
}
}
/**
* Wrapper for \acp_modules::get_module_infos()
*
* @param string $class Module Class
* @param string $basename Module Basename
* @return array Module Information
* @throws \phpbb\db\migration\exception
*/
protected function get_module_info($class, $basename)
{
$module = $this->module_manager->get_module_infos($class, $basename, true);
if (empty($module))
{
throw new \phpbb\db\migration\exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename);
}
return array_pop($module);
}
/**
* Get the list of installed module categories
* key - module_id
* value - module_langname
*
* @return null
*/
protected function get_categories_list()
{
// Select the top level categories
// and 2nd level [sub]categories
$sql = 'SELECT m2.module_id, m2.module_langname
FROM ' . $this->modules_table . ' m1, ' . $this->modules_table . " m2
WHERE m1.parent_id = 0
AND (m1.module_id = m2.module_id OR m2.parent_id = m1.module_id)
ORDER BY m1.module_id, m2.module_id ASC";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$this->module_categories[(int) $row['module_id']] = $row['module_langname'];
}
$this->db->sql_freeresult($result);
}
/**
* Get parent module id
*
* @param string|int $parent_id The parent module_id|module_langname
* @param int|string|array $data The module_id, module_langname for existence checking or module data array for adding
* @param bool $throw_exception The flag indicating if exception should be thrown on error
* @return mixed The int parent module_id, an array of int parent module_id values or false
* @throws \phpbb\db\migration\exception
*/
public function get_parent_module_id($parent_id, $data = '', $throw_exception = true)
{
// Allow '' to be sent as 0
$parent_id = $parent_id ?: 0;
if (!is_numeric($parent_id))
{
// Refresh the $module_categories array
$this->get_categories_list();
// Search for the parent module_langname
$ids = array_keys($this->module_categories, $parent_id);
switch (count($ids))
{
// No parent with the given module_langname exist
case 0:
if ($throw_exception)
{
throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST', $parent_id);
}
return false;
break;
// Return the module id
case 1:
return (int) $ids[0];
break;
default:
// This represents the old behaviour of phpBB 3.0
return $ids;
break;
}
}
return $parent_id;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,880 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\tools;
/**
* Database Tools for handling cross-db actions such as altering columns, etc.
* Currently not supported is returning SQL for creating tables.
*/
class mssql extends tools
{
/**
* Is the used MS SQL Server a SQL Server 2000?
* @var bool
*/
protected $is_sql_server_2000;
/**
* Get the column types for mssql based databases
*
* @return array
*/
public static function get_dbms_type_map()
{
return array(
'mssql' => array(
'INT:' => '[int]',
'BINT' => '[float]',
'ULINT' => '[int]',
'UINT' => '[int]',
'UINT:' => '[int]',
'TINT:' => '[int]',
'USINT' => '[int]',
'BOOL' => '[int]',
'VCHAR' => '[varchar] (255)',
'VCHAR:' => '[varchar] (%d)',
'CHAR:' => '[char] (%d)',
'XSTEXT' => '[varchar] (1000)',
'STEXT' => '[varchar] (3000)',
'TEXT' => '[varchar] (8000)',
'MTEXT' => '[text]',
'XSTEXT_UNI'=> '[nvarchar] (100)',
'STEXT_UNI' => '[nvarchar] (255)',
'TEXT_UNI' => '[nvarchar] (4000)',
'MTEXT_UNI' => '[ntext]',
'TIMESTAMP' => '[int]',
'DECIMAL' => '[float]',
'DECIMAL:' => '[float]',
'PDECIMAL' => '[float]',
'PDECIMAL:' => '[float]',
'VCHAR_UNI' => '[nvarchar] (255)',
'VCHAR_UNI:'=> '[nvarchar] (%d)',
'VCHAR_CI' => '[nvarchar] (255)',
'VARBINARY' => '[varchar] (255)',
),
'mssqlnative' => array(
'INT:' => '[int]',
'BINT' => '[float]',
'ULINT' => '[int]',
'UINT' => '[int]',
'UINT:' => '[int]',
'TINT:' => '[int]',
'USINT' => '[int]',
'BOOL' => '[int]',
'VCHAR' => '[varchar] (255)',
'VCHAR:' => '[varchar] (%d)',
'CHAR:' => '[char] (%d)',
'XSTEXT' => '[varchar] (1000)',
'STEXT' => '[varchar] (3000)',
'TEXT' => '[varchar] (8000)',
'MTEXT' => '[text]',
'XSTEXT_UNI'=> '[nvarchar] (100)',
'STEXT_UNI' => '[nvarchar] (255)',
'TEXT_UNI' => '[nvarchar] (4000)',
'MTEXT_UNI' => '[ntext]',
'TIMESTAMP' => '[int]',
'DECIMAL' => '[float]',
'DECIMAL:' => '[float]',
'PDECIMAL' => '[float]',
'PDECIMAL:' => '[float]',
'VCHAR_UNI' => '[nvarchar] (255)',
'VCHAR_UNI:'=> '[nvarchar] (%d)',
'VCHAR_CI' => '[nvarchar] (255)',
'VARBINARY' => '[varchar] (255)',
),
);
}
/**
* Constructor. Set DB Object and set {@link $return_statements return_statements}.
*
* @param \phpbb\db\driver\driver_interface $db Database connection
* @param bool $return_statements True if only statements should be returned and no SQL being executed
*/
public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false)
{
parent::__construct($db, $return_statements);
// Determine mapping database type
switch ($this->db->get_sql_layer())
{
case 'mssql_odbc':
$this->sql_layer = 'mssql';
break;
case 'mssqlnative':
$this->sql_layer = 'mssqlnative';
break;
}
$this->dbms_type_map = self::get_dbms_type_map();
}
/**
* {@inheritDoc}
*/
function sql_list_tables()
{
$sql = "SELECT name
FROM sysobjects
WHERE type='U'";
$result = $this->db->sql_query($sql);
$tables = array();
while ($row = $this->db->sql_fetchrow($result))
{
$name = current($row);
$tables[$name] = $name;
}
$this->db->sql_freeresult($result);
return $tables;
}
/**
* {@inheritDoc}
*/
function sql_create_table($table_name, $table_data)
{
// holds the DDL for a column
$columns = $statements = array();
if ($this->sql_table_exists($table_name))
{
return $this->_sql_run_sql($statements);
}
// Begin transaction
$statements[] = 'begin';
// Determine if we have created a PRIMARY KEY in the earliest
$primary_key_gen = false;
// Determine if the table requires a sequence
$create_sequence = false;
// Begin table sql statement
$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
if (!isset($table_data['PRIMARY_KEY']))
{
$table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment');
$table_data['PRIMARY_KEY'] = 'mssqlindex';
}
// Iterate through the columns to create a table
foreach ($table_data['COLUMNS'] as $column_name => $column_data)
{
// here lies an array, filled with information compiled on the column's data
$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen"
{
trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR);
}
// here we add the definition of the new column to the list of columns
$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default'];
// see if we have found a primary key set due to a column definition if we have found it, we can stop looking
if (!$primary_key_gen)
{
$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
}
// create sequence DDL based off of the existence of auto incrementing columns
if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
{
$create_sequence = $column_name;
}
}
// this makes up all the columns in the create table statement
$table_sql .= implode(",\n", $columns);
// Close the table for two DBMS and add to the statements
$table_sql .= "\n);";
$statements[] = $table_sql;
// we have yet to create a primary key for this table,
// this means that we can add the one we really wanted instead
if (!$primary_key_gen)
{
// Write primary key
if (isset($table_data['PRIMARY_KEY']))
{
if (!is_array($table_data['PRIMARY_KEY']))
{
$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
}
// We need the data here
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
foreach ($primary_key_stmts as $pk_stmt)
{
$statements[] = $pk_stmt;
}
$this->return_statements = $old_return_statements;
}
}
// Write Keys
if (isset($table_data['KEYS']))
{
foreach ($table_data['KEYS'] as $key_name => $key_data)
{
if (!is_array($key_data[1]))
{
$key_data[1] = array($key_data[1]);
}
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]);
foreach ($key_stmts as $key_stmt)
{
$statements[] = $key_stmt;
}
$this->return_statements = $old_return_statements;
}
}
// Commit Transaction
$statements[] = 'commit';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_list_columns($table_name)
{
$columns = array();
$sql = "SELECT c.name
FROM syscolumns c
LEFT JOIN sysobjects o ON c.id = o.id
WHERE o.name = '{$table_name}'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$column = strtolower(current($row));
$columns[$column] = $column;
}
$this->db->sql_freeresult($result);
return $columns;
}
/**
* {@inheritDoc}
*/
function sql_index_exists($table_name, $index_name)
{
$sql = "EXEC sp_statistics '$table_name'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['TYPE'] == 3)
{
if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* {@inheritDoc}
*/
function sql_unique_index_exists($table_name, $index_name)
{
$sql = "EXEC sp_statistics '$table_name'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// Usually NON_UNIQUE is the column we want to check, but we allow for both
if ($row['TYPE'] == 3)
{
if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* {@inheritDoc}
*/
function sql_prepare_column_data($table_name, $column_name, $column_data)
{
if (strlen($column_name) > 30)
{
trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
}
// Get type
list($column_type, ) = $this->get_column_type($column_data[0]);
// Adjust default value if db-dependent specified
if (is_array($column_data[1]))
{
$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
}
$sql = '';
$return_array = array();
$sql .= " {$column_type} ";
$sql_default = " {$column_type} ";
// For adding columns we need the default definition
if (!is_null($column_data[1]))
{
// For hexadecimal values do not use single quotes
if (strpos($column_data[1], '0x') === 0)
{
$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
$sql_default .= $return_array['default'];
}
else
{
$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
$sql_default .= $return_array['default'];
}
}
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
// $sql .= 'IDENTITY (1, 1) ';
$sql_default .= 'IDENTITY (1, 1) ';
}
$return_array['textimage'] = $column_type === '[text]';
if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment'))
{
$sql .= 'NOT NULL';
$sql_default .= 'NOT NULL';
}
else
{
$sql .= 'NULL';
$sql_default .= 'NULL';
}
$return_array['column_type_sql_default'] = $sql_default;
$return_array['column_type_sql'] = $sql;
return $return_array;
}
/**
* {@inheritDoc}
*/
function sql_column_add($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
// Does not support AFTER, only through temporary table
$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_column_remove($table_name, $column_name, $inline = false)
{
$statements = array();
// We need the data here
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$indexes = $this->get_existing_indexes($table_name, $column_name);
$indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true));
// Drop any indexes
$recreate_indexes = array();
if (!empty($indexes))
{
foreach ($indexes as $index_name => $index_data)
{
$result = $this->sql_index_drop($table_name, $index_name);
$statements = array_merge($statements, $result);
if (count($index_data) > 1)
{
// Remove this column from the index and recreate it
$recreate_indexes[$index_name] = array_diff($index_data, array($column_name));
}
}
}
// Drop primary keys depending on this column
$result = $this->mssql_get_drop_default_primary_key_queries($table_name, $column_name);
$statements = array_merge($statements, $result);
// Drop default value constraint
$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
$statements = array_merge($statements, $result);
// Remove the column
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
if (!empty($recreate_indexes))
{
// Recreate indexes after we removed the column
foreach ($recreate_indexes as $index_name => $index_data)
{
$result = $this->sql_create_index($table_name, $index_name, $index_data);
$statements = array_merge($statements, $result);
}
}
$this->return_statements = $old_return_statements;
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_index_drop($table_name, $index_name)
{
$statements = array();
$statements[] = 'DROP INDEX [' . $table_name . '].[' . $index_name . ']';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_table_drop($table_name)
{
$statements = array();
if (!$this->sql_table_exists($table_name))
{
return $this->_sql_run_sql($statements);
}
// the most basic operation, get rid of the table
$statements[] = 'DROP TABLE ' . $table_name;
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_primary_key($table_name, $column, $inline = false)
{
$statements = array();
$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD ";
$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED (";
$sql .= '[' . implode("],\n\t\t[", $column) . ']';
$sql .= ')';
$statements[] = $sql;
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_unique_index($table_name, $index_name, $column)
{
$statements = array();
if ($this->mssql_is_sql_server_2000())
{
$this->check_index_name_length($table_name, $index_name);
}
$statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_index($table_name, $index_name, $column)
{
$statements = array();
$this->check_index_name_length($table_name, $index_name);
// remove index length
$column = preg_replace('#:.*$#', '', $column);
$statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritdoc}
*/
protected function get_max_index_name_length()
{
if ($this->mssql_is_sql_server_2000())
{
return parent::get_max_index_name_length();
}
else
{
return 128;
}
}
/**
* {@inheritDoc}
*/
function sql_list_index($table_name)
{
$index_array = array();
$sql = "EXEC sp_statistics '$table_name'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['TYPE'] == 3)
{
$index_array[] = strtolower($row['INDEX_NAME']);
}
}
$this->db->sql_freeresult($result);
return $index_array;
}
/**
* {@inheritDoc}
*/
function sql_column_change($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
// We need the data here
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$indexes = $this->get_existing_indexes($table_name, $column_name);
$unique_indexes = $this->get_existing_indexes($table_name, $column_name, true);
// Drop any indexes
if (!empty($indexes) || !empty($unique_indexes))
{
$drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes));
foreach ($drop_indexes as $index_name)
{
$result = $this->sql_index_drop($table_name, $index_name);
$statements = array_merge($statements, $result);
}
}
// Drop default value constraint
$result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
$statements = array_merge($statements, $result);
// Change the column
$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql'];
if (!empty($column_data['default']) && !$this->mssql_is_column_identity($table_name, $column_name))
{
// Add new default value constraint
$statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']';
}
if (!empty($indexes))
{
// Recreate indexes after we changed the column
foreach ($indexes as $index_name => $index_data)
{
$result = $this->sql_create_index($table_name, $index_name, $index_data);
$statements = array_merge($statements, $result);
}
}
if (!empty($unique_indexes))
{
// Recreate unique indexes after we changed the column
foreach ($unique_indexes as $index_name => $index_data)
{
$result = $this->sql_create_unique_index($table_name, $index_name, $index_data);
$statements = array_merge($statements, $result);
}
}
$this->return_statements = $old_return_statements;
return $this->_sql_run_sql($statements);
}
/**
* Get queries to drop the default constraints of a column
*
* We need to drop the default constraints of a column,
* before being able to change their type or deleting them.
*
* @param string $table_name
* @param string $column_name
* @return array Array with SQL statements
*/
protected function mssql_get_drop_default_constraints_queries($table_name, $column_name)
{
$statements = array();
if ($this->mssql_is_sql_server_2000())
{
// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
// Deprecated in SQL Server 2005
$sql = "SELECT so.name AS def_name
FROM sysobjects so
JOIN sysconstraints sc ON so.id = sc.constid
WHERE object_name(so.parent_obj) = '{$table_name}'
AND so.xtype = 'D'
AND sc.colid = (SELECT colid FROM syscolumns
WHERE id = object_id('{$table_name}')
AND name = '{$column_name}')";
}
else
{
$sql = "SELECT dobj.name AS def_name
FROM sys.columns col
LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D')
WHERE col.object_id = object_id('{$table_name}')
AND col.name = '{$column_name}'
AND dobj.name IS NOT NULL";
}
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']';
}
$this->db->sql_freeresult($result);
return $statements;
}
/**
* Get queries to drop the primary keys depending on the specified column
*
* We need to drop primary keys depending on this column before being able
* to delete them.
*
* @param string $table_name
* @param string $column_name
* @return array Array with SQL statements
*/
protected function mssql_get_drop_default_primary_key_queries($table_name, $column_name)
{
$statements = array();
$sql = "SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.Constraint_name
WHERE tc.TABLE_NAME = '{$table_name}'
AND tc.CONSTRAINT_TYPE = 'Primary Key'
AND ccu.COLUMN_NAME = '{$column_name}'";
$result = $this->db->sql_query($sql);
while ($primary_key = $this->db->sql_fetchrow($result))
{
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $primary_key['CONSTRAINT_NAME'] . ']';
}
$this->db->sql_freeresult($result);
return $statements;
}
/**
* Checks to see if column is an identity column
*
* Identity columns cannot have defaults set for them.
*
* @param string $table_name
* @param string $column_name
* @return bool true if identity, false if not
*/
protected function mssql_is_column_identity($table_name, $column_name)
{
if ($this->mssql_is_sql_server_2000())
{
// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
// Deprecated in SQL Server 2005
$sql = "SELECT COLUMNPROPERTY(object_id('{$table_name}'), '{$column_name}', 'IsIdentity') AS is_identity";
}
else
{
$sql = "SELECT is_identity FROM sys.columns
WHERE object_id = object_id('{$table_name}')
AND name = '{$column_name}'";
}
$result = $this->db->sql_query($sql);
$is_identity = $this->db->sql_fetchfield('is_identity');
$this->db->sql_freeresult($result);
return (bool) $is_identity;
}
/**
* Get a list with existing indexes for the column
*
* @param string $table_name
* @param string $column_name
* @param bool $unique Should we get unique indexes or normal ones
* @return array Array with Index name => columns
*/
public function get_existing_indexes($table_name, $column_name, $unique = false)
{
$existing_indexes = array();
if ($this->mssql_is_sql_server_2000())
{
// http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
// Deprecated in SQL Server 2005
$sql = "SELECT DISTINCT ix.name AS phpbb_index_name
FROM sysindexes ix
INNER JOIN sysindexkeys ixc
ON ixc.id = ix.id
AND ixc.indid = ix.indid
INNER JOIN syscolumns cols
ON cols.colid = ixc.colid
AND cols.id = ix.id
WHERE ix.id = object_id('{$table_name}')
AND cols.name = '{$column_name}'
AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0');
}
else
{
$sql = "SELECT DISTINCT ix.name AS phpbb_index_name
FROM sys.indexes ix
INNER JOIN sys.index_columns ixc
ON ixc.object_id = ix.object_id
AND ixc.index_id = ix.index_id
INNER JOIN sys.columns cols
ON cols.column_id = ixc.column_id
AND cols.object_id = ix.object_id
WHERE ix.object_id = object_id('{$table_name}')
AND cols.name = '{$column_name}'
AND ix.is_primary_key = 0
AND ix.is_unique = " . ($unique ? '1' : '0');
}
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE'))
{
$existing_indexes[$row['phpbb_index_name']] = array();
}
}
$this->db->sql_freeresult($result);
if (empty($existing_indexes))
{
return array();
}
if ($this->mssql_is_sql_server_2000())
{
$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
FROM sysindexes ix
INNER JOIN sysindexkeys ixc
ON ixc.id = ix.id
AND ixc.indid = ix.indid
INNER JOIN syscolumns cols
ON cols.colid = ixc.colid
AND cols.id = ix.id
WHERE ix.id = object_id('{$table_name}')
AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
}
else
{
$sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
FROM sys.indexes ix
INNER JOIN sys.index_columns ixc
ON ixc.object_id = ix.object_id
AND ixc.index_id = ix.index_id
INNER JOIN sys.columns cols
ON cols.column_id = ixc.column_id
AND cols.object_id = ix.object_id
WHERE ix.object_id = object_id('{$table_name}')
AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
}
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name'];
}
$this->db->sql_freeresult($result);
return $existing_indexes;
}
/**
* Is the used MS SQL Server a SQL Server 2000?
*
* @return bool
*/
protected function mssql_is_sql_server_2000()
{
if ($this->is_sql_server_2000 === null)
{
$sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version";
$result = $this->db->sql_query($sql);
$properties = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$this->is_sql_server_2000 = $properties['mssql_version'][0] == '8';
}
return $this->is_sql_server_2000;
}
}

View File

@@ -0,0 +1,614 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\tools;
/**
* Database Tools for handling cross-db actions such as altering columns, etc.
* Currently not supported is returning SQL for creating tables.
*/
class postgres extends tools
{
/**
* Get the column types for postgres only
*
* @return array
*/
public static function get_dbms_type_map()
{
return array(
'postgres' => array(
'INT:' => 'INT4',
'BINT' => 'INT8',
'ULINT' => 'INT4', // unsigned
'UINT' => 'INT4', // unsigned
'UINT:' => 'INT4', // unsigned
'USINT' => 'INT2', // unsigned
'BOOL' => 'INT2', // unsigned
'TINT:' => 'INT2',
'VCHAR' => 'varchar(255)',
'VCHAR:' => 'varchar(%d)',
'CHAR:' => 'char(%d)',
'XSTEXT' => 'varchar(1000)',
'STEXT' => 'varchar(3000)',
'TEXT' => 'varchar(8000)',
'MTEXT' => 'TEXT',
'XSTEXT_UNI'=> 'varchar(100)',
'STEXT_UNI' => 'varchar(255)',
'TEXT_UNI' => 'varchar(4000)',
'MTEXT_UNI' => 'TEXT',
'TIMESTAMP' => 'INT4', // unsigned
'DECIMAL' => 'decimal(5,2)',
'DECIMAL:' => 'decimal(%d,2)',
'PDECIMAL' => 'decimal(6,3)',
'PDECIMAL:' => 'decimal(%d,3)',
'VCHAR_UNI' => 'varchar(255)',
'VCHAR_UNI:'=> 'varchar(%d)',
'VCHAR_CI' => 'varchar_ci',
'VARBINARY' => 'bytea',
),
);
}
/**
* Constructor. Set DB Object and set {@link $return_statements return_statements}.
*
* @param \phpbb\db\driver\driver_interface $db Database connection
* @param bool $return_statements True if only statements should be returned and no SQL being executed
*/
public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false)
{
parent::__construct($db, $return_statements);
// Determine mapping database type
$this->sql_layer = 'postgres';
$this->dbms_type_map = self::get_dbms_type_map();
}
/**
* {@inheritDoc}
*/
function sql_list_tables()
{
$sql = 'SELECT relname
FROM pg_stat_user_tables';
$result = $this->db->sql_query($sql);
$tables = array();
while ($row = $this->db->sql_fetchrow($result))
{
$name = current($row);
$tables[$name] = $name;
}
$this->db->sql_freeresult($result);
return $tables;
}
/**
* {@inheritDoc}
*/
function sql_create_table($table_name, $table_data)
{
// holds the DDL for a column
$columns = $statements = array();
if ($this->sql_table_exists($table_name))
{
return $this->_sql_run_sql($statements);
}
// Begin transaction
$statements[] = 'begin';
// Determine if we have created a PRIMARY KEY in the earliest
$primary_key_gen = false;
// Determine if the table requires a sequence
$create_sequence = false;
// Begin table sql statement
$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
// Iterate through the columns to create a table
foreach ($table_data['COLUMNS'] as $column_name => $column_data)
{
// here lies an array, filled with information compiled on the column's data
$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen"
{
trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR);
}
// here we add the definition of the new column to the list of columns
$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
// see if we have found a primary key set due to a column definition if we have found it, we can stop looking
if (!$primary_key_gen)
{
$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
}
// create sequence DDL based off of the existence of auto incrementing columns
if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
{
$create_sequence = $column_name;
}
}
// this makes up all the columns in the create table statement
$table_sql .= implode(",\n", $columns);
// we have yet to create a primary key for this table,
// this means that we can add the one we really wanted instead
if (!$primary_key_gen)
{
// Write primary key
if (isset($table_data['PRIMARY_KEY']))
{
if (!is_array($table_data['PRIMARY_KEY']))
{
$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
}
$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
}
}
// do we need to add a sequence for auto incrementing columns?
if ($create_sequence)
{
$statements[] = "CREATE SEQUENCE {$table_name}_seq;";
}
// close the table
$table_sql .= "\n);";
$statements[] = $table_sql;
// Write Keys
if (isset($table_data['KEYS']))
{
foreach ($table_data['KEYS'] as $key_name => $key_data)
{
if (!is_array($key_data[1]))
{
$key_data[1] = array($key_data[1]);
}
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]);
foreach ($key_stmts as $key_stmt)
{
$statements[] = $key_stmt;
}
$this->return_statements = $old_return_statements;
}
}
// Commit Transaction
$statements[] = 'commit';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_list_columns($table_name)
{
$columns = array();
$sql = "SELECT a.attname
FROM pg_class c, pg_attribute a
WHERE c.relname = '{$table_name}'
AND a.attnum > 0
AND a.attrelid = c.oid";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$column = strtolower(current($row));
$columns[$column] = $column;
}
$this->db->sql_freeresult($result);
return $columns;
}
/**
* {@inheritDoc}
*/
function sql_index_exists($table_name, $index_name)
{
$sql = "SELECT ic.relname as index_name
FROM pg_class bc, pg_class ic, pg_index i
WHERE (bc.oid = i.indrelid)
AND (ic.oid = i.indexrelid)
AND (bc.relname = '" . $table_name . "')
AND (i.indisunique != 't')
AND (i.indisprimary != 't')";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// This DBMS prefixes index names with the table name
$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']);
if (strtolower($row['index_name']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* {@inheritDoc}
*/
function sql_unique_index_exists($table_name, $index_name)
{
$sql = "SELECT ic.relname as index_name, i.indisunique
FROM pg_class bc, pg_class ic, pg_index i
WHERE (bc.oid = i.indrelid)
AND (ic.oid = i.indexrelid)
AND (bc.relname = '" . $table_name . "')
AND (i.indisprimary != 't')";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['indisunique'] != 't')
{
continue;
}
// This DBMS prefixes index names with the table name
$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']);
if (strtolower($row['index_name']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* Function to prepare some column information for better usage
* @access private
*/
function sql_prepare_column_data($table_name, $column_name, $column_data)
{
if (strlen($column_name) > 30)
{
trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
}
// Get type
list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]);
// Adjust default value if db-dependent specified
if (is_array($column_data[1]))
{
$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
}
$sql = " {$column_type} ";
$return_array = array(
'column_type' => $column_type,
'auto_increment' => false,
);
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
$default_val = "nextval('{$table_name}_seq')";
$return_array['auto_increment'] = true;
}
else if (!is_null($column_data[1]))
{
$default_val = "'" . $column_data[1] . "'";
$return_array['null'] = 'NOT NULL';
$sql .= 'NOT NULL ';
}
else
{
// Integers need to have 0 instead of empty string as default
if (strpos($column_type, 'INT') === 0)
{
$default_val = '0';
}
else
{
$default_val = "'" . $column_data[1] . "'";
}
$return_array['null'] = 'NULL';
$sql .= 'NULL ';
}
$return_array['default'] = $default_val;
$sql .= "DEFAULT {$default_val}";
// Unsigned? Then add a CHECK contraint
if (in_array($orig_column_type, $this->unsigned_types))
{
$return_array['constraint'] = "CHECK ({$column_name} >= 0)";
$sql .= " CHECK ({$column_name} >= 0)";
}
$return_array['column_type_sql'] = $sql;
return $return_array;
}
/**
* {@inheritDoc}
*/
function sql_column_add($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
// Does not support AFTER, only through temporary table
if (version_compare($this->db->sql_server_info(true), '8.0', '>='))
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql'];
}
else
{
// old versions cannot add columns with default and null information
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint'];
if (isset($column_data['null']))
{
if ($column_data['null'] == 'NOT NULL')
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL';
}
}
if (isset($column_data['default']))
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
}
}
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_column_remove($table_name, $column_name, $inline = false)
{
$statements = array();
$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_index_drop($table_name, $index_name)
{
$statements = array();
$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name;
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_table_drop($table_name)
{
$statements = array();
if (!$this->sql_table_exists($table_name))
{
return $this->_sql_run_sql($statements);
}
// the most basic operation, get rid of the table
$statements[] = 'DROP TABLE ' . $table_name;
// PGSQL does not "tightly" bind sequences and tables, we must guess...
$sql = "SELECT relname
FROM pg_class
WHERE relkind = 'S'
AND relname = '{$table_name}_seq'";
$result = $this->db->sql_query($sql);
// We don't even care about storing the results. We already know the answer if we get rows back.
if ($this->db->sql_fetchrow($result))
{
$statements[] = "DROP SEQUENCE IF EXISTS {$table_name}_seq;\n";
}
$this->db->sql_freeresult($result);
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_primary_key($table_name, $column, $inline = false)
{
$statements = array();
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_unique_index($table_name, $index_name, $column)
{
$statements = array();
$this->check_index_name_length($table_name, $index_name);
$statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_create_index($table_name, $index_name, $column)
{
$statements = array();
$this->check_index_name_length($table_name, $index_name);
// remove index length
$column = preg_replace('#:.*$#', '', $column);
$statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
return $this->_sql_run_sql($statements);
}
/**
* {@inheritDoc}
*/
function sql_list_index($table_name)
{
$index_array = array();
$sql = "SELECT ic.relname as index_name
FROM pg_class bc, pg_class ic, pg_index i
WHERE (bc.oid = i.indrelid)
AND (ic.oid = i.indexrelid)
AND (bc.relname = '" . $table_name . "')
AND (i.indisunique != 't')
AND (i.indisprimary != 't')";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']);
$index_array[] = $row['index_name'];
}
$this->db->sql_freeresult($result);
return array_map('strtolower', $index_array);
}
/**
* {@inheritDoc}
*/
function sql_column_change($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
$sql = 'ALTER TABLE ' . $table_name . ' ';
$sql_array = array();
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type'];
if (isset($column_data['null']))
{
if ($column_data['null'] == 'NOT NULL')
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL';
}
else if ($column_data['null'] == 'NULL')
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL';
}
}
if (isset($column_data['default']))
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
}
// we don't want to double up on constraints if we change different number data types
if (isset($column_data['constraint']))
{
$constraint_sql = "SELECT consrc as constraint_data
FROM pg_constraint, pg_class bc
WHERE conrelid = bc.oid
AND bc.relname = '{$table_name}'
AND NOT EXISTS (
SELECT *
FROM pg_constraint as c, pg_inherits as i
WHERE i.inhrelid = pg_constraint.conrelid
AND c.conname = pg_constraint.conname
AND c.consrc = pg_constraint.consrc
AND c.conrelid = i.inhparent
)";
$constraint_exists = false;
$result = $this->db->sql_query($constraint_sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (trim($row['constraint_data']) == trim($column_data['constraint']))
{
$constraint_exists = true;
break;
}
}
$this->db->sql_freeresult($result);
if (!$constraint_exists)
{
$sql_array[] = 'ADD ' . $column_data['constraint'];
}
}
$sql .= implode(', ', $sql_array);
$statements[] = $sql;
return $this->_sql_run_sql($statements);
}
/**
* Get a list with existing indexes for the column
*
* @param string $table_name
* @param string $column_name
* @param bool $unique Should we get unique indexes or normal ones
* @return array Array with Index name => columns
*/
public function get_existing_indexes($table_name, $column_name, $unique = false)
{
// Not supported
throw new \Exception('DBMS is not supported');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,678 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\di;
use phpbb\filesystem\filesystem;
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
class container_builder
{
/**
* @var string The environment to use.
*/
protected $environment;
/**
* @var string phpBB Root Path
*/
protected $phpbb_root_path;
/**
* @var string php file extension
*/
protected $php_ext;
/**
* The container under construction
*
* @var ContainerBuilder
*/
protected $container;
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $dbal_connection = null;
/**
* Indicates whether extensions should be used (default to true).
*
* @var bool
*/
protected $use_extensions = true;
/**
* Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config')
*
* @var string
*/
protected $config_path = null;
/**
* Indicates whether the container should be dumped to the filesystem (default to true).
*
* If DEBUG_CONTAINER is set this option is ignored and a new container is build.
*
* @var bool
*/
protected $use_cache = true;
/**
* Indicates if the container should be compiled automatically (default to true).
*
* @var bool
*/
protected $compile_container = true;
/**
* Custom parameters to inject into the container.
*
* Default to:
* array(
* 'core.root_path', $this->phpbb_root_path,
* 'core.php_ext', $this->php_ext,
* );
*
* @var array
*/
protected $custom_parameters = [];
/**
* @var \phpbb\config_php_file
*/
protected $config_php_file;
/**
* @var string
*/
protected $cache_dir;
/**
* @var array
*/
private $container_extensions;
/** @var \Exception */
private $build_exception;
/**
* Constructor
*
* @param string $phpbb_root_path Path to the phpbb includes directory.
* @param string $php_ext php file extension
*/
public function __construct($phpbb_root_path, $php_ext)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
}
/**
* Build and return a new Container respecting the current configuration
*
* @return \phpbb_cache_container|ContainerBuilder
*/
public function get_container()
{
try
{
$container_filename = $this->get_container_filename();
$config_cache = new ConfigCache($container_filename, defined('DEBUG'));
if ($this->use_cache && $config_cache->isFresh())
{
if ($this->use_extensions)
{
$autoload_cache = new ConfigCache($this->get_autoload_filename(), defined('DEBUG'));
if (!$autoload_cache->isFresh())
{
// autoload cache should be refreshed
$this->load_extensions();
}
require($this->get_autoload_filename());
}
require($config_cache->getPath());
$this->container = new \phpbb_cache_container();
}
else
{
$this->container_extensions = [
new extension\core($this->get_config_path()),
];
if ($this->use_extensions)
{
$this->load_extensions();
}
// Add tables extension after all extensions
$this->container_extensions[] = new extension\tables();
// Inject the config
if ($this->config_php_file)
{
$this->container_extensions[] = new extension\config($this->config_php_file);
}
$this->container = $this->create_container($this->container_extensions);
// Easy collections through tags
$this->container->addCompilerPass(new pass\collection_pass());
// Event listeners "phpBB style"
$this->container->addCompilerPass(new RegisterListenersPass('dispatcher', 'event.listener_listener', 'event.listener'));
// Event listeners "Symfony style"
$this->container->addCompilerPass(new RegisterListenersPass('dispatcher'));
if ($this->use_extensions)
{
$this->register_ext_compiler_pass();
}
$filesystem = new filesystem();
$loader = new YamlFileLoader($this->container, new FileLocator($filesystem->realpath($this->get_config_path())));
$loader->load($this->container->getParameter('core.environment') . '/config.yml');
$this->inject_custom_parameters();
if ($this->compile_container)
{
$this->container->compile();
if ($this->use_cache)
{
$this->dump_container($config_cache);
}
}
}
if ($this->compile_container && $this->config_php_file)
{
$this->container->set('config.php', $this->config_php_file);
}
$this->inject_dbal_driver();
return $this->container;
}
catch (\Exception $e)
{
// Don't try to recover if we are in the development environment
if ($this->get_environment() === 'development')
{
throw $e;
}
if ($this->build_exception === null)
{
$this->build_exception = $e;
return $this
->without_extensions()
->without_cache()
->with_custom_parameters(array_merge($this->custom_parameters, [
'container_exception' => $e,
]))
->get_container();
}
else
{
// Rethrow the original exception if it's still failing
throw $this->build_exception;
}
}
}
/**
* Enable the extensions.
*
* @param string $environment The environment to use
* @return $this
*/
public function with_environment($environment)
{
$this->environment = $environment;
return $this;
}
/**
* Enable the extensions.
*
* @return $this
*/
public function with_extensions()
{
$this->use_extensions = true;
return $this;
}
/**
* Disable the extensions.
*
* @return $this
*/
public function without_extensions()
{
$this->use_extensions = false;
return $this;
}
/**
* Enable the caching of the container.
*
* If DEBUG_CONTAINER is set this option is ignored and a new container is build.
*
* @return $this
*/
public function with_cache()
{
$this->use_cache = true;
return $this;
}
/**
* Disable the caching of the container.
*
* @return $this
*/
public function without_cache()
{
$this->use_cache = false;
return $this;
}
/**
* Set the cache directory.
*
* @param string $cache_dir The cache directory.
* @return $this
*/
public function with_cache_dir($cache_dir)
{
$this->cache_dir = $cache_dir;
return $this;
}
/**
* Enable the compilation of the container.
*
* @return $this
*/
public function with_compiled_container()
{
$this->compile_container = true;
return $this;
}
/**
* Disable the compilation of the container.
*
* @return $this
*/
public function without_compiled_container()
{
$this->compile_container = false;
return $this;
}
/**
* Set a custom path to find the configuration of the container.
*
* @param string $config_path
* @return $this
*/
public function with_config_path($config_path)
{
$this->config_path = $config_path;
return $this;
}
/**
* Set custom parameters to inject into the container.
*
* @param array $custom_parameters
* @return $this
*/
public function with_custom_parameters($custom_parameters)
{
$this->custom_parameters = $custom_parameters;
return $this;
}
/**
* Set custom parameters to inject into the container.
*
* @param \phpbb\config_php_file $config_php_file
* @return $this
*/
public function with_config(\phpbb\config_php_file $config_php_file)
{
$this->config_php_file = $config_php_file;
return $this;
}
/**
* Returns the path to the container configuration (default: root_path/config)
*
* @return string
*/
protected function get_config_path()
{
return $this->config_path ?: $this->phpbb_root_path . 'config';
}
/**
* Returns the path to the cache directory (default: root_path/cache/environment).
*
* @return string Path to the cache directory.
*/
protected function get_cache_dir()
{
return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/';
}
/**
* Load the enabled extensions.
*/
protected function load_extensions()
{
if ($this->config_php_file !== null)
{
// Build an intermediate container to load the ext list from the database
$container_builder = new container_builder($this->phpbb_root_path, $this->php_ext);
$ext_container = $container_builder
->without_cache()
->without_extensions()
->with_config($this->config_php_file)
->with_config_path($this->get_config_path())
->with_environment('production')
->without_compiled_container()
->get_container()
;
$ext_container->register('cache.driver', '\\phpbb\\cache\\driver\\dummy');
$ext_container->compile();
$extensions = $ext_container->get('ext.manager')->all_enabled();
// Load each extension found
$autoloaders = '<?php
/**
* Loads all extensions custom auto-loaders.
*
* This file has been auto-generated
* by phpBB while loading the extensions.
*/
';
foreach ($extensions as $ext_name => $path)
{
$extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension';
if (!class_exists($extension_class))
{
$extension_class = '\\phpbb\\extension\\di\\extension_base';
}
$this->container_extensions[] = new $extension_class($ext_name, $path);
// Load extension autoloader
$filename = $path . 'vendor/autoload.php';
if (file_exists($filename))
{
$autoloaders .= "require('{$filename}');\n";
}
}
$configCache = new ConfigCache($this->get_autoload_filename(), false);
$configCache->write($autoloaders);
require($this->get_autoload_filename());
}
else
{
// To load the extensions we need the database credentials.
// Automatically disable the extensions if we don't have them.
$this->use_extensions = false;
}
}
/**
* Dump the container to the disk.
*
* @param ConfigCache $cache The config cache
*/
protected function dump_container($cache)
{
try
{
$dumper = new PhpDumper($this->container);
$proxy_dumper = new ProxyDumper();
$dumper->setProxyDumper($proxy_dumper);
$cached_container_dump = $dumper->dump(array(
'class' => 'phpbb_cache_container',
'base_class' => 'Symfony\\Component\\DependencyInjection\\Container',
));
$cache->write($cached_container_dump, $this->container->getResources());
}
catch (IOException $e)
{
// Don't fail if the cache isn't writeable
}
}
/**
* Create the ContainerBuilder object
*
* @param array $extensions Array of Container extension objects
* @return ContainerBuilder object
*/
protected function create_container(array $extensions)
{
$container = new ContainerBuilder(new ParameterBag($this->get_core_parameters()));
$container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir()));
$extensions_alias = array();
foreach ($extensions as $extension)
{
$container->registerExtension($extension);
$extensions_alias[] = $extension->getAlias();
}
$container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias));
return $container;
}
/**
* Inject the customs parameters into the container
*/
protected function inject_custom_parameters()
{
foreach ($this->custom_parameters as $key => $value)
{
$this->container->setParameter($key, $value);
}
}
/**
* Inject the dbal connection driver into container
*/
protected function inject_dbal_driver()
{
if (empty($this->config_php_file))
{
return;
}
$config_data = $this->config_php_file->get_all();
if (!empty($config_data))
{
if ($this->dbal_connection === null)
{
$dbal_driver_class = $this->config_php_file->convert_30_dbms_to_31($this->config_php_file->get('dbms'));
/** @var \phpbb\db\driver\driver_interface $dbal_connection */
$this->dbal_connection = new $dbal_driver_class();
$this->dbal_connection->sql_connect(
$this->config_php_file->get('dbhost'),
$this->config_php_file->get('dbuser'),
$this->config_php_file->get('dbpasswd'),
$this->config_php_file->get('dbname'),
$this->config_php_file->get('dbport'),
false,
defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK
);
}
$this->container->set('dbal.conn.driver', $this->dbal_connection);
}
}
/**
* Returns the core parameters.
*
* @return array An array of core parameters
*/
protected function get_core_parameters()
{
return array_merge(
array(
'core.root_path' => $this->phpbb_root_path,
'core.php_ext' => $this->php_ext,
'core.environment' => $this->get_environment(),
'core.debug' => defined('DEBUG') ? DEBUG : false,
'core.cache_dir' => $this->get_cache_dir(),
),
$this->get_env_parameters()
);
}
/**
* Gets the environment parameters.
*
* Only the parameters starting with "PHPBB__" are considered.
*
* @return array An array of parameters
*/
protected function get_env_parameters()
{
$parameters = array();
foreach ($_SERVER as $key => $value)
{
if (0 === strpos($key, 'PHPBB__'))
{
$parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
}
}
return $parameters;
}
/**
* Get the filename under which the dumped container will be stored.
*
* @return string Path for dumped container
*/
protected function get_container_filename()
{
$container_params = [
'phpbb_root_path' => $this->phpbb_root_path,
'use_extensions' => $this->use_extensions,
'config_path' => $this->config_path,
];
return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
}
/**
* Get the filename under which the dumped extensions autoloader will be stored.
*
* @return string Path for dumped extensions autoloader
*/
protected function get_autoload_filename()
{
$container_params = [
'phpbb_root_path' => $this->phpbb_root_path,
'use_extensions' => $this->use_extensions,
'config_path' => $this->config_path,
];
return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext;
}
/**
* Return the name of the current environment.
*
* @return string
*/
protected function get_environment()
{
return $this->environment ?: PHPBB_ENVIRONMENT;
}
private function register_ext_compiler_pass()
{
$finder = new Finder();
$finder
->name('*_pass.php')
->path('di/pass')
->files()
->ignoreDotFiles(true)
->ignoreUnreadableDirs(true)
->ignoreVCS(true)
->followLinks()
->in($this->phpbb_root_path . 'ext')
;
/** @var \SplFileInfo $pass */
foreach ($finder as $pass)
{
$filename = $pass->getPathname();
$filename = substr($filename, 0, -strlen('.' . $pass->getExtension()));
$filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename);
$className = preg_replace('#^.*ext/#', '', $filename);
$className = '\\' . str_replace('/', '\\', $className);
if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true))
{
$this->container->addCompilerPass(new $className());
}
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\di\extension;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class container_configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('core');
$rootNode
->children()
->booleanNode('require_dev_dependencies')->defaultValue(false)->end()
->booleanNode('allow_install_dir')->defaultValue(false)->end()
->arrayNode('debug')
->addDefaultsIfNotSet()
->children()
->booleanNode('exceptions')->defaultValue(false)->end()
->booleanNode('load_time')->defaultValue(false)->end()
->booleanNode('sql_explain')->defaultValue(false)->end()
->booleanNode('memory')->defaultValue(false)->end()
->booleanNode('show_errors')->defaultValue(false)->end()
->end()
->end()
->arrayNode('twig')
->addDefaultsIfNotSet()
->children()
->booleanNode('debug')->defaultValue(null)->end()
->booleanNode('auto_reload')->defaultValue(null)->end()
->booleanNode('enable_debug_extension')->defaultValue(false)->end()
->end()
->end()
->arrayNode('session')
->addDefaultsIfNotSet()
->children()
->booleanNode('log_errors')->defaultValue(false)->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\di\extension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* Container core extension
*/
class core extends Extension
{
const TWIG_OPTIONS_POSITION = 7;
/**
* Config path
* @var string
*/
protected $config_path;
/**
* Constructor
*
* @param string $config_path Config path
*/
public function __construct($config_path)
{
$this->config_path = $config_path;
}
/**
* Loads a specific configuration.
*
* @param array $configs An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container)
{
$filesystem = new \phpbb\filesystem\filesystem();
$loader = new YamlFileLoader($container, new FileLocator($filesystem->realpath($this->config_path)));
$loader->load($container->getParameter('core.environment') . '/container/environment.yml');
$config = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($config, $configs);
if ($config['require_dev_dependencies'])
{
if (!class_exists('Goutte\Client', true))
{
trigger_error(
'Composer development dependencies have not been set up for the ' . $container->getParameter('core.environment') . ' environment yet, run ' .
"'php ../composer.phar install --dev' from the phpBB directory to do so.",
E_USER_ERROR
);
}
}
$container->setParameter('allow_install_dir', $config['allow_install_dir']);
// Set the Twig options if defined in the environment
$definition = $container->getDefinition('template.twig.environment');
$twig_environment_options = $definition->getArgument(static::TWIG_OPTIONS_POSITION);
if ($config['twig']['debug'])
{
$twig_environment_options['debug'] = true;
}
if ($config['twig']['auto_reload'])
{
$twig_environment_options['auto_reload'] = true;
}
// Replace the 7th argument, the options passed to the environment
$definition->replaceArgument(static::TWIG_OPTIONS_POSITION, $twig_environment_options);
if ($config['twig']['enable_debug_extension'])
{
$definition = $container->getDefinition('template.twig.extensions.debug');
$definition->addTag('twig.extension');
}
// Set the debug options
foreach ($config['debug'] as $name => $value)
{
$container->setParameter('debug.' . $name, $value);
}
// Set the log options
foreach ($config['session'] as $name => $value)
{
$container->setParameter('session.' . $name, $value);
}
}
/**
* {@inheritdoc}
*/
public function getConfiguration(array $config, ContainerBuilder $container)
{
$r = new \ReflectionClass('\phpbb\di\extension\container_configuration');
$container->addResource(new FileResource($r->getFileName()));
return new container_configuration();
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*/
public function getAlias()
{
return 'core';
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\di\extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* Container tables extension
*/
class tables extends Extension
{
/**
* {@inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
// Tables is a reserved parameter and will be overwritten at all times
$tables = [];
// Add access via 'tables' parameter to acquire array with all tables
$parameterBag = $container->getParameterBag();
$parameters = $parameterBag->all();
foreach ($parameters as $parameter_name => $parameter_value)
{
if (!preg_match('/tables\.(.+)/', $parameter_name, $matches))
{
continue;
}
$tables[$matches[1]] = $parameter_value;
}
$container->setParameter('tables', $tables);
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*/
public function getAlias()
{
return 'tables';
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\di;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Collection of services to be configured at container compile time.
*/
class service_collection extends \ArrayObject
{
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* @var array
*/
protected $service_classes;
/**
* Constructor
*
* @param ContainerInterface $container Container object
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->service_classes = array();
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new service_collection_iterator($this);
}
/**
* {@inheritdoc}
*/
public function offsetGet($index)
{
return $this->container->get($index);
}
/**
* Add a service to the collection
*
* @param string $name The service name
* @return void
*/
public function add($name)
{
$this->offsetSet($name, false);
}
/**
* Add a service's class to the collection
*
* @param string $service_id
* @param string $class
*/
public function add_service_class($service_id, $class)
{
$this->service_classes[$service_id] = $class;
}
/**
* Get services' classes
*
* @return array
*/
public function get_service_classes()
{
return $this->service_classes;
}
/**
* Returns the service associated to a class
*
* @return mixed
* @throw \RuntimeException if the
*/
public function get_by_class($class)
{
$service_id = null;
foreach ($this->service_classes as $id => $service_class)
{
if ($service_class === $class)
{
if ($service_id !== null)
{
throw new \RuntimeException('More than one service definitions found for class "'.$class.'" in collection.');
}
$service_id = $id;
}
}
if ($service_id === null)
{
throw new \RuntimeException('No service found for class "'.$class.'" in collection.');
}
return $this->offsetGet($service_id);
}
}

View File

@@ -0,0 +1,568 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\event;
/**
* Crawls through a markdown file and grabs all events
*/
class md_exporter
{
/** @var string Path where we look for files*/
protected $path;
/** @var string phpBB Root Path */
protected $root_path;
/** @var string The minimum version for the events to return */
protected $min_version;
/** @var string The maximum version for the events to return */
protected $max_version;
/** @var string */
protected $filter;
/** @var string */
protected $current_event;
/** @var array */
protected $events;
/**
* @param string $phpbb_root_path
* @param mixed $extension String 'vendor/ext' to filter, null for phpBB core
* @param string $min_version
* @param string $max_version
*/
public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null)
{
$this->root_path = $phpbb_root_path;
$this->path = $this->root_path;
if ($extension)
{
$this->path .= 'ext/' . $extension . '/';
}
$this->events = array();
$this->events_by_file = array();
$this->filter = $this->current_event = '';
$this->min_version = $min_version;
$this->max_version = $max_version;
}
/**
* Get the list of all events
*
* @return array Array with events: name => details
*/
public function get_events()
{
return $this->events;
}
/**
* @param string $md_file Relative from phpBB root
* @return int Number of events found
* @throws \LogicException
*/
public function crawl_phpbb_directory_adm($md_file)
{
$this->crawl_eventsmd($md_file, 'adm');
$file_list = $this->get_recursive_file_list($this->path . 'adm/style/');
foreach ($file_list as $file)
{
$file_name = 'adm/style/' . $file;
$this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name));
}
return count($this->events);
}
/**
* @param string $md_file Relative from phpBB root
* @return int Number of events found
* @throws \LogicException
*/
public function crawl_phpbb_directory_styles($md_file)
{
$this->crawl_eventsmd($md_file, 'styles');
$styles = array('prosilver');
foreach ($styles as $style)
{
$file_list = $this->get_recursive_file_list(
$this->path . 'styles/' . $style . '/template/'
);
foreach ($file_list as $file)
{
$file_name = 'styles/' . $style . '/template/' . $file;
$this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name));
}
}
return count($this->events);
}
/**
* @param string $md_file Relative from phpBB root
* @param string $filter Should be 'styles' or 'adm'
* @return int Number of events found
* @throws \LogicException
*/
public function crawl_eventsmd($md_file, $filter)
{
if (!file_exists($this->path . $md_file))
{
throw new \LogicException("The event docs file '{$md_file}' could not be found");
}
$file_content = file_get_contents($this->path . $md_file);
$this->filter = $filter;
$events = explode("\n\n", $file_content);
foreach ($events as $event)
{
// Last row of the file
if (strpos($event, "\n===\n") === false)
{
continue;
}
list($event_name, $details) = explode("\n===\n", $event, 2);
$this->validate_event_name($event_name);
$sorted_events = [$this->current_event, $event_name];
natsort($sorted_events);
$this->current_event = $event_name;
if (isset($this->events[$this->current_event]))
{
throw new \LogicException("The event '{$this->current_event}' is defined multiple times");
}
// Use array_values() to get actual first element and check against natural order
if (array_values($sorted_events)[0] === $event_name)
{
throw new \LogicException("The event '{$sorted_events[1]}' should be defined before '{$sorted_events[0]}'");
}
if (($this->filter == 'adm' && strpos($this->current_event, 'acp_') !== 0)
|| ($this->filter == 'styles' && strpos($this->current_event, 'acp_') === 0))
{
continue;
}
list($file_details, $details) = explode("\n* Since: ", $details, 2);
$changed_versions = array();
if (strpos($details, "\n* Changed: ") !== false)
{
list($since, $details) = explode("\n* Changed: ", $details, 2);
while (strpos($details, "\n* Changed: ") !== false)
{
list($changed, $details) = explode("\n* Changed: ", $details, 2);
$changed_versions[] = $changed;
}
list($changed, $description) = explode("\n* Purpose: ", $details, 2);
$changed_versions[] = $changed;
}
else
{
list($since, $description) = explode("\n* Purpose: ", $details, 2);
$changed_versions = array();
}
$files = $this->validate_file_list($file_details);
$since = $this->validate_since($since);
$changes = array();
foreach ($changed_versions as $changed)
{
list($changed_version, $changed_description) = $this->validate_changed($changed);
if (isset($changes[$changed_version]))
{
throw new \LogicException("Duplicate change information found for event '{$this->current_event}'");
}
$changes[$changed_version] = $changed_description;
}
$description = trim($description, "\n") . "\n";
if (!$this->version_is_filtered($since))
{
$is_filtered = false;
foreach ($changes as $version => $null)
{
if ($this->version_is_filtered($version))
{
$is_filtered = true;
break;
}
}
if (!$is_filtered)
{
continue;
}
}
$this->events[$event_name] = array(
'event' => $this->current_event,
'files' => $files,
'since' => $since,
'changed' => $changes,
'description' => $description,
);
}
return count($this->events);
}
/**
* The version to check
*
* @param string $version
* @return bool
*/
protected function version_is_filtered($version)
{
return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<='))
&& (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>='));
}
/**
* Format the php events as a wiki table
*
* @param string $action
* @return string Number of events found
*/
public function export_events_for_wiki($action = '')
{
if ($this->filter === 'adm')
{
if ($action === 'diff')
{
$wiki_page = '=== ACP Template Events ===' . "\n";
}
else
{
$wiki_page = '= ACP Template Events =' . "\n";
}
$wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
$wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n";
}
else
{
if ($action === 'diff')
{
$wiki_page = '=== Template Events ===' . "\n";
}
else
{
$wiki_page = '= Template Events =' . "\n";
}
$wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n";
$wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n";
}
foreach ($this->events as $event_name => $event)
{
$wiki_page .= "|- id=\"{$event_name}\"\n";
$wiki_page .= "| [[#{$event_name}|{$event_name}]] || ";
if ($this->filter === 'adm')
{
$wiki_page .= implode(', ', $event['files']['adm']);
}
else
{
$wiki_page .= implode(', ', $event['files']['prosilver']);
}
$wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n";
}
$wiki_page .= '|}' . "\n";
return $wiki_page;
}
/**
* Validates a template event name
*
* @param $event_name
* @return null
* @throws \LogicException
*/
public function validate_event_name($event_name)
{
if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name))
{
throw new \LogicException("Invalid event name '{$event_name}'");
}
}
/**
* Validate "Since" Information
*
* @param string $since
* @return string
* @throws \LogicException
*/
public function validate_since($since)
{
if (!$this->validate_version($since))
{
throw new \LogicException("Invalid since information found for event '{$this->current_event}'");
}
return $since;
}
/**
* Validate "Changed" Information
*
* @param string $changed
* @return string
* @throws \LogicException
*/
public function validate_changed($changed)
{
if (strpos($changed, ' ') !== false)
{
list($version, $description) = explode(' ', $changed, 2);
}
else
{
$version = $changed;
$description = '';
}
if (!$this->validate_version($version))
{
throw new \LogicException("Invalid changed information found for event '{$this->current_event}'");
}
return array($version, $description);
}
/**
* Validate "version" Information
*
* @param string $version
* @return bool True if valid, false otherwise
*/
public function validate_version($version)
{
return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version);
}
/**
* Validate the files list
*
* @param string $file_details
* @return array
* @throws \LogicException
*/
public function validate_file_list($file_details)
{
$files_list = array(
'prosilver' => array(),
'adm' => array(),
);
// Multi file list
if (strpos($file_details, "* Locations:\n + ") === 0)
{
$file_details = substr($file_details, strlen("* Locations:\n + "));
$files = explode("\n + ", $file_details);
foreach ($files as $file)
{
if (!preg_match('#^([^ ]+)( \([0-9]+\))?$#', $file))
{
throw new \LogicException("Invalid event instances for file '{$file}' found for event '{$this->current_event}'", 1);
}
list($file) = explode(" ", $file);
if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
{
throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2);
}
if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0)
{
$files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/'));
}
else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0)
{
$files_list['adm'][] = substr($file, strlen('adm/style/'));
}
else
{
throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 3);
}
$this->events_by_file[$file][] = $this->current_event;
}
}
else if ($this->filter == 'adm')
{
$file = substr($file_details, strlen('* Location: '));
if (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
{
throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
}
$files_list['adm'][] = substr($file, strlen('adm/style/'));
$this->events_by_file[$file][] = $this->current_event;
}
else
{
throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 1);
}
return $files_list;
}
/**
* Get all template events in a template file
*
* @param string $file
* @return array
* @throws \LogicException
*/
public function crawl_file_for_events($file)
{
if (!file_exists($this->path . $file))
{
throw new \LogicException("File '{$file}' does not exist", 1);
}
$event_list = array();
$file_content = file_get_contents($this->path . $file);
preg_match_all('/(?:{%|<!--) EVENT (.*) (?:%}|-->)/U', $file_content, $event_list);
return $event_list[1];
}
/**
* Validates whether all events from $file are in the md file and vice-versa
*
* @param string $file
* @param array $events
* @return true
* @throws \LogicException
*/
public function validate_events_from_file($file, array $events)
{
if (empty($this->events_by_file[$file]) && empty($events))
{
return true;
}
else if (empty($this->events_by_file[$file]))
{
$event_list = implode("', '", $events);
throw new \LogicException("File '{$file}' should not contain events, but contains: "
. "'{$event_list}'", 1);
}
else if (empty($events))
{
$event_list = implode("', '", $this->events_by_file[$file]);
throw new \LogicException("File '{$file}' contains no events, but should contain: "
. "'{$event_list}'", 1);
}
$missing_events_from_file = array();
foreach ($this->events_by_file[$file] as $event)
{
if (!in_array($event, $events))
{
$missing_events_from_file[] = $event;
}
}
if (!empty($missing_events_from_file))
{
$event_list = implode("', '", $missing_events_from_file);
throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2);
}
$missing_events_from_md = array();
foreach ($events as $event)
{
if (!in_array($event, $this->events_by_file[$file]))
{
$missing_events_from_md[] = $event;
}
}
if (!empty($missing_events_from_md))
{
$event_list = implode("', '", $missing_events_from_md);
throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3);
}
return true;
}
/**
* Returns a list of files in $dir
*
* Works recursive with any depth
*
* @param string $dir Directory to go through
* @return array List of files (including directories)
*/
public function get_recursive_file_list($dir)
{
try
{
$iterator = new \RecursiveIteratorIterator(
new \phpbb\recursive_dot_prefix_filter_iterator(
new \RecursiveDirectoryIterator(
$dir,
\FilesystemIterator::SKIP_DOTS
)
),
\RecursiveIteratorIterator::SELF_FIRST
);
}
catch (\Exception $e)
{
return array();
}
$files = array();
foreach ($iterator as $file_info)
{
/** @var \RecursiveDirectoryIterator $file_info */
if ($file_info->isDir())
{
continue;
}
$relative_path = $iterator->getInnerIterator()->getSubPathname();
if (substr($relative_path, -5) == '.html')
{
$files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path);
}
}
return $files;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\extension;
/**
* The interface extension meta classes have to implement to run custom code
* on enable/disable/purge.
*/
interface extension_interface
{
/**
* Indicate whether or not the extension can be enabled.
*
* @return bool|array True if extension is enableable, array of reasons
* if not, false for generic reason.
*/
public function is_enableable();
/**
* enable_step is executed on enabling an extension until it returns false.
*
* Calls to this function can be made in subsequent requests, when the
* function is invoked through a webserver with a too low max_execution_time.
*
* @param mixed $old_state The return value of the previous call
* of this method, or false on the first call
* @return mixed Returns false after last step, otherwise
* temporary state which is passed as an
* argument to the next step
*/
public function enable_step($old_state);
/**
* Disables the extension.
*
* Calls to this function can be made in subsequent requests, when the
* function is invoked through a webserver with a too low max_execution_time.
*
* @param mixed $old_state The return value of the previous call
* of this method, or false on the first call
* @return mixed Returns false after last step, otherwise
* temporary state which is passed as an
* argument to the next step
*/
public function disable_step($old_state);
/**
* purge_step is executed on purging an extension until it returns false.
*
* Calls to this function can be made in subsequent requests, when the
* function is invoked through a webserver with a too low max_execution_time.
*
* @param mixed $old_state The return value of the previous call
* of this method, or false on the first call
* @return mixed Returns false after last step, otherwise
* temporary state which is passed as an
* argument to the next step
*/
public function purge_step($old_state);
}

View File

@@ -0,0 +1,591 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\extension;
use phpbb\exception\runtime_exception;
use phpbb\file_downloader;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The extension manager provides means to activate/deactivate extensions.
*/
class manager
{
/** @var ContainerInterface */
protected $container;
protected $db;
protected $config;
protected $cache;
protected $php_ext;
protected $extensions;
protected $extension_table;
protected $phpbb_root_path;
protected $cache_name;
/**
* Creates a manager and loads information from database
*
* @param ContainerInterface $container A container
* @param \phpbb\db\driver\driver_interface $db A database connection
* @param \phpbb\config\config $config Config object
* @param \phpbb\filesystem\filesystem_interface $filesystem
* @param string $extension_table The name of the table holding extensions
* @param string $phpbb_root_path Path to the phpbb includes directory.
* @param string $php_ext php file extension, defaults to php
* @param \phpbb\cache\service $cache A cache instance or null
* @param string $cache_name The name of the cache variable, defaults to _ext
*/
public function __construct(ContainerInterface $container, \phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\filesystem\filesystem_interface $filesystem, $extension_table, $phpbb_root_path, $php_ext = 'php', \phpbb\cache\service $cache = null, $cache_name = '_ext')
{
$this->cache = $cache;
$this->cache_name = $cache_name;
$this->config = $config;
$this->container = $container;
$this->db = $db;
$this->extension_table = $extension_table;
$this->filesystem = $filesystem;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false;
if ($this->extensions === false)
{
$this->load_extensions();
}
}
/**
* Loads all extension information from the database
*
* @return null
*/
public function load_extensions()
{
$this->extensions = array();
// Do not try to load any extensions if the extension table
// does not exist or when installing or updating.
// Note: database updater invokes this code, and in 3.0
// there is no extension table therefore the rest of this function
// fails
if (defined('IN_INSTALL') || version_compare($this->config['version'], '3.1.0-dev', '<'))
{
return;
}
$sql = 'SELECT *
FROM ' . $this->extension_table;
$result = $this->db->sql_query($sql);
$extensions = $this->db->sql_fetchrowset($result);
$this->db->sql_freeresult($result);
foreach ($extensions as $extension)
{
$extension['ext_path'] = $this->get_extension_path($extension['ext_name']);
$this->extensions[$extension['ext_name']] = $extension;
}
ksort($this->extensions);
if ($this->cache)
{
$this->cache->put($this->cache_name, $this->extensions);
}
}
/**
* Generates the path to an extension
*
* @param string $name The name of the extension
* @param bool $phpbb_relative Whether the path should be relative to phpbb root
* @return string Path to an extension
*/
public function get_extension_path($name, $phpbb_relative = false)
{
$name = str_replace('.', '', $name);
return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/';
}
/**
* Instantiates the extension meta class for the extension with the given name
*
* @param string $name The extension name
* @return \phpbb\extension\extension_interface Instance of the extension meta class or
* \phpbb\extension\base if the class does not exist
*/
public function get_extension($name)
{
$extension_class_name = str_replace('/', '\\', $name) . '\\ext';
$migrator = $this->container->get('migrator');
if (class_exists($extension_class_name))
{
return new $extension_class_name($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true));
}
else
{
return new \phpbb\extension\base($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true));
}
}
/**
* Instantiates the metadata manager for the extension with the given name
*
* @param string $name The extension name
* @return \phpbb\extension\metadata_manager Instance of the metadata manager
*/
public function create_extension_metadata_manager($name)
{
if (!isset($this->extensions[$name]['metadata']))
{
$metadata = new \phpbb\extension\metadata_manager($name, $this->get_extension_path($name, true));
$this->extensions[$name]['metadata'] = $metadata;
}
return $this->extensions[$name]['metadata'];
}
/**
* Update the database entry for an extension
*
* @param string $name Extension name to update
* @param array $data Data to update in the database
* @param string $action Action to perform, by default 'update', may be also 'insert' or 'delete'
*/
protected function update_state($name, $data, $action = 'update')
{
switch ($action)
{
case 'insert':
$this->extensions[$name] = $data;
$this->extensions[$name]['ext_path'] = $this->get_extension_path($name);
ksort($this->extensions);
$sql = 'INSERT INTO ' . $this->extension_table . ' ' . $this->db->sql_build_array('INSERT', $data);
$this->db->sql_query($sql);
break;
case 'update':
$this->extensions[$name] = array_merge($this->extensions[$name], $data);
$sql = 'UPDATE ' . $this->extension_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
break;
case 'delete':
unset($this->extensions[$name]);
$sql = 'DELETE FROM ' . $this->extension_table . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
break;
}
if ($this->cache)
{
$this->cache->purge();
}
}
/**
* Runs a step of the extension enabling process.
*
* Allows the exentension to enable in a long running script that works
* in multiple steps across requests. State is kept for the extension
* in the extensions table.
*
* @param string $name The extension's name
* @return bool False if enabling is finished, true otherwise
*/
public function enable_step($name)
{
// ignore extensions that are already enabled
if ($this->is_enabled($name))
{
return false;
}
$old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false;
$extension = $this->get_extension($name);
if (!$extension->is_enableable())
{
return false;
}
$state = $extension->enable_step($old_state);
$active = ($state === false);
$extension_data = array(
'ext_name' => $name,
'ext_active' => $active,
'ext_state' => serialize($state),
);
$this->update_state($name, $extension_data, $this->is_configured($name) ? 'update' : 'insert');
if ($active)
{
$this->config->increment('assets_version', 1);
}
return !$active;
}
/**
* Enables an extension
*
* This method completely enables an extension. But it could be long running
* so never call this in a script that has a max_execution time.
*
* @param string $name The extension's name
* @return null
*/
public function enable($name)
{
// @codingStandardsIgnoreStart
while ($this->enable_step($name));
// @codingStandardsIgnoreEnd
}
/**
* Disables an extension
*
* Calls the disable method on the extension's meta class to allow it to
* process the event.
*
* @param string $name The extension's name
* @return bool False if disabling is finished, true otherwise
*/
public function disable_step($name)
{
// ignore extensions that are not enabled
if (!$this->is_enabled($name))
{
return false;
}
$old_state = unserialize($this->extensions[$name]['ext_state']);
$extension = $this->get_extension($name);
$state = $extension->disable_step($old_state);
$active = ($state !== false);
$extension_data = array(
'ext_active' => $active,
'ext_state' => serialize($state),
);
$this->update_state($name, $extension_data);
return $active;
}
/**
* Disables an extension
*
* Disables an extension completely at once. This process could run for a
* while so never call this in a script that has a max_execution time.
*
* @param string $name The extension's name
* @return null
*/
public function disable($name)
{
// @codingStandardsIgnoreStart
while ($this->disable_step($name));
// @codingStandardsIgnoreEnd
}
/**
* Purge an extension
*
* Disables the extension first if active, and then calls purge on the
* extension's meta class to delete the extension's database content.
*
* @param string $name The extension's name
* @return bool False if purging is finished, true otherwise
*/
public function purge_step($name)
{
// ignore extensions that are not configured
if (!$this->is_configured($name))
{
return false;
}
// disable first if necessary
if ($this->extensions[$name]['ext_active'])
{
$this->disable($name);
}
$old_state = unserialize($this->extensions[$name]['ext_state']);
$extension = $this->get_extension($name);
$state = $extension->purge_step($old_state);
$purged = ($state === false);
$extension_data = array(
'ext_state' => serialize($state),
);
$this->update_state($name, $extension_data, $purged ? 'delete' : 'update');
// continue until the state is false
return !$purged;
}
/**
* Purge an extension
*
* Purges an extension completely at once. This process could run for a while
* so never call this in a script that has a max_execution time.
*
* @param string $name The extension's name
* @return null
*/
public function purge($name)
{
// @codingStandardsIgnoreStart
while ($this->purge_step($name));
// @codingStandardsIgnoreEnd
}
/**
* Retrieves a list of all available extensions on the filesystem
*
* @return array An array with extension names as keys and paths to the
* extension as values
*/
public function all_available()
{
$available = array();
if (!is_dir($this->phpbb_root_path . 'ext/'))
{
return $available;
}
$iterator = new \RecursiveIteratorIterator(
new \phpbb\recursive_dot_prefix_filter_iterator(
new \RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', \FilesystemIterator::NEW_CURRENT_AND_KEY | \FilesystemIterator::FOLLOW_SYMLINKS)
),
\RecursiveIteratorIterator::SELF_FIRST
);
$iterator->setMaxDepth(2);
foreach ($iterator as $file_info)
{
if ($file_info->isFile() && $file_info->getFilename() == 'composer.json')
{
$ext_name = $iterator->getInnerIterator()->getSubPath();
$ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name);
if ($this->is_available($ext_name))
{
$available[$ext_name] = $this->get_extension_path($ext_name, true);
}
}
}
ksort($available);
return $available;
}
/**
* Retrieves all configured extensions.
*
* All enabled and disabled extensions are considered configured. A purged
* extension that is no longer in the database is not configured.
*
* @param bool $phpbb_relative Whether the path should be relative to phpbb root
*
* @return array An array with extension names as keys and and the
* database stored extension information as values
*/
public function all_configured($phpbb_relative = true)
{
$configured = array();
foreach ($this->extensions as $name => $data)
{
if ($this->is_configured($name))
{
unset($data['metadata']);
$data['ext_path'] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];
$configured[$name] = $data;
}
}
return $configured;
}
/**
* Retrieves all enabled extensions.
* @param bool $phpbb_relative Whether the path should be relative to phpbb root
*
* @return array An array with extension names as keys and and the
* database stored extension information as values
*/
public function all_enabled($phpbb_relative = true)
{
$enabled = array();
foreach ($this->extensions as $name => $data)
{
if ($this->is_enabled($name))
{
$enabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];
}
}
return $enabled;
}
/**
* Retrieves all disabled extensions.
*
* @param bool $phpbb_relative Whether the path should be relative to phpbb root
*
* @return array An array with extension names as keys and and the
* database stored extension information as values
*/
public function all_disabled($phpbb_relative = true)
{
$disabled = array();
foreach ($this->extensions as $name => $data)
{
if ($this->is_disabled($name))
{
$disabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path'];
}
}
return $disabled;
}
/**
* Check to see if a given extension is available on the filesystem
*
* @param string $name Extension name to check NOTE: Can be user input
* @return bool Depending on whether or not the extension is available
*/
public function is_available($name)
{
$md_manager = $this->create_extension_metadata_manager($name);
try
{
return $md_manager->get_metadata('all') && $md_manager->validate_enable();
}
catch (\phpbb\extension\exception $e)
{
return false;
}
}
/**
* Check to see if a given extension is enabled
*
* @param string $name Extension name to check
* @return bool Depending on whether or not the extension is enabled
*/
public function is_enabled($name)
{
return isset($this->extensions[$name]['ext_active']) && $this->extensions[$name]['ext_active'];
}
/**
* Check to see if a given extension is disabled
*
* @param string $name Extension name to check
* @return bool Depending on whether or not the extension is disabled
*/
public function is_disabled($name)
{
return isset($this->extensions[$name]['ext_active']) && !$this->extensions[$name]['ext_active'];
}
/**
* Check to see if a given extension is configured
*
* All enabled and disabled extensions are considered configured. A purged
* extension that is no longer in the database is not configured.
*
* @param string $name Extension name to check
* @return bool Depending on whether or not the extension is configured
*/
public function is_configured($name)
{
return isset($this->extensions[$name]['ext_active']);
}
/**
* Check the version and return the available updates (for an extension).
*
* @param \phpbb\extension\metadata_manager $md_manager The metadata manager for the version to check.
* @param bool $force_update Ignores cached data. Defaults to false.
* @param bool $force_cache Force the use of the cache. Override $force_update.
* @param string $stability Force the stability (null by default).
* @return array
* @throws runtime_exception
*/
public function version_check(\phpbb\extension\metadata_manager $md_manager, $force_update = false, $force_cache = false, $stability = null)
{
$meta = $md_manager->get_metadata('all');
if (!isset($meta['extra']['version-check']))
{
throw new runtime_exception('NO_VERSIONCHECK');
}
$version_check = $meta['extra']['version-check'];
$version_helper = new \phpbb\version_helper($this->cache, $this->config, new file_downloader());
$version_helper->set_current_version($meta['version']);
$version_helper->set_file_location($version_check['host'], $version_check['directory'], $version_check['filename'], isset($version_check['ssl']) ? $version_check['ssl'] : false);
$version_helper->force_stability($stability);
return $version_helper->get_ext_update_on_branch($force_update, $force_cache);
}
/**
* Check to see if a given extension is purged
*
* An extension is purged if it is available, not enabled and not disabled.
*
* @param string $name Extension name to check
* @return bool Depending on whether or not the extension is purged
*/
public function is_purged($name)
{
return $this->is_available($name) && !$this->is_configured($name);
}
/**
* Instantiates a \phpbb\finder.
*
* @param bool $use_all_available Should we load all extensions, or just enabled ones
* @return \phpbb\finder An extension finder instance
*/
public function get_finder($use_all_available = false)
{
$finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder');
if ($use_all_available)
{
$finder->set_extensions(array_keys($this->all_available()));
}
else
{
$finder->set_extensions(array_keys($this->all_enabled()));
}
return $finder;
}
}

View File

@@ -0,0 +1,410 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\feed\controller;
use phpbb\auth\auth;
use phpbb\config\config;
use phpbb\db\driver\driver_interface;
use \phpbb\event\dispatcher_interface;
use phpbb\exception\http_exception;
use phpbb\feed\feed_interface;
use phpbb\feed\exception\feed_unavailable_exception;
use phpbb\feed\exception\unauthorized_exception;
use phpbb\feed\helper as feed_helper;
use phpbb\controller\helper as controller_helper;
use phpbb\symfony_request;
use phpbb\user;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class feed
{
/**
* @var \Twig_Environment
*/
protected $template;
/**
* @var symfony_request
*/
protected $request;
/**
* @var controller_helper
*/
protected $controller_helper;
/**
* @var config
*/
protected $config;
/**
* @var driver_interface
*/
protected $db;
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var feed_helper
*/
protected $feed_helper;
/**
* @var user
*/
protected $user;
/**
* @var auth
*/
protected $auth;
/**
* @var dispatcher_interface
*/
protected $phpbb_dispatcher;
/**
* @var string
*/
protected $php_ext;
/**
* Constructor
*
* @param \Twig_Environment $twig
* @param symfony_request $request
* @param controller_helper $controller_helper
* @param config $config
* @param driver_interface $db
* @param ContainerInterface $container
* @param feed_helper $feed_helper
* @param user $user
* @param auth $auth
* @param dispatcher_interface $phpbb_dispatcher
* @param string $php_ext
*/
public function __construct(\Twig_Environment $twig, symfony_request $request, controller_helper $controller_helper, config $config, driver_interface $db, ContainerInterface $container, feed_helper $feed_helper, user $user, auth $auth, dispatcher_interface $phpbb_dispatcher, $php_ext)
{
$this->request = $request;
$this->controller_helper = $controller_helper;
$this->config = $config;
$this->db = $db;
$this->container = $container;
$this->feed_helper = $feed_helper;
$this->user = $user;
$this->auth = $auth;
$this->php_ext = $php_ext;
$this->template = $twig;
$this->phpbb_dispatcher = $phpbb_dispatcher;
}
/**
* Controller for /feed/forums route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function forums()
{
if (!$this->config['feed_overall_forums'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.forums'));
}
/**
* Controller for /feed/news route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function news()
{
// Get at least one news forum
$sql = 'SELECT forum_id
FROM ' . FORUMS_TABLE . '
WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
$result = $this->db->sql_query_limit($sql, 1, 0, 600);
$s_feed_news = (int) $this->db->sql_fetchfield('forum_id');
$this->db->sql_freeresult($result);
if (!$s_feed_news)
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.news'));
}
/**
* Controller for /feed/topics route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function topics()
{
if (!$this->config['feed_topics_new'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.topics'));
}
/**
* Controller for /feed/topics_new route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function topics_new()
{
return $this->topics();
}
/**
* Controller for /feed/topics_active route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function topics_active()
{
if (!$this->config['feed_topics_active'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.topics_active'));
}
/**
* Controller for /feed/forum/{forum_id} route
*
* @param int $forum_id
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function forum($forum_id)
{
if (!$this->config['feed_forum'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.forum')->set_forum_id($forum_id));
}
/**
* Controller for /feed/topic/{topic_id} route
*
* @param int $topic_id
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function topic($topic_id)
{
if (!$this->config['feed_topic'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.topic')->set_topic_id($topic_id));
}
/**
* Controller for /feed/{mode] route
*
* @return Response
*
* @throws http_exception when the feed is disabled
*/
public function overall()
{
if (!$this->config['feed_overall'])
{
$this->send_unavailable();
}
return $this->send_feed($this->container->get('feed.overall'));
}
/**
* Display a given feed
*
* @param feed_interface $feed
*
* @return Response
*/
protected function send_feed(feed_interface $feed)
{
try
{
return $this->send_feed_do($feed);
}
catch (feed_unavailable_exception $e)
{
throw new http_exception(Response::HTTP_NOT_FOUND, $e->getMessage(), $e->get_parameters(), $e);
}
catch (unauthorized_exception $e)
{
throw new http_exception(Response::HTTP_FORBIDDEN, $e->getMessage(), $e->get_parameters(), $e);
}
}
/**
* Really send the feed
*
* @param feed_interface $feed
*
* @return Response
*
* @throw exception\feed_exception
*/
protected function send_feed_do(feed_interface $feed)
{
$feed_updated_time = 0;
$item_vars = array();
$board_url = $this->feed_helper->get_board_url();
// Open Feed
$feed->open();
// Iterate through items
while ($row = $feed->get_item())
{
/**
* Event to modify the feed row
*
* @event core.feed_modify_feed_row
* @var feed_interface feed Feed instance
* @var array row Array with feed data
*
* @since 3.1.10-RC1
* @changed 3.3.0 Replace forum_id, mode, topic_id with feed instance
*/
$vars = array('feed', 'row');
extract($this->phpbb_dispatcher->trigger_event('core.feed_modify_feed_row', compact($vars)));
// BBCode options to correctly disable urls, smilies, bbcode...
if ($feed->get('options') === null)
{
// Allow all combinations
$options = 7;
if ($feed->get('enable_bbcode') !== null && $feed->get('enable_smilies') !== null && $feed->get('enable_magic_url') !== null)
{
$options = (($row[$feed->get('enable_bbcode')]) ? OPTION_FLAG_BBCODE : 0) + (($row[$feed->get('enable_smilies')]) ? OPTION_FLAG_SMILIES : 0) + (($row[$feed->get('enable_magic_url')]) ? OPTION_FLAG_LINKS : 0);
}
}
else
{
$options = $row[$feed->get('options')];
}
$title = (isset($row[$feed->get('title')]) && $row[$feed->get('title')] !== '') ? $row[$feed->get('title')] : ((isset($row[$feed->get('title2')])) ? $row[$feed->get('title2')] : '');
$published = ($feed->get('published') !== null) ? (int) $row[$feed->get('published')] : 0;
$updated = ($feed->get('updated') !== null) ? (int) $row[$feed->get('updated')] : 0;
$display_attachments = ($this->auth->acl_get('u_download') && $this->auth->acl_get('f_download', $row['forum_id']) && isset($row['post_attachment']) && $row['post_attachment']) ? true : false;
$item_row = array(
'author' => ($feed->get('creator') !== null) ? $row[$feed->get('creator')] : '',
'published' => ($published > 0) ? $this->feed_helper->format_date($published) : '',
'updated' => ($updated > 0) ? $this->feed_helper->format_date($updated) : '',
'link' => '',
'title' => censor_text($title),
'category' => ($this->config['feed_item_statistics'] && !empty($row['forum_id'])) ? $board_url . '/viewforum.' . $this->php_ext . '?f=' . $row['forum_id'] : '',
'category_name' => ($this->config['feed_item_statistics'] && isset($row['forum_name'])) ? $row['forum_name'] : '',
'description' => censor_text($this->feed_helper->generate_content($row[$feed->get('text')], $row[$feed->get('bbcode_uid')], $row[$feed->get('bitfield')], $options, $row['forum_id'], ($display_attachments ? $feed->get_attachments($row['post_id']) : array()))),
'statistics' => '',
);
// Adjust items, fill link, etc.
$feed->adjust_item($item_row, $row);
$item_vars[] = $item_row;
$feed_updated_time = max($feed_updated_time, $published, $updated);
}
// If we do not have any items at all, sending the current time is better than sending no time.
if (!$feed_updated_time)
{
$feed_updated_time = time();
}
$feed->close();
$content = $this->template->render('feed.xml.twig', array(
// Some default assignments
// FEED_IMAGE is not used (atom)
'FEED_IMAGE' => '',
'SELF_LINK' => $this->controller_helper->route($this->request->attributes->get('_route'), $this->request->attributes->get('_route_params'), true, '', UrlGeneratorInterface::ABSOLUTE_URL),
'FEED_LINK' => $board_url . '/index.' . $this->php_ext,
'FEED_TITLE' => $this->config['sitename'],
'FEED_SUBTITLE' => $this->config['site_desc'],
'FEED_UPDATED' => $this->feed_helper->format_date($feed_updated_time),
'FEED_LANG' => $this->user->lang['USER_LANG'],
'FEED_AUTHOR' => $this->config['sitename'],
// Feed entries
'FEED_ROWS' => $item_vars,
));
$response = new Response($content);
$response->headers->set('Content-Type', 'application/atom+xml');
$response->setCharset('UTF-8');
$response->setLastModified(new \DateTime('@' . $feed_updated_time));
if (!empty($this->user->data['is_bot']))
{
// Let reverse proxies know we detected a bot.
$response->headers->set('X-PHPBB-IS-BOT', 'yes');
}
return $response;
}
/**
* Throw and exception saying that the feed isn't available
*
* @throw http_exception
*/
protected function send_unavailable()
{
throw new http_exception(404, 'FEATURE_NOT_AVAILABLE');
}
}

View File

@@ -0,0 +1,147 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\feed;
/**
* Active Topics feed
*
* This will give you the last {$this->num_items} topics
* with replies made within the last {$this->sort_days} days
* including the last post.
*/
class topics_active extends topic_base
{
protected $sort_days = 7;
/**
* {@inheritdoc}
*/
public function set_keys()
{
parent::set_keys();
$this->set('author_id', 'topic_last_poster_id');
$this->set('creator', 'topic_last_poster_name');
}
/**
* {@inheritdoc}
*/
protected function get_sql()
{
$forum_ids_read = $this->get_readable_forums();
if (empty($forum_ids_read))
{
return false;
}
$in_fid_ary = array_intersect($forum_ids_read, $this->get_forum_ids());
$in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums());
if (empty($in_fid_ary))
{
return false;
}
// Search for topics in last X days
$last_post_time_sql = ($this->sort_days) ? ' AND topic_last_post_time > ' . (time() - ($this->sort_days * 24 * 3600)) : '';
// We really have to get the post ids first!
$sql = 'SELECT topic_last_post_id, topic_last_post_time
FROM ' . TOPICS_TABLE . '
WHERE topic_moved_id = 0
AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $in_fid_ary) . '
' . $last_post_time_sql . '
ORDER BY topic_last_post_time DESC, topic_last_post_id DESC';
$result = $this->db->sql_query_limit($sql, $this->num_items);
$post_ids = array();
while ($row = $this->db->sql_fetchrow($result))
{
$post_ids[] = (int) $row['topic_last_post_id'];
}
$this->db->sql_freeresult($result);
if (empty($post_ids))
{
return false;
}
parent::fetch_attachments($post_ids);
$this->sql = array(
'SELECT' => 'f.forum_id, f.forum_name,
t.topic_id, t.topic_title, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views,
t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_post_time,
p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, t.topic_visibility',
'FROM' => array(
TOPICS_TABLE => 't',
POSTS_TABLE => 'p',
),
'LEFT_JOIN' => array(
array(
'FROM' => array(FORUMS_TABLE => 'f'),
'ON' => 'p.forum_id = f.forum_id',
),
),
'WHERE' => 'p.topic_id = t.topic_id
AND ' . $this->db->sql_in_set('p.post_id', $post_ids),
'ORDER_BY' => 'p.post_time DESC, p.post_id DESC',
);
return true;
}
/**
* Returns the ids of the forums not excluded from the active list
*
* @return int[]
*/
private function get_forum_ids()
{
static $forum_ids;
$cache_name = 'feed_topic_active_forum_ids';
if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false)
{
$sql = 'SELECT forum_id
FROM ' . FORUMS_TABLE . '
WHERE forum_type = ' . FORUM_POST . '
AND ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '= 0') . '
AND ' . $this->db->sql_bit_and('forum_flags', round(log(FORUM_FLAG_ACTIVE_TOPICS, 2)), '<> 0');
$result = $this->db->sql_query($sql);
$forum_ids = array();
while ($forum_id = (int) $this->db->sql_fetchfield('forum_id'))
{
$forum_ids[$forum_id] = $forum_id;
}
$this->db->sql_freeresult($result);
$this->cache->put('_' . $cache_name, $forum_ids, 180);
}
return $forum_ids;
}
/**
* {@inheritdoc}
*/
public function adjust_item(&$item_row, &$row)
{
parent::adjust_item($item_row, $row);
$item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title'];
}
}

View File

@@ -0,0 +1,584 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
use phpbb\language\language;
/**
* Responsible for holding all file relevant information, as well as doing file-specific operations.
* The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on.
*/
class filespec
{
/** @var string File name */
protected $filename = '';
/** @var string Real name of file */
protected $realname = '';
/** @var string Upload name of file */
protected $uploadname = '';
/** @var string Mimetype of file */
protected $mimetype = '';
/** @var string File extension */
protected $extension = '';
/** @var int File size */
protected $filesize = 0;
/** @var int Width of file */
protected $width = 0;
/** @var int Height of file */
protected $height = 0;
/** @var array Image info including type and size */
protected $image_info = array();
/** @var string Destination file name */
protected $destination_file = '';
/** @var string Destination file path */
protected $destination_path = '';
/** @var bool Whether file was moved */
protected $file_moved = false;
/** @var bool Whether file is local */
protected $local = false;
/** @var bool Class initialization flag */
protected $class_initialized = false;
/** @var array Error array */
public $error = array();
/** @var upload Instance of upload class */
public $upload;
/** @var \phpbb\filesystem\filesystem_interface */
protected $filesystem;
/** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper class */
protected $php_ini;
/** @var \FastImageSize\FastImageSize */
protected $imagesize;
/** @var language Language class */
protected $language;
/** @var string phpBB root path */
protected $phpbb_root_path;
/** @var \phpbb\plupload\plupload The plupload object */
protected $plupload;
/** @var \phpbb\mimetype\guesser phpBB Mimetype guesser */
protected $mimetype_guesser;
/**
* File upload class
*
* @param \phpbb\filesystem\filesystem_interface $phpbb_filesystem Filesystem
* @param language $language Language
* @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper
* @param \FastImageSize\FastImageSize $imagesize Imagesize class
* @param string $phpbb_root_path phpBB root path
* @param \phpbb\mimetype\guesser $mimetype_guesser Mime type guesser
* @param \phpbb\plupload\plupload $plupload Plupload
*/
public function __construct(\phpbb\filesystem\filesystem_interface $phpbb_filesystem, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null)
{
$this->filesystem = $phpbb_filesystem;
$this->language = $language;
$this->php_ini = $php_ini;
$this->imagesize = $imagesize;
$this->phpbb_root_path = $phpbb_root_path;
$this->plupload = $plupload;
$this->mimetype_guesser = $mimetype_guesser;
}
/**
* Set upload ary
*
* @param array $upload_ary Upload ary
*
* @return filespec This instance of the filespec class
*/
public function set_upload_ary($upload_ary)
{
if (!isset($upload_ary) || !count($upload_ary))
{
return $this;
}
$this->class_initialized = true;
$this->filename = $upload_ary['tmp_name'];
$this->filesize = $upload_ary['size'];
$name = $upload_ary['name'];
$name = trim(utf8_basename($name));
$this->realname = $this->uploadname = $name;
$this->mimetype = $upload_ary['type'];
// Opera adds the name to the mime type
$this->mimetype = (strpos($this->mimetype, '; name') !== false) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype;
if (!$this->mimetype)
{
$this->mimetype = 'application/octet-stream';
}
$this->extension = strtolower(self::get_extension($this->realname));
// Try to get real filesize from temporary folder (not always working) ;)
$this->filesize = ($this->get_filesize($this->filename)) ?: $this->filesize;
$this->width = $this->height = 0;
$this->file_moved = false;
$this->local = (isset($upload_ary['local_mode'])) ? true : false;
return $this;
}
/**
* Set the upload namespace
*
* @param upload $namespace Instance of upload class
*
* @return filespec This instance of the filespec class
*/
public function set_upload_namespace($namespace)
{
$this->upload = $namespace;
return $this;
}
/**
* Check if class members were not properly initialised yet
*
* @return bool True if there was an init error, false if not
*/
public function init_error()
{
return !$this->class_initialized;
}
/**
* Set error in error array
*
* @param mixed $error Content for error array
*
* @return \phpbb\files\filespec This instance of the filespec class
*/
public function set_error($error)
{
$this->error[] = $error;
return $this;
}
/**
* Cleans destination filename
*
* @param string $mode Either real, unique, or unique_ext. Real creates a
* realname, filtering some characters, lowering every
* character. Unique creates a unique filename.
* @param string $prefix Prefix applied to filename
* @param string $user_id The user_id is only needed for when cleaning a user's avatar
*/
public function clean_filename($mode = 'unique', $prefix = '', $user_id = '')
{
if ($this->init_error())
{
return;
}
switch ($mode)
{
case 'real':
// Remove every extension from filename (to not let the mime bug being exposed)
if (strpos($this->realname, '.') !== false)
{
$this->realname = substr($this->realname, 0, strpos($this->realname, '.'));
}
// Replace any chars which may cause us problems with _
$bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|');
$this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname)));
$this->realname = preg_replace("/%(\w{2})/", '_', $this->realname);
$this->realname = $prefix . $this->realname . '.' . $this->extension;
break;
case 'unique':
$this->realname = $prefix . md5(unique_id());
break;
case 'avatar':
$this->extension = strtolower($this->extension);
$this->realname = $prefix . $user_id . '.' . $this->extension;
break;
case 'unique_ext':
default:
$this->realname = $prefix . md5(unique_id()) . '.' . $this->extension;
}
}
/**
* Get property from file object
*
* @param string $property Name of property
*
* @return mixed Content of property
*/
public function get($property)
{
if ($this->init_error() || !isset($this->$property))
{
return false;
}
return $this->$property;
}
/**
* Check if file is an image (mime type)
*
* @return bool true if it is an image, false if not
*/
public function is_image()
{
return (strpos($this->mimetype, 'image/') === 0);
}
/**
* Check if the file got correctly uploaded
*
* @return bool true if it is a valid upload, false if not
*/
public function is_uploaded()
{
$is_plupload = $this->plupload && $this->plupload->is_active();
if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename))
{
return false;
}
if (($this->local || $is_plupload) && !file_exists($this->filename))
{
return false;
}
return true;
}
/**
* Remove file
*/
public function remove()
{
if ($this->file_moved)
{
@unlink($this->destination_file);
}
}
/**
* Get file extension
*
* @param string $filename Filename that needs to be checked
*
* @return string Extension of the supplied filename
*/
static public function get_extension($filename)
{
$filename = utf8_basename($filename);
if (strpos($filename, '.') === false)
{
return '';
}
$filename = explode('.', $filename);
return array_pop($filename);
}
/**
* Get mime type
*
* @param string $filename Filename that needs to be checked
* @return string Mime type of supplied filename
*/
public function get_mimetype($filename)
{
if ($this->mimetype_guesser !== null)
{
$mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname);
if ($mimetype !== 'application/octet-stream')
{
$this->mimetype = $mimetype;
}
}
return $this->mimetype;
}
/**
* Get file size
*
* @param string $filename File name of file to check
*
* @return int File size
*/
public function get_filesize($filename)
{
return @filesize($filename);
}
/**
* Check the first 256 bytes for forbidden content
*
* @param array $disallowed_content Array containg disallowed content
*
* @return bool False if disallowed content found, true if not
*/
public function check_content($disallowed_content)
{
if (empty($disallowed_content))
{
return true;
}
$fp = @fopen($this->filename, 'rb');
if ($fp !== false)
{
$ie_mime_relevant = fread($fp, 256);
fclose($fp);
foreach ($disallowed_content as $forbidden)
{
if (stripos($ie_mime_relevant, '<' . $forbidden) !== false)
{
return false;
}
}
}
return true;
}
/**
* Move file to destination folder
* The phpbb_root_path variable will be applied to the destination path
*
* @param string $destination Destination path, for example $config['avatar_path']
* @param bool $overwrite If set to true, an already existing file will be overwritten
* @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped
* @param string|bool $chmod Permission mask for chmodding the file after a successful move.
* The mode entered here reflects the mode defined by {@link phpbb_chmod()}
*
* @return bool True if file was moved, false if not
* @access public
*/
public function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false)
{
if (count($this->error))
{
return false;
}
$chmod = ($chmod === false) ? CHMOD_READ | CHMOD_WRITE : $chmod;
// We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it...
$this->destination_path = $this->phpbb_root_path . $destination;
// Check if the destination path exist...
if (!file_exists($this->destination_path))
{
@unlink($this->filename);
return false;
}
$upload_mode = ($this->php_ini->getBool('open_basedir')) ? 'move' : 'copy';
$upload_mode = ($this->local) ? 'local' : $upload_mode;
$this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname);
// Check if the file already exist, else there is something wrong...
if (file_exists($this->destination_file) && !$overwrite)
{
@unlink($this->filename);
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file);
$this->file_moved = false;
return false;
}
else
{
if (file_exists($this->destination_file))
{
@unlink($this->destination_file);
}
switch ($upload_mode)
{
case 'copy':
if (!@copy($this->filename, $this->destination_file))
{
if (!@move_uploaded_file($this->filename, $this->destination_file))
{
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file);
}
}
break;
case 'move':
if (!@move_uploaded_file($this->filename, $this->destination_file))
{
if (!@copy($this->filename, $this->destination_file))
{
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file);
}
}
break;
case 'local':
if (!@copy($this->filename, $this->destination_file))
{
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file);
}
break;
}
// Remove temporary filename
@unlink($this->filename);
if (count($this->error))
{
return false;
}
try
{
$this->filesystem->phpbb_chmod($this->destination_file, $chmod);
}
catch (\phpbb\filesystem\exception\filesystem_exception $e)
{
// Do nothing
}
}
// Try to get real filesize from destination folder
$this->filesize = ($this->get_filesize($this->destination_file)) ?: $this->filesize;
// Get mimetype of supplied file
$this->mimetype = $this->get_mimetype($this->destination_file);
if ($this->is_image() && !$skip_image_check)
{
$this->width = $this->height = 0;
$this->image_info = $this->imagesize->getImageSize($this->destination_file, $this->mimetype);
if ($this->image_info !== false)
{
$this->width = $this->image_info['width'];
$this->height = $this->image_info['height'];
// Check image type
$types = upload::image_types();
if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']]))
{
if (!isset($types[$this->image_info['type']]))
{
$this->error[] = $this->language->lang('IMAGE_FILETYPE_INVALID', $this->image_info['type'], $this->mimetype);
}
else
{
$this->error[] = $this->language->lang('IMAGE_FILETYPE_MISMATCH', $types[$this->image_info['type']][0], $this->extension);
}
}
// Make sure the dimensions match a valid image
if (empty($this->width) || empty($this->height))
{
$this->error[] = $this->language->lang('ATTACHED_IMAGE_NOT_IMAGE');
}
}
else
{
$this->error[] = $this->language->lang('UNABLE_GET_IMAGE_SIZE');
}
}
$this->file_moved = true;
$this->additional_checks();
unset($this->upload);
return true;
}
/**
* Performing additional checks
*
* @return bool False if issue was found, true if not
*/
public function additional_checks()
{
if (!$this->file_moved)
{
return false;
}
// Filesize is too big or it's 0 if it was larger than the maxsize in the upload form
if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0))
{
$max_filesize = get_formatted_filesize($this->upload->max_filesize, false);
$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']);
return false;
}
if (!$this->upload->valid_dimensions($this))
{
$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_SIZE',
$this->language->lang('PIXELS', (int) $this->upload->min_width),
$this->language->lang('PIXELS', (int) $this->upload->min_height),
$this->language->lang('PIXELS', (int) $this->upload->max_width),
$this->language->lang('PIXELS', (int) $this->upload->max_height),
$this->language->lang('PIXELS', (int) $this->width),
$this->language->lang('PIXELS', (int) $this->height));
return false;
}
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
/**
* @deprecated 3.2.0-dev (To be removed 4.0.0) use \phpbb\filesystem\filesystem instead
*/
class filesystem extends \phpbb\filesystem\filesystem
{
}

View File

@@ -0,0 +1,916 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\filesystem;
use phpbb\filesystem\exception\filesystem_exception;
/**
* A class with various functions that are related to paths, files and the filesystem
*/
class filesystem implements filesystem_interface
{
/**
* Store some information about file ownership for phpBB's chmod function
*
* @var array
*/
protected $chmod_info;
/**
* Stores current working directory
*
* @var string|bool current working directory or false if it cannot be recovered
*/
protected $working_directory;
/**
* Symfony's Filesystem component
*
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $symfony_filesystem;
/**
* Constructor
*/
public function __construct()
{
$this->chmod_info = array();
$this->symfony_filesystem = new \Symfony\Component\Filesystem\Filesystem();
$this->working_directory = null;
}
/**
* {@inheritdoc}
*/
public function chgrp($files, $group, $recursive = false)
{
try
{
$this->symfony_filesystem->chgrp($files, $group, $recursive);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
// Try to recover filename
// By the time this is written that is at the end of the message
$error = trim($e->getMessage());
$file = substr($error, strrpos($error, ' '));
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_GROUP', $file, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false)
{
if (is_null($perms))
{
// Default to read permission for compatibility reasons
$perms = self::CHMOD_READ;
}
// Check if we got a permission flag
if ($perms > self::CHMOD_ALL)
{
$file_perm = $perms;
// Extract permissions
//$owner = ($file_perm >> 6) & 7; // This will be ignored
$group = ($file_perm >> 3) & 7;
$other = ($file_perm >> 0) & 7;
// Does any permissions provided? if so we add execute bit for directories
$group = ($group !== 0) ? ($group | self::CHMOD_EXECUTE) : $group;
$other = ($other !== 0) ? ($other | self::CHMOD_EXECUTE) : $other;
// Compute directory permissions
$dir_perm = (self::CHMOD_ALL << 6) + ($group << 3) + ($other << 3);
}
else
{
// Add execute bit to owner if execute bit is among perms
$owner_perm = (self::CHMOD_READ | self::CHMOD_WRITE) | ($perms & self::CHMOD_EXECUTE);
$file_perm = ($owner_perm << 6) + ($perms << 3) + ($perms << 0);
// Compute directory permissions
$perm = ($perms !== 0) ? ($perms | self::CHMOD_EXECUTE) : $perms;
$dir_perm = (($owner_perm | self::CHMOD_EXECUTE) << 6) + ($perm << 3) + ($perm << 0);
}
// Symfony's filesystem component does not support extra execution flags on directories
// so we need to implement it again
foreach ($this->to_iterator($files) as $file)
{
if ($recursive && is_dir($file) && !is_link($file))
{
$this->chmod(new \FilesystemIterator($file), $perms, true);
}
// Don't chmod links as mostly those require 0777 and that cannot be changed
if (is_dir($file) || (is_link($file) && $force_chmod_link))
{
if (true !== @chmod($file, $dir_perm))
{
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_PERMISSIONS', $file, array());
}
}
else if (is_file($file))
{
if (true !== @chmod($file, $file_perm))
{
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_PERMISSIONS', $file, array());
}
}
}
}
/**
* {@inheritdoc}
*/
public function chown($files, $user, $recursive = false)
{
try
{
$this->symfony_filesystem->chown($files, $user, $recursive);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
// Try to recover filename
// By the time this is written that is at the end of the message
$error = trim($e->getMessage());
$file = substr($error, strrpos($error, ' '));
throw new filesystem_exception('FILESYSTEM_CANNOT_CHANGE_FILE_GROUP', $file, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function clean_path($path)
{
$exploded = explode('/', $path);
$filtered = array();
foreach ($exploded as $part)
{
if ($part === '.' && !empty($filtered))
{
continue;
}
if ($part === '..' && !empty($filtered) && $filtered[count($filtered) - 1] !== '.' && $filtered[count($filtered) - 1] !== '..')
{
array_pop($filtered);
}
else
{
$filtered[] = $part;
}
}
$path = implode('/', $filtered);
return $path;
}
/**
* {@inheritdoc}
*/
public function copy($origin_file, $target_file, $override = false)
{
try
{
$this->symfony_filesystem->copy($origin_file, $target_file, $override);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
throw new filesystem_exception('FILESYSTEM_CANNOT_COPY_FILES', '', array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function dump_file($filename, $content)
{
try
{
$this->symfony_filesystem->dumpFile($filename, $content);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
throw new filesystem_exception('FILESYSTEM_CANNOT_DUMP_FILE', $filename, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function exists($files)
{
return $this->symfony_filesystem->exists($files);
}
/**
* {@inheritdoc}
*/
public function is_absolute_path($path)
{
return (isset($path[0]) && $path[0] === '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false;
}
/**
* {@inheritdoc}
*/
public function is_readable($files, $recursive = false)
{
foreach ($this->to_iterator($files) as $file)
{
if ($recursive && is_dir($file) && !is_link($file))
{
if (!$this->is_readable(new \FilesystemIterator($file), true))
{
return false;
}
}
if (!is_readable($file))
{
return false;
}
}
return true;
}
/**
* {@inheritdoc}
*/
public function is_writable($files, $recursive = false)
{
if (defined('PHP_WINDOWS_VERSION_MAJOR') || !function_exists('is_writable'))
{
foreach ($this->to_iterator($files) as $file)
{
if ($recursive && is_dir($file) && !is_link($file))
{
if (!$this->is_writable(new \FilesystemIterator($file), true))
{
return false;
}
}
if (!$this->phpbb_is_writable($file))
{
return false;
}
}
}
else
{
// use built in is_writable
foreach ($this->to_iterator($files) as $file)
{
if ($recursive && is_dir($file) && !is_link($file))
{
if (!$this->is_writable(new \FilesystemIterator($file), true))
{
return false;
}
}
if (!is_writable($file))
{
return false;
}
}
}
return true;
}
/**
* {@inheritdoc}
*/
public function make_path_relative($end_path, $start_path)
{
return $this->symfony_filesystem->makePathRelative($end_path, $start_path);
}
/**
* {@inheritdoc}
*/
public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array())
{
try
{
$this->symfony_filesystem->mirror($origin_dir, $target_dir, $iterator, $options);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
$msg = $e->getMessage();
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"'));
throw new filesystem_exception('FILESYSTEM_CANNOT_MIRROR_DIRECTORY', $filename, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function mkdir($dirs, $mode = 0777)
{
try
{
$this->symfony_filesystem->mkdir($dirs, $mode);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
$msg = $e->getMessage();
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"'));
throw new filesystem_exception('FILESYSTEM_CANNOT_CREATE_DIRECTORY', $filename, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function phpbb_chmod($files, $perms = null, $recursive = false, $force_chmod_link = false)
{
if (is_null($perms))
{
// Default to read permission for compatibility reasons
$perms = self::CHMOD_READ;
}
if (empty($this->chmod_info))
{
if (!function_exists('fileowner') || !function_exists('filegroup'))
{
$this->chmod_info['process'] = false;
}
else
{
$common_php_owner = @fileowner(__FILE__);
$common_php_group = @filegroup(__FILE__);
// And the owner and the groups PHP is running under.
$php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false;
$php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false;
// If we are unable to get owner/group, then do not try to set them by guessing
if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group)
{
$this->chmod_info['process'] = false;
}
else
{
$this->chmod_info = array(
'process' => true,
'common_owner' => $common_php_owner,
'common_group' => $common_php_group,
'php_uid' => $php_uid,
'php_gids' => $php_gids,
);
}
}
}
if ($this->chmod_info['process'])
{
try
{
foreach ($this->to_iterator($files) as $file)
{
$file_uid = @fileowner($file);
$file_gid = @filegroup($file);
// Change owner
if ($file_uid !== $this->chmod_info['common_owner'])
{
$this->chown($file, $this->chmod_info['common_owner'], $recursive);
}
// Change group
if ($file_gid !== $this->chmod_info['common_group'])
{
$this->chgrp($file, $this->chmod_info['common_group'], $recursive);
}
clearstatcache();
$file_uid = @fileowner($file);
$file_gid = @filegroup($file);
}
}
catch (filesystem_exception $e)
{
$this->chmod_info['process'] = false;
}
}
// Still able to process?
if ($this->chmod_info['process'])
{
if ($file_uid === $this->chmod_info['php_uid'])
{
$php = 'owner';
}
else if (in_array($file_gid, $this->chmod_info['php_gids']))
{
$php = 'group';
}
else
{
// Since we are setting the everyone bit anyway, no need to do expensive operations
$this->chmod_info['process'] = false;
}
}
// We are not able to determine or change something
if (!$this->chmod_info['process'])
{
$php = 'other';
}
switch ($php)
{
case 'owner':
try
{
$this->chmod($files, $perms, $recursive, $force_chmod_link);
clearstatcache();
if ($this->is_readable($files) && $this->is_writable($files))
{
break;
}
}
catch (filesystem_exception $e)
{
// Do nothing
}
case 'group':
try
{
$this->chmod($files, $perms, $recursive, $force_chmod_link);
clearstatcache();
if ((!($perms & self::CHMOD_READ) || $this->is_readable($files, $recursive)) && (!($perms & self::CHMOD_WRITE) || $this->is_writable($files, $recursive)))
{
break;
}
}
catch (filesystem_exception $e)
{
// Do nothing
}
case 'other':
default:
$this->chmod($files, $perms, $recursive, $force_chmod_link);
break;
}
}
/**
* {@inheritdoc}
*/
public function realpath($path)
{
if (!function_exists('realpath'))
{
return $this->phpbb_own_realpath($path);
}
$realpath = realpath($path);
// Strangely there are provider not disabling realpath but returning strange values. :o
// We at least try to cope with them.
if ((!$this->is_absolute_path($path) && $realpath === $path) || $realpath === false)
{
return $this->phpbb_own_realpath($path);
}
// Check for DIRECTORY_SEPARATOR at the end (and remove it!)
if (substr($realpath, -1) === DIRECTORY_SEPARATOR)
{
$realpath = substr($realpath, 0, -1);
}
return $realpath;
}
/**
* {@inheritdoc}
*/
public function remove($files)
{
try
{
$this->symfony_filesystem->remove($files);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
// Try to recover filename
// By the time this is written that is at the end of the message
$error = trim($e->getMessage());
$file = substr($error, strrpos($error, ' '));
throw new filesystem_exception('FILESYSTEM_CANNOT_DELETE_FILES', $file, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function rename($origin, $target, $overwrite = false)
{
try
{
$this->symfony_filesystem->rename($origin, $target, $overwrite);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
$msg = $e->getMessage();
$filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"'));
throw new filesystem_exception('FILESYSTEM_CANNOT_RENAME_FILE', $filename, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function symlink($origin_dir, $target_dir, $copy_on_windows = false)
{
try
{
$this->symfony_filesystem->symlink($origin_dir, $target_dir, $copy_on_windows);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
throw new filesystem_exception('FILESYSTEM_CANNOT_CREATE_SYMLINK', $origin_dir, array(), $e);
}
}
/**
* {@inheritdoc}
*/
public function touch($files, $time = null, $access_time = null)
{
try
{
$this->symfony_filesystem->touch($files, $time, $access_time);
}
catch (\Symfony\Component\Filesystem\Exception\IOException $e)
{
// Try to recover filename
// By the time this is written that is at the end of the message
$error = trim($e->getMessage());
$file = substr($error, strrpos($error, ' '));
throw new filesystem_exception('FILESYSTEM_CANNOT_TOUCH_FILES', $file, array(), $e);
}
}
/**
* phpBB's implementation of is_writable
*
* @todo Investigate if is_writable is still buggy
*
* @param string $file file/directory to check if writable
*
* @return bool true if the given path is writable
*/
protected function phpbb_is_writable($file)
{
if (file_exists($file))
{
// Canonicalise path to absolute path
$file = $this->realpath($file);
if (is_dir($file))
{
// Test directory by creating a file inside the directory
$result = @tempnam($file, 'i_w');
if (is_string($result) && file_exists($result))
{
unlink($result);
// Ensure the file is actually in the directory (returned realpathed)
return (strpos($result, $file) === 0) ? true : false;
}
}
else
{
$handle = @fopen($file, 'c');
if (is_resource($handle))
{
fclose($handle);
return true;
}
}
}
else
{
// file does not exist test if we can write to the directory
$dir = dirname($file);
if (file_exists($dir) && is_dir($dir) && $this->phpbb_is_writable($dir))
{
return true;
}
}
return false;
}
/**
* Try to resolve real path when PHP's realpath failes to do so
*
* @param string $path
* @return bool|string
*/
protected function phpbb_own_realpath($path)
{
// Replace all directory separators with '/'
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
$is_absolute_path = false;
$path_prefix = '';
if ($this->is_absolute_path($path))
{
$is_absolute_path = true;
}
else
{
// Resolve working directory and store it
if (is_null($this->working_directory))
{
if (function_exists('getcwd'))
{
$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', getcwd());
}
//
// From this point on we really just guessing
// If chdir were called we screwed
//
else if (function_exists('debug_backtrace'))
{
$call_stack = debug_backtrace(0);
$this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', dirname($call_stack[count($call_stack) - 1]['file']));
}
else
{
//
// Assuming that the working directory is phpBB root
// we could use this as a fallback, when phpBB will use controllers
// everywhere this will be a safe assumption
//
//$dir_parts = explode(DIRECTORY_SEPARATOR, __DIR__);
//$namespace_parts = explode('\\', trim(__NAMESPACE__, '\\'));
//$namespace_part_count = count($namespace_parts);
// Check if we still loading from root
//if (array_slice($dir_parts, -$namespace_part_count) === $namespace_parts)
//{
// $this->working_directory = implode('/', array_slice($dir_parts, 0, -$namespace_part_count));
//}
//else
//{
// $this->working_directory = false;
//}
$this->working_directory = false;
}
}
if ($this->working_directory !== false)
{
$is_absolute_path = true;
$path = $this->working_directory . '/' . $path;
}
}
if ($is_absolute_path)
{
if (defined('PHP_WINDOWS_VERSION_MAJOR'))
{
$path_prefix = $path[0] . ':';
$path = substr($path, 2);
}
else
{
$path_prefix = '';
}
}
$resolved_path = $this->resolve_path($path, $path_prefix, $is_absolute_path);
if ($resolved_path === false)
{
return false;
}
if (!@file_exists($resolved_path) || (!@is_dir($resolved_path . '/') && !is_file($resolved_path)))
{
return false;
}
// Return OS specific directory separators
$resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved_path);
// Check for DIRECTORY_SEPARATOR at the end (and remove it!)
if (substr($resolved, -1) === DIRECTORY_SEPARATOR)
{
return substr($resolved, 0, -1);
}
return $resolved;
}
/**
* Convert file(s) to \Traversable object
*
* This is the same function as Symfony's toIterator, but that is private
* so we cannot use it.
*
* @param string|array|\Traversable $files filename/list of filenames
* @return \Traversable
*/
protected function to_iterator($files)
{
if (!$files instanceof \Traversable)
{
$files = new \ArrayObject(is_array($files) ? $files : array($files));
}
return $files;
}
/**
* Try to resolve symlinks in path
*
* @param string $path The path to resolve
* @param string $prefix The path prefix (on windows the drive letter)
* @param bool $absolute Whether or not the path is absolute
* @param bool $return_array Whether or not to return path parts
*
* @return string|array|bool returns the resolved path or an array of parts of the path if $return_array is true
* or false if path cannot be resolved
*/
protected function resolve_path($path, $prefix = '', $absolute = false, $return_array = false)
{
if ($return_array)
{
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
trim ($path, '/');
$path_parts = explode('/', $path);
$resolved = array();
$resolved_path = $prefix;
$file_found = false;
foreach ($path_parts as $path_part)
{
if ($file_found)
{
return false;
}
if (empty($path_part) || ($path_part === '.' && ($absolute || !empty($resolved))))
{
continue;
}
else if ($absolute && $path_part === '..')
{
if (empty($resolved))
{
// No directories above root
return false;
}
array_pop($resolved);
$resolved_path = false;
}
else if ($path_part === '..' && !empty($resolved) && !in_array($resolved[count($resolved) - 1], array('.', '..')))
{
array_pop($resolved);
$resolved_path = false;
}
else
{
if ($resolved_path === false)
{
if (empty($resolved))
{
$resolved_path = ($absolute) ? $prefix . '/' . $path_part : $path_part;
}
else
{
$tmp_array = $resolved;
if ($absolute)
{
array_unshift($tmp_array, $prefix);
}
$resolved_path = implode('/', $tmp_array);
}
}
$current_path = $resolved_path . '/' . $path_part;
// Resolve symlinks
if (@is_link($current_path))
{
if (!function_exists('readlink'))
{
return false;
}
$link = readlink($current_path);
// Is link has an absolute path in it?
if ($this->is_absolute_path($link))
{
if (defined('PHP_WINDOWS_VERSION_MAJOR'))
{
$prefix = $link[0] . ':';
$link = substr($link, 2);
}
else
{
$prefix = '';
}
$resolved = $this->resolve_path($link, $prefix, true, true);
$absolute = true;
}
else
{
$resolved = $this->resolve_path($resolved_path . '/' . $link, $prefix, $absolute, true);
}
if (!$resolved)
{
return false;
}
$resolved_path = false;
}
else if (@is_dir($current_path . '/'))
{
$resolved[] = $path_part;
$resolved_path = $current_path;
}
else if (@is_file($current_path))
{
$resolved[] = $path_part;
$resolved_path = $current_path;
$file_found = true;
}
else
{
return false;
}
}
}
// If at the end of the path there were a .. or .
// we need to build the path again.
// Only doing this when a string is expected in return
if ($resolved_path === false && $return_array === false)
{
if (empty($resolved))
{
$resolved_path = ($absolute) ? $prefix . '/' : './';
}
else
{
$tmp_array = $resolved;
if ($absolute)
{
array_unshift($tmp_array, $prefix);
}
$resolved_path = implode('/', $tmp_array);
}
}
return ($return_array) ? $resolved : $resolved_path;
}
}

View File

@@ -0,0 +1,284 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\filesystem;
/**
* Interface for phpBB's filesystem service
*/
interface filesystem_interface
{
/**
* chmod all permissions flag
*
* @var int
*/
const CHMOD_ALL = 7;
/**
* chmod read permissions flag
*
* @var int
*/
const CHMOD_READ = 4;
/**
* chmod write permissions flag
*
* @var int
*/
const CHMOD_WRITE = 2;
/**
* chmod execute permissions flag
*
* @var int
*/
const CHMOD_EXECUTE = 1;
/**
* Change owner group of files/directories
*
* @param string|array|\Traversable $files The file(s)/directorie(s) to change group
* @param string $group The group that should own the files/directories
* @param bool $recursive If the group should be changed recursively
* @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function chgrp($files, $group, $recursive = false);
/**
* Global function for chmodding directories and files for internal use
*
* The function accepts filesystem_interface::CHMOD_ flags in the permission argument
* or the user can specify octal values (or any integer if it makes sense). All directories will have
* an execution bit appended, if the user group (owner, group or other) has any bit specified.
*
* @param string|array|\Traversable $files The file/directory to be chmodded
* @param int $perms Permissions to set
* @param bool $recursive If the permissions should be changed recursively
* @param bool $force_chmod_link Try to apply permissions to symlinks as well
*
* @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false);
/**
* Change owner group of files/directories
*
* @param string|array|\Traversable $files The file(s)/directorie(s) to change group
* @param string $user The owner user name
* @param bool $recursive Whether change the owner recursively or not
*
* @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function chown($files, $user, $recursive = false);
/**
* Eliminates useless . and .. components from specified path.
*
* @param string $path Path to clean
*
* @return string Cleaned path
*/
public function clean_path($path);
/**
* Copies a file.
*
* This method only copies the file if the origin file is newer than the target file.
*
* By default, if the target already exists, it is not overridden.
*
* @param string $origin_file The original filename
* @param string $target_file The target filename
* @param bool $override Whether to override an existing file or not
*
* @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied
*/
public function copy($origin_file, $target_file, $override = false);
/**
* Atomically dumps content into a file.
*
* @param string $filename The file to be written to.
* @param string $content The data to write into the file.
*
* @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be written
*/
public function dump_file($filename, $content);
/**
* Checks the existence of files or directories.
*
* @param string|array|\Traversable $files files/directories to check
*
* @return bool Returns true if all files/directories exist, false otherwise
*/
public function exists($files);
/**
* Checks if a path is absolute or not
*
* @param string $path Path to check
*
* @return bool true if the path is absolute, false otherwise
*/
public function is_absolute_path($path);
/**
* Checks if files/directories are readable
*
* @param string|array|\Traversable $files files/directories to check
* @param bool $recursive Whether or not directories should be checked recursively
*
* @return bool True when the files/directories are readable, otherwise false.
*/
public function is_readable($files, $recursive = false);
/**
* Test if a file/directory is writable
*
* @param string|array|\Traversable $files files/directories to perform write test on
* @param bool $recursive Whether or not directories should be checked recursively
*
* @return bool True when the files/directories are writable, otherwise false.
*/
public function is_writable($files, $recursive = false);
/**
* Given an existing path, convert it to a path relative to a given starting path
*
* @param string $end_path Absolute path of target
* @param string $start_path Absolute path where traversal begins
*
* @return string Path of target relative to starting path
*/
public function make_path_relative($end_path, $start_path);
/**
* Mirrors a directory to another.
*
* @param string $origin_dir The origin directory
* @param string $target_dir The target directory
* @param \Traversable $iterator A Traversable instance
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied.
* The filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array());
/**
* Creates a directory recursively.
*
* @param string|array|\Traversable $dirs The directory path
* @param int $mode The directory mode
*
* @throws \phpbb\filesystem\exception\filesystem_exception On any directory creation failure
* The filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function mkdir($dirs, $mode = 0777);
/**
* Global function for chmodding directories and files for internal use
*
* This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions.
* The function determines owner and group from common.php file and sets the same to the provided file.
* The function uses bit fields to build the permissions.
* The function sets the appropriate execute bit on directories.
*
* Supported constants representing bit fields are:
*
* filesystem_interface::CHMOD_ALL - all permissions (7)
* filesystem_interface::CHMOD_READ - read permission (4)
* filesystem_interface::CHMOD_WRITE - write permission (2)
* filesystem_interface::CHMOD_EXECUTE - execute permission (1)
*
* NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions.
*
* @param string|array|\Traversable $file The file/directory to be chmodded
* @param int $perms Permissions to set
* @param bool $recursive If the permissions should be changed recursively
* @param bool $force_chmod_link Try to apply permissions to symlinks as well
*
* @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function phpbb_chmod($file, $perms = null, $recursive = false, $force_chmod_link = false);
/**
* A wrapper for PHP's realpath
*
* Try to resolve realpath when PHP's realpath is not available, or
* known to be buggy.
*
* @param string $path Path to resolve
*
* @return string Resolved path
*/
public function realpath($path);
/**
* Removes files or directories.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws \phpbb\filesystem\exception\filesystem_exception When removal fails.
* The filename which triggered the error can be
* retrieved by filesystem_exception::get_filename()
*/
public function remove($files);
/**
* Renames a file or a directory.
*
* @param string $origin The origin filename or directory
* @param string $target The new filename or directory
* @param bool $overwrite Whether to overwrite the target if it already exists
*
* @throws \phpbb\filesystem\exception\filesystem_exception When target file or directory already exists,
* or origin cannot be renamed.
*/
public function rename($origin, $target, $overwrite = false);
/**
* Creates a symbolic link or copy a directory.
*
* @param string $origin_dir The origin directory path
* @param string $target_dir The symbolic link name
* @param bool $copy_on_windows Whether to copy files if on Windows
*
* @throws \phpbb\filesystem\exception\filesystem_exception When symlink fails
*/
public function symlink($origin_dir, $target_dir, $copy_on_windows = false);
/**
* Sets access and modification time of file.
*
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create
* @param int $time The touch time as a Unix timestamp
* @param int $access_time The access time as a Unix timestamp
*
* @throws \phpbb\filesystem\exception\filesystem_exception When touch fails
*/
public function touch($files, $time = null, $access_time = null);
}

View File

@@ -0,0 +1,547 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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;
/**
* The finder provides a simple way to locate files in the core and a set of extensions
*/
class finder
{
protected $extensions;
protected $filesystem;
protected $phpbb_root_path;
protected $cache;
protected $php_ext;
/**
* The cache variable name used to store $this->cached_queries in $this->cache.
*
* Allows the use of multiple differently configured finders with the same cache.
* @var string
*/
protected $cache_name;
/**
* An associative array, containing all search parameters set in methods.
* @var array
*/
protected $query;
/**
* A map from md5 hashes of serialized queries to their previously retrieved
* results.
* @var array
*/
protected $cached_queries;
/**
* Creates a new finder instance with its dependencies
*
* @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem instance
* @param string $phpbb_root_path Path to the phpbb root directory
* @param \phpbb\cache\service $cache A cache instance or null
* @param string $php_ext php file extension
* @param string $cache_name The name of the cache variable, defaults to
* _ext_finder
*/
public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path = '', \phpbb\cache\service $cache = null, $php_ext = 'php', $cache_name = '_ext_finder')
{
$this->filesystem = $filesystem;
$this->phpbb_root_path = $phpbb_root_path;
$this->cache = $cache;
$this->php_ext = $php_ext;
$this->cache_name = $cache_name;
$this->query = array(
'core_path' => false,
'core_suffix' => false,
'core_prefix' => false,
'core_directory' => false,
'extension_suffix' => false,
'extension_prefix' => false,
'extension_directory' => false,
);
$this->extensions = array();
$this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false;
}
/**
* Set the array of extensions
*
* @param array $extensions A list of extensions that should be searched as well
* @param bool $replace_list Should the list be emptied before adding the extensions
* @return \phpbb\finder This object for chaining calls
*/
public function set_extensions(array $extensions, $replace_list = true)
{
if ($replace_list)
{
$this->extensions = array();
}
foreach ($extensions as $ext_name)
{
$this->extensions[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/';
}
return $this;
}
/**
* Sets a core path to be searched in addition to extensions
*
* @param string $core_path The path relative to phpbb_root_path
* @return \phpbb\finder This object for chaining calls
*/
public function core_path($core_path)
{
$this->query['core_path'] = $core_path;
return $this;
}
/**
* Sets the suffix all files found in extensions and core must match.
*
* There is no default file extension, so to find PHP files only, you will
* have to specify .php as a suffix. However when using get_classes, the .php
* file extension is automatically added to suffixes.
*
* @param string $suffix A filename suffix
* @return \phpbb\finder This object for chaining calls
*/
public function suffix($suffix)
{
$this->core_suffix($suffix);
$this->extension_suffix($suffix);
return $this;
}
/**
* Sets a suffix all files found in extensions must match
*
* There is no default file extension, so to find PHP files only, you will
* have to specify .php as a suffix. However when using get_classes, the .php
* file extension is automatically added to suffixes.
*
* @param string $extension_suffix A filename suffix
* @return \phpbb\finder This object for chaining calls
*/
public function extension_suffix($extension_suffix)
{
$this->query['extension_suffix'] = $extension_suffix;
return $this;
}
/**
* Sets a suffix all files found in the core path must match
*
* There is no default file extension, so to find PHP files only, you will
* have to specify .php as a suffix. However when using get_classes, the .php
* file extension is automatically added to suffixes.
*
* @param string $core_suffix A filename suffix
* @return \phpbb\finder This object for chaining calls
*/
public function core_suffix($core_suffix)
{
$this->query['core_suffix'] = $core_suffix;
return $this;
}
/**
* Sets the prefix all files found in extensions and core must match
*
* @param string $prefix A filename prefix
* @return \phpbb\finder This object for chaining calls
*/
public function prefix($prefix)
{
$this->core_prefix($prefix);
$this->extension_prefix($prefix);
return $this;
}
/**
* Sets a prefix all files found in extensions must match
*
* @param string $extension_prefix A filename prefix
* @return \phpbb\finder This object for chaining calls
*/
public function extension_prefix($extension_prefix)
{
$this->query['extension_prefix'] = $extension_prefix;
return $this;
}
/**
* Sets a prefix all files found in the core path must match
*
* @param string $core_prefix A filename prefix
* @return \phpbb\finder This object for chaining calls
*/
public function core_prefix($core_prefix)
{
$this->query['core_prefix'] = $core_prefix;
return $this;
}
/**
* Sets a directory all files found in extensions and core must be contained in
*
* Automatically sets the core_directory if its value does not differ from
* the current directory.
*
* @param string $directory
* @return \phpbb\finder This object for chaining calls
*/
public function directory($directory)
{
$this->core_directory($directory);
$this->extension_directory($directory);
return $this;
}
/**
* Sets a directory all files found in extensions must be contained in
*
* @param string $extension_directory
* @return \phpbb\finder This object for chaining calls
*/
public function extension_directory($extension_directory)
{
$this->query['extension_directory'] = $this->sanitise_directory($extension_directory);
return $this;
}
/**
* Sets a directory all files found in the core path must be contained in
*
* @param string $core_directory
* @return \phpbb\finder This object for chaining calls
*/
public function core_directory($core_directory)
{
$this->query['core_directory'] = $this->sanitise_directory($core_directory);
return $this;
}
/**
* Removes occurrences of /./ and makes sure path ends without trailing slash
*
* @param string $directory A directory pattern
* @return string A cleaned up directory pattern
*/
protected function sanitise_directory($directory)
{
$directory = $this->filesystem->clean_path($directory);
$dir_len = strlen($directory);
if ($dir_len > 1 && $directory[$dir_len - 1] === '/')
{
$directory = substr($directory, 0, -1);
}
return $directory;
}
/**
* Finds classes matching the configured options if they follow phpBB naming rules.
*
* The php file extension is automatically added to suffixes.
*
* Note: If a file is matched but contains a class name not following the
* phpBB naming rules an incorrect class name will be returned.
*
* @param bool $cache Whether the result should be cached
* @return array An array of found class names
*/
public function get_classes($cache = true)
{
$this->query['extension_suffix'] .= '.' . $this->php_ext;
$this->query['core_suffix'] .= '.' . $this->php_ext;
$files = $this->find($cache, false);
return $this->get_classes_from_files($files);
}
/**
* Get class names from a list of files
*
* @param array $files Array of files (from find())
* @return array Array of class names
*/
public function get_classes_from_files($files)
{
$classes = array();
foreach ($files as $file => $ext_name)
{
$class = substr($file, 0, -strlen('.' . $this->php_ext));
if ($ext_name === '/' && preg_match('#^includes/#', $file))
{
$class = preg_replace('#^includes/#', '', $class);
$classes[] = 'phpbb_' . str_replace('/', '_', $class);
}
else
{
$class = preg_replace('#^ext/#', '', $class);
$classes[] = '\\' . str_replace('/', '\\', $class);
}
}
return $classes;
}
/**
* Finds all directories matching the configured options
*
* @param bool $cache Whether the result should be cached
* @param bool $extension_keys Whether the result should have extension name as array key
* @return array An array of paths to found directories
*/
public function get_directories($cache = true, $extension_keys = false)
{
return $this->find_with_root_path($cache, true, $extension_keys);
}
/**
* Finds all files matching the configured options.
*
* @param bool $cache Whether the result should be cached
* @return array An array of paths to found files
*/
public function get_files($cache = true)
{
return $this->find_with_root_path($cache, false);
}
/**
* A wrapper around the general find which prepends a root path to results
*
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
* @param bool $extension_keys If true, result will be associative array
* with extension name as key
* @return array An array of paths to found items
*/
protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false)
{
$items = $this->find($cache, $is_dir);
$result = array();
foreach ($items as $item => $ext_name)
{
if ($extension_keys)
{
$result[$ext_name] = $this->phpbb_root_path . $item;
}
else
{
$result[] = $this->phpbb_root_path . $item;
}
}
return $result;
}
/**
* Finds all file system entries matching the configured options
*
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
* @return array An array of paths to found items
*/
public function find($cache = true, $is_dir = false)
{
$extensions = $this->extensions;
if ($this->query['core_path'])
{
$extensions['/'] = $this->phpbb_root_path . $this->query['core_path'];
}
$files = array();
$file_list = $this->find_from_paths($extensions, $cache, $is_dir);
foreach ($file_list as $file)
{
$files[$file['named_path']] = $file['ext_name'];
}
return $files;
}
/**
* Finds all file system entries matching the configured options for one
* specific extension
*
* @param string $extension_name Name of the extension
* @param string $extension_path Relative path to the extension root directory
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
* @return array An array of paths to found items
*/
public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false)
{
$extensions = array(
$extension_name => $extension_path,
);
$files = array();
$file_list = $this->find_from_paths($extensions, $cache, $is_dir);
foreach ($file_list as $file)
{
$files[$file['named_path']] = $file['ext_name'];
}
return $files;
}
/**
* Finds all file system entries matching the configured options from
* an array of paths
*
* @param array $extensions Array of extensions (name => full relative path)
* @param bool $cache Whether the result should be cached
* @param bool $is_dir Directories will be returned when true, only files
* otherwise
* @return array An array of paths to found items
*/
public function find_from_paths($extensions, $cache = true, $is_dir = false)
{
$this->query['is_dir'] = $is_dir;
$query = md5(serialize($this->query) . serialize($extensions));
if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query]))
{
return $this->cached_queries[$query];
}
$files = array();
foreach ($extensions as $name => $path)
{
$ext_name = $name;
if (!file_exists($path))
{
continue;
}
if ($name === '/')
{
$location = $this->query['core_path'];
$name = '';
$suffix = $this->query['core_suffix'];
$prefix = $this->query['core_prefix'];
$directory = $this->query['core_directory'];
}
else
{
$location = 'ext/';
$name .= '/';
$suffix = $this->query['extension_suffix'];
$prefix = $this->query['extension_prefix'];
$directory = $this->query['extension_directory'];
}
// match only first directory if leading slash is given
if ($directory === '/')
{
$directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#');
}
else if ($directory && $directory[0] === '/')
{
if (!$is_dir)
{
$path .= substr($directory, 1);
}
$directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#');
}
else
{
$directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#');
}
if ($is_dir)
{
$directory_pattern .= '$';
}
$directory_pattern = '#' . $directory_pattern . '#';
if (is_dir($path))
{
$iterator = new \RecursiveIteratorIterator(
new \phpbb\recursive_dot_prefix_filter_iterator(
new \RecursiveDirectoryIterator(
$path,
\FilesystemIterator::SKIP_DOTS
)
),
\RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file_info)
{
$filename = $file_info->getFilename();
if ($file_info->isDir() == $is_dir)
{
if ($is_dir)
{
$relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR;
if ($relative_path[0] !== DIRECTORY_SEPARATOR)
{
$relative_path = DIRECTORY_SEPARATOR . $relative_path;
}
}
else
{
$relative_path = $iterator->getInnerIterator()->getSubPathname();
if ($directory && $directory[0] === '/')
{
$relative_path = str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR . $relative_path;
}
else
{
$relative_path = DIRECTORY_SEPARATOR . $relative_path;
}
}
if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) &&
(!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) &&
(!$directory || preg_match($directory_pattern, $relative_path)))
{
$files[] = array(
'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)),
'ext_name' => $ext_name,
'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/',
'filename' => $filename,
);
}
}
}
}
}
if ($cache && $this->cache)
{
$this->cached_queries[$query] = $files;
$this->cache->put($this->cache_name, $this->cached_queries);
}
return $files;
}
}

View File

@@ -0,0 +1,294 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\group;
use phpbb\auth\auth;
use phpbb\cache\service as cache;
use phpbb\config\config;
use phpbb\language\language;
use phpbb\event\dispatcher_interface;
use phpbb\path_helper;
use phpbb\user;
class helper
{
/** @var auth */
protected $auth;
/** @var cache */
protected $cache;
/** @var config */
protected $config;
/** @var language */
protected $language;
/** @var dispatcher_interface */
protected $dispatcher;
/** @var path_helper */
protected $path_helper;
/** @var user */
protected $user;
/** @var string phpBB root path */
protected $phpbb_root_path;
/** @var array Return templates for a group name string */
protected $name_strings;
/**
* Constructor
*
* @param auth $auth Authentication object
* @param cache $cache Cache service object
* @param config $config Configuration object
* @param language $language Language object
* @param dispatcher_interface $dispatcher Event dispatcher object
* @param path_helper $path_helper Path helper object
* @param user $user User object
*/
public function __construct(auth $auth, cache $cache, config $config, language $language, dispatcher_interface $dispatcher, path_helper $path_helper, user $user)
{
$this->auth = $auth;
$this->cache = $cache;
$this->config = $config;
$this->language = $language;
$this->dispatcher = $dispatcher;
$this->path_helper = $path_helper;
$this->user = $user;
$this->phpbb_root_path = $path_helper->get_phpbb_root_path();
/** @html Group name spans and links for usage in the template */
$this->name_strings = array(
'base_url' => "{$path_helper->get_phpbb_root_path()}memberlist.{$path_helper->get_php_ext()}?mode=group&amp;g={GROUP_ID}",
'tpl_noprofile' => '<span class="username">{GROUP_NAME}</span>',
'tpl_noprofile_colour' => '<span class="username-coloured" style="color: {GROUP_COLOUR};">{GROUP_NAME}</span>',
'tpl_profile' => '<a class="username" href="{PROFILE_URL}">{GROUP_NAME}</a>',
'tpl_profile_colour' => '<a class="username-coloured" href="{PROFILE_URL}" style="color: {GROUP_COLOUR};">{GROUP_NAME}</a>',
);
}
/**
* @param $group_name string The stored group name
*
* @return string Group name or translated group name if it exists
*/
public function get_name($group_name)
{
return $this->language->is_set('G_' . utf8_strtoupper($group_name)) ? $this->language->lang('G_' . utf8_strtoupper($group_name)) : $group_name;
}
/**
* Get group name details for placing into templates.
*
* @html Group name spans and links
*
* @param string $mode Profile (for getting an url to the profile),
* group_name (for obtaining the group name),
* colour (for obtaining the group colour),
* full (for obtaining a coloured group name link to the group's profile),
* no_profile (the same as full but forcing no profile link)
* @param int $group_id The group id
* @param string $group_name The group name
* @param string $group_colour The group colour
* @param mixed $custom_profile_url optional parameter to specify a profile url. The group id gets appended to this url as &amp;g={group_id}
*
* @return string A string consisting of what is wanted based on $mode.
*/
public function get_name_string($mode, $group_id, $group_name, $group_colour = '', $custom_profile_url = false)
{
$s_is_bots = ($group_name === 'BOTS');
// This switch makes sure we only run code required for the mode
switch ($mode)
{
case 'full':
case 'no_profile':
case 'colour':
// Build correct group colour
$group_colour = $group_colour ? '#' . $group_colour : '';
// Return colour
if ($mode === 'colour')
{
$group_name_string = $group_colour;
break;
}
// no break;
case 'group_name':
// Build correct group name
$group_name = $this->get_name($group_name);
// Return group name
if ($mode === 'group_name')
{
$group_name_string = $group_name;
break;
}
// no break;
case 'profile':
// Build correct profile url - only show if not anonymous and permission to view profile if registered user
// For anonymous the link leads to a login page.
if ($group_id && !$s_is_bots && ($this->user->data['user_id'] == ANONYMOUS || $this->auth->acl_get('u_viewprofile')))
{
$profile_url = ($custom_profile_url !== false) ? $custom_profile_url . '&amp;g=' . (int) $group_id : str_replace(array('={GROUP_ID}', '=%7BGROUP_ID%7D'), '=' . (int) $group_id, append_sid($this->name_strings['base_url']));
}
else
{
$profile_url = '';
}
// Return profile
if ($mode === 'profile')
{
$group_name_string = $profile_url;
break;
}
// no break;
}
if (!isset($group_name_string))
{
if (($mode === 'full' && empty($profile_url)) || $mode === 'no_profile' || $s_is_bots)
{
$group_name_string = str_replace(array('{GROUP_COLOUR}', '{GROUP_NAME}'), array($group_colour, $group_name), (!$group_colour) ? $this->name_strings['tpl_noprofile'] : $this->name_strings['tpl_noprofile_colour']);
}
else
{
$group_name_string = str_replace(array('{PROFILE_URL}', '{GROUP_COLOUR}', '{GROUP_NAME}'), array($profile_url, $group_colour, $group_name), (!$group_colour) ? $this->name_strings['tpl_profile'] : $this->name_strings['tpl_profile_colour']);
}
}
$name_strings = $this->name_strings;
/**
* Use this event to change the output of the group name
*
* @event core.modify_group_name_string
* @var string mode profile|group_name|colour|full|no_profile
* @var int group_id The group identifier
* @var string group_name The group name
* @var string group_colour The group colour
* @var string custom_profile_url Optional parameter to specify a profile url.
* @var string group_name_string The string that has been generated
* @var array name_strings Array of original return templates
* @since 3.2.8-RC1
*/
$vars = array(
'mode',
'group_id',
'group_name',
'group_colour',
'custom_profile_url',
'group_name_string',
'name_strings',
);
extract($this->dispatcher->trigger_event('core.modify_group_name_string', compact($vars)));
return $group_name_string;
}
/**
* Get group rank title and image
*
* @html Group rank image element
*
* @param array $group_data The current stored group data
*
* @return array An associative array containing the rank title (title),
* the rank image as full img tag (img) and the rank image source (img_src)
*/
public function get_rank($group_data)
{
$group_rank_data = array(
'title' => null,
'img' => null,
'img_src' => null,
);
/**
* Preparing a group's rank before displaying
*
* @event core.get_group_rank_before
* @var array group_data Array with group's data
* @since 3.2.8-RC1
*/
$vars = array('group_data');
extract($this->dispatcher->trigger_event('core.get_group_rank_before', compact($vars)));
if (!empty($group_data['group_rank']))
{
// Only obtain ranks if group rank is set
$ranks = $this->cache->obtain_ranks();
if (isset($ranks['special'][$group_data['group_rank']]))
{
$rank = $ranks['special'][$group_data['group_rank']];
$group_rank_data['title'] = $rank['rank_title'];
$group_rank_data['img_src'] = (!empty($rank['rank_image'])) ? $this->path_helper->update_web_root_path($this->phpbb_root_path . $this->config['ranks_path'] . '/' . $rank['rank_image']) : '';
/** @html Group rank image element for usage in the template */
$group_rank_data['img'] = (!empty($rank['rank_image'])) ? '<img src="' . $group_rank_data['img_src'] . '" alt="' . $rank['rank_title'] . '" title="' . $rank['rank_title'] . '" />' : '';
}
}
/**
* Modify a group's rank before displaying
*
* @event core.get_group_rank_after
* @var array group_data Array with group's data
* @var array group_rank_data Group rank data
* @since 3.2.8-RC1
*/
$vars = array(
'group_data',
'group_rank_data',
);
extract($this->dispatcher->trigger_event('core.get_group_rank_after', compact($vars)));
return $group_rank_data;
}
/**
* Get group avatar.
* Wrapper function for phpbb_get_group_avatar()
*
* @param array $group_row Row from the groups table
* @param string $alt Optional language string for alt tag within image, can be a language key or text
* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
* @param bool $lazy If true, will be lazy loaded (requires JS)
*
* @return string Avatar html
*/
function get_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false)
{
return phpbb_get_group_avatar($group_row, $alt, $ignore_config, $lazy);
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\help\controller;
/**
* BBCode help page
*/
class bbcode extends controller
{
/**
* @return string The title of the page
*/
public function display()
{
$this->language->add_lang('help/bbcode');
$this->template->assign_block_vars('navlinks', array(
'BREADCRUMB_NAME' => $this->language->lang('BBCODE_GUIDE'),
'U_BREADCRUMB' => $this->helper->route('phpbb_help_bbcode_controller'),
));
$this->manager->add_block(
'HELP_BBCODE_BLOCK_INTRO',
false,
array(
'HELP_BBCODE_INTRO_BBCODE_QUESTION' => 'HELP_BBCODE_INTRO_BBCODE_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_TEXT',
false,
array(
'HELP_BBCODE_TEXT_BASIC_QUESTION' => 'HELP_BBCODE_TEXT_BASIC_ANSWER',
'HELP_BBCODE_TEXT_COLOR_QUESTION' => 'HELP_BBCODE_TEXT_COLOR_ANSWER',
'HELP_BBCODE_TEXT_COMBINE_QUESTION' => 'HELP_BBCODE_TEXT_COMBINE_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_QUOTES',
false,
array(
'HELP_BBCODE_QUOTES_TEXT_QUESTION' => 'HELP_BBCODE_QUOTES_TEXT_ANSWER',
'HELP_BBCODE_QUOTES_CODE_QUESTION' => 'HELP_BBCODE_QUOTES_CODE_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_LISTS',
false,
array(
'HELP_BBCODE_LISTS_UNORDERER_QUESTION' => 'HELP_BBCODE_LISTS_UNORDERER_ANSWER',
'HELP_BBCODE_LISTS_ORDERER_QUESTION' => 'HELP_BBCODE_LISTS_ORDERER_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_LINKS',
true,
array(
'HELP_BBCODE_LINKS_BASIC_QUESTION' => 'HELP_BBCODE_LINKS_BASIC_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_IMAGES',
false,
array(
'HELP_BBCODE_IMAGES_BASIC_QUESTION' => 'HELP_BBCODE_IMAGES_BASIC_ANSWER',
'HELP_BBCODE_IMAGES_ATTACHMENT_QUESTION' => 'HELP_BBCODE_IMAGES_ATTACHMENT_ANSWER',
)
);
$this->manager->add_block(
'HELP_BBCODE_BLOCK_OTHERS',
false,
array(
'HELP_BBCODE_OTHERS_CUSTOM_QUESTION' => 'HELP_BBCODE_OTHERS_CUSTOM_ANSWER',
)
);
return $this->language->lang('BBCODE_GUIDE');
}
}

View File

@@ -0,0 +1,170 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\help\controller;
/**
* FAQ help page
*/
class faq extends controller
{
/**
* @return string The title of the page
*/
public function display()
{
$this->language->add_lang('help/faq');
$this->template->assign_block_vars('navlinks', array(
'BREADCRUMB_NAME' => $this->language->lang('FAQ_EXPLAIN'),
'U_BREADCRUMB' => $this->helper->route('phpbb_help_faq_controller'),
));
$this->manager->add_block(
'HELP_FAQ_BLOCK_LOGIN',
false,
array(
'HELP_FAQ_LOGIN_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_ANSWER',
'HELP_FAQ_LOGIN_COPPA_QUESTION' => 'HELP_FAQ_LOGIN_COPPA_ANSWER',
'HELP_FAQ_LOGIN_CANNOT_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_REGISTER_ANSWER',
'HELP_FAQ_LOGIN_REGISTER_CONFIRM_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_ANSWER',
'HELP_FAQ_LOGIN_CANNOT_LOGIN_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANSWER',
'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_ANSWER',
'HELP_FAQ_LOGIN_LOST_PASSWORD_QUESTION' => 'HELP_FAQ_LOGIN_LOST_PASSWORD_ANSWER',
'HELP_FAQ_LOGIN_AUTO_LOGOUT_QUESTION' => 'HELP_FAQ_LOGIN_AUTO_LOGOUT_ANSWER',
'HELP_FAQ_LOGIN_DELETE_COOKIES_QUESTION' => 'HELP_FAQ_LOGIN_DELETE_COOKIES_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_USERSETTINGS',
false,
array(
'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_QUESTION' => 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_ANSWER',
'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_QUESTION' => 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_ANSWER',
'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER',
'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'HELP_FAQ_USERSETTINGS_SERVERTIME_ANSWER',
'HELP_FAQ_USERSETTINGS_LANGUAGE_QUESTION' => 'HELP_FAQ_USERSETTINGS_LANGUAGE_ANSWER',
'HELP_FAQ_USERSETTINGS_AVATAR_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_ANSWER',
'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_ANSWER',
'HELP_FAQ_USERSETTINGS_RANK_QUESTION' => 'HELP_FAQ_USERSETTINGS_RANK_ANSWER',
'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_QUESTION' => 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_POSTING',
false,
array(
'HELP_FAQ_POSTING_CREATE_QUESTION' => 'HELP_FAQ_POSTING_CREATE_ANSWER',
'HELP_FAQ_POSTING_EDIT_DELETE_QUESTION' => 'HELP_FAQ_POSTING_EDIT_DELETE_ANSWER',
'HELP_FAQ_POSTING_SIGNATURE_QUESTION' => 'HELP_FAQ_POSTING_SIGNATURE_ANSWER',
'HELP_FAQ_POSTING_POLL_CREATE_QUESTION' => 'HELP_FAQ_POSTING_POLL_CREATE_ANSWER',
'HELP_FAQ_POSTING_POLL_ADD_QUESTION' => 'HELP_FAQ_POSTING_POLL_ADD_ANSWER',
'HELP_FAQ_POSTING_POLL_EDIT_QUESTION' => 'HELP_FAQ_POSTING_POLL_EDIT_ANSWER',
'HELP_FAQ_POSTING_FORUM_RESTRICTED_QUESTION' => 'HELP_FAQ_POSTING_FORUM_RESTRICTED_ANSWER',
'HELP_FAQ_POSTING_NO_ATTACHMENTS_QUESTION' => 'HELP_FAQ_POSTING_NO_ATTACHMENTS_ANSWER',
'HELP_FAQ_POSTING_WARNING_QUESTION' => 'HELP_FAQ_POSTING_WARNING_ANSWER',
'HELP_FAQ_POSTING_REPORT_QUESTION' => 'HELP_FAQ_POSTING_REPORT_ANSWER',
'HELP_FAQ_POSTING_DRAFT_QUESTION' => 'HELP_FAQ_POSTING_DRAFT_ANSWER',
'HELP_FAQ_POSTING_QUEUE_QUESTION' => 'HELP_FAQ_POSTING_QUEUE_ANSWER',
'HELP_FAQ_POSTING_BUMP_QUESTION' => 'HELP_FAQ_POSTING_BUMP_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_FORMATTING',
false,
array(
'HELP_FAQ_FORMATTING_BBOCDE_QUESTION' => 'HELP_FAQ_FORMATTING_BBOCDE_ANSWER',
'HELP_FAQ_FORMATTING_HTML_QUESTION' => 'HELP_FAQ_FORMATTING_HTML_ANSWER',
'HELP_FAQ_FORMATTING_SMILIES_QUESTION' => 'HELP_FAQ_FORMATTING_SMILIES_ANSWER',
'HELP_FAQ_FORMATTING_IMAGES_QUESTION' => 'HELP_FAQ_FORMATTING_IMAGES_ANSWER',
'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_QUESTION' => 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_ANSWER',
'HELP_FAQ_FORMATTING_ANNOUNCEMENT_QUESTION' => 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_ANSWER',
'HELP_FAQ_FORMATTING_STICKIES_QUESTION' => 'HELP_FAQ_FORMATTING_STICKIES_ANSWER',
'HELP_FAQ_FORMATTING_LOCKED_QUESTION' => 'HELP_FAQ_FORMATTING_LOCKED_ANSWER',
'HELP_FAQ_FORMATTING_ICONS_QUESTION' => 'HELP_FAQ_FORMATTING_ICONS_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_GROUPS',
true,
array(
'HELP_FAQ_GROUPS_ADMINISTRATORS_QUESTION' => 'HELP_FAQ_GROUPS_ADMINISTRATORS_ANSWER',
'HELP_FAQ_GROUPS_MODERATORS_QUESTION' => 'HELP_FAQ_GROUPS_MODERATORS_ANSWER',
'HELP_FAQ_GROUPS_USERGROUPS_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_ANSWER',
'HELP_FAQ_GROUPS_USERGROUPS_JOIN_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_ANSWER',
'HELP_FAQ_GROUPS_USERGROUPS_LEAD_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_ANSWER',
'HELP_FAQ_GROUPS_COLORS_QUESTION' => 'HELP_FAQ_GROUPS_COLORS_ANSWER',
'HELP_FAQ_GROUPS_DEFAULT_QUESTION' => 'HELP_FAQ_GROUPS_DEFAULT_ANSWER',
'HELP_FAQ_GROUPS_TEAM_QUESTION' => 'HELP_FAQ_GROUPS_TEAM_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_PMS',
false,
array(
'HELP_FAQ_PMS_CANNOT_SEND_QUESTION' => 'HELP_FAQ_PMS_CANNOT_SEND_ANSWER',
'HELP_FAQ_PMS_UNWANTED_QUESTION' => 'HELP_FAQ_PMS_UNWANTED_ANSWER',
'HELP_FAQ_PMS_SPAM_QUESTION' => 'HELP_FAQ_PMS_SPAM_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_FRIENDS',
false,
array(
'HELP_FAQ_FRIENDS_BASIC_QUESTION' => 'HELP_FAQ_FRIENDS_BASIC_ANSWER',
'HELP_FAQ_FRIENDS_MANAGE_QUESTION' => 'HELP_FAQ_FRIENDS_MANAGE_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_SEARCH',
false,
array(
'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'HELP_FAQ_SEARCH_FORUM_ANSWER',
'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER',
'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'HELP_FAQ_SEARCH_BLANK_ANSWER',
'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'HELP_FAQ_SEARCH_MEMBERS_ANSWER',
'HELP_FAQ_SEARCH_OWN_QUESTION' => 'HELP_FAQ_SEARCH_OWN_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_BOOKMARKS',
false,
array(
'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER',
'HELP_FAQ_BOOKMARKS_TOPIC_QUESTION' => 'HELP_FAQ_BOOKMARKS_TOPIC_ANSWER',
'HELP_FAQ_BOOKMARKS_FORUM_QUESTION' => 'HELP_FAQ_BOOKMARKS_FORUM_ANSWER',
'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_ATTACHMENTS',
false,
array(
'HELP_FAQ_ATTACHMENTS_ALLOWED_QUESTION' => 'HELP_FAQ_ATTACHMENTS_ALLOWED_ANSWER',
'HELP_FAQ_ATTACHMENTS_OWN_QUESTION' => 'HELP_FAQ_ATTACHMENTS_OWN_ANSWER',
)
);
$this->manager->add_block(
'HELP_FAQ_BLOCK_ISSUES',
false,
array(
'HELP_FAQ_ISSUES_WHOIS_PHPBB_QUESTION' => 'HELP_FAQ_ISSUES_WHOIS_PHPBB_ANSWER',
'HELP_FAQ_ISSUES_FEATURE_QUESTION' => 'HELP_FAQ_ISSUES_FEATURE_ANSWER',
'HELP_FAQ_ISSUES_LEGAL_QUESTION' => 'HELP_FAQ_ISSUES_LEGAL_ANSWER',
'HELP_FAQ_ISSUES_ADMIN_QUESTION' => 'HELP_FAQ_ISSUES_ADMIN_ANSWER',
)
);
return $this->language->lang('FAQ_EXPLAIN');
}
}

View File

@@ -0,0 +1,413 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\controller;
use phpbb\install\helper\config;
use phpbb\install\helper\navigation\navigation_provider;
use phpbb\language\language;
use phpbb\language\language_file_helper;
use phpbb\path_helper;
use phpbb\request\request;
use phpbb\request\request_interface;
use phpbb\routing\router;
use phpbb\symfony_request;
use phpbb\template\template;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;
/**
* A duplicate of \phpbb\controller\helper
*
* This class is necessary because of controller\helper's legacy function calls
* to page_header() page_footer() functions which has unavailable dependencies.
*/
class helper
{
/**
* @var config
*/
protected $installer_config;
/**
* @var \phpbb\language\language
*/
protected $language;
/**
* @var bool|string
*/
protected $language_cookie;
/**
* @var \phpbb\language\language_file_helper
*/
protected $lang_helper;
/**
* @var \phpbb\install\helper\navigation\navigation_provider
*/
protected $navigation_provider;
/**
* @var \phpbb\template\template
*/
protected $template;
/**
* @var \phpbb\path_helper
*/
protected $path_helper;
/**
* @var \phpbb\request\request
*/
protected $phpbb_request;
/**
* @var \phpbb\symfony_request
*/
protected $request;
/**
* @var \phpbb\routing\router
*/
protected $router;
/**
* @var string
*/
protected $phpbb_admin_path;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* Constructor
*
* @param config $config
* @param language $language
* @param language_file_helper $lang_helper
* @param navigation_provider $nav
* @param template $template
* @param path_helper $path_helper
* @param request $phpbb_request
* @param symfony_request $request
* @param router $router
* @param string $phpbb_root_path
*/
public function __construct(config $config, language $language, language_file_helper $lang_helper, navigation_provider $nav, template $template, path_helper $path_helper, request $phpbb_request, symfony_request $request, router $router, $phpbb_root_path)
{
$this->installer_config = $config;
$this->language = $language;
$this->language_cookie = false;
$this->lang_helper = $lang_helper;
$this->navigation_provider = $nav;
$this->template = $template;
$this->path_helper = $path_helper;
$this->phpbb_request = $phpbb_request;
$this->request = $request;
$this->router = $router;
$this->phpbb_root_path = $phpbb_root_path;
$this->phpbb_admin_path = $phpbb_root_path . 'adm/';
}
/**
* Automate setting up the page and creating the response object.
*
* @param string $template_file The template handle to render
* @param string $page_title The title of the page to output
* @param bool $selected_language True to enable language selector it, false otherwise
* @param int $status_code The status code to be sent to the page header
*
* @return Response object containing rendered page
*/
public function render($template_file, $page_title = '', $selected_language = false, $status_code = 200)
{
$this->page_header($page_title, $selected_language);
$this->template->set_filenames(array(
'body' => $template_file,
));
$response = new Response($this->template->assign_display('body'), $status_code);
// Set language cookie
if ($this->language_cookie !== false)
{
$cookie = new Cookie('lang', $this->language_cookie, time() + 3600);
$response->headers->setCookie($cookie);
$this->language_cookie = false;
}
return $response;
}
/**
* Returns path from route name
*
* @param string $route_name
* @param array $parameters
*
* @return string
*/
public function route($route_name, $parameters = array())
{
$url = $this->router->generate($route_name, $parameters);
return $url;
}
/**
* Handles language selector form
*/
public function handle_language_select()
{
$lang = null;
// Check if language form has been submited
$submit = $this->phpbb_request->variable('change_lang', '');
if (!empty($submit))
{
$lang = $this->phpbb_request->variable('language', '');
}
// Retrieve language from cookie
$lang_cookie = $this->phpbb_request->variable('lang', '', false, request_interface::COOKIE);
if (empty($lang) && !empty($lang_cookie))
{
$lang = $lang_cookie;
}
$lang = (!empty($lang) && strpos($lang, '/') === false) ? $lang : null;
$this->language_cookie = $lang;
$this->render_language_select($lang);
if ($lang !== null)
{
$this->language->set_user_language($lang, true);
$this->installer_config->set('user_language', $lang);
}
}
/**
* Process navigation data to reflect active/completed stages
*
* @param \phpbb\install\helper\iohandler\iohandler_interface|null $iohandler
*/
public function handle_navigation($iohandler = null)
{
$nav_data = $this->installer_config->get_navigation_data();
// Set active navigation stage
if (isset($nav_data['active']) && is_array($nav_data['active']))
{
if ($iohandler !== null)
{
$iohandler->set_active_stage_menu($nav_data['active']);
}
$this->navigation_provider->set_nav_property($nav_data['active'], array(
'selected' => true,
'completed' => false,
));
}
// Set finished navigation stages
if (isset($nav_data['finished']) && is_array($nav_data['finished']))
{
foreach ($nav_data['finished'] as $finished_stage)
{
if ($iohandler !== null)
{
$iohandler->set_finished_stage_menu($finished_stage);
}
$this->navigation_provider->set_nav_property($finished_stage, array(
'selected' => false,
'completed' => true,
));
}
}
}
/**
* Set default template variables
*
* @param string $page_title Title of the page
* @param bool $selected_language True to enable language selector it, false otherwise
*/
protected function page_header($page_title, $selected_language = false)
{
// Path to templates
$paths = array($this->phpbb_root_path . 'install/update/new/adm/', $this->phpbb_admin_path);
$paths = array_filter($paths, 'is_dir');
$path = array_shift($paths);
$path = substr($path, strlen($this->phpbb_root_path));
$this->template->assign_vars(array(
'L_CHANGE' => $this->language->lang('CHANGE'),
'L_COLON' => $this->language->lang('COLON'),
'L_INSTALL_PANEL' => $this->language->lang('INSTALL_PANEL'),
'L_SELECT_LANG' => $this->language->lang('SELECT_LANG'),
'L_SKIP' => $this->language->lang('SKIP'),
'PAGE_TITLE' => $this->language->lang($page_title),
'T_IMAGE_PATH' => $this->path_helper->get_web_root_path() . $path . 'images',
'T_JQUERY_LINK' => $this->path_helper->get_web_root_path() . $path . '../assets/javascript/jquery-3.4.1.min.js',
'T_TEMPLATE_PATH' => $this->path_helper->get_web_root_path() . $path . 'style',
'T_ASSETS_PATH' => $this->path_helper->get_web_root_path() . $path . '../assets',
'S_CONTENT_DIRECTION' => $this->language->lang('DIRECTION'),
'S_CONTENT_FLOW_BEGIN' => ($this->language->lang('DIRECTION') === 'ltr') ? 'left' : 'right',
'S_CONTENT_FLOW_END' => ($this->language->lang('DIRECTION') === 'ltr') ? 'right' : 'left',
'S_CONTENT_ENCODING' => 'UTF-8',
'S_LANG_SELECT' => $selected_language,
'S_USER_LANG' => $this->language->lang('USER_LANG'),
));
$this->render_navigation();
}
/**
* Render navigation
*/
protected function render_navigation()
{
// Get navigation items
$nav_array = $this->navigation_provider->get();
$nav_array = $this->sort_navigation_level($nav_array);
$active_main_menu = $this->get_active_main_menu($nav_array);
// Pass navigation to template
foreach ($nav_array as $key => $entry)
{
$this->template->assign_block_vars('t_block1', array(
'L_TITLE' => $this->language->lang($entry['label']),
'S_SELECTED' => ($active_main_menu === $key),
'U_TITLE' => $this->route($entry['route']),
));
if (is_array($entry[0]) && $active_main_menu === $key)
{
$entry[0] = $this->sort_navigation_level($entry[0]);
foreach ($entry[0] as $name => $sub_entry)
{
if (isset($sub_entry['stage']) && $sub_entry['stage'] === true)
{
$this->template->assign_block_vars('l_block2', array(
'L_TITLE' => $this->language->lang($sub_entry['label']),
'S_SELECTED' => (isset($sub_entry['selected']) && $sub_entry['selected'] === true),
'S_COMPLETE' => (isset($sub_entry['completed']) && $sub_entry['completed'] === true),
'STAGE_NAME' => $name,
));
}
else
{
$this->template->assign_block_vars('l_block1', array(
'L_TITLE' => $this->language->lang($sub_entry['label']),
'S_SELECTED' => (isset($sub_entry['route']) && $sub_entry['route'] === $this->request->get('_route')),
'U_TITLE' => $this->route($sub_entry['route']),
));
}
}
}
}
}
/**
* Render language select form
*
* @param string $selected_language
*/
protected function render_language_select($selected_language = null)
{
$langs = $this->lang_helper->get_available_languages();
foreach ($langs as $lang)
{
$this->template->assign_block_vars('language_select_item', array(
'VALUE' => $lang['iso'],
'NAME' => $lang['local_name'],
'SELECTED' => ($lang['iso'] === $selected_language),
));
}
}
/**
* Returns the name of the active main menu item
*
* @param array $nav_array
*
* @return string|bool Returns the name of the active main menu element, if the element not found, returns false
*/
protected function get_active_main_menu($nav_array)
{
$active_route = $this->request->get('_route');
foreach ($nav_array as $nav_name => $nav_options)
{
$current_menu = $nav_name;
if (isset($nav_options['route']) && $nav_options['route'] === $active_route)
{
return $nav_name;
}
if (is_array($nav_options[0]))
{
foreach ($nav_options[0] as $sub_menus)
{
if (isset($sub_menus['route']) && $sub_menus['route'] === $active_route)
{
return $current_menu;
}
}
}
}
return false;
}
/**
* Sorts the top level of navigation array
*
* @param array $nav_array Navigation array
*
* @return array
*/
protected function sort_navigation_level($nav_array)
{
$sorted = array();
foreach ($nav_array as $key => $nav)
{
$order = (isset($nav['order'])) ? $nav['order'] : 0;
$sorted[$order][$key] = $nav;
}
// Linearization of navigation array
$nav_array = array();
ksort($sorted);
foreach ($sorted as $nav)
{
$nav_array = array_merge($nav_array, $nav);
}
return $nav_array;
}
}

View File

@@ -0,0 +1,191 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\helper;
use phpbb\install\exception\cannot_build_container_exception;
use phpbb\language\language;
use phpbb\request\request;
class container_factory
{
/**
* @var language
*/
protected $language;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $php_ext;
/**
* @var \phpbb\request\request
*/
protected $request;
/**
* @var update_helper
*/
protected $update_helper;
/**
* The full phpBB container
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Constructor
*
* @param language $language Language service
* @param request $request Request interface
* @param update_helper $update_helper Update helper
* @param string $phpbb_root_path Path to phpBB's root
* @param string $php_ext Extension of PHP files
*/
public function __construct(language $language, request $request, update_helper $update_helper, $phpbb_root_path, $php_ext)
{
$this->language = $language;
$this->request = $request;
$this->update_helper = $update_helper;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->container = null;
}
/**
* Container getter
*
* @param null|string $service_name Name of the service to return
*
* @return \Symfony\Component\DependencyInjection\ContainerInterface|Object phpBB's dependency injection container
* or the service specified in $service_name
*
* @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException If the service is not defined
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException When a circular reference is detected
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException When the service is not defined
*/
public function get($service_name = null)
{
// Check if container was built, if not try to build it
if ($this->container === null)
{
$this->build_container();
}
return ($service_name === null) ? $this->container : $this->container->get($service_name);
}
/**
* Returns the specified parameter from the container
*
* @param string $param_name
*
* @return mixed
*
* @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built
*/
public function get_parameter($param_name)
{
// Check if container was built, if not try to build it
if ($this->container === null)
{
$this->build_container();
}
return $this->container->getParameter($param_name);
}
/**
* Build dependency injection container
*
* @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built
*/
protected function build_container()
{
// If the container has been already built just return.
// Although this should never happen
if ($this->container instanceof \Symfony\Component\DependencyInjection\ContainerInterface)
{
return;
}
// Check whether container can be built
// We need config.php for that so let's check if it has been set up yet
if (!filesize($this->phpbb_root_path . 'config.' . $this->php_ext))
{
throw new cannot_build_container_exception();
}
$phpbb_config_php_file = new \phpbb\config_php_file($this->phpbb_root_path, $this->php_ext);
$phpbb_container_builder = new \phpbb\di\container_builder($this->phpbb_root_path, $this->php_ext);
// For BC with functions that we need during install
global $phpbb_container, $table_prefix;
$disable_super_globals = $this->request->super_globals_disabled();
// This is needed because container_builder::get_env_parameters() uses $_SERVER
if ($disable_super_globals)
{
$this->request->enable_super_globals();
}
$other_config_path = $this->phpbb_root_path . 'install/update/new/config';
$config_path = (is_dir($other_config_path)) ? $other_config_path : $this->phpbb_root_path . 'config';
$this->container = $phpbb_container_builder
->with_environment('production')
->with_config($phpbb_config_php_file)
->with_config_path($config_path)
->without_compiled_container()
->get_container();
// Setting request is required for the compatibility globals as those are generated from
// this container
if (!$this->container->isFrozen())
{
$this->container->register('request')->setSynthetic(true);
$this->container->register('language')->setSynthetic(true);
}
$this->container->set('request', $this->request);
$this->container->set('language', $this->language);
$this->container->compile();
$phpbb_container = $this->container;
$table_prefix = $phpbb_config_php_file->get('table_prefix');
// Restore super globals to previous state
if ($disable_super_globals)
{
$this->request->disable_super_globals();
}
// Get compatibility globals and constants
$this->update_helper->include_file('includes/compatibility_globals.' . $this->php_ext);
register_compatibility_globals();
$this->update_helper->include_file('includes/constants.' . $this->php_ext);
}
}

View File

@@ -0,0 +1,421 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\helper;
use phpbb\install\exception\invalid_dbms_exception;
/**
* Database related general functionality for installer
*/
class database
{
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var array
*/
protected $supported_dbms = array(
// Note: php 5.5 alpha 2 deprecated mysql.
// Keep mysqli before mysql in this list.
'mysqli' => array(
'LABEL' => 'MySQL with MySQLi Extension',
'SCHEMA' => 'mysql_41',
'MODULE' => 'mysqli',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\mysqli',
'AVAILABLE' => true,
'2.0.x' => true,
),
'mssql_odbc'=> array(
'LABEL' => 'MS SQL Server [ ODBC ]',
'SCHEMA' => 'mssql',
'MODULE' => 'odbc',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\mssql_odbc',
'AVAILABLE' => true,
'2.0.x' => true,
),
'mssqlnative' => array(
'LABEL' => 'MS SQL Server 2005+ [ Native ]',
'SCHEMA' => 'mssql',
'MODULE' => 'sqlsrv',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\mssqlnative',
'AVAILABLE' => true,
'2.0.x' => false,
),
'oracle' => array(
'LABEL' => 'Oracle',
'SCHEMA' => 'oracle',
'MODULE' => 'oci8',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\oracle',
'AVAILABLE' => true,
'2.0.x' => false,
),
'postgres' => array(
'LABEL' => 'PostgreSQL 8.3+',
'SCHEMA' => 'postgres',
'MODULE' => 'pgsql',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\postgres',
'AVAILABLE' => true,
'2.0.x' => true,
),
'sqlite3' => array(
'LABEL' => 'SQLite3',
'SCHEMA' => 'sqlite',
'MODULE' => 'sqlite3',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\sqlite3',
'AVAILABLE' => true,
'2.0.x' => false,
),
);
/**
* Constructor
*
* @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem interface
* @param string $phpbb_root_path Path to phpBB's root
*/
public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path)
{
$this->filesystem = $filesystem;
$this->phpbb_root_path = $phpbb_root_path;
}
/**
* Returns an array of available DBMS supported by phpBB
*
* If a DBMS is specified it will only return data for that DBMS
* and will load its extension if necessary.
*
* @param mixed $dbms name of the DBMS that's info is required or false for all DBMS info
* @param bool $return_unavailable set it to true if you expect unavailable but supported DBMS
* returned as well
* @param bool $only_20x_options set it to true if you only want to recover 2.0.x options
*
* @return array Array of available and supported DBMS
*/
public function get_available_dbms($dbms = false, $return_unavailable = false, $only_20x_options = false)
{
$available_dbms = $this->supported_dbms;
if ($dbms)
{
if (isset($this->supported_dbms[$dbms]))
{
$available_dbms = array($dbms => $this->supported_dbms[$dbms]);
}
else
{
return array();
}
}
$any_dbms_available = false;
foreach ($available_dbms as $db_name => $db_array)
{
if ($only_20x_options && !$db_array['2.0.x'])
{
if ($return_unavailable)
{
$available_dbms[$db_name]['AVAILABLE'] = false;
}
else
{
unset($available_dbms[$db_name]);
}
continue;
}
$dll = $db_array['MODULE'];
if (!@extension_loaded($dll))
{
if ($return_unavailable)
{
$available_dbms[$db_name]['AVAILABLE'] = false;
}
else
{
unset($available_dbms[$db_name]);
}
continue;
}
$any_dbms_available = true;
}
if ($return_unavailable)
{
$available_dbms['ANY_DB_SUPPORT'] = $any_dbms_available;
}
return $available_dbms;
}
/**
* Removes "/* style" as well as "# style" comments from $input.
*
* @param string $sql_query Input string
*
* @return string Input string with comments removed
*/
public function remove_comments($sql_query)
{
// Remove /* */ comments (http://ostermiller.org/findcomment.html)
$sql_query = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $sql_query);
// Remove # style comments
$sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query));
return $sql_query;
}
/**
* split_sql_file() will split an uploaded sql file into single sql statements.
*
* Note: expects trim() to have already been run on $sql.
*
* @param string $sql SQL statements
* @param string $delimiter Delimiter between sql statements
*
* @return array Array of sql statements
*/
public function split_sql_file($sql, $delimiter)
{
$sql = str_replace("\r" , '', $sql);
$data = preg_split('/' . preg_quote($delimiter, '/') . '$/m', $sql);
$data = array_map('trim', $data);
// The empty case
$end_data = end($data);
if (empty($end_data))
{
unset($data[key($data)]);
}
return $data;
}
/**
* Validates table prefix
*
* @param string $dbms The selected dbms
* @param string $table_prefix The table prefix to validate
*
* @return bool|array true if table prefix is valid, array of errors otherwise
*
* @throws \phpbb\install\exception\invalid_dbms_exception When $dbms is not a valid
*/
public function validate_table_prefix($dbms, $table_prefix)
{
$errors = array();
if (!preg_match('#^[a-zA-Z][a-zA-Z0-9_]*$#', $table_prefix))
{
$errors[] = array(
'title' => 'INST_ERR_DB_INVALID_PREFIX',
);
}
// Do dbms specific checks
$dbms_info = $this->get_available_dbms($dbms);
switch ($dbms_info[$dbms]['SCHEMA'])
{
case 'mysql_41':
$prefix_length = 36;
break;
case 'mssql':
$prefix_length = 90;
break;
case 'oracle':
$prefix_length = 6;
break;
case 'postgres':
$prefix_length = 36;
break;
case 'sqlite':
$prefix_length = 200;
break;
default:
throw new invalid_dbms_exception();
break;
}
// Check the prefix length to ensure that index names are not too long
if (strlen($table_prefix) > $prefix_length)
{
$errors[] = array(
'title' => array('INST_ERR_PREFIX_TOO_LONG', $prefix_length),
);
}
return (empty($errors)) ? true : $errors;
}
/**
* Check if the user provided database parameters are correct
*
* This function checks the database connection data and also checks for
* any other problems that could cause an error during the installation
* such as if there is any database table names conflicting.
*
* Note: The function assumes that $table_prefix has been already validated
* with validate_table_prefix().
*
* @param string $dbms Selected database type
* @param string $dbhost Database host address
* @param int $dbport Database port number
* @param string $dbuser Database username
* @param string $dbpass Database password
* @param string $dbname Database name
* @param string $table_prefix Database table prefix
*
* @return array|bool Returns true if test is successful, array of errors otherwise
*/
public function check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix)
{
$dbms_info = $this->get_available_dbms($dbms);
$dbms_info = $dbms_info[$dbms];
$errors = array();
// Instantiate it and set return on error true
/** @var \phpbb\db\driver\driver_interface $db */
$db = new $dbms_info['DRIVER'];
$db->sql_return_on_error(true);
// Check that we actually have a database name before going any further
if (!in_array($dbms_info['SCHEMA'], array('sqlite', 'oracle'), true) && $dbname === '')
{
$errors[] = array(
'title' => 'INST_ERR_DB_NO_NAME',
);
}
// Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea
if ($dbms_info['SCHEMA'] === 'sqlite'
&& stripos($this->filesystem->realpath($dbhost), $this->filesystem->realpath($this->phpbb_root_path) === 0))
{
$errors[] = array(
'title' =>'INST_ERR_DB_FORUM_PATH',
);
}
// Check if SQLite database is writable
if ($dbms_info['SCHEMA'] === 'sqlite'
&& (($this->filesystem->exists($dbhost) && !$this->filesystem->is_writable($dbhost)) || !$this->filesystem->is_writable(pathinfo($dbhost, PATHINFO_DIRNAME))))
{
$errors[] = array(
'title' =>'INST_ERR_DB_NO_WRITABLE',
);
}
// Try to connect to db
if (is_array($db->sql_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, false, true)))
{
$db_error = $db->sql_error();
$errors[] = array(
'title' => 'INST_ERR_DB_CONNECT',
'description' => ($db_error['message']) ? utf8_convert_message($db_error['message']) : 'INST_ERR_DB_NO_ERROR',
);
}
else
{
// Check if there is any table name collisions
$temp_prefix = strtolower($table_prefix);
$table_ary = array(
$temp_prefix . 'attachments',
$temp_prefix . 'config',
$temp_prefix . 'sessions',
$temp_prefix . 'topics',
$temp_prefix . 'users',
);
$db_tools_factory = new \phpbb\db\tools\factory();
$db_tools = $db_tools_factory->get($db);
$tables = $db_tools->sql_list_tables();
$tables = array_map('strtolower', $tables);
$table_intersect = array_intersect($tables, $table_ary);
if (count($table_intersect))
{
$errors[] = array(
'title' => 'INST_ERR_PREFIX',
);
}
// Check if database version is supported
switch ($dbms)
{
case 'sqlite3':
if (version_compare($db->sql_server_info(true), '3.6.15', '<'))
{
$errors[] = array(
'title' => 'INST_ERR_DB_NO_SQLITE3',
);
}
break;
case 'oracle':
$sql = "SELECT *
FROM NLS_DATABASE_PARAMETERS
WHERE PARAMETER = 'NLS_RDBMS_VERSION'
OR PARAMETER = 'NLS_CHARACTERSET'";
$result = $db->sql_query($sql);
while ($row = $db->sql_fetchrow($result))
{
$stats[$row['parameter']] = $row['value'];
}
$db->sql_freeresult($result);
if (version_compare($stats['NLS_RDBMS_VERSION'], '9.2', '<') && $stats['NLS_CHARACTERSET'] !== 'UTF8')
{
$errors[] = array(
'title' => 'INST_ERR_DB_NO_ORACLE',
);
}
break;
case 'postgres':
$sql = "SHOW server_encoding;";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
if ($row['server_encoding'] !== 'UNICODE' && $row['server_encoding'] !== 'UTF8')
{
$errors[] = array(
'title' => 'INST_ERR_DB_NO_POSTGRES',
);
}
break;
}
}
return (empty($errors)) ? true : $errors;
}
}

View File

@@ -0,0 +1,222 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\helper\iohandler;
/**
* Input-Output handler interface for the installer
*/
interface iohandler_interface
{
/**
* Renders or returns response message
*
* @param bool $no_more_output Whether or not there will be more output in this output unit
*/
public function send_response($no_more_output = false);
/**
* Returns input variable
*
* @param string $name Name of the input variable to obtain
* @param mixed $default A default value that is returned if the variable was not set.
* This function will always return a value of the same type as the default.
* @param bool $multibyte If $default is a string this parameter has to be true if the variable may contain any UTF-8 characters
* Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks
*
* @return mixed Value of the input variable
*/
public function get_input($name, $default, $multibyte = false);
/**
* Returns raw input variable
*
* @param string $name Name of the input variable to obtain
* @param mixed $default A default value that is returned if the variable was not set.
* This function will always return a value of the same type as the default.
*
* @return mixed Value of the raw input variable
*/
public function get_raw_input($name, $default);
/**
* Returns server variable
*
* This function should work the same as request_interface::server().
*
* @param string $name Name of the server variable
* @param mixed $default Default value to return when the requested variable does not exist
*
* @return mixed Value of the server variable
*/
public function get_server_variable($name, $default = '');
/**
* Wrapper function for request_interface::header()
*
* @param string $name Name of the request header variable
* @param mixed $default Default value to return when the requested variable does not exist
*
* @return mixed
*/
public function get_header_variable($name, $default = '');
/**
* Returns true if the connection is encrypted
*
* @return bool
*/
public function is_secure();
/**
* Adds an error message to the rendering queue
*
* Note: When an array is passed into the parameters below, it will be
* resolved as printf($param[0], $param[1], ...).
*
* @param string|array $error_title Title of the error message.
* @param string|bool|array $error_description Description of the error (and possibly guidelines to resolve it),
* or false if the error description is not available.
*/
public function add_error_message($error_title, $error_description = false);
/**
* Adds a warning message to the rendering queue
*
* Note: When an array is passed into the parameters below, it will be
* resolved as printf($param[0], $param[1], ...).
*
* @param string|array $warning_title Title of the warning message
* @param string|bool|array $warning_description Description of the warning (and possibly guidelines to resolve it),
* or false if the warning description is not available
*/
public function add_warning_message($warning_title, $warning_description = false);
/**
* Adds a log message to the rendering queue
*
* Note: When an array is passed into the parameters below, it will be
* resolved as printf($param[0], $param[1], ...).
*
* @param string|array $log_title Title of the log message
* @param string|bool|array $log_description Description of the log,
* or false if the log description is not available
*/
public function add_log_message($log_title, $log_description = false);
/**
* Adds a success message to the rendering queue
*
* Note: When an array is passed into the parameters below, it will be
* resolved as printf($param[0], $param[1], ...).
*
* @param string|array $success_title Title of the success message
* @param string|bool|array $success_description Description of the success,
* or false if the success description is not available
*
* @return null
*/
public function add_success_message($success_title, $success_description = false);
/**
* Adds a requested data group to the rendering queue
*
* @param string $title Language variable with the title of the form
* @param array $form An array describing the required data (options etc)
*/
public function add_user_form_group($title, $form);
/**
* Returns the rendering information for the form
*
* @param string $title Language variable with the title of the form
* @param array $form An array describing the required data (options etc)
*
* @return string Information to render the form
*/
public function generate_form_render_data($title, $form);
/**
* Sets the number of tasks belonging to the installer in the current mode.
*
* @param int $task_count Number of tasks
* @param bool $restart Whether or not to restart the progress bar, false by default
*/
public function set_task_count($task_count, $restart = false);
/**
* Sets the progress information
*
* @param string $task_lang_key Language key for the name of the task
* @param int $task_number Position of the current task in the task queue
*/
public function set_progress($task_lang_key, $task_number);
/**
* Sends refresh request to the client
*/
public function request_refresh();
/**
* Marks stage as active in the navigation bar
*
* @param array $menu_path Array to the navigation elem
*/
public function set_active_stage_menu($menu_path);
/**
* Marks stage as completed in the navigation bar
*
* @param array $menu_path Array to the navigation elem
*/
public function set_finished_stage_menu($menu_path);
/**
* Finish the progress bar
*
* @param string $message_lang_key Language key for the message
*/
public function finish_progress($message_lang_key);
/**
* Adds a download link
*
* @param string $route Route for the link
* @param string $title Language key for the title
* @param string|null|array $msg Language key for the message
*/
public function add_download_link($route, $title, $msg = null);
/**
* Redirects the user to a new page
*
* @param string $url URL to redirect to
* @param bool $use_ajax Whether or not to use AJAX redirect
*/
public function redirect($url, $use_ajax = false);
/**
* Renders the status of update files
*
* @param array $status_array Array containing files in groups to render
*/
public function render_update_file_status($status_array);
/**
* Sends and sets cookies
*
* @param string $cookie_name Name of the cookie to set
* @param string $cookie_value Value of the cookie to set
*/
public function set_cookie($cookie_name, $cookie_value);
}

View File

@@ -0,0 +1,146 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class installer_configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('installer');
$rootNode
->children()
->arrayNode('admin')
->children()
->scalarNode('name')->defaultValue('admin')->cannotBeEmpty()->end()
->scalarNode('password')->defaultValue('adminadmin')->cannotBeEmpty()->end()
->scalarNode('email')->defaultValue('admin@example.org')->cannotBeEmpty()->end()
->end()
->end()
->arrayNode('board')
->children()
->scalarNode('lang')
->defaultValue('en')
->cannotBeEmpty()
->end()
->scalarNode('name')
->defaultValue('My Board')
->cannotBeEmpty()
->end()
->scalarNode('description')
->defaultValue('My amazing new phpBB board')
->cannotBeEmpty()
->end()
->end()
->end()
->arrayNode('database')
->children()
->scalarNode('dbms')
->defaultValue('sqlite3')
->cannotBeEmpty()
->isRequired()
->end()
->scalarNode('dbhost')
->defaultValue(null)
->end()
->scalarNode('dbport')
->defaultValue(null)
->end()
->scalarNode('dbuser')
->defaultValue(null)
->end()
->scalarNode('dbpasswd')
->defaultValue(null)
->end()
->scalarNode('dbname')
->defaultValue(null)
->end()
->scalarNode('table_prefix')
->defaultValue('phpbb_')
->cannotBeEmpty()
->isRequired()
->end()
->end()
->end()
->arrayNode('email')
->canBeEnabled()
->addDefaultsIfNotSet()
->children()
->booleanNode('smtp_delivery')
->defaultValue(false)
->treatNullLike(false)
->end()
->scalarNode('smtp_host')
->defaultValue(null)
->end()
->scalarNode('smtp_port')
->defaultValue(null)
->end()
->scalarNode('smtp_auth')
->defaultValue(null)
->end()
->scalarNode('smtp_user')
->defaultValue(null)
->end()
->scalarNode('smtp_pass')
->defaultValue(null)
->end()
->end()
->end()
->arrayNode('server')
->children()
->booleanNode('cookie_secure')
->defaultValue(false)
->treatNullLike(false)
->end()
->scalarNode('server_protocol')
->defaultValue('http://')
->cannotBeEmpty()
->end()
->booleanNode('force_server_vars')
->defaultValue(false)
->treatNullLike(false)
->end()
->scalarNode('server_name')
->defaultValue('localhost')
->cannotBeEmpty()
->end()
->integerNode('server_port')
->defaultValue(80)
->min(1)
->end()
->scalarNode('script_path')
->defaultValue('/')
->cannotBeEmpty()
->end()
->end()
->end()
->arrayNode('extensions')
->prototype('scalar')->end()
->defaultValue([])
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,367 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\module\install_database\task;
use phpbb\install\exception\resource_limit_reached_exception;
/**
* Create database schema
*/
class add_config_settings extends \phpbb\install\task_base
{
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var \phpbb\install\helper\config
*/
protected $install_config;
/**
* @var \phpbb\install\helper\iohandler\iohandler_interface
*/
protected $iohandler;
/**
* @var \phpbb\language\language
*/
protected $language;
/**
* @var \phpbb\passwords\manager
*/
protected $password_manager;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $config_table;
/**
* @var string
*/
protected $user_table;
/**
* @var string
*/
protected $topics_table;
/**
* @var string
*/
protected $forums_table;
/**
* @var string
*/
protected $posts_table;
/**
* @var string
*/
protected $moderator_cache_table;
/**
* Constructor
*
* @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem service
* @param \phpbb\install\helper\config $install_config Installer's config helper
* @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler
* @param \phpbb\install\helper\container_factory $container Installer's DI container
* @param \phpbb\language\language $language Language service
* @param string $phpbb_root_path Path to phpBB's root
*/
public function __construct(\phpbb\filesystem\filesystem_interface $filesystem,
\phpbb\install\helper\config $install_config,
\phpbb\install\helper\iohandler\iohandler_interface $iohandler,
\phpbb\install\helper\container_factory $container,
\phpbb\language\language $language,
$phpbb_root_path)
{
$this->db = $container->get('dbal.conn');
$this->filesystem = $filesystem;
$this->install_config = $install_config;
$this->iohandler = $iohandler;
$this->language = $language;
$this->password_manager = $container->get('passwords.manager');
$this->phpbb_root_path = $phpbb_root_path;
// Table names
$this->config_table = $container->get_parameter('tables.config');
$this->forums_table = $container->get_parameter('tables.forums');
$this->topics_table = $container->get_parameter('tables.topics');
$this->user_table = $container->get_parameter('tables.users');
$this->moderator_cache_table = $container->get_parameter('tables.moderator_cache');
$this->posts_table = $container->get_parameter('tables.posts');
parent::__construct(true);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->db->sql_return_on_error(true);
$server_name = $this->install_config->get('server_name');
$current_time = time();
$user_ip = phpbb_ip_normalise($this->iohandler->get_server_variable('REMOTE_ADDR'));
$user_ip = ($user_ip === false) ? '' : $user_ip;
$referer = $this->iohandler->get_server_variable('REFERER');
// Calculate cookie domain
$cookie_domain = $server_name;
if (strpos($cookie_domain, 'www.') === 0)
{
$cookie_domain = substr($cookie_domain, 3);
}
// Set default config and post data, this applies to all DB's
$sql_ary = array(
'INSERT INTO ' . $this->config_table . " (config_name, config_value)
VALUES ('board_startdate', '$current_time')",
'INSERT INTO ' . $this->config_table . " (config_name, config_value)
VALUES ('default_lang', '" . $this->db->sql_escape($this->install_config->get('default_lang')) . "')",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_name')) . "'
WHERE config_name = 'server_name'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_port')) . "'
WHERE config_name = 'server_port'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "'
WHERE config_name = 'board_email'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "'
WHERE config_name = 'board_contact'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($cookie_domain) . "'
WHERE config_name = 'cookie_domain'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "'
WHERE config_name = 'default_dateformat'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('email_enable')) . "'
WHERE config_name = 'email_enable'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_delivery')) . "'
WHERE config_name = 'smtp_delivery'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_host')) . "'
WHERE config_name = 'smtp_host'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_port')) . "'
WHERE config_name = 'smtp_port'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_auth')) . "'
WHERE config_name = 'smtp_auth_method'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_user')) . "'
WHERE config_name = 'smtp_username'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_pass')) . "'
WHERE config_name = 'smtp_password'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('cookie_secure')) . "'
WHERE config_name = 'cookie_secure'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('force_server_vars')) . "'
WHERE config_name = 'force_server_vars'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('script_path')) . "'
WHERE config_name = 'script_path'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_protocol')) . "'
WHERE config_name = 'server_protocol'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "'
WHERE config_name = 'newest_username'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . md5(mt_rand()) . "'
WHERE config_name = 'avatar_salt'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . md5(mt_rand()) . "'
WHERE config_name = 'plupload_salt'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_name')) . "'
WHERE config_name = 'sitename'",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_description')) . "'
WHERE config_name = 'site_desc'",
'UPDATE ' . $this->user_table . "
SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "',
user_password='" . $this->password_manager->hash($this->install_config->get('admin_passwd')) . "',
user_ip = '" . $this->db->sql_escape($user_ip) . "',
user_lang = '" . $this->db->sql_escape($this->install_config->get('user_language', 'en')) . "',
user_email='" . $this->db->sql_escape($this->install_config->get('board_email')) . "',
user_dateformat='" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "',
username_clean = '" . $this->db->sql_escape(utf8_clean_string($this->install_config->get('admin_name'))) . "'
WHERE username = 'Admin'",
'UPDATE ' . $this->moderator_cache_table . "
SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "'
WHERE username = 'Admin'",
'UPDATE ' . $this->forums_table . "
SET forum_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "'
WHERE forum_last_poster_name = 'Admin'",
'UPDATE ' . $this->topics_table . "
SET topic_first_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "',
topic_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "'
WHERE topic_first_poster_name = 'Admin'
OR topic_last_poster_name = 'Admin'",
'UPDATE ' . $this->user_table . "
SET user_regdate = $current_time",
'UPDATE ' . $this->posts_table . "
SET post_time = $current_time, poster_ip = '" . $this->db->sql_escape($user_ip) . "'",
'UPDATE ' . $this->topics_table . "
SET topic_time = $current_time, topic_last_post_time = $current_time",
'UPDATE ' . $this->forums_table . "
SET forum_last_post_time = $current_time",
'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($this->db->sql_server_info(true)) . "'
WHERE config_name = 'dbms_version'",
);
if (@extension_loaded('gd'))
{
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = 'core.captcha.plugins.gd'
WHERE config_name = 'captcha_plugin'";
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = '1'
WHERE config_name = 'captcha_gd'";
}
$ref = substr($referer, strpos($referer, '://') + 3);
if (!(stripos($ref, $server_name) === 0))
{
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = '0'
WHERE config_name = 'referer_validation'";
}
// We set a (semi-)unique cookie name to bypass login issues related to the cookie name.
$cookie_name = 'phpbb3_';
$rand_str = md5(mt_rand());
$rand_str = str_replace('0', 'z', base_convert($rand_str, 16, 35));
$rand_str = substr($rand_str, 0, 5);
$cookie_name .= strtolower($rand_str);
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = '" . $this->db->sql_escape($cookie_name) . "'
WHERE config_name = 'cookie_name'";
// Disable avatars if upload directory is not writable
if (!$this->filesystem->is_writable($this->phpbb_root_path . 'images/avatars/upload/'))
{
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = '0'
WHERE config_name = 'allow_avatar'";
$sql_ary[] = 'UPDATE ' . $this->config_table . "
SET config_value = '0'
WHERE config_name = 'allow_avatar_upload'";
}
$i = $this->install_config->get('add_config_settings_index', 0);
$total = count($sql_ary);
$sql_ary = array_slice($sql_ary, $i);
foreach ($sql_ary as $sql)
{
if (!$this->db->sql_query($sql))
{
$error = $this->db->sql_error($this->db->get_sql_error_sql());
$this->iohandler->add_error_message('INST_ERR_DB', $error['message']);
}
$i++;
// Stop execution if resource limit is reached
if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0)
{
break;
}
}
if ($i < $total)
{
$this->install_config->set('add_config_settings_index', $i);
throw new resource_limit_reached_exception();
}
}
/**
* {@inheritdoc}
*/
static public function get_step_count()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function get_task_lang_name()
{
return 'TASK_ADD_CONFIG_SETTINGS';
}
}

View File

@@ -0,0 +1,227 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\module\install_database\task;
use phpbb\install\exception\resource_limit_reached_exception;
/**
* Create database schema
*/
class create_schema extends \phpbb\install\task_base
{
/**
* @var \phpbb\install\helper\config
*/
protected $config;
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* @var \phpbb\db\tools\tools_interface
*/
protected $db_tools;
/**
* @var \phpbb\install\helper\database
*/
protected $database_helper;
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var \phpbb\install\helper\iohandler\iohandler_interface
*/
protected $iohandler;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $php_ext;
/**
* Constructor
*
* @param \phpbb\install\helper\config $config Installer's config provider
* @param \phpbb\install\helper\database $db_helper Installer's database helper
* @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem service
* @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler
* @param string $phpbb_root_path Path phpBB's root
* @param string $php_ext Extension of PHP files
*/
public function __construct(\phpbb\install\helper\config $config,
\phpbb\install\helper\database $db_helper,
\phpbb\filesystem\filesystem_interface $filesystem,
\phpbb\install\helper\iohandler\iohandler_interface $iohandler,
$phpbb_root_path,
$php_ext)
{
$dbms = $db_helper->get_available_dbms($config->get('dbms'));
$dbms = $dbms[$config->get('dbms')]['DRIVER'];
$factory = new \phpbb\db\tools\factory();
$this->db = new $dbms();
$this->db->sql_connect(
$config->get('dbhost'),
$config->get('dbuser'),
$config->get('dbpasswd'),
$config->get('dbname'),
$config->get('dbport'),
false,
false
);
$this->config = $config;
$this->db_tools = $factory->get($this->db);
$this->database_helper = $db_helper;
$this->filesystem = $filesystem;
$this->iohandler = $iohandler;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
parent::__construct(true);
}
/**
* {@inheritdoc}
*/
public function run()
{
// As this task may take a large amount of time to complete refreshing the page might be necessary for some
// server configurations with limited resources
if (!$this->config->get('pre_schema_forced_refresh'))
{
if ($this->config->get_time_remaining() < 5)
{
$this->config->set('pre_schema_forced_refresh', true);
throw new resource_limit_reached_exception();
}
}
$this->db->sql_return_on_error(true);
$dbms = $this->config->get('dbms');
$dbms_info = $this->database_helper->get_available_dbms($dbms);
$schema_name = $dbms_info[$dbms]['SCHEMA'];
$delimiter = $dbms_info[$dbms]['DELIM'];
$table_prefix = $this->config->get('table_prefix');
if ($dbms === 'mysql')
{
$schema_name .= '_41';
}
$db_schema_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql';
// Load database vendor specific code if there is any
if ($this->filesystem->exists($db_schema_path))
{
$sql_query = @file_get_contents($db_schema_path);
$sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query);
$sql_query = $this->database_helper->remove_comments($sql_query);
$sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter);
foreach ($sql_query as $sql)
{
if (!$this->db->sql_query($sql))
{
$error = $this->db->sql_error($this->db->get_sql_error_sql());
$this->iohandler->add_error_message('INST_ERR_DB', $error['message']);
}
}
unset($sql_query);
}
$change_prefix = false;
// Generate database schema
if ($this->filesystem->exists($this->phpbb_root_path . 'install/schemas/schema.json'))
{
$db_table_schema = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema.json');
$db_table_schema = json_decode($db_table_schema, true);
$change_prefix = true;
}
else
{
global $table_prefix;
$table_prefix = $this->config->get('table_prefix');
if (!defined('CONFIG_TABLE'))
{
// We need to include the constants file for the table constants
// when we generate the schema from the migration files.
include ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext);
}
$finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, null, $this->php_ext);
$migrator_classes = $finder->core_path('phpbb/db/migration/data/')->get_classes();
$factory = new \phpbb\db\tools\factory();
$db_tools = $factory->get($this->db, true);
$schema_generator = new \phpbb\db\migration\schema_generator(
$migrator_classes,
new \phpbb\config\config(array()),
$this->db,
$db_tools,
$this->phpbb_root_path,
$this->php_ext,
$table_prefix
);
$db_table_schema = $schema_generator->get_schema();
}
if (!defined('CONFIG_TABLE'))
{
// CONFIG_TABLE is required by sql_create_index() to check the
// length of index names. However table_prefix is not defined
// here yet, so we need to create the constant ourselves.
define('CONFIG_TABLE', $table_prefix . 'config');
}
foreach ($db_table_schema as $table_name => $table_data)
{
$this->db_tools->sql_create_table(
( ($change_prefix) ? ($table_prefix . substr($table_name, 6)) : $table_name ),
$table_data
);
}
}
/**
* {@inheritdoc}
*/
static public function get_step_count()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function get_task_lang_name()
{
return 'TASK_CREATE_DATABASE_SCHEMA';
}
}

View File

@@ -0,0 +1,157 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\module\install_database\task;
/**
* Set up database for table generation
*/
class set_up_database extends \phpbb\install\task_base
{
/**
* @var \phpbb\install\helper\config
*/
protected $config;
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* @var \phpbb\install\helper\database
*/
protected $database_helper;
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var \phpbb\install\helper\iohandler\iohandler_interface
*/
protected $iohandler;
/**
* @var string
*/
protected $schema_file_path;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* Constructor
*
* @param \phpbb\install\helper\config $config
* @param \phpbb\install\helper\database $db_helper
* @param \phpbb\filesystem\filesystem_interface $filesystem
* @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler
* @param string $phpbb_root_path
*/
public function __construct(\phpbb\install\helper\config $config,
\phpbb\install\helper\database $db_helper,
\phpbb\filesystem\filesystem_interface $filesystem,
\phpbb\install\helper\iohandler\iohandler_interface $iohandler,
$phpbb_root_path)
{
$dbms = $db_helper->get_available_dbms($config->get('dbms'));
$dbms = $dbms[$config->get('dbms')]['DRIVER'];
$this->db = new $dbms();
$this->db->sql_connect(
$config->get('dbhost'),
$config->get('dbuser'),
$config->get('dbpasswd'),
$config->get('dbname'),
$config->get('dbport'),
false,
false
);
$this->config = $config;
$this->database_helper = $db_helper;
$this->filesystem = $filesystem;
$this->iohandler = $iohandler;
$this->phpbb_root_path = $phpbb_root_path;
parent::__construct(false);
}
/**
* {@inheritdoc}
*/
public function check_requirements()
{
$dbms = $this->config->get('dbms');
$dbms_info = $this->database_helper->get_available_dbms($dbms);
$schema_name = $dbms_info[$dbms]['SCHEMA'];
if ($dbms === 'mysql')
{
$schema_name .= '_41';
}
$this->schema_file_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql';
return $this->filesystem->exists($this->schema_file_path);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->db->sql_return_on_error(true);
$dbms = $this->config->get('dbms');
$dbms_info = $this->database_helper->get_available_dbms($dbms);
$delimiter = $dbms_info[$dbms]['DELIM'];
$table_prefix = $this->config->get('table_prefix');
$sql_query = @file_get_contents($this->schema_file_path);
$sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query);
$sql_query = $this->database_helper->remove_comments($sql_query);
$sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter);
foreach ($sql_query as $sql)
{
if (!$this->db->sql_query($sql))
{
$error = $this->db->sql_error($this->db->get_sql_error_sql());
$this->iohandler->add_error_message('INST_ERR_DB', $error['message']);
}
}
unset($sql_query);
}
/**
* {@inheritdoc}
*/
static public function get_step_count()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function get_task_lang_name()
{
return 'TASK_SETUP_DATABASE';
}
}

View File

@@ -0,0 +1,243 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\module\install_filesystem\task;
use phpbb\install\exception\user_interaction_required_exception;
/**
* Dumps config file
*/
class create_config_file extends \phpbb\install\task_base
{
/**
* @var \phpbb\filesystem\filesystem_interface
*/
protected $filesystem;
/**
* @var \phpbb\install\helper\database
*/
protected $db_helper;
/**
* @var \phpbb\install\helper\config
*/
protected $install_config;
/**
* @var \phpbb\install\helper\iohandler\iohandler_interface
*/
protected $iohandler;
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var string
*/
protected $php_ext;
/**
* @var array
*/
protected $options;
/**
* Constructor
*
* @param \phpbb\filesystem\filesystem_interface $filesystem
* @param \phpbb\install\helper\config $install_config
* @param \phpbb\install\helper\database $db_helper
* @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler
* @param string $phpbb_root_path
* @param string $php_ext
* @param array $options
*/
public function __construct(\phpbb\filesystem\filesystem_interface $filesystem,
\phpbb\install\helper\config $install_config,
\phpbb\install\helper\database $db_helper,
\phpbb\install\helper\iohandler\iohandler_interface $iohandler,
$phpbb_root_path,
$php_ext,
$options = array())
{
$this->install_config = $install_config;
$this->db_helper = $db_helper;
$this->filesystem = $filesystem;
$this->iohandler = $iohandler;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->options = array_merge(array(
'debug' => false,
'debug_container' => false,
'environment' => null,
), $options);
parent::__construct(true);
}
/**
* {@inheritdoc}
*/
public function run()
{
$config_written = true;
// Create config.php
$path_to_config = $this->phpbb_root_path . 'config.' . $this->php_ext;
$fp = @fopen($path_to_config, 'w');
if (!$fp)
{
$config_written = false;
}
$config_content = $this->get_config_data($this->options['debug'], $this->options['debug_container'], $this->options['environment']);
if (!@fwrite($fp, $config_content))
{
$config_written = false;
}
@fclose($fp);
// chmod config.php to be only readable
if ($config_written)
{
try
{
$this->filesystem->phpbb_chmod($path_to_config, \phpbb\filesystem\filesystem_interface::CHMOD_READ);
}
catch (\phpbb\filesystem\exception\filesystem_exception $e)
{
// Do nothing, the user will get a notice later
}
}
else
{
$this->iohandler->add_error_message('UNABLE_TO_WRITE_CONFIG_FILE');
throw new user_interaction_required_exception();
}
// Create a lock file to indicate that there is an install in progress
$fp = @fopen($this->phpbb_root_path . 'cache/install_lock', 'wb');
if ($fp === false)
{
// We were unable to create the lock file - abort
$this->iohandler->add_error_message('UNABLE_TO_WRITE_LOCK');
throw new user_interaction_required_exception();
}
@fclose($fp);
try
{
$this->filesystem->phpbb_chmod($this->phpbb_root_path . 'cache/install_lock', 0777);
}
catch (\phpbb\filesystem\exception\filesystem_exception $e)
{
// Do nothing, the user will get a notice later
}
}
/**
* Returns the content which should be dumped to config.php
*
* @param bool $debug If the debug constants should be enabled by default or not
* @param bool $debug_container If the container should be compiled on
* every page load or not
* @param string $environment The environment to use
*
* @return string content to be written to the config file
*/
protected function get_config_data($debug = false, $debug_container = false, $environment = null)
{
$config_content = "<?php\n";
$config_content .= "// phpBB 3.3.x auto-generated configuration file\n// Do not change anything in this file!\n";
$dbms = $this->install_config->get('dbms');
$db_driver = $this->db_helper->get_available_dbms($dbms);
$db_driver = $db_driver[$dbms]['DRIVER'];
$config_data_array = array(
'dbms' => $db_driver,
'dbhost' => $this->install_config->get('dbhost'),
'dbport' => $this->install_config->get('dbport'),
'dbname' => $this->install_config->get('dbname'),
'dbuser' => $this->install_config->get('dbuser'),
'dbpasswd' => $this->install_config->get('dbpasswd'),
'table_prefix' => $this->install_config->get('table_prefix'),
'phpbb_adm_relative_path' => 'adm/',
'acm_type' => 'phpbb\cache\driver\file',
);
foreach ($config_data_array as $key => $value)
{
$config_content .= "\${$key} = '" . str_replace("'", "\\'", str_replace('\\', '\\\\', $value)) . "';\n";
}
$config_content .= "\n@define('PHPBB_INSTALLED', true);\n";
if ($environment)
{
$config_content .= "@define('PHPBB_ENVIRONMENT', 'test');\n";
}
else if ($debug)
{
$config_content .= "@define('PHPBB_ENVIRONMENT', 'development');\n";
}
else
{
$config_content .= "@define('PHPBB_ENVIRONMENT', 'production');\n";
}
if ($debug_container)
{
$config_content .= "@define('DEBUG_CONTAINER', true);\n";
}
else
{
$config_content .= "// @define('DEBUG_CONTAINER', true);\n";
}
if ($environment === 'test')
{
$config_content .= "@define('DEBUG_TEST', true);\n";
// Mandatory for the functional tests, will be removed by PHPBB3-12623
$config_content .= "@define('DEBUG', true);\n";
}
return $config_content;
}
/**
* {@inheritdoc}
*/
static public function get_step_count()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function get_task_lang_name()
{
return 'TASK_CREATE_CONFIG_FILE';
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install\module\requirements\task;
/**
* Installer task that checks if the server meats phpBB requirements
*/
class check_server_environment extends \phpbb\install\task_base
{
/**
* @var \phpbb\install\helper\database
*/
protected $database_helper;
/**
* @var \phpbb\install\helper\iohandler\iohandler_interface
*/
protected $response_helper;
/**
* @var bool
*/
protected $tests_passed;
/**
* Constructor
*
* @param \phpbb\install\helper\database $database_helper
* @param \phpbb\install\helper\iohandler\iohandler_interface $response
*/
public function __construct(\phpbb\install\helper\database $database_helper,
\phpbb\install\helper\iohandler\iohandler_interface $response)
{
$this->database_helper = $database_helper;
$this->response_helper = $response;
$this->tests_passed = true;
parent::__construct(true);
}
/**
* {@inheritdoc}
*/
public function run()
{
//
// Check requirements
// The error messages should be set in the check_ functions
//
// Check PHP version
$this->check_php_version();
// Check for getimagesize()
$this->check_image_size();
// Check for PCRE support
$this->check_pcre();
// Check for JSON support
$this->check_json();
// XML extension support check
$this->check_xml();
// Check for dbms support
$this->check_available_dbms();
return $this->tests_passed;
}
/**
* Sets $this->tests_passed
*
* @param bool $is_passed
*/
protected function set_test_passed($is_passed)
{
// If one test failed, tests_passed should be false
$this->tests_passed = (!$this->tests_passed) ? false : $is_passed;
}
/**
* Check if the requirements for PHP version is met
*/
protected function check_php_version()
{
if (version_compare(PHP_VERSION, '7.1.3', '<'))
{
$this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN');
$this->set_test_passed(false);
return;
}
$this->set_test_passed(true);
}
/**
* Checks if the installed PHP has getimagesize() available
*/
protected function check_image_size()
{
if (!@function_exists('getimagesize'))
{
$this->response_helper->add_error_message('PHP_GETIMAGESIZE_SUPPORT', 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN');
$this->set_test_passed(false);
return;
}
$this->set_test_passed(true);
}
/**
* Checks if the installed PHP supports PCRE
*/
protected function check_pcre()
{
if (@preg_match('//u', ''))
{
$this->set_test_passed(true);
return;
}
$this->response_helper->add_error_message('PCRE_UTF_SUPPORT', 'PCRE_UTF_SUPPORT_EXPLAIN');
$this->set_test_passed(false);
}
/**
* Checks whether PHP's JSON extension is available or not
*/
protected function check_json()
{
if (@extension_loaded('json'))
{
$this->set_test_passed(true);
return;
}
$this->response_helper->add_error_message('PHP_JSON_SUPPORT', 'PHP_JSON_SUPPORT_EXPLAIN');
$this->set_test_passed(false);
}
/**
* Checks whether or not the XML PHP extension is available (Required by the text formatter)
*/
protected function check_xml()
{
if (class_exists('DOMDocument'))
{
$this->set_test_passed(true);
return;
}
$this->response_helper->add_error_message('PHP_XML_SUPPORT', 'PHP_XML_SUPPORT_EXPLAIN');
$this->set_test_passed(false);
}
/**
* Check if any supported DBMS is available
*/
protected function check_available_dbms()
{
$available_dbms = $this->database_helper->get_available_dbms(false, true);
if ($available_dbms['ANY_DB_SUPPORT'])
{
$this->set_test_passed(true);
return;
}
$this->response_helper->add_error_message('PHP_SUPPORTED_DB', 'PHP_SUPPORTED_DB_EXPLAIN');
$this->set_test_passed(false);
}
/**
* {@inheritdoc}
*/
static public function get_step_count()
{
return 0;
}
/**
* {@inheritdoc}
*/
public function get_task_lang_name()
{
return '';
}
}

View File

@@ -0,0 +1,213 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\install;
use phpbb\di\ordered_service_collection;
use phpbb\install\exception\resource_limit_reached_exception;
use phpbb\install\helper\config;
use phpbb\install\helper\iohandler\iohandler_interface;
/**
* Base class for installer module
*/
abstract class module_base implements module_interface
{
/**
* @var config
*/
protected $install_config;
/**
* @var iohandler_interface
*/
protected $iohandler;
/**
* @var bool
*/
protected $is_essential;
/**
* Array of tasks for installer module
*
* @var ordered_service_collection
*/
protected $task_collection;
/**
* @var array
*/
protected $task_step_count;
/**
* @var bool
*/
protected $allow_progress_bar;
/**
* Installer module constructor
*
* @param ordered_service_collection $tasks array of installer tasks for installer module
* @param bool $essential flag indicating whether the module is essential or not
* @param bool $allow_progress_bar flag indicating whether or not to send progress information from within the module
*/
public function __construct(ordered_service_collection $tasks, $essential = true, $allow_progress_bar = true)
{
$this->task_collection = $tasks;
$this->is_essential = $essential;
$this->allow_progress_bar = $allow_progress_bar;
}
/**
* Dependency getter
*
* @param config $config
* @param iohandler_interface $iohandler
*/
public function setup(config $config, iohandler_interface $iohandler)
{
$this->install_config = $config;
$this->iohandler = $iohandler;
}
/**
* {@inheritdoc}
*/
public function is_essential()
{
return $this->is_essential;
}
/**
* {@inheritdoc}
*
* Overwrite this method if your task is non-essential!
*/
public function check_requirements()
{
return true;
}
/**
* {@inheritdoc}
*/
public function run()
{
// Recover install progress
$task_index = $this->recover_progress();
$iterator = $this->task_collection->getIterator();
if ($task_index < $iterator->count())
{
$iterator->seek($task_index);
}
else
{
$this->install_config->set_finished_task(0);
return;
}
while ($iterator->valid())
{
$task = $iterator->current();
$name = $iterator->key();
// Check if we can run the task
if (!$task->is_essential() && !$task->check_requirements())
{
$this->iohandler->add_log_message(array(
'SKIP_TASK',
$name,
));
$this->install_config->increment_current_task_progress($this->task_step_count[$name] ?? false);
}
else
{
// Send progress information
if ($this->allow_progress_bar)
{
$this->iohandler->set_progress(
$task->get_task_lang_name(),
$this->install_config->get_current_task_progress()
);
$this->iohandler->send_response();
}
$task->run();
if ($this->allow_progress_bar)
{
// Only increment progress by one, as if a task has more than one steps
// then that should be incremented in the task itself
$this->install_config->increment_current_task_progress();
}
}
$task_index++;
$this->install_config->set_finished_task($task_index);
$iterator->next();
// Send progress information
if ($this->allow_progress_bar)
{
$this->iohandler->set_progress(
$task->get_task_lang_name(),
$this->install_config->get_current_task_progress()
);
}
$this->iohandler->send_response();
// Stop execution if resource limit is reached
if ($iterator->valid() && ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0))
{
throw new resource_limit_reached_exception();
}
}
// Module finished, so clear task progress
$this->install_config->set_finished_task(0);
}
/**
* Returns the next task's name
*
* @return string Index of the array element of the next task
*/
protected function recover_progress()
{
$progress_array = $this->install_config->get_progress_data();
return $progress_array['last_task_index'];
}
/**
* {@inheritdoc}
*/
public function get_step_count()
{
$task_step_count = 0;
$task_class_names = $this->task_collection->get_service_classes();
foreach ($task_class_names as $name => $task_class)
{
$step_count = $task_class::get_step_count();
$task_step_count += $step_count;
$this->task_step_count[$name] = $step_count;
}
return $task_step_count;
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\language;
use \phpbb\language\exception\language_file_not_found;
/**
* Language file loader
*/
class language_file_loader
{
/**
* @var string Path to phpBB's root
*/
protected $phpbb_root_path;
/**
* @var string Extension of PHP files
*/
protected $php_ext;
/**
* @var \phpbb\extension\manager Extension manager
*/
protected $extension_manager;
/**
* Constructor
*
* @param string $phpbb_root_path Path to phpBB's root
* @param string $php_ext Extension of PHP files
*/
public function __construct($phpbb_root_path, $php_ext)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->extension_manager = null;
}
/**
* Extension manager setter
*
* @param \phpbb\extension\manager $extension_manager Extension manager
*/
public function set_extension_manager(\phpbb\extension\manager $extension_manager)
{
$this->extension_manager = $extension_manager;
}
/**
* Loads language array for the given component
*
* @param string $component Name of the language component
* @param string|array $locale ISO code of the language to load, or array of ISO codes if you want to
* specify additional language fallback steps
* @param array $lang Array reference containing language strings
*/
public function load($component, $locale, &$lang)
{
$locale = (array) $locale;
// Determine path to language directory
$path = $this->phpbb_root_path . 'language/';
$this->load_file($path, $component, $locale, $lang);
}
/**
* Loads language array for the given extension component
*
* @param string $extension Name of the extension
* @param string $component Name of the language component
* @param string|array $locale ISO code of the language to load, or array of ISO codes if you want to
* specify additional language fallback steps
* @param array $lang Array reference containing language strings
*/
public function load_extension($extension, $component, $locale, &$lang)
{
// Check if extension manager was loaded
if ($this->extension_manager === null)
{
// If not, let's return
return;
}
$locale = (array) $locale;
// Determine path to language directory
$path = $this->extension_manager->get_extension_path($extension, true) . 'language/';
$this->load_file($path, $component, $locale, $lang);
}
/**
* Prepares language file loading
*
* @param string $path Path to search for file in
* @param string $component Name of the language component
* @param array $locale Array containing language fallback options
* @param array $lang Array reference of language strings
*/
protected function load_file($path, $component, $locale, &$lang)
{
// This is BC stuff and not the best idea as it makes language fallback
// implementation quite hard like below.
if (strpos($this->phpbb_root_path . $component, $path) === 0)
{
// Filter out the path
$path_diff = str_replace($path, '', dirname($this->phpbb_root_path . $component));
$language_file = basename($component, '.' . $this->php_ext);
$component = '';
// This step is needed to resolve language/en/subdir style $component
// $path already points to the language base directory so we need to eliminate
// the first directory from the path (that should be the language directory)
$path_diff_parts = explode('/', $path_diff);
if (count($path_diff_parts) > 1)
{
array_shift($path_diff_parts);
$component = implode('/', $path_diff_parts) . '/';
}
$component .= $language_file;
}
// Determine filename
$filename = $component . '.' . $this->php_ext;
// Determine path to file
$file_path = $this->get_language_file_path($path, $filename, $locale);
// Load language array
$this->load_language_file($file_path, $lang);
}
/**
* This function implements language fallback logic
*
* @param string $path Path to language directory
* @param string $filename Filename to load language strings from
* @param array $locales Array containing language fallback options
*
* @return string Relative path to language file
*
* @throws language_file_not_found When the path to the file cannot be resolved
*/
protected function get_language_file_path($path, $filename, $locales)
{
$language_file_path = $filename;
// Language fallback logic
foreach ($locales as $locale)
{
$language_file_path = $path . $locale . '/' . $filename;
// If we are in install, try to use the updated version, when available
if (defined('IN_INSTALL'))
{
$install_language_path = str_replace('language/', 'install/update/new/language/', $language_file_path);
if (file_exists($install_language_path))
{
return $install_language_path;
}
}
if (file_exists($language_file_path))
{
return $language_file_path;
}
}
// The language file is not exist
throw new language_file_not_found('Language file ' . $language_file_path . ' couldn\'t be opened.');
}
/**
* Loads language file
*
* @param string $path Path to language file to load
* @param array $lang Reference of the array of language strings
*/
protected function load_language_file($path, &$lang)
{
// Do not suppress error if in DEBUG mode
if (defined('DEBUG'))
{
include $path;
}
else
{
@include $path;
}
}
}

View File

@@ -0,0 +1,153 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\lock;
/**
* Database locking class
*/
class db
{
/**
* Name of the config variable this lock uses
* @var string
*/
private $config_name;
/**
* Unique identifier for this lock.
*
* @var string
*/
private $unique_id;
/**
* Stores the state of this lock
* @var bool
*/
private $locked;
/**
* The phpBB configuration
* @var \phpbb\config\config
*/
private $config;
/**
* A database connection
* @var \phpbb\db\driver\driver_interface
*/
private $db;
/**
* Creates a named released instance of the lock.
*
* You have to call acquire() to actually create the lock.
*
* @param string $config_name A config variable to be used for locking
* @param \phpbb\config\config $config The phpBB configuration
* @param \phpbb\db\driver\driver_interface $db A database connection
*/
public function __construct($config_name, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db)
{
$this->config_name = $config_name;
$this->config = $config;
$this->db = $db;
}
/**
* Tries to acquire the lock by updating
* the configuration variable in the database.
*
* As a lock may only be held by one process at a time, lock
* acquisition may fail if another process is holding the lock
* or if another process obtained the lock but never released it.
* Locks are forcibly released after a timeout of 1 hour.
*
* @return bool true if lock was acquired
* false otherwise
*/
public function acquire()
{
if ($this->locked)
{
return false;
}
if (!isset($this->config[$this->config_name]))
{
$this->config->set($this->config_name, '0', false);
}
$lock_value = $this->config[$this->config_name];
// make sure lock cannot be acquired by multiple processes
if ($lock_value)
{
// if the other process is running more than an hour already we have to assume it
// aborted without cleaning the lock
$time = explode(' ', $lock_value);
$time = $time[0];
if ($time + 3600 >= time())
{
return false;
}
}
$this->unique_id = time() . ' ' . unique_id();
// try to update the config value, if it was already modified by another
// process we failed to acquire the lock.
$this->locked = $this->config->set_atomic($this->config_name, $lock_value, $this->unique_id, false);
if ($this->locked == true)
{
if ($this->config->ensure_lock($this->config_name, $this->unique_id))
{
return true;
}
}
return $this->locked;
}
/**
* Does this process own the lock?
*
* @return bool true if lock is owned
* false otherwise
*/
public function owns_lock()
{
return (bool) $this->locked;
}
/**
* Releases the lock.
*
* The lock must have been previously obtained, that is, acquire() call
* was issued and returned true.
*
* Note: Attempting to release a lock that is already released,
* that is, calling release() multiple times, is harmless.
*
* @return null
*/
public function release()
{
if ($this->locked)
{
$this->config->set_atomic($this->config_name, $this->unique_id, '0', false);
$this->locked = false;
}
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @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\lock;
/**
* File locking class
*/
class flock
{
/**
* Path to the file to which access is controlled
*
* @var string
*/
private $path;
/**
* File pointer for the lock file
* @var string
*/
private $lock_fp;
/**
* Constructor.
*
* You have to call acquire() to actually acquire the lock.
*
* @param string $path Path to the file to which access is controlled
*/
public function __construct($path)
{
$this->path = $path;
$this->lock_fp = null;
}
/**
* Tries to acquire the lock.
*
* If the lock is already held by another process, this call will block
* until the other process releases the lock. If a lock is acquired and
* is not released before script finishes but the process continues to
* live (apache/fastcgi) then subsequent processes trying to acquire
* the same lock will be blocked forever.
*
* If the lock is already held by the same process via another instance
* of this class, this call will block forever.
*
* If flock function is disabled in php or fails to work, lock
* acquisition will fail and false will be returned.
*
* @return bool true if lock was acquired
* false otherwise
*/
public function acquire()
{
if ($this->lock_fp)
{
return false;
}
// For systems that can't have two processes opening
// one file for writing simultaneously
if (file_exists($this->path . '.lock'))
{
$mode = 'rb';
}
else
{
$mode = 'wb';
}
$this->lock_fp = @fopen($this->path . '.lock', $mode);
if ($mode == 'wb')
{
if (!$this->lock_fp)
{
// Two processes may attempt to create lock file at the same time.
// Have the losing process try opening the lock file again for reading
// on the assumption that the winning process created it
$mode = 'rb';
$this->lock_fp = @fopen($this->path . '.lock', $mode);
}
else
{
// Only need to set mode when the lock file is written
@chmod($this->path . '.lock', 0666);
}
}
if ($this->lock_fp)
{
if (!@flock($this->lock_fp, LOCK_EX))
{
throw new \phpbb\exception\http_exception(500, 'Failure while aqcuiring locks.');
}
}
return (bool) $this->lock_fp;
}
/**
* Does this process own the lock?
*
* @return bool true if lock is owned
* false otherwise
*/
public function owns_lock()
{
return (bool) $this->lock_fp;
}
/**
* Releases the lock.
*
* The lock must have been previously obtained, that is, acquire() call
* was issued and returned true.
*
* Note: Attempting to release a lock that is already released,
* that is, calling release() multiple times, is harmless.
*
* @return null
*/
public function release()
{
if ($this->lock_fp)
{
@flock($this->lock_fp, LOCK_UN);
fclose($this->lock_fp);
$this->lock_fp = null;
}
}
}

Some files were not shown because too many files have changed in this diff Show More