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,264 @@
<?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;
/**
* Apache authentication provider for phpBB3
*/
class apache extends \phpbb\auth\provider\base
{
/**
* phpBB passwords manager
*
* @var \phpbb\passwords\manager
*/
protected $passwords_manager;
/**
* Apache Authentication Constructor
*
* @param \phpbb\db\driver\driver_interface $db Database object
* @param \phpbb\config\config $config Config object
* @param \phpbb\passwords\manager $passwords_manager Passwords Manager object
* @param \phpbb\request\request $request Request object
* @param \phpbb\user $user User object
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext)
{
$this->db = $db;
$this->config = $config;
$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 init()
{
if (!$this->request->is_set('PHP_AUTH_USER', \phpbb\request\request_interface::SERVER) || $this->user->data['username'] !== htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')))
{
return $this->user->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', \phpbb\request\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, $php_auth_pw),
);
}
// 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', \phpbb\request\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))
{
set_var($php_auth_user, $php_auth_user, 'string', true);
set_var($php_auth_pw, $php_auth_pw, '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, $php_auth_pw));
$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.
* @param string $password The password of the new user.
* @return array Contains data that can be passed directly to
* the user_add function.
*/
private function user_row($username, $password)
{
// 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' => $this->passwords_manager->hash($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', \phpbb\request\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 \phpbb\auth\provider\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,240 @@
<?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;
/**
* Database authentication provider for phpBB3
* This is for authentication via the integrated user table
*/
class db extends \phpbb\auth\provider\base
{
/**
* phpBB passwords manager
*
* @var \phpbb\passwords\manager
*/
protected $passwords_manager;
/**
* DI container
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $phpbb_container;
/**
* Database Authentication Constructor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\config\config $config
* @param \phpbb\passwords\manager $passwords_manager
* @param \phpbb\request\request $request
* @param \phpbb\user $user
* @param \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container DI container
* @param string $phpbb_root_path
* @param string $php_ext
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, $phpbb_root_path, $php_ext)
{
$this->db = $db;
$this->config = $config;
$this->passwords_manager = $passwords_manager;
$this->request = $request;
$this->user = $user;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->phpbb_container = $phpbb_container;
}
/**
* {@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)
{
/* @var $captcha_factory \phpbb\captcha\factory */
$captcha_factory = $this->phpbb_container->get('captcha.factory');
$captcha = $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,348 @@
<?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;
/**
* Database authentication provider for phpBB3
* This is for authentication via the integrated user table
*/
class ldap extends \phpbb\auth\provider\base
{
/**
* phpBB passwords manager
*
* @var \phpbb\passwords\manager
*/
protected $passwords_manager;
/**
* LDAP Authentication Constructor
*
* @param \phpbb\db\driver\driver_interface $db Database object
* @param \phpbb\config\config $config Config object
* @param \phpbb\passwords\manager $passwords_manager Passwords manager object
* @param \phpbb\user $user User object
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\user $user)
{
$this->db = $db;
$this->config = $config;
$this->passwords_manager = $passwords_manager;
$this->user = $user;
}
/**
* {@inheritdoc}
*/
public function init()
{
if (!@extension_loaded('ldap'))
{
return $this->user->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->user->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->user->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->user->lang['LDAP_SEARCH_FAILED'];
}
$result = @ldap_get_entries($ldap, $search);
@ldap_close($ldap);
if (!is_array($result) || count($result) < 2)
{
return sprintf($this->user->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->user->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' => $this->passwords_manager->hash($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,747 @@
<?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\Consumer\Credentials;
/**
* OAuth authentication provider for phpBB3
*/
class oauth extends \phpbb\auth\provider\base
{
/**
* Database driver
*
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* phpBB config
*
* @var \phpbb\config\config
*/
protected $config;
/**
* phpBB passwords manager
*
* @var \phpbb\passwords\manager
*/
protected $passwords_manager;
/**
* phpBB request object
*
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* phpBB user
*
* @var \phpbb\user
*/
protected $user;
/**
* OAuth token table
*
* @var string
*/
protected $auth_provider_oauth_token_storage_table;
/**
* OAuth state table
*
* @var string
*/
protected $auth_provider_oauth_state_table;
/**
* OAuth account association table
*
* @var string
*/
protected $auth_provider_oauth_token_account_assoc;
/**
* All OAuth service providers
*
* @var \phpbb\di\service_collection Contains \phpbb\auth\provider\oauth\service_interface
*/
protected $service_providers;
/**
* Users table
*
* @var string
*/
protected $users_table;
/**
* Cached current uri object
*
* @var \OAuth\Common\Http\Uri\UriInterface|null
*/
protected $current_uri;
/**
* DI container
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $phpbb_container;
/**
* phpBB event dispatcher
*
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* phpBB root path
*
* @var string
*/
protected $phpbb_root_path;
/**
* PHP file extension
*
* @var string
*/
protected $php_ext;
/**
* OAuth Authentication Constructor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\config\config $config
* @param \phpbb\passwords\manager $passwords_manager
* @param \phpbb\request\request_interface $request
* @param \phpbb\user $user
* @param string $auth_provider_oauth_token_storage_table
* @param string $auth_provider_oauth_state_table
* @param string $auth_provider_oauth_token_account_assoc
* @param \phpbb\di\service_collection $service_providers Contains \phpbb\auth\provider\oauth\service_interface
* @param string $users_table
* @param \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container DI container
* @param \phpbb\event\dispatcher_interface $dispatcher phpBB event dispatcher
* @param string $phpbb_root_path
* @param string $php_ext
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_state_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, \phpbb\event\dispatcher_interface $dispatcher, $phpbb_root_path, $php_ext)
{
$this->db = $db;
$this->config = $config;
$this->passwords_manager = $passwords_manager;
$this->request = $request;
$this->user = $user;
$this->auth_provider_oauth_token_storage_table = $auth_provider_oauth_token_storage_table;
$this->auth_provider_oauth_state_table = $auth_provider_oauth_state_table;
$this->auth_provider_oauth_token_account_assoc = $auth_provider_oauth_token_account_assoc;
$this->service_providers = $service_providers;
$this->users_table = $users_table;
$this->phpbb_container = $phpbb_container;
$this->dispatcher = $dispatcher;
$this->phpbb_root_path = $phpbb_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->user->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'))
{
$provider = new \phpbb\auth\provider\db($this->db, $this->config, $this->passwords_manager, $this->request, $this->user, $this->phpbb_container, $this->phpbb_root_path, $this->php_ext);
return $provider->login($username, $password);
}
// Request the name of the OAuth service
$service_name_original = $this->request->variable('oauth_service', '', false);
$service_name = 'auth.provider.oauth.service.' . strtolower($service_name_original);
if ($service_name_original === '' || !array_key_exists($service_name, $this->service_providers))
{
return array(
'status' => LOGIN_ERROR_EXTERNAL_AUTH,
'error_msg' => 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST',
'user_row' => array('user_id' => ANONYMOUS),
);
}
// Get the service credentials for the given service
$service_credentials = $this->service_providers[$service_name]->get_service_credentials();
$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);
$query = 'mode=login&login=external&oauth_service=' . $service_name_original;
$service = $this->get_service($service_name_original, $storage, $service_credentials, $query, $this->service_providers[$service_name]->get_auth_scope());
if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET))
|| ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET)))
{
$this->service_providers[$service_name]->set_external_service_provider($service);
$unique_id = $this->service_providers[$service_name]->perform_auth_login();
// Check to see if this provider is already assosciated with an account
$data = array(
'provider' => $service_name_original,
'oauth_provider_id' => $unique_id
);
$sql = 'SELECT user_id FROM ' . $this->auth_provider_oauth_token_account_assoc . '
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' => $service_name_original,
);
/**
* 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 \OAuth\Common\Service\ServiceInterface service OAuth service
* @since 3.2.3-RC1
* @changed 3.2.6-RC1 Added redirect_data
*/
$vars = array(
'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 array(
'status' => LOGIN_SUCCESS_LINK_PROFILE,
'error_msg' => 'LOGIN_OAUTH_ACCOUNT_NOT_LINKED',
'user_row' => array(),
'redirect_data' => $redirect_data,
);
}
// Retrieve the user's account
$sql = 'SELECT user_id, username, user_password, user_passchg, user_email, 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)
{
throw new \Exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY');
}
/**
* 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->phpbb_root_path, $this->php_ext);
$message = $this->user->lang($message, $till_date, '<a href="' . $contact_link . '">', '</a>');
$message .= !empty($ban['ban_give_reason']) ? '<br /><br />' . $this->user->lang('BOARD_BAN_REASON', $ban['ban_give_reason']) : '';
$message .= !empty($ban['ban_triggered_by']) ? '<br /><br /><em>' . $this->user->lang('BAN_TRIGGERED_BY_' . strtoupper($ban['ban_triggered_by'])) . '</em>' : '';
return array(
'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 = array(
'row',
);
extract($this->dispatcher->trigger_event('core.auth_oauth_login_after', compact($vars)));
// The user is now authenticated and can be logged in
return array(
'status' => LOGIN_SUCCESS,
'error_msg' => false,
'user_row' => $row,
);
}
else
{
if ($service::OAUTH_VERSION === 1)
{
$token = $service->requestRequestToken();
$url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken()));
}
else
{
$url = $service->getAuthorizationUri();
}
header('Location: ' . $url);
}
}
/**
* Returns the cached current_uri object or creates and caches it if it is
* not already created. In each case the query string is updated based on
* the $query parameter.
*
* @param string $service_name The name of the service
* @param string $query The query string of the current_uri
* used in redirects
* @return \OAuth\Common\Http\Uri\UriInterface
*/
protected function get_current_uri($service_name, $query)
{
if ($this->current_uri)
{
$this->current_uri->setQuery($query);
return $this->current_uri;
}
$uri_factory = new \OAuth\Common\Http\Uri\UriFactory();
$super_globals = $this->request->get_super_global(\phpbb\request\request_interface::SERVER);
if (!empty($super_globals['HTTP_X_FORWARDED_PROTO']) && $super_globals['HTTP_X_FORWARDED_PROTO'] === 'https')
{
$super_globals['HTTPS'] = 'on';
$super_globals['SERVER_PORT'] = 443;
}
$current_uri = $uri_factory->createFromSuperGlobalArray($super_globals);
$current_uri->setQuery($query);
$this->current_uri = $current_uri;
return $current_uri;
}
/**
* Returns a new service object
*
* @param string $service_name The name of the service
* @param \phpbb\auth\provider\oauth\token_storage $storage
* @param array $service_credentials {@see \phpbb\auth\provider\oauth\oauth::get_service_credentials}
* @param string $query The query string of the
* current_uri used in redirection
* @param array $scopes The scope of the request against
* the api.
* @return \OAuth\Common\Service\ServiceInterface
* @throws \Exception
*/
protected function get_service($service_name, \phpbb\auth\provider\oauth\token_storage $storage, array $service_credentials, $query, array $scopes = array())
{
$current_uri = $this->get_current_uri($service_name, $query);
// Setup the credentials for the requests
$credentials = new Credentials(
$service_credentials['key'],
$service_credentials['secret'],
$current_uri->getAbsoluteUri()
);
$service_factory = new \OAuth\ServiceFactory();
$service = $service_factory->createService($service_name, $credentials, $storage, $scopes);
if (!$service)
{
throw new \Exception('AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED');
}
return $service;
}
/**
* {@inheritdoc}
*/
public function get_login_data()
{
$login_data = array(
'TEMPLATE_FILE' => 'login_body_oauth.html',
'BLOCK_VAR_NAME' => 'oauth',
'BLOCK_VARS' => array(),
);
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'])
{
$actual_name = str_replace('auth.provider.oauth.service.', '', $service_name);
$redirect_url = build_url(false) . '&login=external&oauth_service=' . $actual_name;
$login_data['BLOCK_VARS'][$service_name] = array(
'REDIRECT_URL' => redirect($redirect_url, true),
'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)],
);
}
}
return $login_data;
}
/**
* {@inheritdoc}
*/
public function acp()
{
$ret = array();
foreach ($this->service_providers as $service_name => $service_provider)
{
$actual_name = str_replace('auth.provider.oauth.service.', '', $service_name);
$ret[] = 'auth_oauth_' . $actual_name . '_key';
$ret[] = 'auth_oauth_' . $actual_name . '_secret';
}
return $ret;
}
/**
* {@inheritdoc}
*/
public function get_acp_template($new_config)
{
$ret = array(
'BLOCK_VAR_NAME' => 'oauth_services',
'BLOCK_VARS' => array(),
'TEMPLATE_FILE' => 'auth_provider_oauth.html',
'TEMPLATE_VARS' => array(),
);
foreach ($this->service_providers as $service_name => $service_provider)
{
$actual_name = str_replace('auth.provider.oauth.service.', '', $service_name);
$ret['BLOCK_VARS'][$actual_name] = array(
'ACTUAL_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)],
'KEY' => $new_config['auth_oauth_' . $actual_name . '_key'],
'NAME' => $actual_name,
'SECRET' => $new_config['auth_oauth_' . $actual_name . '_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'], array(
'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 = 'auth.provider.oauth.service.' . strtolower($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);
}
}
/**
* 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|null Returns a language constant (string) if an error is
* encountered, or null on success.
*/
protected function link_account_login_link(array $link_data, $service_name)
{
$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_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 the query string
$query = 'mode=login_link&login_link_oauth_service=' . strtolower($link_data['oauth_service']);
// Prepare for an authentication request
$service_credentials = $this->service_providers[$service_name]->get_service_credentials();
$scopes = $this->service_providers[$service_name]->get_auth_scope();
$service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $query, $scopes);
$this->service_providers[$service_name]->set_external_service_provider($service);
// The user has already authenticated successfully, request to authenticate again
$unique_id = $this->service_providers[$service_name]->perform_token_auth();
// Insert into table, they will be able to log in after this
$data = array(
'user_id' => $link_data['user_id'],
'provider' => 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']);
}
/**
* 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|null Returns a language constant (string) if an error is
* encountered, or null on success.
*/
protected function link_account_auth_link(array $link_data, $service_name)
{
$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);
$query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . strtolower($link_data['oauth_service']);
$service_credentials = $this->service_providers[$service_name]->get_service_credentials();
$scopes = $this->service_providers[$service_name]->get_auth_scope();
$service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $query, $scopes);
if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET))
|| ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET)))
{
$this->service_providers[$service_name]->set_external_service_provider($service);
$unique_id = $this->service_providers[$service_name]->perform_auth_login();
// Insert into table, they will be able to log in after this
$data = array(
'user_id' => $this->user->data['user_id'],
'provider' => strtolower($link_data['oauth_service']),
'oauth_provider_id' => $unique_id,
);
$this->link_account_perform_link($data);
}
else
{
if ($service::OAUTH_VERSION === 1)
{
$token = $service->requestRequestToken();
$url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken()));
}
else
{
$url = $service->getAuthorizationUri();
}
header('Location: ' . $url);
}
}
/**
* 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)
{
$sql = 'INSERT INTO ' . $this->auth_provider_oauth_token_account_assoc . '
' . $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 = array(
'data',
);
extract($this->dispatcher->trigger_event('core.auth_oauth_link_after', compact($vars)));
}
/**
* {@inheritdoc}
*/
public function logout($data, $new_session)
{
// Clear all tokens belonging to the user
$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);
$storage->clearAllTokens();
return;
}
/**
* {@inheritdoc}
*/
public function get_auth_link_data($user_id = 0)
{
$block_vars = array();
// Get all external accounts tied to the current user
$data = array(
'user_id' => ($user_id <= 0) ? (int) $this->user->data['user_id'] : (int) $user_id,
);
$sql = 'SELECT oauth_provider_id, provider FROM ' . $this->auth_provider_oauth_token_account_assoc . '
WHERE ' . $this->db->sql_build_array('SELECT', $data);
$result = $this->db->sql_query($sql);
$rows = $this->db->sql_fetchrowset($result);
$this->db->sql_freeresult($result);
$oauth_user_ids = array();
if ($rows !== false && count($rows))
{
foreach ($rows as $row)
{
$oauth_user_ids[$row['provider']] = $row['oauth_provider_id'];
}
}
unset($rows);
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'])
{
$actual_name = str_replace('auth.provider.oauth.service.', '', $service_name);
$block_vars[$service_name] = array(
'HIDDEN_FIELDS' => array(
'link' => (!isset($oauth_user_ids[$actual_name])),
'oauth_service' => $actual_name,
),
'SERVICE_ID' => $actual_name,
'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)],
'UNIQUE_ID' => (isset($oauth_user_ids[$actual_name])) ? $oauth_user_ids[$actual_name] : null,
);
}
}
return array(
'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->auth_provider_oauth_token_account_assoc . "
WHERE provider = '" . $this->db->sql_escape($link_data['oauth_service']) . "'
AND user_id = " . (int) $user_id;
$this->db->sql_query($sql);
// Clear all tokens belonging to the user on this service
$service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']);
$storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table);
$storage->clearToken($service_name);
}
}

View File

@@ -0,0 +1,51 @@
<?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 \phpbb\auth\provider\oauth\service\service_interface
{
/**
* External OAuth service provider
*
* @var \OAuth\Common\Service\ServiceInterface
*/
protected $service_provider;
/**
* {@inheritdoc}
*/
public function get_external_service_provider()
{
return $this->service_provider;
}
/**
* {@inheritdoc}
*/
public function get_auth_scope()
{
return array();
}
/**
* {@inheritdoc}
*/
public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider)
{
$this->service_provider = $service_provider;
}
}

View File

@@ -0,0 +1,94 @@
<?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 \phpbb\auth\provider\oauth\service\base
{
/**
* phpBB config
*
* @var \phpbb\config\config
*/
protected $config;
/**
* phpBB request
*
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* Constructor
*
* @param \phpbb\config\config $config
* @param \phpbb\request\request_interface $request
*/
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 array(
'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 \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
// This was a callback request from bitly, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
// Send a request with it
$result = json_decode($this->service_provider->request('user/info'), true);
// 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 \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
// Send a request with it
$result = json_decode($this->service_provider->request('user/info'), true);
// Return the unique identifier returned from bitly
return $result['data']['login'];
}
}

View File

@@ -0,0 +1,94 @@
<?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
{
/**
* phpBB config
*
* @var \phpbb\config\config
*/
protected $config;
/**
* phpBB request
*
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* Constructor
*
* @param \phpbb\config\config $config
* @param \phpbb\request\request_interface $request
*/
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 array(
'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');
}
// This was a callback request, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
// Send a request with it
$result = json_decode($this->service_provider->request('/me'), true);
// 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');
}
// Send a request with it
$result = json_decode($this->service_provider->request('/me'), true);
// Return the unique identifier
return $result['id'];
}
}

View File

@@ -0,0 +1,105 @@
<?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
{
/**
* phpBB config
*
* @var \phpbb\config\config
*/
protected $config;
/**
* phpBB request
*
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* Constructor
*
* @param \phpbb\config\config $config
* @param \phpbb\request\request_interface $request
*/
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 array(
'userinfo_email',
'userinfo_profile',
);
}
/**
* {@inheritdoc}
*/
public function get_service_credentials()
{
return array(
'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');
}
// This was a callback request, get the token
$this->service_provider->requestAccessToken($this->request->variable('code', ''));
// Send a request with it
$result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
// 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');
}
// Send a request with it
$result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
// Return the unique identifier
return $result['id'];
}
}

View File

@@ -0,0 +1,73 @@
<?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 the external library service provider once it has been set
*
* @param \OAuth\Common\Service\ServiceInterface|null
*/
public function get_external_service_provider();
/**
* 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();
/**
* 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,102 @@
<?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 \phpbb\auth\provider\oauth\service\base
{
/**
* phpBB config
*
* @var \phpbb\config\config
*/
protected $config;
/**
* phpBB request
*
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* Constructor
*
* @param \phpbb\config\config $config
* @param \phpbb\request\request_interface $request
*/
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 array(
'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 \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
$storage = $this->service_provider->getStorage();
$token = $storage->retrieveAccessToken('Twitter');
$tokensecret = $token->getRequestTokenSecret();
// This was a callback request from twitter, get the token
$this->service_provider->requestAccessToken(
$this->request->variable('oauth_token', ''),
$this->request->variable('oauth_verifier', ''),
$tokensecret
);
// Send a request with it
$result = json_decode($this->service_provider->request('account/verify_credentials.json'), true);
// Return the unique identifier returned from twitter
return $result['id'];
}
/**
* {@inheritdoc}
*/
public function perform_token_auth()
{
if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter))
{
throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE');
}
// Send a request with it
$result = json_decode($this->service_provider->request('account/verify_credentials.json'), true);
// Return the unique identifier returned from twitter
return $result['id'];
}
}

View File

@@ -0,0 +1,592 @@
<?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
{
/**
* Cache driver.
*
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* phpBB user
*
* @var \phpbb\user
*/
protected $user;
/**
* OAuth token table
*
* @var string
*/
protected $oauth_token_table;
/**
* OAuth state table
*
* @var string
*/
protected $oauth_state_table;
/**
* @var object|TokenInterface
*/
protected $cachedToken;
/**
* @var string
*/
protected $cachedState;
/**
* Creates token storage for phpBB.
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\user $user
* @param string $oauth_token_table
* @param string $oauth_state_table
*/
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 = array(
'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 = array(
'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'] . '
' . ((int) $this->user->data['user_id'] === ANONYMOUS ? "AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'" : '') . "
AND provider = '" . $this->db->sql_escape($service) . "'";
$this->db->sql_query($sql);
if (!$this->db->sql_affectedrows())
{
$data = array(
'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 = array(
'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_acess_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 = array(
'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 = array(
'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 = array(
'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 assosciated with the token
*
* @param int $user_id
*/
public function set_user_id($user_id)
{
if (!$this->cachedToken)
{
return;
}
$sql = 'UPDATE ' . $this->oauth_token_table . '
SET ' . $this->db->sql_build_array('UPDATE', array(
'user_id' => (int) $user_id
)) . '
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 name of the OAuth service
* @return bool true if they have token, false if they don't
*/
public function has_access_token_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedToken)
{
return true;
}
$data = array(
'session_id' => $this->user->data['session_id'],
'provider' => $service,
);
return $this->_has_acess_token($data);
}
/**
* Checks to see if a state exists solely by the session_id of the user
*
* @param string $service The name of the OAuth service
* @return bool true if they have state, false if they don't
*/
public function has_state_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return true;
}
$data = array(
'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
* @return bool
*/
protected function _has_acess_token($data)
{
return (bool) $this->get_access_token_row($data);
}
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 = array(
'session_id' => $this->user->data['session_id'],
'provider' => $service,
);
return $this->_retrieve_access_token($data);
}
public function retrieve_state_by_session($service)
{
$service = $this->get_service_name_for_db($service);
if ($this->cachedState)
{
return $this->cachedState;
}
$data = array(
'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
* @return mixed
* @throws \OAuth\Common\Storage\Exception\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
* @return mixed
* @throws \OAuth\Common\Storage\Exception\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
* @return mixed
*/
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
* @return mixed
*/
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;
}
public function json_encode_token(TokenInterface $token)
{
$members = array(
'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);
}
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
$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 name of the service as it must be stored in the database.
*
* @param string $service The name of the OAuth service
* @return string The name of the OAuth service as it needs to be stored
* in the database.
*/
protected function get_service_name_for_db($service)
{
// Enforce the naming convention for oauth services
if (strpos($service, 'auth.provider.oauth.service.') !== 0)
{
$service = 'auth.provider.oauth.service.' . strtolower($service);
}
return $service;
}
}

View File

@@ -0,0 +1,197 @@
<?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 impletmented.
*/
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 configuraton values are passed to it as an array.
* It then returns the name of the acp file related to this authentication
* provider.
* @param array $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,216 @@
<?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)
{
$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;
}
// 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,331 @@
<?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'],
));
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,134 @@
<?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
*/
public function __construct()
{
// Call the parent constructor
parent::__construct();
$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);
}
foreach (explode(',', PHPBB_ACM_MEMCACHED) as $u)
{
preg_match('#(.*)/(\d+)#', $u, $parts);
$this->memcached->addServer(trim($parts[1]), (int) trim($parts[2]));
}
}
/**
* {@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 $phpbb_root_path, $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 existance 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,167 @@
<?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;
}
/**
* 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,160 @@
<?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;
}
throw new \RuntimeException("You have specified an invalid dbms driver: $dbms");
}
}

View File

@@ -0,0 +1,171 @@
<?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 occured 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 string $task_name The name of the task that should be run.
* @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 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,76 @@
<?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 (!$extension->is_enableable())
{
$io->error($this->user->lang('CLI_EXTENSION_NOT_ENABLEABLE', $name));
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,331 @@
<?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 occured.
* @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 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'], $this->config['max_pass_chars']),
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,885 @@
<?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)
{
$is_visible = $this->auth->acl_get('m_approve', $forum_id) || $data[$mode . '_visibility'] == ITEM_APPROVED;
/**
* 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
{
$where_sql .= $table_alias . $mode . '_visibility = ' . ITEM_APPROVED;
}
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 occured.
*/
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: 3.3.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,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\cron;
/**
* Cron manager class.
*
* Finds installed cron tasks, stores task objects, provides task selection.
*/
class manager
{
/**
* Set of \phpbb\cron\task\wrapper objects.
* Array holding all tasks that have been found.
*
* @var array
*/
protected $tasks = array();
protected $phpbb_root_path;
protected $php_ext;
/**
* Constructor. Loads all available tasks.
*
* @param array|\Traversable $tasks Provides an iterable set of task names
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct($tasks, $phpbb_root_path, $php_ext)
{
$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 \phpbb\cron\task\wrapper($task, $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())
{
$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,106 @@
<?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;
/**
* Cron task wrapper class.
* Enhances cron tasks with convenience methods that work identically for all tasks.
*/
class wrapper
{
protected $task;
protected $phpbb_root_path;
protected $php_ext;
/**
* Constructor.
*
* Wraps a task $task, which must implement cron_task interface.
*
* @param \phpbb\cron\task\task $task The cron task to wrap.
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $php_ext PHP file extension
*/
public function __construct(\phpbb\cron\task\task $task, $phpbb_root_path, $php_ext)
{
$this->task = $task;
$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 \phpbb\cron\task\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()
{
$name = $this->get_name();
if ($this->is_parametrized())
{
$params = $this->task->get_parameters();
$extra = '';
foreach ($params as $key => $value)
{
$extra .= '&amp;' . $key . '=' . urlencode($value);
}
}
else
{
$extra = '';
}
$url = append_sid($this->phpbb_root_path . 'cron.' . $this->php_ext, 'cron_type=' . $name . $extra);
return $url;
}
/**
* 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,453 @@
<?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
{
/**
* 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,443 @@
<?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 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,385 @@
<?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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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,451 @@
<?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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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,490 @@
<?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 != '')
{
@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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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 existance 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,822 @@
<?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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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,501 @@
<?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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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,431 @@
<?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;
// EXPLAIN only in extra debug mode
if (defined('DEBUG'))
{
$this->sql_report('start', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$this->sql_report('stop', $query);
}
else if (defined('PHPBB_DISPLAY_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 (defined('DEBUG'))
{
$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,403 @@
<?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();
}
if ($this->db->get_sql_layer() === 'mysqli')
{
$this->write_data_mysqli($table_name);
}
else
{
$this->write_data_mysql($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 data from database table (for MySQL 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_mysql($table_name)
{
if (!$this->is_initialized)
{
throw new extractor_not_initialized_exception();
}
$sql = "SELECT *
FROM $table_name";
$result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id());
if ($result != false)
{
$fields_cnt = mysql_num_fields($result);
// Get field information
$field = array();
for ($i = 0; $i < $fields_cnt; $i++)
{
$field[] = mysql_fetch_field($result, $i);
}
$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 = mysql_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]->numeric && ($field[$j]->type !== 'timestamp'))
{
$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;
}
}
mysql_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 verisons 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
{
// equivelant 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 surpressed 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,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 existance 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 existance 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 existance 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,673 @@
<?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 = array(new extension\core($this->get_config_path()));
if ($this->use_extensions)
{
$this->load_extensions();
}
// 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\\ContainerBuilder',
));
$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,52 @@
<?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()
->arrayNode('debug')
->addDefaultsIfNotSet()
->children()
->booleanNode('exceptions')->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()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,124 @@
<?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
);
}
}
// 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);
}
}
/**
* {@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,106 @@
<?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);
}
// Because of a PHP issue we have to redefine offsetExists
// (even with a call to the parent):
// https://bugs.php.net/bug.php?id=66834
// https://bugs.php.net/bug.php?id=67067
// But it triggers a sniffer issue that we have to skip
// @codingStandardsIgnoreStart
/**
* {@inheritdoc}
*/
public function offsetExists($index)
{
return parent::offsetExists($index);
}
// @codingStandardsIgnoreEnd
/**
* {@inheritdoc}
*/
public function offsetGet($index)
{
return $this->container->get($index);
}
/**
* Add a service to the collection
*
* @param string $name The service name
* @return null
*/
public function add($name)
{
$this->offsetSet($name, null);
}
/**
* 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;
}
}

View File

@@ -0,0 +1,561 @@
<?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 (!file_exists($this->path . $file) || substr($file, -5) !== '.html')
{
throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1);
}
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}'", 2);
}
$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}'", 2);
}
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,70 @@
<?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
*/
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,633 @@
<?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'];
}
/**
* 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->extensions[$name] = $extension_data;
$this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']);
ksort($this->extensions);
$sql = 'SELECT COUNT(ext_name) as row_count
FROM ' . $this->extension_table . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$result = $this->db->sql_query($sql);
$count = $this->db->sql_fetchfield('row_count');
$this->db->sql_freeresult($result);
if ($count)
{
$sql = 'UPDATE ' . $this->extension_table . '
SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
}
else
{
$sql = 'INSERT INTO ' . $this->extension_table . '
' . $this->db->sql_build_array('INSERT', $extension_data);
$this->db->sql_query($sql);
}
if ($this->cache)
{
$this->cache->purge();
}
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);
// continue until the state is false
if ($state !== false)
{
$extension_data = array(
'ext_state' => serialize($state),
);
$this->extensions[$name]['ext_state'] = serialize($state);
$sql = 'UPDATE ' . $this->extension_table . '
SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
if ($this->cache)
{
$this->cache->purge();
}
return true;
}
$extension_data = array(
'ext_active' => false,
'ext_state' => serialize(false),
);
$this->extensions[$name]['ext_active'] = false;
$this->extensions[$name]['ext_state'] = serialize(false);
$sql = 'UPDATE ' . $this->extension_table . '
SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
if ($this->cache)
{
$this->cache->purge();
}
return false;
}
/**
* 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);
// continue until the state is false
if ($state !== false)
{
$extension_data = array(
'ext_state' => serialize($state),
);
$this->extensions[$name]['ext_state'] = serialize($state);
$sql = 'UPDATE ' . $this->extension_table . '
SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
if ($this->cache)
{
$this->cache->purge();
}
return true;
}
unset($this->extensions[$name]);
$sql = 'DELETE FROM ' . $this->extension_table . "
WHERE ext_name = '" . $this->db->sql_escape($name) . "'";
$this->db->sql_query($sql);
if ($this->cache)
{
$this->cache->purge();
}
return false;
}
/**
* 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,411 @@
<?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 int forum_id Forum ID
* @var string mode Feeds mode (forums|topics|topics_new|topics_active|news)
* @var array row Array with feed data
* @var int topic_id Topic ID
*
* @since 3.1.10-RC1
*/
$vars = array('forum_id', 'mode', 'row', 'topic_id');
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 withing 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') || $this->php_ini->getBool('safe_mode')) ? '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 3.3.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('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('CANNOT_CHANGE_FILE_PERMISSIONS', $file, array());
}
}
else if (is_file($file))
{
if (true !== @chmod($file, $file_perm))
{
throw new filesystem_exception('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('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('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('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('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('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('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('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('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('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 appropiate 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 aswell
* @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 occurances 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,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\group;
class helper
{
/** @var \phpbb\language\language */
protected $language;
/**
* Constructor
*
* @param \phpbb\language\language $language Language object
*/
public function __construct(\phpbb\language\language $language)
{
$this->language = $language;
}
/**
* @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;
}
}

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\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->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,165 @@
<?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->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.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 compatibilty 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,439 @@
<?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,
),
'mysql' => array(
'LABEL' => 'MySQL',
'SCHEMA' => 'mysql',
'MODULE' => 'mysql',
'DELIM' => ';',
'DRIVER' => 'phpbb\db\driver\mysql',
'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':
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 'mysqli':
if (version_compare($db->sql_server_info(true), '4.1.3', '<'))
{
$errors[] = array(
'title' => 'INST_ERR_DB_NO_MYSQLI',
);
}
break;
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 paramater 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,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\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)
->cannotBeEmpty()
->end()
->scalarNode('script_path')
->defaultValue('/')
->cannotBeEmpty()
->end()
->end()
->end()
->arrayNode('extensions')
->prototype('scalar')->end()
->defaultValue([])
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,368 @@
<?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')) . "',
user_email_hash = " . $this->db->sql_escape(phpbb_email_hash($this->install_config->get('board_email'))) . ",
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,234 @@
<?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')
{
if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
{
$schema_name .= '_41';
}
else
{
$schema_name .= '_40';
}
}
$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,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\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')
{
if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
{
$schema_name .= '_41';
}
else
{
$schema_name .= '_40';
}
}
$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,244 @@
<?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.2.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";
$config_content .= "// @define('PHPBB_DISPLAY_LOAD_TIME', 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,209 @@
<?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()
{
$php_version = PHP_VERSION;
if (version_compare($php_version, '5.4') < 0)
{
$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]);
}
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,206 @@
<?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
*
* @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,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\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);
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,141 @@
<?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)
{
@flock($this->lock_fp, LOCK_EX);
}
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;
}
}
}

View File

@@ -0,0 +1,175 @@
<?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\message;
/**
* Abstract class form
*/
abstract class form
{
/** @var \phpbb\auth\auth */
protected $auth;
/** @var \phpbb\config\config */
protected $config;
/** @var \phpbb\db\driver\driver_interface */
protected $db;
/** @var \phpbb\message\message */
protected $message;
/** @var \phpbb\user */
protected $user;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $phpEx;
/** @var array */
protected $errors = array();
/** @var bool */
protected $cc_sender;
/** @var string */
protected $body;
/**
* Construct
*
* @param \phpbb\auth\auth $auth
* @param \phpbb\config\config $config
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\user $user
* @param string $phpbb_root_path
* @param string $phpEx
*/
public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, $phpbb_root_path, $phpEx)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->phpEx = $phpEx;
$this->user = $user;
$this->auth = $auth;
$this->config = $config;
$this->db = $db;
$this->message = new message($config['server_name']);
$this->message->set_sender_from_user($this->user);
}
/**
* Returns the title for the email form page
*
* @return string
*/
public function get_page_title()
{
return $this->user->lang['SEND_EMAIL'];
}
/**
* Returns the file name of the form template
*
* @return string
*/
public function get_template_file()
{
return 'memberlist_email.html';
}
/**
* Checks whether the user is allowed to use the form
*
* @return false|string Error string if not allowed, false otherwise
*/
public function check_allow()
{
if (!$this->config['email_enable'])
{
return 'EMAIL_DISABLED';
}
if (time() - $this->user->data['user_emailtime'] < $this->config['flood_interval'])
{
return 'FLOOD_EMAIL_LIMIT';
}
return false;
}
/**
* Get the return link after the message has been sent
*
* @return string
*/
public function get_return_message()
{
return sprintf($this->user->lang['RETURN_INDEX'], '<a href="' . append_sid($this->phpbb_root_path . 'index.' . $this->phpEx) . '">', '</a>');
}
/**
* Bind the values of the request to the form
*
* @param \phpbb\request\request_interface $request
* @return null
*/
public function bind(\phpbb\request\request_interface $request)
{
$this->cc_sender = $request->is_set_post('cc_sender');
$this->body = $request->variable('message', '', true);
}
/**
* Submit form, generate the email and send it
*
* @param \messenger $messenger
* @return null
*/
public function submit(\messenger $messenger)
{
if (!check_form_key('memberlist_email'))
{
$this->errors[] = 'FORM_INVALID';
}
if (!count($this->errors))
{
$sql = 'UPDATE ' . USERS_TABLE . '
SET user_emailtime = ' . time() . '
WHERE user_id = ' . $this->user->data['user_id'];
$this->db->sql_query($sql);
if ($this->cc_sender && $this->user->data['is_registered'])
{
$this->message->cc_sender();
}
$this->message->send($messenger, phpbb_get_board_contact($this->config, $this->phpEx));
meta_refresh(3, append_sid($this->phpbb_root_path . 'index.' . $this->phpEx));
trigger_error($this->user->lang['EMAIL_SENT'] . '<br /><br />' . $this->get_return_message());
}
}
/**
* Render the template of the form
*
* @param \phpbb\template\template $template
* @return null
*/
public function render(\phpbb\template\template $template)
{
add_form_key('memberlist_email');
$template->assign_vars(array(
'ERROR_MESSAGE' => (count($this->errors)) ? implode('<br />', $this->errors) : '',
));
}
}

View File

@@ -0,0 +1,156 @@
<?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\mimetype;
class guesser
{
/**
* @const Default priority for mimetype guessers
*/
const PRIORITY_DEFAULT = 0;
/**
* @var array guessers
*/
protected $guessers;
/**
* Construct a mimetype guesser object
*
* @param array $mimetype_guessers Mimetype guesser service collection
*/
public function __construct($mimetype_guessers)
{
$this->register_guessers($mimetype_guessers);
}
/**
* Register MimeTypeGuessers and sort them by priority
*
* @param array $mimetype_guessers Mimetype guesser service collection
*
* @throws \LogicException If incorrect or not mimetype guessers have
* been supplied to class
*/
protected function register_guessers($mimetype_guessers)
{
foreach ($mimetype_guessers as $guesser)
{
$is_supported = (method_exists($guesser, 'is_supported')) ? 'is_supported' : '';
$is_supported = (method_exists($guesser, 'isSupported')) ? 'isSupported' : $is_supported;
if (empty($is_supported))
{
throw new \LogicException('Incorrect mimetype guesser supplied.');
}
if ($guesser->$is_supported())
{
$this->guessers[] = $guesser;
}
}
if (empty($this->guessers))
{
throw new \LogicException('No mimetype guesser supplied.');
}
// Sort guessers by priority
usort($this->guessers, array($this, 'sort_priority'));
}
/**
* Sort the priority of supplied guessers
* This is a compare function for usort. A guesser with higher priority
* should be used first and vice versa. usort() orders the array values
* from low to high depending on what the comparison function returns
* to it. Return value should be smaller than 0 if value a is smaller
* than value b. This has been reversed in the comparision function in
* order to sort the guessers from high to low.
* Method has been set to public in order to allow proper testing.
*
* @param object $guesser_a Mimetype guesser a
* @param object $guesser_b Mimetype guesser b
*
* @return int If both guessers have the same priority 0, bigger
* than 0 if first guesser has lower priority, and lower
* than 0 if first guesser has higher priority
*/
public function sort_priority($guesser_a, $guesser_b)
{
$priority_a = (int) (method_exists($guesser_a, 'get_priority')) ? $guesser_a->get_priority() : self::PRIORITY_DEFAULT;
$priority_b = (int) (method_exists($guesser_b, 'get_priority')) ? $guesser_b->get_priority() : self::PRIORITY_DEFAULT;
return $priority_b - $priority_a;
}
/**
* Guess mimetype of supplied file
*
* @param string $file Path to file
* @param string $file_name The real file name
*
* @return string Guess for mimetype of file
*/
public function guess($file, $file_name = '')
{
if (!is_file($file))
{
return false;
}
if (!is_readable($file))
{
return false;
}
$mimetype = 'application/octet-stream';
foreach ($this->guessers as $guesser)
{
$mimetype_guess = $guesser->guess($file, $file_name);
$mimetype = $this->choose_mime_type($mimetype, $mimetype_guess);
}
// Return any mimetype if we got a result or the fallback value
return $mimetype;
}
/**
* Choose the best mime type based on the current mime type and the guess
* If a guesser returns nulls or application/octet-stream, we will keep
* the current guess. Guesses with a slash inside them will be favored over
* already existing ones. However, any guess that will pass the first check
* will always overwrite the default application/octet-stream.
*
* @param string $mime_type The current mime type
* @param string $guess The current mime type guess
*
* @return string The best mime type based on current mime type and guess
*/
public function choose_mime_type($mime_type, $guess)
{
if ($guess === null || $guess == 'application/octet-stream')
{
return $mime_type;
}
if ($mime_type == 'application/octet-stream' || strpos($guess, '/') !== false)
{
$mime_type = $guess;
}
return $mime_type;
}
}

View File

@@ -0,0 +1,149 @@
<?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\notification\type;
/**
* Post approved notifications class
* This class handles notifications for posts when they are approved (to their authors)
*/
class approve_post extends \phpbb\notification\type\post
{
/**
* Get notification type name
*
* @return string
*/
public function get_type()
{
return 'notification.type.approve_post';
}
/**
* Language key used to output the text
*
* @var string
*/
protected $language_key = 'NOTIFICATION_POST_APPROVED';
/**
* Inherit notification read status from post.
*
* @var bool
*/
protected $inherit_read_status = false;
/**
* Notification option data (for outputting to the user)
*
* @var bool|array False if the service should use it's default data
* Array of data (including keys 'id', 'lang', and 'group')
*/
static public $notification_option = array(
'id' => 'moderation_queue',
'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
'group' => 'NOTIFICATION_GROUP_POSTING',
);
/**
* Is available
*/
public function is_available()
{
return !$this->auth->acl_get('m_approve');
}
/**
* Find the users who want to receive notifications
*
* @param array $post Data from submit_post
* @param array $options Options for finding users for notification
*
* @return array
*/
public function find_users_for_notification($post, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
), $options);
$users = array();
$users[$post['poster_id']] = $this->notification_manager->get_default_methods();
return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array(
'item_type' => static::$notification_option['id'],
)));
}
/**
* Pre create insert array function
* This allows you to perform certain actions, like run a query
* and load data, before create_insert_array() is run. The data
* returned from this function will be sent to create_insert_array().
*
* @param array $post Post data from submit_post
* @param array $notify_users Notify users list
* Formated from find_users_for_notification()
* @return array Whatever you want to send to create_insert_array().
*/
public function pre_create_insert_array($post, $notify_users)
{
// In the parent class, this is used to check if the post is already
// read by a user and marks the notification read if it was marked read.
// Returning an empty array in effect, forces it to be marked as unread
// (and also saves a query)
return array();
}
/**
* {@inheritdoc}
*/
public function create_insert_array($post, $pre_create_data = array())
{
$this->set_data('post_subject', $post['post_subject']);
parent::create_insert_array($post, $pre_create_data);
$this->notification_time = time();
}
/**
* {@inheritdoc}
*/
public function get_insert_array()
{
$data = parent::get_insert_array();
$data['notification_time'] = $this->notification_time;
return $data;
}
/**
* Get email template
*
* @return string|bool
*/
public function get_email_template()
{
return 'post_approved';
}
/**
* {inheritDoc}
*/
public function get_redirect_url()
{
return $this->get_url();
}
}

View File

@@ -0,0 +1,140 @@
<?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\notification\type;
/**
* Topic approved notifications class
* This class handles notifications for topics when they are approved (for authors)
*/
class approve_topic extends \phpbb\notification\type\topic
{
/**
* Get notification type name
*
* @return string
*/
public function get_type()
{
return 'notification.type.approve_topic';
}
/**
* Language key used to output the text
*
* @var string
*/
protected $language_key = 'NOTIFICATION_TOPIC_APPROVED';
/**
* Inherit notification read status from topic.
*
* @var bool
*/
protected $inherit_read_status = false;
/**
* Notification option data (for outputting to the user)
*
* @var bool|array False if the service should use it's default data
* Array of data (including keys 'id', 'lang', and 'group')
*/
static public $notification_option = array(
'id' => 'moderation_queue',
'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE',
'group' => 'NOTIFICATION_GROUP_POSTING',
);
/**
* Is available
*/
public function is_available()
{
return !$this->auth->acl_get('m_approve');
}
/**
* Find the users who want to receive notifications
*
* @param array $post Data from submit_post
* @param array $options Options for finding users for notification
*
* @return array
*/
public function find_users_for_notification($post, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
), $options);
$users = array();
$users[$post['poster_id']] = $this->notification_manager->get_default_methods();
return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array(
'item_type' => static::$notification_option['id'],
)));
}
/**
* Pre create insert array function
* This allows you to perform certain actions, like run a query
* and load data, before create_insert_array() is run. The data
* returned from this function will be sent to create_insert_array().
*
* @param array $post Post data from submit_post
* @param array $notify_users Notify users list
* Formated from find_users_for_notification()
* @return array Whatever you want to send to create_insert_array().
*/
public function pre_create_insert_array($post, $notify_users)
{
// In the parent class, this is used to check if the post is already
// read by a user and marks the notification read if it was marked read.
// Returning an empty array in effect, forces it to be marked as unread
// (and also saves a query)
return array();
}
/**
* {@inheritdoc}
*/
public function create_insert_array($post, $pre_create_data = array())
{
parent::create_insert_array($post, $pre_create_data);
$this->notification_time = time();
}
/**
* {@inheritdoc}
*/
public function get_insert_array()
{
$data = parent::get_insert_array();
$data['notification_time'] = $this->notification_time;
return $data;
}
/**
* Get email template
*
* @return string|bool
*/
public function get_email_template()
{
return 'topic_approved';
}
}

View File

@@ -0,0 +1,577 @@
<?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\notification\type;
/**
* Base notifications class
*/
abstract class base implements \phpbb\notification\type\type_interface
{
/** @var \phpbb\notification\manager */
protected $notification_manager;
/** @var \phpbb\db\driver\driver_interface */
protected $db;
/** @var \phpbb\language\language */
protected $language;
/** @var \phpbb\user */
protected $user;
/** @var \phpbb\auth\auth */
protected $auth;
/** @var string */
protected $phpbb_root_path;
/** @var string */
protected $php_ext;
/** @var string */
protected $user_notifications_table;
/**
* Notification option data (for outputting to the user)
*
* @var bool|array False if the service should use its default data
* Array of data (including keys 'id', 'lang', and 'group')
*/
static public $notification_option = false;
/**
* The notification_type_id, set upon creation of the class
* This is the notification_type_id from the notification_types table
*
* @var int
*/
protected $notification_type_id;
/**
* Identification data
* notification_type_id - ID of the item type (auto generated, from notification types table)
* item_id - ID of the item (e.g. post_id, msg_id)
* item_parent_id - Parent item id (ex: for topic => forum_id, for post => topic_id, etc)
* user_id
* notification_read
* notification_time
* notification_data (special serialized field that each notification type can use to store stuff)
*
* @var array $data Notification row from the database
* This must be private, all interaction should use __get(), __set(), get_data(), set_data()
*/
private $data = array();
/**
* Notification Type Base Constructor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\language\language $language
* @param \phpbb\user $user
* @param \phpbb\auth\auth $auth
* @param string $phpbb_root_path
* @param string $php_ext
* @param string $user_notifications_table
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table)
{
$this->db = $db;
$this->language = $language;
$this->user = $user;
$this->auth = $auth;
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $php_ext;
$this->user_notifications_table = $user_notifications_table;
}
/**
* Set notification manager (required)
*
* @param \phpbb\notification\manager $notification_manager
*/
public function set_notification_manager(\phpbb\notification\manager $notification_manager)
{
$this->notification_manager = $notification_manager;
$this->notification_type_id = $this->notification_manager->get_notification_type_id($this->get_type());
}
/**
* Set initial data from the database
*
* @param array $data Row directly from the database
*/
public function set_initial_data($data = array())
{
// The row from the database (unless this is a new notification we're going to add)
$this->data = $data;
$this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array();
}
/**
* Magic method to get data from this notification
*
* @param mixed $name
* @return mixed
*/
public function __get($name)
{
return (!isset($this->data[$name])) ? null : $this->data[$name];
}
/**
* Magic method to set data on this notification
*
* @param mixed $name
* @param mixed $value
*
* @return null
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
}
/**
* Magic method to get a string of this notification
*
* Primarily for testing
*
* @return mixed
*/
public function __toString()
{
return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type();
}
/**
* Get special data (only important for the classes that extend this)
*
* @param string $name Name of the variable to get
* @return mixed
*/
protected function get_data($name)
{
return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null);
}
/**
* Set special data (only important for the classes that extend this)
*
* @param string $name Name of the variable to set
* @param mixed $value Value to set to the variable
* @return mixed
*/
protected function set_data($name, $value)
{
$this->data['notification_data'][$name] = $value;
}
/**
* {@inheritdoc}
*/
public function create_insert_array($type_data, $pre_create_data = array())
{
// Defaults
$this->data = array_merge(array(
'item_id' => static::get_item_id($type_data),
'notification_type_id' => $this->notification_type_id,
'item_parent_id' => static::get_item_parent_id($type_data),
'notification_time' => time(),
'notification_read' => false,
'notification_data' => array(),
), $this->data);
}
/**
* {@inheritdoc}
*/
public function get_insert_array()
{
$data = $this->data;
$data['notification_data'] = serialize($data['notification_data']);
return $data;
}
/**
* Function for preparing the data for update in an SQL query
* (The service handles insertion)
*
* @param array $type_data Data unique to this notification type
* @return array Array of data ready to be updated in the database
*/
public function create_update_array($type_data)
{
$this->create_insert_array($type_data);
$data = $this->get_insert_array();
// Unset data unique to each row
unset(
$data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function)
$data['notification_id'],
$data['notification_read'],
$data['user_id']
);
return $data;
}
/**
* Mark this item read
*
* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
* @return string|null If $return is False, nothing will be returned, else the sql code to update this item
*/
public function mark_read($return = false)
{
return $this->mark(false, $return);
}
/**
* Mark this item unread
*
* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
* @return string|null If $return is False, nothing will be returned, else the sql code to update this item
*/
public function mark_unread($return = false)
{
return $this->mark(true, $return);
}
/**
* {inheritDoc}
*/
public function get_redirect_url()
{
return $this->get_url();
}
/**
* Prepare to output the notification to the template
*
* @return array Template variables
*/
public function prepare_for_display()
{
$mark_hash = generate_link_hash('mark_notification_read');
if ($this->get_url())
{
$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash);
}
else
{
$redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : '');
$u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&amp;hash=' . $mark_hash . '&amp;redirect=' . urlencode($redirect));
}
return array(
'NOTIFICATION_ID' => $this->notification_id,
'STYLING' => $this->get_style_class(),
'AVATAR' => $this->get_avatar(),
'FORMATTED_TITLE' => $this->get_title(),
'REFERENCE' => $this->get_reference(),
'FORUM' => $this->get_forum(),
'REASON' => $this->get_reason(),
'URL' => $this->get_url(),
'TIME' => $this->user->format_date($this->notification_time),
'UNREAD' => !$this->notification_read,
'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '',
);
}
/**
* -------------- Fall back functions -------------------
*/
/**
* URL to unsubscribe to this notification (fall back)
*
* @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item
* @return false
*/
public function get_unsubscribe_url($method = false)
{
return false;
}
/**
* Get the CSS style class of the notification (fall back)
*
* @return string
*/
public function get_style_class()
{
return '';
}
/**
* Get the user's avatar (fall back)
*
* @return string
*/
public function get_avatar()
{
return '';
}
/**
* Get the reference of the notifcation (fall back)
*
* @return string
*/
public function get_reference()
{
return '';
}
/**
* Get the forum of the notification reference (fall back)
*
* @return string
*/
public function get_forum()
{
return '';
}
/**
* Get the reason for the notifcation (fall back)
*
* @return string
*/
public function get_reason()
{
return '';
}
/**
* Get the special items to load (fall back)
*
* @return array
*/
public function get_load_special()
{
return array();
}
/**
* Load the special items (fall back)
*
* @param array $data
* @param array $notifications
*/
public function load_special($data, $notifications)
{
return;
}
/**
* Is available (fall back)
*
* @return bool
*/
public function is_available()
{
return true;
}
/**
* Pre create insert array function (fall back)
*
* @param array $type_data
* @param array $notify_users
* @return array
*/
public function pre_create_insert_array($type_data, $notify_users)
{
return array();
}
/**
* -------------- Helper functions -------------------
*/
/**
* Find the users who want to receive notifications (helper)
*
* @param array|bool $user_ids User IDs to check if they want to receive notifications
* (Bool False to check all users besides anonymous and bots (USER_IGNORE))
* @param array $options
* @return array
*/
protected function check_user_notification_options($user_ids = false, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
'item_type' => $this->get_type(),
'item_id' => 0, // Global by default
), $options);
if ($user_ids === false)
{
$user_ids = array();
$sql = 'SELECT user_id
FROM ' . USERS_TABLE . '
WHERE user_id <> ' . ANONYMOUS . '
AND user_type <> ' . USER_IGNORE;
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$user_ids[] = $row['user_id'];
}
$this->db->sql_freeresult($result);
}
if (empty($user_ids))
{
return array();
}
$rowset = $output = array();
$sql = 'SELECT user_id, method, notify
FROM ' . $this->user_notifications_table . '
WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . "
AND item_type = '" . $this->db->sql_escape($options['item_type']) . "'
AND item_id = " . (int) $options['item_id'];
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']]))
{
continue;
}
if (!isset($rowset[$row['user_id']]))
{
$rowset[$row['user_id']] = array();
}
$rowset[$row['user_id']][$row['method']] = $row['notify'];
if (!isset($output[$row['user_id']]))
{
$output[$row['user_id']] = array();
}
if ($row['notify'])
{
$output[$row['user_id']][] = $row['method'];
}
}
$this->db->sql_freeresult($result);
$default_methods = $this->notification_manager->get_default_methods();
foreach ($user_ids as $user_id)
{
if (isset($options['ignore_users'][$user_id]))
{
continue;
}
if (!array_key_exists($user_id, $rowset))
{
// No rows at all for this user, use the default methods
$output[$user_id] = $default_methods;
}
else
{
foreach ($default_methods as $default_method)
{
if (!array_key_exists($default_method, $rowset[$user_id]))
{
// No user preference for this type recorded, but it should be enabled by default.
$output[$user_id][] = $default_method;
}
}
}
}
return $output;
}
/**
* Mark this item read/unread helper
*
* @param bool $unread Unread (True/False) (Default: False)
* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
* @return string|null If $return is False, nothing will be returned, else the sql code to update this item
*/
protected function mark($unread = true, $return = false)
{
$this->notification_read = (bool) !$unread;
if ($return)
{
$where = array(
'notification_type_id = ' . (int) $this->notification_type_id,
'item_id = ' . (int) $this->item_id,
'user_id = ' . (int) $this->user_id,
);
$where = implode(' AND ', $where);
return $where;
}
else
{
$this->notification_manager->mark_notifications($this->get_type(), (int) $this->item_id, (int) $this->user_id, false, $this->notification_read);
}
return null;
}
/**
* Get a list of users that are authorised to receive notifications
*
* @param array $users Array of users that have subscribed to a notification
* @param int $forum_id Forum ID of the forum
* @param array $options Array of notification options
* @param bool $sort Whether the users array should be sorted. Default: false
* @return array Array of users that are authorised recipients
*/
protected function get_authorised_recipients($users, $forum_id, $options, $sort = false)
{
if (empty($users))
{
return array();
}
$users = array_unique($users);
if ($sort)
{
sort($users);
}
$auth_read = $this->auth->acl_get_list($users, 'f_read', $forum_id);
if (empty($auth_read))
{
return array();
}
return $this->check_user_notification_options($auth_read[$forum_id]['f_read'], $options);
}
}

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\notification\type;
/**
* Post notifications class
* This class handles notifications for replies to a topic
*/
class post extends \phpbb\notification\type\base
{
/**
* Get notification type name
*
* @return string
*/
public function get_type()
{
return 'notification.type.post';
}
/**
* Language key used to output the text
*
* @var string
*/
protected $language_key = 'NOTIFICATION_POST';
/**
* Inherit notification read status from post.
*
* @var bool
*/
protected $inherit_read_status = true;
/**
* Notification option data (for outputting to the user)
*
* @var bool|array False if the service should use it's default data
* Array of data (including keys 'id', 'lang', and 'group')
*/
static public $notification_option = array(
'lang' => 'NOTIFICATION_TYPE_POST',
'group' => 'NOTIFICATION_GROUP_POSTING',
);
/** @var \phpbb\user_loader */
protected $user_loader;
/** @var \phpbb\config\config */
protected $config;
public function set_config(\phpbb\config\config $config)
{
$this->config = $config;
}
public function set_user_loader(\phpbb\user_loader $user_loader)
{
$this->user_loader = $user_loader;
}
/**
* Is available
*/
public function is_available()
{
return $this->config['allow_topic_notify'];
}
/**
* Get the id of the item
*
* @param array $post The data from the post
* @return int The post id
*/
static public function get_item_id($post)
{
return (int) $post['post_id'];
}
/**
* Get the id of the parent
*
* @param array $post The data from the post
* @return int The topic id
*/
static public function get_item_parent_id($post)
{
return (int) $post['topic_id'];
}
/**
* Find the users who want to receive notifications
*
* @param array $post Data from submit_post
* @param array $options Options for finding users for notification
*
* @return array
*/
public function find_users_for_notification($post, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
), $options);
$users = array();
$sql = 'SELECT user_id
FROM ' . TOPICS_WATCH_TABLE . '
WHERE topic_id = ' . (int) $post['topic_id'] . '
AND notify_status = ' . NOTIFY_YES . '
AND user_id <> ' . (int) $post['poster_id'];
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$users[] = (int) $row['user_id'];
}
$this->db->sql_freeresult($result);
$sql = 'SELECT user_id
FROM ' . FORUMS_WATCH_TABLE . '
WHERE forum_id = ' . (int) $post['forum_id'] . '
AND notify_status = ' . NOTIFY_YES . '
AND user_id <> ' . (int) $post['poster_id'];
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$users[] = (int) $row['user_id'];
}
$this->db->sql_freeresult($result);
$notify_users = $this->get_authorised_recipients($users, $post['forum_id'], $options, true);
if (empty($notify_users))
{
return array();
}
// Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications
$notified_users = $this->notification_manager->get_notified_users($this->get_type(), array(
'item_parent_id' => static::get_item_parent_id($post),
'read' => 0,
));
foreach ($notified_users as $user => $notification_data)
{
unset($notify_users[$user]);
/** @var post $notification */
$notification = $this->notification_manager->get_item_type_class($this->get_type(), $notification_data);
$update_responders = $notification->add_responders($post);
if (!empty($update_responders))
{
$this->notification_manager->update_notification($notification, $update_responders, array(
'item_parent_id' => self::get_item_parent_id($post),
'read' => 0,
'user_id' => $user,
));
}
}
return $notify_users;
}
/**
* Get the user's avatar
*/
public function get_avatar()
{
return $this->user_loader->get_avatar($this->get_data('poster_id'), false, true);
}
/**
* Get the HTML formatted title of this notification
*
* @return string
*/
public function get_title()
{
$responders = $this->get_data('responders');
$usernames = array();
if (!is_array($responders))
{
$responders = array();
}
$responders = array_merge(array(array(
'poster_id' => $this->get_data('poster_id'),
'username' => $this->get_data('post_username'),
)), $responders);
$responders_cnt = count($responders);
$responders = $this->trim_user_ary($responders);
$trimmed_responders_cnt = $responders_cnt - count($responders);
foreach ($responders as $responder)
{
if ($responder['username'])
{
$usernames[] = $responder['username'];
}
else
{
$usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile');
}
}
if ($trimmed_responders_cnt > 20)
{
$usernames[] = $this->language->lang('NOTIFICATION_MANY_OTHERS');
}
else if ($trimmed_responders_cnt)
{
$usernames[] = $this->language->lang('NOTIFICATION_X_OTHERS', $trimmed_responders_cnt);
}
return $this->language->lang(
$this->language_key,
phpbb_generate_string_list($usernames, $this->user),
$responders_cnt
);
}
/**
* Get the HTML formatted reference of the notification
*
* @return string
*/
public function get_reference()
{
return $this->language->lang(
'NOTIFICATION_REFERENCE',
censor_text($this->get_data('topic_title'))
);
}
/**
* Get email template
*
* @return string|bool
*/
public function get_email_template()
{
return 'topic_notify';
}
/**
* Get email template variables
*
* @return array
*/
public function get_email_template_variables()
{
if ($this->get_data('post_username'))
{
$username = $this->get_data('post_username');
}
else
{
$username = $this->user_loader->get_username($this->get_data('poster_id'), 'username');
}
return array(
'AUTHOR_NAME' => htmlspecialchars_decode($username),
'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))),
'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))),
'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}",
'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&e=1&view=unread#unread",
'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}",
'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}",
'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic",
);
}
/**
* Get the url to this item
*
* @return string URL
*/
public function get_url()
{
return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}");
}
/**
* {inheritDoc}
*/
public function get_redirect_url()
{
return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "t={$this->item_parent_id}&amp;view=unread#unread");
}
/**
* Users needed to query before this notification can be displayed
*
* @return array Array of user_ids
*/
public function users_to_query()
{
$responders = $this->get_data('responders');
$users = array(
$this->get_data('poster_id'),
);
if (is_array($responders))
{
foreach ($responders as $responder)
{
$users[] = $responder['poster_id'];
}
}
return $this->trim_user_ary($users);
}
/**
* Trim the user array passed down to 3 users if the array contains
* more than 4 users.
*
* @param array $users Array of users
* @return array Trimmed array of user_ids
*/
public function trim_user_ary($users)
{
if (count($users) > 4)
{
array_splice($users, 3);
}
return $users;
}
/**
* Pre create insert array function
* This allows you to perform certain actions, like run a query
* and load data, before create_insert_array() is run. The data
* returned from this function will be sent to create_insert_array().
*
* @param array $post Post data from submit_post
* @param array $notify_users Notify users list
* Formated from find_users_for_notification()
* @return array Whatever you want to send to create_insert_array().
*/
public function pre_create_insert_array($post, $notify_users)
{
if (!count($notify_users) || !$this->inherit_read_status)
{
return array();
}
$tracking_data = array();
$sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . '
WHERE topic_id = ' . (int) $post['topic_id'] . '
AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users));
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$tracking_data[$row['user_id']] = $row['mark_time'];
}
$this->db->sql_freeresult($result);
return $tracking_data;
}
/**
* {@inheritdoc}
*/
public function create_insert_array($post, $pre_create_data = array())
{
$this->set_data('poster_id', $post['poster_id']);
$this->set_data('topic_title', $post['topic_title']);
$this->set_data('post_subject', $post['post_subject']);
$this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''));
$this->set_data('forum_id', $post['forum_id']);
$this->set_data('forum_name', $post['forum_name']);
$this->notification_time = $post['post_time'];
// Topics can be "read" before they are public (while awaiting approval).
// Make sure that if the user has read the topic, it's marked as read in the notification
if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)
{
$this->notification_read = true;
}
parent::create_insert_array($post, $pre_create_data);
}
/**
* Add responders to the notification
*
* @param mixed $post
* @return array Array of responder data
*/
public function add_responders($post)
{
// Do not add them as a responder if they were the original poster that created the notification
if ($this->get_data('poster_id') == $post['poster_id'])
{
return array();
}
$responders = $this->get_data('responders');
$responders = ($responders === null) ? array() : $responders;
// Do not add more than 25 responders,
// we trim the username list to "a, b, c and x others" anyway
// so there is no use to add all of them anyway.
if (count($responders) > 25)
{
return array();
}
foreach ($responders as $responder)
{
// Do not add them as a responder multiple times
if ($responder['poster_id'] == $post['poster_id'])
{
return array();
}
}
$responders[] = array(
'poster_id' => $post['poster_id'],
'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''),
);
$this->set_data('responders', $responders);
$serialized_data = serialize($this->get_data(false));
// If the data is longer then 4000 characters, it would cause a SQL error.
// We don't add the username to the list if this is the case.
if (utf8_strlen($serialized_data) >= 4000)
{
return array();
}
$data_array = array_merge(array(
'post_time' => $post['post_time'],
'post_id' => $post['post_id'],
'topic_id' => $post['topic_id']
), $this->get_data(false));
return $data_array;
}
}

View File

@@ -0,0 +1,307 @@
<?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\notification\type;
/**
* Topic notifications class
* This class handles notifications for new topics
*/
class topic extends \phpbb\notification\type\base
{
/**
* Get notification type name
*
* @return string
*/
public function get_type()
{
return 'notification.type.topic';
}
/**
* Language key used to output the text
*
* @var string
*/
protected $language_key = 'NOTIFICATION_TOPIC';
/**
* Inherit notification read status from topic.
*
* @var bool
*/
protected $inherit_read_status = true;
/**
* Notification option data (for outputting to the user)
*
* @var bool|array False if the service should use it's default data
* Array of data (including keys 'id', 'lang', and 'group')
*/
static public $notification_option = array(
'lang' => 'NOTIFICATION_TYPE_TOPIC',
'group' => 'NOTIFICATION_GROUP_POSTING',
);
/** @var \phpbb\user_loader */
protected $user_loader;
/** @var \phpbb\config\config */
protected $config;
public function set_config(\phpbb\config\config $config)
{
$this->config = $config;
}
public function set_user_loader(\phpbb\user_loader $user_loader)
{
$this->user_loader = $user_loader;
}
/**
* Is available
*/
public function is_available()
{
return $this->config['allow_forum_notify'];
}
/**
* Get the id of the item
*
* @param array $post The data from the post
* @return int The topic id
*/
static public function get_item_id($post)
{
return (int) $post['topic_id'];
}
/**
* Get the id of the parent
*
* @param array $post The data from the post
* @return int The forum id
*/
static public function get_item_parent_id($post)
{
return (int) $post['forum_id'];
}
/**
* Find the users who want to receive notifications
*
* @param array $topic Data from the topic
* @param array $options Options for finding users for notification
*
* @return array
*/
public function find_users_for_notification($topic, $options = array())
{
$options = array_merge(array(
'ignore_users' => array(),
), $options);
$users = array();
$sql = 'SELECT user_id
FROM ' . FORUMS_WATCH_TABLE . '
WHERE forum_id = ' . (int) $topic['forum_id'] . '
AND notify_status = ' . NOTIFY_YES . '
AND user_id <> ' . (int) $topic['poster_id'];
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$users[] = (int) $row['user_id'];
}
$this->db->sql_freeresult($result);
return $this->get_authorised_recipients($users, $topic['forum_id'], $options);
}
/**
* Get the user's avatar
*/
public function get_avatar()
{
return $this->user_loader->get_avatar($this->get_data('poster_id'), false, true);
}
/**
* Get the HTML formatted title of this notification
*
* @return string
*/
public function get_title()
{
if ($this->get_data('post_username'))
{
$username = $this->get_data('post_username');
}
else
{
$username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile');
}
return $this->language->lang(
$this->language_key,
$username
);
}
/**
* Get the HTML formatted reference of the notification
*
* @return string
*/
public function get_reference()
{
return $this->language->lang(
'NOTIFICATION_REFERENCE',
censor_text($this->get_data('topic_title'))
);
}
/**
* Get the forum of the notification reference
*
* @return string
*/
public function get_forum()
{
return $this->language->lang(
'NOTIFICATION_FORUM',
$this->get_data('forum_name')
);
}
/**
* Get email template
*
* @return string|bool
*/
public function get_email_template()
{
return 'newtopic_notify';
}
/**
* Get email template variables
*
* @return array
*/
public function get_email_template_variables()
{
$board_url = generate_board_url();
if ($this->get_data('post_username'))
{
$username = $this->get_data('post_username');
}
else
{
$username = $this->user_loader->get_username($this->get_data('poster_id'), 'username');
}
return array(
'AUTHOR_NAME' => htmlspecialchars_decode($username),
'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')),
'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))),
'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}",
'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}",
'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}",
'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum",
);
}
/**
* Get the url to this item
*
* @return string URL
*/
public function get_url()
{
return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&amp;t={$this->item_id}");
}
/**
* Users needed to query before this notification can be displayed
*
* @return array Array of user_ids
*/
public function users_to_query()
{
return array($this->get_data('poster_id'));
}
/**
* Pre create insert array function
* This allows you to perform certain actions, like run a query
* and load data, before create_insert_array() is run. The data
* returned from this function will be sent to create_insert_array().
*
* @param array $post Post data from submit_post
* @param array $notify_users Notify users list
* Formated from find_users_for_notification()
* @return array Whatever you want to send to create_insert_array().
*/
public function pre_create_insert_array($post, $notify_users)
{
if (!count($notify_users) || !$this->inherit_read_status)
{
return array();
}
$tracking_data = array();
$sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . '
WHERE topic_id = ' . (int) $post['topic_id'] . '
AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users));
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
$tracking_data[$row['user_id']] = $row['mark_time'];
}
$this->db->sql_freeresult($result);
return $tracking_data;
}
/**
* {@inheritdoc}
*/
public function create_insert_array($post, $pre_create_data = array())
{
$this->set_data('poster_id', $post['poster_id']);
$this->set_data('topic_title', $post['topic_title']);
$this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''));
$this->set_data('forum_name', $post['forum_name']);
$this->notification_time = $post['post_time'];
// Topics can be "read" before they are public (while awaiting approval).
// Make sure that if the user has read the topic, it's marked as read in the notification
if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time)
{
$this->notification_read = true;
}
parent::create_insert_array($post, $pre_create_data);
}
}

View File

@@ -0,0 +1,218 @@
<?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\notification\type;
/**
* Base notifications interface
*/
interface type_interface
{
/**
* Get notification type name
*
* @return string
*/
public function get_type();
/**
* Set initial data from the database
*
* @param array $data Row directly from the database
*/
public function set_initial_data($data);
/**
* Get the id of the item
*
* @param array $type_data The type specific data
*/
static public function get_item_id($type_data);
/**
* Get the id of the parent
*
* @param array $type_data The type specific data
*/
static public function get_item_parent_id($type_data);
/**
* Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options)
*
* @return bool True/False whether or not this is available to the user
*/
public function is_available();
/**
* Find the users who want to receive notifications
*
* @param array $type_data The type specific data
* @param array $options Options for finding users for notification
* ignore_users => array of users and user types that should not receive notifications from this type because they've already been notified
* e.g.: array(2 => array(''), 3 => array('', 'email'), ...)
*
* @return array
*/
public function find_users_for_notification($type_data, $options);
/**
* Users needed to query before this notification can be displayed
*
* @return array Array of user_ids
*/
public function users_to_query();
/**
* Get the special items to load
*
* @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type
*/
public function get_load_special();
/**
* Load the special items
*
* @param array $data Data from get_load_special()
* @param array $notifications Array of notifications (key is notification_id, value is the notification objects)
*/
public function load_special($data, $notifications);
/**
* Get the CSS style class of the notification
*
* @return string
*/
public function get_style_class();
/**
* Get the HTML formatted title of this notification
*
* @return string
*/
public function get_title();
/**
* Get the HTML formatted reference of the notification
*
* @return string
*/
public function get_reference();
/**
* Get the forum of the notification reference
*
* @return string
*/
public function get_forum();
/**
* Get the url to this item
*
* @return string URL
*/
public function get_url();
/**
* Get the url to redirect after the item has been marked as read
*
* @return string URL
*/
public function get_redirect_url();
/**
* URL to unsubscribe to this notification
*
* @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item
*/
public function get_unsubscribe_url($method);
/**
* Get the user's avatar (the user who caused the notification typically)
*
* @return string
*/
public function get_avatar();
/**
* Prepare to output the notification to the template
*/
public function prepare_for_display();
/**
* Get email template
*
* @return string|bool
*/
public function get_email_template();
/**
* Get email template variables
*
* @return array
*/
public function get_email_template_variables();
/**
* Pre create insert array function
* This allows you to perform certain actions, like run a query
* and load data, before create_insert_array() is run. The data
* returned from this function will be sent to create_insert_array().
*
* @param array $type_data The type specific data
* @param array $notify_users Notify users list
* Formated from find_users_for_notification()
* @return array Whatever you want to send to create_insert_array().
*/
public function pre_create_insert_array($type_data, $notify_users);
/**
* Function for preparing the data for insertion in an SQL query
*
* @param array $type_data The type specific data
* @param array $pre_create_data Data from pre_create_insert_array()
*/
public function create_insert_array($type_data, $pre_create_data);
/**
* Function for getting the data for insertion in an SQL query
*
* @return array Array of data ready to be inserted into the database
*/
public function get_insert_array();
/**
* Function for preparing the data for update in an SQL query
* (The service handles insertion)
*
* @param array $type_data Data unique to this notification type
*
* @return array Array of data ready to be updated in the database
*/
public function create_update_array($type_data);
/**
* Mark this item read
*
* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
* @return string
*/
public function mark_read($return = false);
/**
* Mark this item unread
*
* @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False)
* @return string
*/
public function mark_unread($return = false);
}

View File

@@ -0,0 +1,407 @@
<?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\passwords;
class manager
{
/**
* Default hashing method
*/
protected $type = false;
/**
* Hashing algorithm type map
* Will be used to map hash prefix to type
*/
protected $type_map = false;
/**
* Service collection of hashing algorithms
* Needs to be public for passwords helper
*/
public $algorithms = false;
/**
* Password convert flag. Signals that password should be converted
*/
public $convert_flag = false;
/**
* Passwords helper
* @var \phpbb\passwords\helper
*/
protected $helper;
/**
* phpBB configuration
* @var \phpbb\config\config
*/
protected $config;
/**
* @var bool Whether or not initialized() has been called
*/
private $initialized = false;
/**
* @var array Hashing driver service collection
*/
private $hashing_algorithms;
/**
* @var array List of default driver types
*/
private $defaults;
/**
* Construct a passwords object
*
* @param \phpbb\config\config $config phpBB configuration
* @param array $hashing_algorithms Hashing driver service collection
* @param \phpbb\passwords\helper $helper Passwords helper object
* @param array $defaults List of default driver types
*/
public function __construct(\phpbb\config\config $config, $hashing_algorithms, helper $helper, $defaults)
{
$this->config = $config;
$this->helper = $helper;
$this->hashing_algorithms = $hashing_algorithms;
$this->defaults = $defaults;
}
/**
* Initialize the internal state
*/
protected function initialize()
{
if (!$this->initialized)
{
$this->initialized = true;
$this->fill_type_map($this->hashing_algorithms);
$this->register_default_type($this->defaults);
}
}
/**
* Register default type
* Will register the first supported type from the list of default types
*
* @param array $defaults List of default types in order from first to
* use to last to use
*/
protected function register_default_type($defaults)
{
foreach ($defaults as $type)
{
if ($this->algorithms[$type]->is_supported())
{
$this->type = $this->algorithms[$type]->get_prefix();
break;
}
}
}
/**
* Fill algorithm type map
*
* @param \phpbb\di\service_collection $hashing_algorithms
*/
protected function fill_type_map($hashing_algorithms)
{
foreach ($hashing_algorithms as $algorithm)
{
if (!isset($this->type_map[$algorithm->get_prefix()]))
{
$this->type_map[$algorithm->get_prefix()] = $algorithm;
}
}
$this->algorithms = $hashing_algorithms;
}
/**
* Get the algorithm specified by a specific prefix
*
* @param string $prefix Password hash prefix
*
* @return object|bool The hash type object or false if prefix is not
* supported
*/
protected function get_algorithm($prefix)
{
if (isset($this->type_map[$prefix]))
{
return $this->type_map[$prefix];
}
else
{
return false;
}
}
/**
* Detect the hash type of the supplied hash
*
* @param string $hash Password hash that should be checked
*
* @return object|bool The hash type object or false if the specified
* type is not supported
*/
public function detect_algorithm($hash)
{
/*
* preg_match() will also show hashing algos like $2a\H$, which
* is a combination of bcrypt and phpass. Legacy algorithms
* like md5 will not be matched by this and need to be treated
* differently.
*/
if (!preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match))
{
return false;
}
$this->initialize();
// Be on the lookout for multiple hashing algorithms
// 2 is correct: H\2a > 2, H\P > 2
if (strlen($match[1]) > 2 && strpos($match[1], '\\') !== false)
{
$hash_types = explode('\\', $match[1]);
$return_ary = array();
foreach ($hash_types as $type)
{
// we do not support the same hashing
// algorithm more than once
if (isset($return_ary[$type]))
{
return false;
}
$return_ary[$type] = $this->get_algorithm('$' . $type . '$');
if (empty($return_ary[$type]))
{
return false;
}
}
return $return_ary;
}
// get_algorithm() will automatically return false if prefix
// is not supported
return $this->get_algorithm($match[0]);
}
/**
* Hash supplied password
*
* @param string $password Password that should be hashed
* @param string $type Hash type. Will default to standard hash type if
* none is supplied
* @return string|bool Password hash of supplied password or false if
* if something went wrong during hashing
*/
public function hash($password, $type = '')
{
if (strlen($password) > 4096)
{
// If the password is too huge, we will simply reject it
// and not let the server try to hash it.
return false;
}
$this->initialize();
// Try to retrieve algorithm by service name if type doesn't
// start with dollar sign
if (!is_array($type) && strpos($type, '$') !== 0 && isset($this->algorithms[$type]))
{
$type = $this->algorithms[$type]->get_prefix();
}
$type = ($type === '') ? $this->type : $type;
if (is_array($type))
{
return $this->combined_hash_password($password, $type);
}
if (isset($this->type_map[$type]))
{
$hashing_algorithm = $this->type_map[$type];
}
else
{
return false;
}
return $hashing_algorithm->hash($password);
}
/**
* Check supplied password against hash and set convert_flag if password
* needs to be converted to different format (preferrably newer one)
*
* @param string $password Password that should be checked
* @param string $hash Stored hash
* @param array $user_row User's row in users table
* @return string|bool True if password is correct, false if not
*/
public function check($password, $hash, $user_row = array())
{
if (strlen($password) > 4096)
{
// If the password is too huge, we will simply reject it
// and not let the server try to hash it.
return false;
}
// Empty hashes can't be checked
if (empty($hash))
{
return false;
}
$this->initialize();
// First find out what kind of hash we're dealing with
$stored_hash_type = $this->detect_algorithm($hash);
if ($stored_hash_type == false)
{
// Still check MD5 hashes as that is what the installer
// will default to for the admin user
return $this->get_algorithm('$H$')->check($password, $hash);
}
// Multiple hash passes needed
if (is_array($stored_hash_type))
{
$correct = $this->check_combined_hash($password, $stored_hash_type, $hash);
$this->convert_flag = ($correct === true) ? true : false;
return $correct;
}
if ($stored_hash_type->get_prefix() !== $this->type)
{
$this->convert_flag = true;
}
else
{
if ($stored_hash_type instanceof driver\rehashable_driver_interface)
{
$this->convert_flag = $stored_hash_type->needs_rehash($hash);
}
else
{
$this->convert_flag = false;
}
}
// Check all legacy hash types if prefix is $CP$
if ($stored_hash_type->get_prefix() === '$CP$')
{
// Remove $CP$ prefix for proper checking
$hash = substr($hash, 4);
foreach ($this->type_map as $algorithm)
{
if ($algorithm->is_legacy() && $algorithm->check($password, $hash, $user_row) === true)
{
return true;
}
}
}
return $stored_hash_type->check($password, $hash);
}
/**
* Create combined hash from already hashed password
*
* @param string $password_hash Complete current password hash
* @param string $type Type of the hashing algorithm the password hash
* should be combined with
* @return string|bool Combined password hash if combined hashing was
* successful, else false
*/
public function combined_hash_password($password_hash, $type)
{
$this->initialize();
$data = array(
'prefix' => '$',
'settings' => '$',
);
$hash_settings = $this->helper->get_combined_hash_settings($password_hash);
$hash = $hash_settings[0];
// Put settings of current hash into data array
$stored_hash_type = $this->detect_algorithm($password_hash);
$this->helper->combine_hash_output($data, 'prefix', $stored_hash_type->get_prefix());
$this->helper->combine_hash_output($data, 'settings', $stored_hash_type->get_settings_only($password_hash));
// Hash current hash with the defined types
foreach ($type as $cur_type)
{
if (isset($this->algorithms[$cur_type]))
{
$new_hash_type = $this->algorithms[$cur_type];
}
else
{
$new_hash_type = $this->get_algorithm($cur_type);
}
if (!$new_hash_type)
{
return false;
}
$new_hash = $new_hash_type->hash(str_replace($stored_hash_type->get_settings_only($password_hash), '', $hash));
$this->helper->combine_hash_output($data, 'prefix', $new_hash_type->get_prefix());
$this->helper->combine_hash_output($data, 'settings', substr(str_replace('$', '\\', $new_hash_type->get_settings_only($new_hash, true)), 0));
$hash = str_replace($new_hash_type->get_settings_only($new_hash), '', $this->helper->obtain_hash_only($new_hash));
}
return $this->helper->combine_hash_output($data, 'hash', $hash);
}
/**
* Check combined password hash against the supplied password
*
* @param string $password Password entered by user
* @param array $stored_hash_type An array containing the hash types
* as described by stored password hash
* @param string $hash Stored password hash
*
* @return bool True if password is correct, false if not
*/
public function check_combined_hash($password, $stored_hash_type, $hash)
{
$i = 0;
$data = array(
'prefix' => '$',
'settings' => '$',
);
$hash_settings = $this->helper->get_combined_hash_settings($hash);
foreach ($stored_hash_type as $key => $hash_type)
{
$rebuilt_hash = $this->helper->rebuild_hash($hash_type->get_prefix(), $hash_settings[$i]);
$this->helper->combine_hash_output($data, 'prefix', $key);
$this->helper->combine_hash_output($data, 'settings', $hash_settings[$i]);
$cur_hash = $hash_type->hash($password, $rebuilt_hash);
$password = str_replace($rebuilt_hash, '', $cur_hash);
$i++;
}
return ($hash === $this->helper->combine_hash_output($data, 'hash', $password));
}
}

View File

@@ -0,0 +1,364 @@
<?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 permissions
{
/**
* Event dispatcher object
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* User object
* @var \phpbb\user
*/
protected $user;
/**
* Constructor
*
* @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher
* @param \phpbb\user $user User Object
*/
public function __construct(\phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\user $user)
{
$this->dispatcher = $phpbb_dispatcher;
$this->user = $user;
$categories = $this->categories;
$types = $this->types;
$permissions = $this->permissions;
/**
* Allows to specify additional permission categories, types and permissions
*
* @event core.permissions
* @var array types Array with permission types (a_, u_, m_, etc.)
* @var array categories Array with permission categories (pm, post, settings, misc, etc.)
* @var array permissions Array with permissions. Each Permission has the following layout:
* '<type><permission>' => array(
* 'lang' => 'Language Key with a Short description', // Optional, if not set,
* // the permissions identifier '<type><permission>' is used with
* // all uppercase.
* 'cat' => 'Identifier of the category, the permission should be displayed in',
* ),
* Example:
* 'u_viewprofile' => array(
* 'lang' => 'ACL_U_VIEWPROFILE',
* 'cat' => 'profile',
* ),
* @since 3.1.0-a1
*/
$vars = array('types', 'categories', 'permissions');
extract($phpbb_dispatcher->trigger_event('core.permissions', compact($vars)));
$this->categories = $categories;
$this->types = $types;
$this->permissions = $permissions;
}
/**
* Returns an array with all the permission categories (pm, post, settings, misc, etc.)
*
* @return array Layout: cat-identifier => Language key
*/
public function get_categories()
{
return $this->categories;
}
/**
* Returns the language string of a permission category
*
* @param string $category Identifier of the category
* @return string Language string
*/
public function get_category_lang($category)
{
return $this->user->lang($this->categories[$category]);
}
/**
* Returns an array with all the permission types (a_, u_, m_, etc.)
*
* @return array Layout: type-identifier => Language key
*/
public function get_types()
{
return $this->types;
}
/**
* Returns the language string of a permission type
*
* @param string $type Identifier of the type
* @param mixed $scope Scope of the type (should be 'global', 'local' or false)
* @return string Language string
*/
public function get_type_lang($type, $scope = false)
{
if ($scope && isset($this->types[$scope][$type]))
{
$lang_key = $this->types[$scope][$type];
}
else if (isset($this->types[$type]))
{
$lang_key = $this->types[$type];
}
else
{
$lang_key = 'ACL_TYPE_' . strtoupper(($scope) ? $scope . '_' . $type : $type);
}
return $this->user->lang($lang_key);
}
/**
* Returns an array with all the permissions.
* Each Permission has the following layout:
* '<type><permission>' => array(
* 'lang' => 'Language Key with a Short description', // Optional, if not set,
* // the permissions identifier '<type><permission>' is used with
* // all uppercase.
* 'cat' => 'Identifier of the category, the permission should be displayed in',
* ),
* Example:
* 'u_viewprofile' => array(
* 'lang' => 'ACL_U_VIEWPROFILE',
* 'cat' => 'profile',
* ),
*
* @return array
*/
public function get_permissions()
{
return $this->permissions;
}
/**
* Returns the category of a permission
*
* @param string $permission Identifier of the permission
* @return string Returns the category identifier of the permission
*/
public function get_permission_category($permission)
{
return (isset($this->permissions[$permission]['cat'])) ? $this->permissions[$permission]['cat'] : 'misc';
}
/**
* Checks if a category has been defined
*
* @param string $category Identifier of the category
* @return bool True if the category is defined, false otherwise
*/
public function category_defined($category)
{
return isset($this->categories[$category]);
}
/**
* Checks if a permission has been defined
*
* @param string $permission Identifier of the permission
* @return bool True if the permission is defined, false otherwise
*/
public function permission_defined($permission)
{
return isset($this->permissions[$permission]);
}
/**
* Returns the language string of a permission
*
* @param string $permission Identifier of the permission
* @return string Language string
*/
public function get_permission_lang($permission)
{
return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission));
}
protected $types = array(
'u_' => 'ACL_TYPE_U_',
'a_' => 'ACL_TYPE_A_',
'm_' => 'ACL_TYPE_M_',
'f_' => 'ACL_TYPE_F_',
'global' => array(
'm_' => 'ACL_TYPE_GLOBAL_M_',
),
);
protected $categories = array(
'actions' => 'ACL_CAT_ACTIONS',
'content' => 'ACL_CAT_CONTENT',
'forums' => 'ACL_CAT_FORUMS',
'misc' => 'ACL_CAT_MISC',
'permissions' => 'ACL_CAT_PERMISSIONS',
'pm' => 'ACL_CAT_PM',
'polls' => 'ACL_CAT_POLLS',
'post' => 'ACL_CAT_POST',
'post_actions' => 'ACL_CAT_POST_ACTIONS',
'posting' => 'ACL_CAT_POSTING',
'profile' => 'ACL_CAT_PROFILE',
'settings' => 'ACL_CAT_SETTINGS',
'topic_actions' => 'ACL_CAT_TOPIC_ACTIONS',
'user_group' => 'ACL_CAT_USER_GROUP',
);
protected $permissions = array(
// User Permissions
'u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'),
'u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'),
'u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'),
'u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'),
'u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'),
'u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'),
'u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'),
'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'),
'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'),
'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'),
'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'),
'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'),
'u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'),
'u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'),
'u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'),
'u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'),
'u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'),
'u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'),
'u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'),
'u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'),
'u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'),
'u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'),
'u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'),
'u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'),
'u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'),
'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'),
'u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'),
'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'),
'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'),
'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'),
'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'),
'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'),
'u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'),
// Forum Permissions
'f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'),
'f_list_topics' => array('lang' => 'ACL_F_LIST_TOPICS', 'cat' => 'actions'),
'f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'),
'f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'),
'f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'),
'f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'),
'f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'),
'f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'),
'f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'),
'f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'),
'f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'),
'f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'),
'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'),
'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'),
'f_announce_global' => array('lang' => 'ACL_F_ANNOUNCE_GLOBAL', 'cat' => 'post'),
'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'),
'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'),
'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'),
'f_softdelete' => array('lang' => 'ACL_F_SOFTDELETE', 'cat' => 'post'),
'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'),
'f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'),
'f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'),
'f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'),
'f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'),
'f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'),
'f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'),
'f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'),
'f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'),
'f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'),
'f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'),
'f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'),
'f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'),
// Moderator Permissions
'm_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'),
'm_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'),
'm_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'),
'm_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'),
'm_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'),
'm_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'post_actions'),
'm_softdelete' => array('lang' => 'ACL_M_SOFTDELETE', 'cat' => 'post_actions'),
'm_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'),
'm_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'),
'm_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'),
'm_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'),
'm_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'),
'm_pm_report' => array('lang' => 'ACL_M_PM_REPORT', 'cat' => 'misc'),
'm_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'),
// Admin Permissions
'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'),
'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'),
'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'),
'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'),
'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'),
'a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'),
'a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'),
'a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'),
'a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'),
'a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'),
'a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'),
'a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'),
'a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'),
'a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'),
'a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'),
'a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'),
'a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'),
'a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'),
'a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'),
'a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'),
'a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'),
'a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'),
'a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'),
'a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'),
'a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'),
'a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'),
'a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'),
'a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'),
'a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'),
'a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'),
'a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'),
'a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'),
'a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'),
'a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'),
'a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'),
'a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'),
'a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'),
'a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'),
'a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'),
'a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'),
'a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'),
);
}

View File

@@ -0,0 +1,402 @@
<?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\plupload;
/**
* This class handles all server-side plupload functions
*/
class plupload
{
/**
* @var string
*/
protected $phpbb_root_path;
/**
* @var \phpbb\config\config
*/
protected $config;
/**
* @var \phpbb\request\request_interface
*/
protected $request;
/**
* @var \phpbb\user
*/
protected $user;
/**
* @var \bantu\IniGetWrapper\IniGetWrapper
*/
protected $php_ini;
/**
* @var \phpbb\mimetype\guesser
*/
protected $mimetype_guesser;
/**
* Final destination for uploaded files, i.e. the "files" directory.
* @var string
*/
protected $upload_directory;
/**
* Temporary upload directory for plupload uploads.
* @var string
*/
protected $temporary_directory;
/**
* Constructor.
*
* @param string $phpbb_root_path
* @param \phpbb\config\config $config
* @param \phpbb\request\request_interface $request
* @param \phpbb\user $user
* @param \bantu\IniGetWrapper\IniGetWrapper $php_ini
* @param \phpbb\mimetype\guesser $mimetype_guesser
*/
public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \phpbb\mimetype\guesser $mimetype_guesser)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->config = $config;
$this->request = $request;
$this->user = $user;
$this->php_ini = $php_ini;
$this->mimetype_guesser = $mimetype_guesser;
$this->set_default_directories();
}
/**
* Plupload allows for chunking so we must check for that and assemble
* the whole file first before performing any checks on it.
*
* @param string $form_name The name of the file element in the upload form
*
* @return array|null null if there are no chunks to piece together
* otherwise array containing the path to the
* pieced-together file and its size
*/
public function handle_upload($form_name)
{
$chunks_expected = $this->request->variable('chunks', 0);
// If chunking is disabled or we are not using plupload, just return
// and handle the file as usual
if ($chunks_expected < 2)
{
return;
}
$file_name = $this->request->variable('name', '');
$chunk = $this->request->variable('chunk', 0);
$this->user->add_lang('plupload');
$this->prepare_temporary_directory();
$file_path = $this->temporary_filepath($file_name);
$this->integrate_uploaded_file($form_name, $chunk, $file_path);
// If we are done with all the chunks, strip the .part suffix and then
// handle the resulting file as normal, otherwise die and await the
// next chunk.
if ($chunk == $chunks_expected - 1)
{
rename("{$file_path}.part", $file_path);
// Reset upload directories to defaults once completed
$this->set_default_directories();
// Need to modify some of the $_FILES values to reflect the new file
return array(
'tmp_name' => $file_path,
'name' => $this->request->variable('real_filename', '', true),
'size' => filesize($file_path),
'type' => $this->mimetype_guesser->guess($file_path, $file_name),
);
}
else
{
$json_response = new \phpbb\json_response();
$json_response->send(array(
'jsonrpc' => '2.0',
'id' => 'id',
'result' => null,
));
}
}
/**
* Fill in the plupload configuration options in the template
*
* @param \phpbb\cache\service $cache
* @param \phpbb\template\template $template
* @param string $s_action The URL to submit the POST data to
* @param int $forum_id The ID of the forum
* @param int $max_files Maximum number of files allowed. 0 for unlimited.
*
* @return null
*/
public function configure(\phpbb\cache\service $cache, \phpbb\template\template $template, $s_action, $forum_id, $max_files)
{
$filters = $this->generate_filter_string($cache, $forum_id);
$chunk_size = $this->get_chunk_size();
$resize = $this->generate_resize_string();
$template->assign_vars(array(
'S_RESIZE' => $resize,
'S_PLUPLOAD' => true,
'FILTERS' => $filters,
'CHUNK_SIZE' => $chunk_size,
'S_PLUPLOAD_URL' => htmlspecialchars_decode($s_action),
'MAX_ATTACHMENTS' => $max_files,
'ATTACH_ORDER' => ($this->config['display_order']) ? 'asc' : 'desc',
'L_TOO_MANY_ATTACHMENTS' => $this->user->lang('TOO_MANY_ATTACHMENTS', $max_files),
));
$this->user->add_lang('plupload');
}
/**
* Checks whether the page request was sent by plupload or not
*
* @return bool
*/
public function is_active()
{
return $this->request->header('X-PHPBB-USING-PLUPLOAD', false);
}
/**
* Returns whether the current HTTP request is a multipart request.
*
* @return bool
*/
public function is_multipart()
{
$content_type = $this->request->server('CONTENT_TYPE');
return strpos($content_type, 'multipart') === 0;
}
/**
* Sends an error message back to the client via JSON response
*
* @param int $code The error code
* @param string $msg The translation string of the message to be sent
*
* @return null
*/
public function emit_error($code, $msg)
{
$json_response = new \phpbb\json_response();
$json_response->send(array(
'jsonrpc' => '2.0',
'id' => 'id',
'error' => array(
'code' => $code,
'message' => $this->user->lang($msg),
),
));
}
/**
* Looks at the list of allowed extensions and generates a string
* appropriate for use in configuring plupload with
*
* @param \phpbb\cache\service $cache
* @param string $forum_id The ID of the forum
*
* @return string
*/
public function generate_filter_string(\phpbb\cache\service $cache, $forum_id)
{
$attach_extensions = $cache->obtain_attach_extensions($forum_id);
unset($attach_extensions['_allowed_']);
$groups = array();
// Re-arrange the extension array to $groups[$group_name][]
foreach ($attach_extensions as $extension => $extension_info)
{
if (!isset($groups[$extension_info['group_name']]))
{
$groups[$extension_info['group_name']] = array();
}
$groups[$extension_info['group_name']][] = $extension;
}
$filters = array();
foreach ($groups as $group => $extensions)
{
$filters[] = sprintf(
"{title: '%s', extensions: '%s'}",
addslashes(ucfirst(strtolower($group))),
addslashes(implode(',', $extensions))
);
}
return implode(',', $filters);
}
/**
* Generates a string that is used to tell plupload to automatically resize
* files before uploading them.
*
* @return string
*/
public function generate_resize_string()
{
$resize = '';
if ($this->config['img_max_height'] > 0 && $this->config['img_max_width'] > 0)
{
$resize = sprintf(
'resize: {width: %d, height: %d, quality: 85},',
(int) $this->config['img_max_width'],
(int) $this->config['img_max_height']
);
}
return $resize;
}
/**
* Checks various php.ini values and the maximum file size to determine
* the maximum size chunks a file can be split up into for upload
*
* @return int
*/
public function get_chunk_size()
{
$max = min(
$this->php_ini->getBytes('upload_max_filesize'),
$this->php_ini->getBytes('post_max_size'),
max(1, $this->php_ini->getBytes('memory_limit')),
$this->config['max_filesize']
);
// Use half of the maximum possible to leave plenty of room for other
// POST data.
return floor($max / 2);
}
protected function temporary_filepath($file_name)
{
// Must preserve the extension for plupload to work.
return sprintf(
'%s/%s_%s%s',
$this->temporary_directory,
$this->config['plupload_salt'],
md5($file_name),
\phpbb\files\filespec::get_extension($file_name)
);
}
/**
* Checks whether the chunk we are about to deal with was actually uploaded
* by PHP and actually exists, if not, it generates an error
*
* @param string $form_name The name of the file in the form data
*
* @return null
*/
protected function integrate_uploaded_file($form_name, $chunk, $file_path)
{
$is_multipart = $this->is_multipart();
$upload = $this->request->file($form_name);
if ($is_multipart && (!isset($upload['tmp_name']) || !is_uploaded_file($upload['tmp_name'])))
{
$this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED');
}
$tmp_file = $this->temporary_filepath($upload['tmp_name']);
if (!phpbb_is_writable($this->temporary_directory) || !move_uploaded_file($upload['tmp_name'], $tmp_file))
{
$this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED');
}
$out = fopen("{$file_path}.part", $chunk == 0 ? 'wb' : 'ab');
if (!$out)
{
$this->emit_error(102, 'PLUPLOAD_ERR_OUTPUT');
}
$in = fopen(($is_multipart) ? $tmp_file : 'php://input', 'rb');
if (!$in)
{
$this->emit_error(101, 'PLUPLOAD_ERR_INPUT');
}
while ($buf = fread($in, 4096))
{
fwrite($out, $buf);
}
fclose($in);
fclose($out);
if ($is_multipart)
{
unlink($tmp_file);
}
}
/**
* Creates the temporary directory if it does not already exist.
*
* @return null
*/
protected function prepare_temporary_directory()
{
if (!file_exists($this->temporary_directory))
{
mkdir($this->temporary_directory);
copy(
$this->upload_directory . '/index.htm',
$this->temporary_directory . '/index.htm'
);
}
}
/**
* Sets the default directories for uploads
*
* @return null
*/
protected function set_default_directories()
{
$this->upload_directory = $this->phpbb_root_path . $this->config['upload_path'];
$this->temporary_directory = $this->upload_directory . '/plupload';
}
/**
* Sets the upload directories to the specified paths
*
* @param string $upload_directory Upload directory
* @param string $temporary_directory Temporary directory
*
* @return null
*/
public function set_upload_directories($upload_directory, $temporary_directory)
{
$this->upload_directory = $upload_directory;
$this->temporary_directory = $temporary_directory;
}
}

View File

@@ -0,0 +1,104 @@
<?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\report;
abstract class report_handler implements report_handler_interface
{
/**
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* @var \phpbb\event\dispatcher_interface
*/
protected $dispatcher;
/**
* @var \phpbb\config\config
*/
protected $config;
/**
* @var \phpbb\auth\auth
*/
protected $auth;
/**
* @var \phpbb\user
*/
protected $user;
/**
* @var \phpbb\notification\manager
*/
protected $notifications;
/**
* @var array
*/
protected $report_data;
/**
* Construtor
*
* @param \phpbb\db\driver\driver_interface $db
* @param \phpbb\event\dispatcher_interface $dispatcher
* @param \phpbb\config\db $config
* @param \phpbb\auth\auth $auth
* @param \phpbb\user $user
* @param \phpbb\notification\manager $notification
*/
public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\notification\manager $notification)
{
$this->db = $db;
$this->dispatcher = $dispatcher;
$this->config = $config;
$this->auth = $auth;
$this->user = $user;
$this->notifications = $notification;
$this->report_data = array();
}
/**
* Creates a report entity in the database
*
* @param array $report_data
* @return int the ID of the created entity
*/
protected function create_report(array $report_data)
{
$sql_ary = array(
'reason_id' => (int) $report_data['reason_id'],
'post_id' => $report_data['post_id'],
'pm_id' => $report_data['pm_id'],
'user_id' => (int) $this->user->data['user_id'],
'user_notify' => (int) $report_data['user_notify'],
'report_closed' => 0,
'report_time' => (int) time(),
'report_text' => (string) $report_data['report_text'],
'reported_post_text' => $report_data['reported_post_text'],
'reported_post_uid' => $report_data['reported_post_uid'],
'reported_post_bitfield' => $report_data['reported_post_bitfield'],
'reported_post_enable_bbcode' => $report_data['reported_post_enable_bbcode'],
'reported_post_enable_smilies' => $report_data['reported_post_enable_smilies'],
'reported_post_enable_magic_url' => $report_data['reported_post_enable_magic_url'],
);
$sql = 'INSERT INTO ' . REPORTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
$this->db->sql_query($sql);
return $this->db->sql_nextid();
}
}

View File

@@ -0,0 +1,454 @@
<?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\request;
/**
* All application input is accessed through this class.
*
* It provides a method to disable access to input data through super globals.
* This should force MOD authors to read about data validation.
*/
class request implements \phpbb\request\request_interface
{
/**
* @var array The names of super global variables that this class should protect if super globals are disabled.
*/
protected $super_globals = array(
\phpbb\request\request_interface::POST => '_POST',
\phpbb\request\request_interface::GET => '_GET',
\phpbb\request\request_interface::REQUEST => '_REQUEST',
\phpbb\request\request_interface::COOKIE => '_COOKIE',
\phpbb\request\request_interface::SERVER => '_SERVER',
\phpbb\request\request_interface::FILES => '_FILES',
);
/**
* @var array Stores original contents of $_REQUEST array.
*/
protected $original_request = null;
/**
* @var
*/
protected $super_globals_disabled = false;
/**
* @var array An associative array that has the value of super global constants as keys and holds their data as values.
*/
protected $input;
/**
* @var \phpbb\request\type_cast_helper_interface An instance of a type cast helper providing convenience methods for type conversions.
*/
protected $type_cast_helper;
/**
* Initialises the request class, that means it stores all input data in {@link $input input}
* and then calls {@link \phpbb\request\deactivated_super_global \phpbb\request\deactivated_super_global}
*/
public function __construct(\phpbb\request\type_cast_helper_interface $type_cast_helper = null, $disable_super_globals = true)
{
if ($type_cast_helper)
{
$this->type_cast_helper = $type_cast_helper;
}
else
{
$this->type_cast_helper = new \phpbb\request\type_cast_helper();
}
foreach ($this->super_globals as $const => $super_global)
{
$this->input[$const] = isset($GLOBALS[$super_global]) ? $GLOBALS[$super_global] : array();
}
// simulate request_order = GP
$this->original_request = $this->input[\phpbb\request\request_interface::REQUEST];
$this->input[\phpbb\request\request_interface::REQUEST] = $this->input[\phpbb\request\request_interface::POST] + $this->input[\phpbb\request\request_interface::GET];
if ($disable_super_globals)
{
$this->disable_super_globals();
}
}
/**
* Getter for $super_globals_disabled
*
* @return bool Whether super globals are disabled or not.
*/
public function super_globals_disabled()
{
return $this->super_globals_disabled;
}
/**
* Disables access of super globals specified in $super_globals.
* This is achieved by overwriting the super globals with instances of {@link \phpbb\request\deactivated_super_global \phpbb\request\deactivated_super_global}
*/
public function disable_super_globals()
{
if (!$this->super_globals_disabled)
{
foreach ($this->super_globals as $const => $super_global)
{
unset($GLOBALS[$super_global]);
$GLOBALS[$super_global] = new \phpbb\request\deactivated_super_global($this, $super_global, $const);
}
$this->super_globals_disabled = true;
}
}
/**
* Enables access of super globals specified in $super_globals if they were disabled by {@link disable_super_globals disable_super_globals}.
* This is achieved by making the super globals point to the data stored within this class in {@link $input input}.
*/
public function enable_super_globals()
{
if ($this->super_globals_disabled)
{
foreach ($this->super_globals as $const => $super_global)
{
$GLOBALS[$super_global] = $this->input[$const];
}
$GLOBALS['_REQUEST'] = $this->original_request;
$this->super_globals_disabled = false;
}
}
/**
* This function allows overwriting or setting a value in one of the super global arrays.
*
* Changes which are performed on the super globals directly will not have any effect on the results of
* other methods this class provides. Using this function should be avoided if possible! It will
* consume twice the the amount of memory of the value
*
* @param string $var_name The name of the variable that shall be overwritten
* @param mixed $value The value which the variable shall contain.
* If this is null the variable will be unset.
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global shall be changed
*/
public function overwrite($var_name, $value, $super_global = \phpbb\request\request_interface::REQUEST)
{
if (!isset($this->super_globals[$super_global]))
{
return;
}
// setting to null means unsetting
if ($value === null)
{
unset($this->input[$super_global][$var_name]);
if (!$this->super_globals_disabled())
{
unset($GLOBALS[$this->super_globals[$super_global]][$var_name]);
}
}
else
{
$this->input[$super_global][$var_name] = $value;
if (!$this->super_globals_disabled())
{
$GLOBALS[$this->super_globals[$super_global]][$var_name] = $value;
}
}
}
/**
* Central type safe input handling function.
* All variables in GET or POST requests should be retrieved through this function to maximise security.
*
* @param string|array $var_name The form variable's name from which data shall be retrieved.
* If the value is an array this may be an array of indizes which will give
* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
* then specifying array("var", 1) as the name will return "a".
* @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 paramater 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
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global should be used
*
* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
* the same as that of $default. If the variable is not set $default is returned.
*/
public function variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST)
{
return $this->_variable($var_name, $default, $multibyte, $super_global, true);
}
/**
* Get a variable, but without trimming strings.
* Same functionality as variable(), except does not run trim() on strings.
* This method should be used when handling passwords.
*
* @param string|array $var_name The form variable's name from which data shall be retrieved.
* If the value is an array this may be an array of indizes which will give
* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
* then specifying array("var", 1) as the name will return "a".
* @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 paramater 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
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global should be used
*
* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
* the same as that of $default. If the variable is not set $default is returned.
*/
public function untrimmed_variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST)
{
return $this->_variable($var_name, $default, $multibyte, $super_global, false);
}
/**
* {@inheritdoc}
*/
public function raw_variable($var_name, $default, $super_global = \phpbb\request\request_interface::REQUEST)
{
$path = false;
// deep direct access to multi dimensional arrays
if (is_array($var_name))
{
$path = $var_name;
// make sure at least the variable name is specified
if (empty($path))
{
return (is_array($default)) ? array() : $default;
}
// the variable name is the first element on the path
$var_name = array_shift($path);
}
if (!isset($this->input[$super_global][$var_name]))
{
return (is_array($default)) ? array() : $default;
}
$var = $this->input[$super_global][$var_name];
if ($path)
{
// walk through the array structure and find the element we are looking for
foreach ($path as $key)
{
if (is_array($var) && isset($var[$key]))
{
$var = $var[$key];
}
else
{
return (is_array($default)) ? array() : $default;
}
}
}
return $var;
}
/**
* Shortcut method to retrieve SERVER variables.
*
* Also fall back to getenv(), some CGI setups may need it (probably not, but
* whatever).
*
* @param string|array $var_name See \phpbb\request\request_interface::variable
* @param mixed $Default See \phpbb\request\request_interface::variable
*
* @return mixed The server variable value.
*/
public function server($var_name, $default = '')
{
$multibyte = true;
if ($this->is_set($var_name, \phpbb\request\request_interface::SERVER))
{
return $this->variable($var_name, $default, $multibyte, \phpbb\request\request_interface::SERVER);
}
else
{
$var = getenv($var_name);
$this->type_cast_helper->recursive_set_var($var, $default, $multibyte);
return $var;
}
}
/**
* Shortcut method to retrieve the value of client HTTP headers.
*
* @param string|array $header_name The name of the header to retrieve.
* @param mixed $default See \phpbb\request\request_interface::variable
*
* @return mixed The header value.
*/
public function header($header_name, $default = '')
{
$var_name = 'HTTP_' . str_replace('-', '_', strtoupper($header_name));
return $this->server($var_name, $default);
}
/**
* Shortcut method to retrieve $_FILES variables
*
* @param string $form_name The name of the file input form element
*
* @return array The uploaded file's information or an empty array if the
* variable does not exist in _FILES.
*/
public function file($form_name)
{
return $this->variable($form_name, array('name' => 'none'), true, \phpbb\request\request_interface::FILES);
}
/**
* Checks whether a certain variable was sent via POST.
* To make sure that a request was sent using POST you should call this function
* on at least one variable.
*
* @param string $name The name of the form variable which should have a
* _p suffix to indicate the check in the code that creates the form too.
*
* @return bool True if the variable was set in a POST request, false otherwise.
*/
public function is_set_post($name)
{
return $this->is_set($name, \phpbb\request\request_interface::POST);
}
/**
* Checks whether a certain variable is set in one of the super global
* arrays.
*
* @param string $var Name of the variable
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies the super global which shall be checked
*
* @return bool True if the variable was sent as input
*/
public function is_set($var, $super_global = \phpbb\request\request_interface::REQUEST)
{
return isset($this->input[$super_global][$var]);
}
/**
* Checks whether the current request is an AJAX request (XMLHttpRequest)
*
* @return bool True if the current request is an ajax request
*/
public function is_ajax()
{
return $this->header('X-Requested-With') == 'XMLHttpRequest';
}
/**
* Checks if the current request is happening over HTTPS.
*
* @return bool True if the request is secure.
*/
public function is_secure()
{
$https = $this->server('HTTPS');
$https = $this->server('HTTP_X_FORWARDED_PROTO') === 'https' ? 'on' : $https;
return !empty($https) && $https !== 'off';
}
/**
* Returns all variable names for a given super global
*
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* The super global from which names shall be taken
*
* @return array All variable names that are set for the super global.
* Pay attention when using these, they are unsanitised!
*/
public function variable_names($super_global = \phpbb\request\request_interface::REQUEST)
{
if (!isset($this->input[$super_global]))
{
return array();
}
return array_keys($this->input[$super_global]);
}
/**
* Helper function used by variable() and untrimmed_variable().
*
* @param string|array $var_name The form variable's name from which data shall be retrieved.
* If the value is an array this may be an array of indizes which will give
* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
* then specifying array("var", 1) as the name will return "a".
* @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 paramater 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
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global should be used
* @param bool $trim Indicates whether trim() should be applied to string values.
*
* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
* the same as that of $default. If the variable is not set $default is returned.
*/
protected function _variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST, $trim = true)
{
$var = $this->raw_variable($var_name, $default, $super_global);
// Return prematurely if raw variable is empty array or the same as
// the default. Using strict comparison to ensure that one can't
// prevent proper type checking on any input variable
if ($var === array() || $var === $default)
{
return $var;
}
$this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim);
return $var;
}
/**
* {@inheritdoc}
*/
public function get_super_global($super_global = \phpbb\request\request_interface::REQUEST)
{
return $this->input[$super_global];
}
/**
* {@inheritdoc}
*/
public function escape($var, $multibyte)
{
if (is_array($var))
{
$result = array();
foreach ($var as $key => $value)
{
$this->type_cast_helper->set_var($key, $key, gettype($key), $multibyte);
$result[$key] = $this->escape($value, $multibyte);
}
$var = $result;
}
else
{
$this->type_cast_helper->set_var($var, $var, 'string', $multibyte);
}
return $var;
}
}

View File

@@ -0,0 +1,177 @@
<?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\request;
/**
* An interface through which all application input can be accessed.
*/
interface request_interface
{
/**#@+
* Constant identifying the super global with the same name.
*/
const POST = 0;
const GET = 1;
const REQUEST = 2;
const COOKIE = 3;
const SERVER = 4;
const FILES = 5;
/**#@-*/
/**
* This function allows overwriting or setting a value in one of the super global arrays.
*
* Changes which are performed on the super globals directly will not have any effect on the results of
* other methods this class provides. Using this function should be avoided if possible! It will
* consume twice the the amount of memory of the value
*
* @param string $var_name The name of the variable that shall be overwritten
* @param mixed $value The value which the variable shall contain.
* If this is null the variable will be unset.
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global shall be changed
*/
public function overwrite($var_name, $value, $super_global = \phpbb\request\request_interface::REQUEST);
/**
* Central type safe input handling function.
* All variables in GET or POST requests should be retrieved through this function to maximise security.
*
* @param string|array $var_name The form variable's name from which data shall be retrieved.
* If the value is an array this may be an array of indizes which will give
* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
* then specifying array("var", 1) as the name will return "a".
* @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 paramater 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
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global should be used
*
* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
* the same as that of $default. If the variable is not set $default is returned.
*/
public function variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST);
/**
* Get a variable without trimming strings and without escaping.
* This method MUST NOT be used with queries.
* Same functionality as variable(), except does not run trim() on strings
* and does not escape input.
* This method should only be used when the raw input is needed without
* any escaping, i.e. for database password during the installation.
*
* @param string|array $var_name The form variable's name from which data shall be retrieved.
* If the value is an array this may be an array of indizes which will give
* direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a")
* then specifying array("var", 1) as the name will return "a".
* @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 \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies which super global should be used
*
* @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the
* the same as that of $default. If the variable is not set $default is returned.
*/
public function raw_variable($var_name, $default, $super_global = \phpbb\request\request_interface::REQUEST);
/**
* Shortcut method to retrieve SERVER variables.
*
* @param string|array $var_name See \phpbb\request\request_interface::variable
* @param mixed $default See \phpbb\request\request_interface::variable
*
* @return mixed The server variable value.
*/
public function server($var_name, $default = '');
/**
* Shortcut method to retrieve the value of client HTTP headers.
*
* @param string|array $header_name The name of the header to retrieve.
* @param mixed $default See \phpbb\request\request_interface::variable
*
* @return mixed The header value.
*/
public function header($var_name, $default = '');
/**
* Checks whether a certain variable was sent via POST.
* To make sure that a request was sent using POST you should call this function
* on at least one variable.
*
* @param string $name The name of the form variable which should have a
* _p suffix to indicate the check in the code that creates the form too.
*
* @return bool True if the variable was set in a POST request, false otherwise.
*/
public function is_set_post($name);
/**
* Checks whether a certain variable is set in one of the super global
* arrays.
*
* @param string $var Name of the variable
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* Specifies the super global which shall be checked
*
* @return bool True if the variable was sent as input
*/
public function is_set($var, $super_global = \phpbb\request\request_interface::REQUEST);
/**
* Checks whether the current request is an AJAX request (XMLHttpRequest)
*
* @return bool True if the current request is an ajax request
*/
public function is_ajax();
/**
* Checks if the current request is happening over HTTPS.
*
* @return bool True if the request is secure.
*/
public function is_secure();
/**
* Returns all variable names for a given super global
*
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* The super global from which names shall be taken
*
* @return array All variable names that are set for the super global.
* Pay attention when using these, they are unsanitised!
*/
public function variable_names($super_global = \phpbb\request\request_interface::REQUEST);
/**
* Returns the original array of the requested super global
*
* @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global
* The super global which will be returned
*
* @return array The original array of the requested super global.
*/
public function get_super_global($super_global = \phpbb\request\request_interface::REQUEST);
/**
* Escape a string variable.
*
* @param mixed $value The contents to fill with
* @param bool $multibyte Indicates whether string values may contain UTF-8 characters.
* Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks.
* @return string|array
*/
public function escape($value, $multibyte);
}

View File

@@ -0,0 +1,124 @@
<?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\request;
/**
* A helper class that provides convenience methods for type casting.
*/
class type_cast_helper implements \phpbb\request\type_cast_helper_interface
{
/**
* Set variable $result to a particular type.
*
* @param mixed &$result The variable to fill
* @param mixed $var The contents to fill with
* @param mixed $type The variable type. Will be used with {@link settype()}
* @param bool $multibyte Indicates whether string values may contain UTF-8 characters.
* Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks.
* @param bool $trim Indicates whether trim() should be applied to string values.
* Default is true.
*/
public function set_var(&$result, $var, $type, $multibyte = false, $trim = true)
{
settype($var, $type);
$result = $var;
if ($type == 'string')
{
$result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result);
if ($trim)
{
$result = trim($result);
}
$result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8');
if ($multibyte)
{
$result = utf8_normalize_nfc($result);
}
if (!empty($result))
{
// Make sure multibyte characters are wellformed
if ($multibyte)
{
if (!preg_match('/^./u', $result))
{
$result = '';
}
}
else
{
// no multibyte, allow only ASCII (0-127)
$result = preg_replace('/[\x80-\xFF]/', '?', $result);
}
}
}
}
/**
* Recursively sets a variable to a given type using {@link set_var set_var}
*
* @param string $var The value which shall be sanitised (passed by reference).
* @param mixed $default Specifies the type $var shall have.
* If it is an array and $var is not one, then an empty array is returned.
* Otherwise var is cast to the same type, and if $default is an array all
* keys and values are cast recursively using this function too.
* @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters.
* Default is false, causing all bytes outside the ASCII range (0-127) to
* be replaced with question marks.
* @param bool $trim Indicates whether trim() should be applied to string values.
* Default is true.
*/
public function recursive_set_var(&$var, $default, $multibyte, $trim = true)
{
if (is_array($var) !== is_array($default))
{
$var = (is_array($default)) ? array() : $default;
return;
}
if (!is_array($default))
{
$type = gettype($default);
$this->set_var($var, $var, $type, $multibyte, $trim);
}
else
{
// make sure there is at least one key/value pair to use get the
// types from
if (empty($default))
{
$var = array();
return;
}
list($default_key, $default_value) = each($default);
$key_type = gettype($default_key);
$_var = $var;
$var = array();
foreach ($_var as $k => $v)
{
$this->set_var($k, $k, $key_type, $multibyte);
$this->recursive_set_var($v, $default_value, $multibyte, $trim);
$var[$k] = $v;
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,992 @@
<?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\search;
define('SPHINX_MAX_MATCHES', 20000);
define('SPHINX_CONNECT_RETRIES', 3);
define('SPHINX_CONNECT_WAIT_TIME', 300);
/**
* Fulltext search based on the sphinx search deamon
*/
class fulltext_sphinx
{
/**
* Associative array holding index stats
* @var array
*/
protected $stats = array();
/**
* Holds the words entered by user, obtained by splitting the entered query on whitespace
* @var array
*/
protected $split_words = array();
/**
* Holds unique sphinx id
* @var string
*/
protected $id;
/**
* Stores the names of both main and delta sphinx indexes
* separated by a semicolon
* @var string
*/
protected $indexes;
/**
* Sphinx searchd client object
* @var SphinxClient
*/
protected $sphinx;
/**
* Relative path to board root
* @var string
*/
protected $phpbb_root_path;
/**
* PHP Extension
* @var string
*/
protected $php_ext;
/**
* Auth object
* @var \phpbb\auth\auth
*/
protected $auth;
/**
* Config object
* @var \phpbb\config\config
*/
protected $config;
/**
* Database connection
* @var \phpbb\db\driver\driver_interface
*/
protected $db;
/**
* Database Tools object
* @var \phpbb\db\tools\tools_interface
*/
protected $db_tools;
/**
* Stores the database type if supported by sphinx
* @var string
*/
protected $dbtype;
/**
* phpBB event dispatcher object
* @var \phpbb\event\dispatcher_interface
*/
protected $phpbb_dispatcher;
/**
* User object
* @var \phpbb\user
*/
protected $user;
/**
* Stores the generated content of the sphinx config file
* @var string
*/
protected $config_file_data = '';
/**
* Contains tidied search query.
* Operators are prefixed in search query and common words excluded
* @var string
*/
protected $search_query;
/**
* Constructor
* Creates a new \phpbb\search\fulltext_postgres, which is used as a search backend
*
* @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false
* @param string $phpbb_root_path Relative path to phpBB root
* @param string $phpEx PHP file extension
* @param \phpbb\auth\auth $auth Auth object
* @param \phpbb\config\config $config Config object
* @param \phpbb\db\driver\driver_interface Database object
* @param \phpbb\user $user User object
* @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object
*/
public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher)
{
$this->phpbb_root_path = $phpbb_root_path;
$this->php_ext = $phpEx;
$this->config = $config;
$this->phpbb_dispatcher = $phpbb_dispatcher;
$this->user = $user;
$this->db = $db;
$this->auth = $auth;
// Initialize \phpbb\db\tools\tools object
global $phpbb_container; // TODO inject into object
$this->db_tools = $phpbb_container->get('dbal.tools');
if (!$this->config['fulltext_sphinx_id'])
{
$this->config->set('fulltext_sphinx_id', unique_id());
}
$this->id = $this->config['fulltext_sphinx_id'];
$this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main';
if (!class_exists('SphinxClient'))
{
require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext);
}
// Initialize sphinx client
$this->sphinx = new \SphinxClient();
$this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312));
$error = false;
}
/**
* Returns the name of this search backend to be displayed to administrators
*
* @return string Name
*/
public function get_name()
{
return 'Sphinx Fulltext';
}
/**
* Returns the search_query
*
* @return string search query
*/
public function get_search_query()
{
return $this->search_query;
}
/**
* Returns false as there is no word_len array
*
* @return false
*/
public function get_word_length()
{
return false;
}
/**
* Returns an empty array as there are no common_words
*
* @return array common words that are ignored by search backend
*/
public function get_common_words()
{
return array();
}
/**
* Checks permissions and paths, if everything is correct it generates the config file
*
* @return string|bool Language key of the error/incompatiblity encountered, or false if successful
*/
public function init()
{
if ($this->db->get_sql_layer() != 'mysql' && $this->db->get_sql_layer() != 'mysql4' && $this->db->get_sql_layer() != 'mysqli' && $this->db->get_sql_layer() != 'postgres')
{
return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE'];
}
// Move delta to main index each hour
$this->config->set('search_gc', 3600);
return false;
}
/**
* Generates content of sphinx.conf
*
* @return bool True if sphinx.conf content is correctly generated, false otherwise
*/
protected function config_generate()
{
// Check if Database is supported by Sphinx
if ($this->db->get_sql_layer() =='mysql' || $this->db->get_sql_layer() == 'mysql4' || $this->db->get_sql_layer() == 'mysqli')
{
$this->dbtype = 'mysql';
}
else if ($this->db->get_sql_layer() == 'postgres')
{
$this->dbtype = 'pgsql';
}
else
{
$this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE');
return false;
}
// Check if directory paths have been filled
if (!$this->config['fulltext_sphinx_data_path'])
{
$this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA');
return false;
}
include($this->phpbb_root_path . 'config.' . $this->php_ext);
/* Now that we're sure everything was entered correctly,
generate a config for the index. We use a config value
fulltext_sphinx_id for this, as it should be unique. */
$config_object = new \phpbb\search\sphinx\config($this->config_file_data);
$config_data = array(
'source source_phpbb_' . $this->id . '_main' => array(
array('type', $this->dbtype . ' # mysql or pgsql'),
// This config value sql_host needs to be changed incase sphinx and sql are on different servers
array('sql_host', $dbhost . ' # SQL server host sphinx connects to'),
array('sql_user', '[dbuser]'),
array('sql_pass', '[dbpassword]'),
array('sql_db', $dbname),
array('sql_port', $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'),
array('sql_query_pre', 'SET NAMES \'utf8\''),
array('sql_query_pre', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'),
array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''),
array('sql_range_step', '5000'),
array('sql_query', 'SELECT
p.post_id AS id,
p.forum_id,
p.topic_id,
p.poster_id,
p.post_visibility,
CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
p.post_time,
p.post_subject,
p.post_subject as title,
p.post_text as data,
t.topic_last_post_time,
0 as deleted
FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t
WHERE
p.topic_id = t.topic_id
AND p.post_id >= $start AND p.post_id <= $end'),
array('sql_query_post', ''),
array('sql_query_post_index', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'),
array('sql_attr_uint', 'forum_id'),
array('sql_attr_uint', 'topic_id'),
array('sql_attr_uint', 'poster_id'),
array('sql_attr_uint', 'post_visibility'),
array('sql_attr_bool', 'topic_first_post'),
array('sql_attr_bool', 'deleted'),
array('sql_attr_timestamp', 'post_time'),
array('sql_attr_timestamp', 'topic_last_post_time'),
array('sql_attr_string', 'post_subject'),
),
'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array(
array('sql_query_pre', 'SET NAMES \'utf8\''),
array('sql_query_range', ''),
array('sql_range_step', ''),
array('sql_query', 'SELECT
p.post_id AS id,
p.forum_id,
p.topic_id,
p.poster_id,
p.post_visibility,
CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post,
p.post_time,
p.post_subject,
p.post_subject as title,
p.post_text as data,
t.topic_last_post_time,
0 as deleted
FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t
WHERE
p.topic_id = t.topic_id
AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'),
array('sql_query_post_index', ''),
),
'index index_phpbb_' . $this->id . '_main' => array(
array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'),
array('source', 'source_phpbb_' . $this->id . '_main'),
array('docinfo', 'extern'),
array('morphology', 'none'),
array('stopwords', ''),
array('min_word_len', '2'),
array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'),
array('min_prefix_len', '0'),
array('min_infix_len', '0'),
),
'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array(
array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'),
array('source', 'source_phpbb_' . $this->id . '_delta'),
),
'indexer' => array(
array('mem_limit', $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'),
),
'searchd' => array(
array('listen' , ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')),
array('log', $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'),
array('query_log', $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'),
array('read_timeout', '5'),
array('max_children', '30'),
array('pid_file', $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'),
array('binlog_path', $this->config['fulltext_sphinx_data_path']),
),
);
$non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true);
$delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true);
/**
* Allow adding/changing the Sphinx configuration data
*
* @event core.search_sphinx_modify_config_data
* @var array config_data Array with the Sphinx configuration data
* @var array non_unique Array with the Sphinx non-unique variables to delete
* @var array delete Array with the Sphinx variables to delete
* @since 3.1.7-RC1
*/
$vars = array(
'config_data',
'non_unique',
'delete',
);
extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars)));
foreach ($config_data as $section_name => $section_data)
{
$section = $config_object->get_section_by_name($section_name);
if (!$section)
{
$section = $config_object->add_section($section_name);
}
foreach ($delete as $key => $void)
{
$section->delete_variables_by_name($key);
}
foreach ($non_unique as $key => $void)
{
$section->delete_variables_by_name($key);
}
foreach ($section_data as $entry)
{
$key = $entry[0];
$value = $entry[1];
if (!isset($non_unique[$key]))
{
$variable = $section->get_variable_by_name($key);
if (!$variable)
{
$section->create_variable($key, $value);
}
else
{
$variable->set_value($value);
}
}
else
{
$section->create_variable($key, $value);
}
}
}
$this->config_file_data = $config_object->get_data();
return true;
}
/**
* Splits keywords entered by a user into an array of words stored in $this->split_words
* Stores the tidied search query in $this->search_query
*
* @param string $keywords Contains the keyword as entered by the user
* @param string $terms is either 'all' or 'any'
* @return false if no valid keywords were found and otherwise true
*/
public function split_keywords(&$keywords, $terms)
{
if ($terms == 'all')
{
$match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#');
$replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', '');
$keywords = preg_replace($match, $replace, $keywords);
$this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED);
}
else
{
$this->sphinx->SetMatchMode(SPH_MATCH_ANY);
}
// Keep quotes and new lines
$keywords = str_replace(array('&quot;', "\n"), array('"', ' '), trim($keywords));
if (strlen($keywords) > 0)
{
$this->search_query = str_replace('"', '&quot;', $keywords);
return true;
}
return false;
}
/**
* Performs a search on keywords depending on display specific params. You have to run split_keywords() first
*
* @param string $type contains either posts or topics depending on what should be searched for
* @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched)
* @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words)
* @param array $sort_by_sql contains SQL code for the ORDER BY part of a query
* @param string $sort_key is the key of $sort_by_sql for the selected sorting
* @param string $sort_dir is either a or d representing ASC and DESC
* @param string $sort_days specifies the maximum amount of days a post may be old
* @param array $ex_fid_ary specifies an array of forum ids which should not be searched
* @param string $post_visibility specifies which types of posts the user can view in which forums
* @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched
* @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty
* @param string $author_name specifies the author match, when ANONYMOUS is also a search-match
* @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
* @param int $start indicates the first index of the page
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page)
{
global $user, $phpbb_log;
// No keywords? No posts.
if (!strlen($this->search_query) && !count($author_ary))
{
return false;
}
$id_ary = array();
// Sorting
if ($type == 'topics')
{
switch ($sort_key)
{
case 'a':
$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
break;
case 'f':
$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
break;
case 'i':
case 's':
$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
break;
case 't':
default:
$this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'));
break;
}
}
else
{
switch ($sort_key)
{
case 'a':
$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id');
break;
case 'f':
$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id');
break;
case 'i':
case 's':
$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject');
break;
case 't':
default:
$this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time');
break;
}
}
// Most narrow filters first
if ($topic_id)
{
$this->sphinx->SetFilter('topic_id', array($topic_id));
}
/**
* Allow modifying the Sphinx search options
*
* @event core.search_sphinx_keywords_modify_options
* @var string type Searching type ('posts', 'topics')
* @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all')
* @var string terms Searching terms ('all', 'any')
* @var int sort_days Time, in days, of the oldest possible post to list
* @var string sort_key The sort type used from the possible sort types
* @var int topic_id Limit the search to this topic_id only
* @var array ex_fid_ary Which forums not to search on
* @var string post_visibility Post visibility data
* @var array author_ary Array of user_id containing the users to filter the results to
* @var string author_name The username to search on
* @var object sphinx The Sphinx searchd client object
* @since 3.1.7-RC1
*/
$sphinx = $this->sphinx;
$vars = array(
'type',
'fields',
'terms',
'sort_days',
'sort_key',
'topic_id',
'ex_fid_ary',
'post_visibility',
'author_ary',
'author_name',
'sphinx',
);
extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars)));
$this->sphinx = $sphinx;
unset($sphinx);
$search_query_prefix = '';
switch ($fields)
{
case 'titleonly':
// Only search the title
if ($terms == 'all')
{
$search_query_prefix = '@title ';
}
// Weight for the title
$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
// 1 is first_post, 0 is not first post
$this->sphinx->SetFilter('topic_first_post', array(1));
break;
case 'msgonly':
// Only search the body
if ($terms == 'all')
{
$search_query_prefix = '@data ';
}
// Weight for the body
$this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5));
break;
case 'firstpost':
// More relative weight for the title, also search the body
$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
// 1 is first_post, 0 is not first post
$this->sphinx->SetFilter('topic_first_post', array(1));
break;
default:
// More relative weight for the title, also search the body
$this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1));
break;
}
if (count($author_ary))
{
$this->sphinx->SetFilter('poster_id', $author_ary);
}
// As this is not simply possible at the moment, we limit the result to approved posts.
// This will make it impossible for moderators to search unapproved and softdeleted posts,
// but at least it will also cause the same for normal users.
$this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED));
if (count($ex_fid_ary))
{
// All forums that a user is allowed to access
$fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true))));
// All forums that the user wants to and can search in
$search_forums = array_diff($fid_ary, $ex_fid_ary);
if (count($search_forums))
{
$this->sphinx->SetFilter('forum_id', $search_forums);
}
}
$this->sphinx->SetFilter('deleted', array(0));
$this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES);
$result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
// Could be connection to localhost:9312 failed (errno=111,
// msg=Connection refused) during rotate, retry if so
$retries = SPHINX_CONNECT_RETRIES;
while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
{
usleep(SPHINX_CONNECT_WAIT_TIME);
$result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
}
if ($this->sphinx->GetLastError())
{
$phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError()));
if ($this->auth->acl_get('a_'))
{
trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError()));
}
else
{
trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG'));
}
}
$result_count = $result['total_found'];
if ($result_count && $start >= $result_count)
{
$start = floor(($result_count - 1) / $per_page) * $per_page;
$this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES);
$result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
// Could be connection to localhost:9312 failed (errno=111,
// msg=Connection refused) during rotate, retry if so
$retries = SPHINX_CONNECT_RETRIES;
while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--)
{
usleep(SPHINX_CONNECT_WAIT_TIME);
$result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('&quot;', '"', $this->search_query)), $this->indexes);
}
}
$id_ary = array();
if (isset($result['matches']))
{
if ($type == 'posts')
{
$id_ary = array_keys($result['matches']);
}
else
{
foreach ($result['matches'] as $key => $value)
{
$id_ary[] = $value['attrs']['topic_id'];
}
}
}
else
{
return false;
}
$id_ary = array_slice($id_ary, 0, (int) $per_page);
return $result_count;
}
/**
* Performs a search on an author's posts without caring about message contents. Depends on display specific params
*
* @param string $type contains either posts or topics depending on what should be searched for
* @param boolean $firstpost_only if true, only topic starting posts will be considered
* @param array $sort_by_sql contains SQL code for the ORDER BY part of a query
* @param string $sort_key is the key of $sort_by_sql for the selected sorting
* @param string $sort_dir is either a or d representing ASC and DESC
* @param string $sort_days specifies the maximum amount of days a post may be old
* @param array $ex_fid_ary specifies an array of forum ids which should not be searched
* @param string $post_visibility specifies which types of posts the user can view in which forums
* @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched
* @param array $author_ary an array of author ids
* @param string $author_name specifies the author match, when ANONYMOUS is also a search-match
* @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered
* @param int $start indicates the first index of the page
* @param int $per_page number of ids each page is supposed to contain
* @return boolean|int total number of results
*/
public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page)
{
$this->search_query = '';
$this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN);
$fields = ($firstpost_only) ? 'firstpost' : 'all';
$terms = 'all';
return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page);
}
/**
* Updates wordlist and wordmatch tables when a message is posted or changed
*
* @param string $mode Contains the post mode: edit, post, reply, quote
* @param int $post_id The id of the post which is modified/created
* @param string &$message New or updated post content
* @param string &$subject New or updated post subject
* @param int $poster_id Post author's user id
* @param int $forum_id The id of the forum in which the post is located
*/
public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id)
{
/**
* Event to modify method arguments before the Sphinx search index is updated
*
* @event core.search_sphinx_index_before
* @var string mode Contains the post mode: edit, post, reply, quote
* @var int post_id The id of the post which is modified/created
* @var string message New or updated post content
* @var string subject New or updated post subject
* @var int poster_id Post author's user id
* @var int forum_id The id of the forum in which the post is located
* @since 3.2.3-RC1
*/
$vars = array(
'mode',
'post_id',
'message',
'subject',
'poster_id',
'forum_id',
);
extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_index_before', compact($vars)));
if ($mode == 'edit')
{
$this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id)));
}
else if ($mode != 'post' && $post_id)
{
// Update topic_last_post_time for full topic
$sql_array = array(
'SELECT' => 'p1.post_id',
'FROM' => array(
POSTS_TABLE => 'p1',
),
'LEFT_JOIN' => array(array(
'FROM' => array(
POSTS_TABLE => 'p2'
),
'ON' => 'p1.topic_id = p2.topic_id',
)),
'WHERE' => 'p2.post_id = ' . ((int) $post_id),
);
$sql = $this->db->sql_build_query('SELECT', $sql_array);
$result = $this->db->sql_query($sql);
$post_updates = array();
$post_time = time();
while ($row = $this->db->sql_fetchrow($result))
{
$post_updates[(int) $row['post_id']] = array($post_time);
}
$this->db->sql_freeresult($result);
if (count($post_updates))
{
$this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates);
}
}
}
/**
* Delete a post from the index after it was deleted
*/
public function index_remove($post_ids, $author_ids, $forum_ids)
{
$values = array();
foreach ($post_ids as $post_id)
{
$values[$post_id] = array(1);
}
$this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values);
}
/**
* Nothing needs to be destroyed
*/
public function tidy($create = false)
{
$this->config->set('search_last_gc', time(), false);
}
/**
* Create sphinx table
*
* @return string|bool error string is returned incase of errors otherwise false
*/
public function create_index($acp_module, $u_action)
{
if (!$this->index_created())
{
$table_data = array(
'COLUMNS' => array(
'counter_id' => array('UINT', 0),
'max_doc_id' => array('UINT', 0),
),
'PRIMARY_KEY' => 'counter_id',
);
$this->db_tools->sql_create_table(SPHINX_TABLE, $table_data);
$sql = 'TRUNCATE TABLE ' . SPHINX_TABLE;
$this->db->sql_query($sql);
$data = array(
'counter_id' => '1',
'max_doc_id' => '0',
);
$sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data);
$this->db->sql_query($sql);
}
return false;
}
/**
* Drop sphinx table
*
* @return string|bool error string is returned incase of errors otherwise false
*/
public function delete_index($acp_module, $u_action)
{
if (!$this->index_created())
{
return false;
}
$this->db_tools->sql_table_drop(SPHINX_TABLE);
return false;
}
/**
* Returns true if the sphinx table was created
*
* @return bool true if sphinx table was created
*/
public function index_created($allow_new_files = true)
{
$created = false;
if ($this->db_tools->sql_table_exists(SPHINX_TABLE))
{
$created = true;
}
return $created;
}
/**
* Returns an associative array containing information about the indexes
*
* @return string|bool Language string of error false otherwise
*/
public function index_stats()
{
if (empty($this->stats))
{
$this->get_stats();
}
return array(
$this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0,
$this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0,
$this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0,
);
}
/**
* Collects stats that can be displayed on the index maintenance page
*/
protected function get_stats()
{
if ($this->index_created())
{
$sql = 'SELECT COUNT(post_id) as total_posts
FROM ' . POSTS_TABLE;
$result = $this->db->sql_query($sql);
$this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts');
$this->db->sql_freeresult($result);
$sql = 'SELECT COUNT(p.post_id) as main_posts
FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m
WHERE p.post_id <= m.max_doc_id
AND m.counter_id = 1';
$result = $this->db->sql_query($sql);
$this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts');
$this->db->sql_freeresult($result);
}
}
/**
* Returns a list of options for the ACP to display
*
* @return associative array containing template and config variables
*/
public function acp()
{
$config_vars = array(
'fulltext_sphinx_data_path' => 'string',
'fulltext_sphinx_host' => 'string',
'fulltext_sphinx_port' => 'string',
'fulltext_sphinx_indexer_mem_limit' => 'int',
);
$tpl = '
<span class="error">' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. '</span>
<dl>
<dt><label for="fulltext_sphinx_data_path">' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '</span></dt>
<dd><input id="fulltext_sphinx_data_path" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_data_path]" value="' . $this->config['fulltext_sphinx_data_path'] . '" /></dd>
</dl>
<dl>
<dt><label for="fulltext_sphinx_host">' . $this->user->lang['FULLTEXT_SPHINX_HOST'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '</span></dt>
<dd><input id="fulltext_sphinx_host" type="text" size="40" maxlength="255" name="config[fulltext_sphinx_host]" value="' . $this->config['fulltext_sphinx_host'] . '" /></dd>
</dl>
<dl>
<dt><label for="fulltext_sphinx_port">' . $this->user->lang['FULLTEXT_SPHINX_PORT'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '</span></dt>
<dd><input id="fulltext_sphinx_port" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_port]" value="' . $this->config['fulltext_sphinx_port'] . '" /></dd>
</dl>
<dl>
<dt><label for="fulltext_sphinx_indexer_mem_limit">' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '</span></dt>
<dd><input id="fulltext_sphinx_indexer_mem_limit" type="number" min="0" max="9999999999" name="config[fulltext_sphinx_indexer_mem_limit]" value="' . $this->config['fulltext_sphinx_indexer_mem_limit'] . '" /> ' . $this->user->lang['MIB'] . '</dd>
</dl>
<dl>
<dt><label for="fulltext_sphinx_config_file">' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE'] . $this->user->lang['COLON'] . '</label><br /><span>' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '</span></dt>
<dd>' . (($this->config_generate()) ? '<textarea readonly="readonly" rows="6" id="sphinx_config_data">' . htmlspecialchars($this->config_file_data) . '</textarea>' : $this->config_file_data) . '</dd>
<dl>
';
// These are fields required in the config table
return array(
'tpl' => $tpl,
'config' => $config_vars
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
<?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\template;
class asset
{
protected $components = array();
/** @var \phpbb\path_helper **/
protected $path_helper;
/** @var \phpbb\filesystem\filesystem */
protected $filesystem;
/**
* Constructor
*
* @param string $url URL
* @param \phpbb\path_helper $path_helper Path helper object
* @param \phpbb\filesystem\filesystem $filesystem
*/
public function __construct($url, \phpbb\path_helper $path_helper, \phpbb\filesystem\filesystem $filesystem)
{
$this->path_helper = $path_helper;
$this->filesystem = $filesystem;
$this->set_url($url);
}
/**
* Set URL
*
* @param string $url URL
*/
public function set_url($url)
{
if (version_compare(PHP_VERSION, '5.4.7') < 0 && substr($url, 0, 2) === '//')
{
// Workaround for PHP 5.4.6 and older bug #62844 - add fake scheme and then remove it
$this->components = parse_url('http:' . $url);
$this->components['scheme'] = '';
return;
}
$this->components = parse_url($url);
}
/**
* Convert URL components into string
*
* @param array $components URL components
* @return string URL
*/
protected function join_url($components)
{
$path = '';
if (isset($components['scheme']))
{
$path = $components['scheme'] === '' ? '//' : $components['scheme'] . '://';
}
if (isset($components['user']) || isset($components['pass']))
{
if ($path === '' && !isset($components['port']))
{
$path = '//';
}
$path .= $components['user'];
if (isset($components['pass']))
{
$path .= ':' . $components['pass'];
}
$path .= '@';
}
if (isset($components['host']))
{
if ($path === '' && !isset($components['port']))
{
$path = '//';
}
$path .= $components['host'];
if (isset($components['port']))
{
$path .= ':' . $components['port'];
}
}
if (isset($components['path']))
{
$path .= $components['path'];
}
if (isset($components['query']))
{
$path .= '?' . $components['query'];
}
if (isset($components['fragment']))
{
$path .= '#' . $components['fragment'];
}
return $path;
}
/**
* Get URL
*
* @return string URL
*/
public function get_url()
{
return $this->path_helper->update_web_root_path($this->join_url($this->components));
}
/**
* Checks if URL is local and relative
*
* @return boolean True if URL is local and relative
*/
public function is_relative()
{
if (empty($this->components) || !isset($this->components['path']))
{
// Invalid URL
return false;
}
return !isset($this->components['scheme']) && !isset($this->components['host']) && substr($this->components['path'], 0, 1) !== '/';
}
/**
* Get path component of current URL
*
* @return string Path
*/
public function get_path()
{
return isset($this->components['path']) ? $this->components['path'] : '';
}
/**
* Set path component
*
* @param string $path Path component
* @param boolean $urlencode If true, parts of path should be encoded with rawurlencode()
*/
public function set_path($path, $urlencode = false)
{
// Since 1.7.0 Twig returns the real path of the file. We need it to be relative.
$real_root_path = $this->filesystem->realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR;
// If the asset is under the phpBB root path we need to remove its path and then prepend $phpbb_root_path
if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path)
{
$path = $this->path_helper->get_phpbb_root_path() . str_replace('\\', '/', substr($path, strlen($real_root_path)));
}
else
{
// Else we make the path relative to the current working directory
$real_root_path = $this->filesystem->realpath('.') . DIRECTORY_SEPARATOR;
if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path)
{
$path = str_replace('\\', '/', substr($path, strlen($real_root_path)));
}
}
if ($urlencode)
{
$paths = explode('/', $path);
foreach ($paths as &$dir)
{
$dir = rawurlencode($dir);
}
$path = implode('/', $paths);
}
$this->components['path'] = $path;
}
/**
* Add assets_version parameter to URL.
* Parameter will not be added if assets_version already exists in URL
*
* @param string $version Version
*/
public function add_assets_version($version)
{
if (!isset($this->components['query']))
{
$this->components['query'] = 'assets_version=' . $version;
return;
}
$query = $this->components['query'];
if (!preg_match('/(^|[&;])assets_version=/', $query))
{
$this->components['query'] = $query . '&amp;assets_version=' . $version;
}
}
}

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