Import SMF 2.1.4
32
LICENSE
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
Copyright © 2023 Simple Machines. All rights reserved.
|
||||||
|
|
||||||
|
Developed by: Simple Machines Forum Project
|
||||||
|
Simple Machines
|
||||||
|
https://www.simplemachines.org
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of SMF2.1 nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
Packages/.htaccess
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<Files *>
|
||||||
|
Order Deny,Allow
|
||||||
|
Deny from all
|
||||||
|
Allow from localhost
|
||||||
|
</Files>
|
5
Packages/backups/.htaccess
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<Files *>
|
||||||
|
Order Deny,Allow
|
||||||
|
Deny from all
|
||||||
|
Allow from localhost
|
||||||
|
</Files>
|
9
Packages/backups/index.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Try to handle it with the upper level index.php. (it should know what to do.)
|
||||||
|
if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
|
||||||
|
include (dirname(dirname(__FILE__)) . '/index.php');
|
||||||
|
else
|
||||||
|
exit;
|
||||||
|
|
||||||
|
?>
|
18
Packages/index.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is here solely to protect your Packages directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Look for Settings.php....
|
||||||
|
if (file_exists(dirname(dirname(__FILE__)) . '/Settings.php'))
|
||||||
|
{
|
||||||
|
// Found it!
|
||||||
|
require(dirname(dirname(__FILE__)) . '/Settings.php');
|
||||||
|
header('location: ' . $boardurl);
|
||||||
|
}
|
||||||
|
// Can't find it... just forget it.
|
||||||
|
else
|
||||||
|
exit;
|
||||||
|
|
||||||
|
?>
|
272
Settings.php
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings file contains all of the basic settings that need to be present when a database/cache is not available.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
########## Maintenance ##########
|
||||||
|
/**
|
||||||
|
* The maintenance "mode"
|
||||||
|
* Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you'll have to make it 0 again manually!)
|
||||||
|
* 0 is default and disables maintenance mode.
|
||||||
|
*
|
||||||
|
* @var int 0, 1, 2
|
||||||
|
* @global int $maintenance
|
||||||
|
*/
|
||||||
|
$maintenance = 0;
|
||||||
|
/**
|
||||||
|
* Title for the Maintenance Mode message.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @global int $mtitle
|
||||||
|
*/
|
||||||
|
$mtitle = 'Maintenance Mode';
|
||||||
|
/**
|
||||||
|
* Description of why the forum is in maintenance mode.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @global string $mmessage
|
||||||
|
*/
|
||||||
|
$mmessage = 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!';
|
||||||
|
|
||||||
|
########## Forum Info ##########
|
||||||
|
/**
|
||||||
|
* The name of your forum.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$mbname = 'My Community';
|
||||||
|
/**
|
||||||
|
* The default language file set for the forum.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$language = 'english';
|
||||||
|
/**
|
||||||
|
* URL to your forum's folder. (without the trailing /!)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$boardurl = 'http://127.0.0.1/smf';
|
||||||
|
/**
|
||||||
|
* Email address to send emails from. (like noreply@yourdomain.com.)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$webmaster_email = 'noreply@myserver.com';
|
||||||
|
/**
|
||||||
|
* Name of the cookie to set for authentication.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cookiename = 'SMFCookie11';
|
||||||
|
|
||||||
|
########## Database Info ##########
|
||||||
|
/**
|
||||||
|
* The database type
|
||||||
|
* Default options: mysql, postgresql
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_type = 'mysql';
|
||||||
|
/**
|
||||||
|
* The database port
|
||||||
|
* 0 to use default port for the database type
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$db_port = 0;
|
||||||
|
/**
|
||||||
|
* The server to connect to (or a Unix socket)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_server = 'localhost';
|
||||||
|
/**
|
||||||
|
* The database name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_name = 'smf';
|
||||||
|
/**
|
||||||
|
* Database username
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_user = 'root';
|
||||||
|
/**
|
||||||
|
* Database password
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_passwd = '';
|
||||||
|
/**
|
||||||
|
* Database user for when connecting with SSI
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$ssi_db_user = '';
|
||||||
|
/**
|
||||||
|
* Database password for when connecting with SSI
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$ssi_db_passwd = '';
|
||||||
|
/**
|
||||||
|
* A prefix to put in front of your table names.
|
||||||
|
* This helps to prevent conflicts
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_prefix = 'smf_';
|
||||||
|
/**
|
||||||
|
* Use a persistent database connection
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$db_persist = false;
|
||||||
|
/**
|
||||||
|
* Send emails on database connection error
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$db_error_send = false;
|
||||||
|
/**
|
||||||
|
* Override the default behavior of the database layer for mb4 handling
|
||||||
|
* null keep the default behavior untouched
|
||||||
|
*
|
||||||
|
* @var null|bool
|
||||||
|
*/
|
||||||
|
$db_mb4 = null;
|
||||||
|
|
||||||
|
########## Cache Info ##########
|
||||||
|
/**
|
||||||
|
* Select a cache system. You want to leave this up to the cache area of the admin panel for
|
||||||
|
* proper detection of memcached, output_cache or SMF file_system
|
||||||
|
* (you can add more with a mod).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cache_accelerator = '';
|
||||||
|
/**
|
||||||
|
* The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$cache_enable = 0;
|
||||||
|
/**
|
||||||
|
* This is only used for memcache / memcached. Should be a string of 'server:port,server:port'
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
$cache_memcached = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is only for the 'smf' file cache system. It is the path to the cache directory.
|
||||||
|
* It is also recommended that you place this in /tmp/ if you are going to use this.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cachedir = dirname(__FILE__) . '/cache';
|
||||||
|
|
||||||
|
########## Image Proxy ##########
|
||||||
|
# This is done entirely in Settings.php to avoid loading the DB while serving the images
|
||||||
|
/**
|
||||||
|
* Whether the proxy is enabled or not
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$image_proxy_enabled = true;
|
||||||
|
/**
|
||||||
|
* Secret key to be used by the proxy
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$image_proxy_secret = 'smfisawesome';
|
||||||
|
/**
|
||||||
|
* Maximum file size (in KB) for individual files
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$image_proxy_maxsize = 5192;
|
||||||
|
|
||||||
|
########## Directories/Files ##########
|
||||||
|
# Note: These directories do not have to be changed unless you move things.
|
||||||
|
/**
|
||||||
|
* The absolute path to the forum's folder. (not just '.'!)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$boarddir = dirname(__FILE__);
|
||||||
|
/**
|
||||||
|
* Path to the Sources directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$sourcedir = dirname(__FILE__) . '/Sources';
|
||||||
|
/**
|
||||||
|
* Path to the Packages directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$packagesdir = dirname(__FILE__) . '/Packages';
|
||||||
|
/**
|
||||||
|
* Path to the tasks directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$tasksdir = $sourcedir . '/tasks';
|
||||||
|
|
||||||
|
# Make sure the paths are correct... at least try to fix them.
|
||||||
|
if (!is_dir(realpath($boarddir)) && file_exists(dirname(__FILE__) . '/agreement.txt'))
|
||||||
|
$boarddir = dirname(__FILE__);
|
||||||
|
if (!is_dir(realpath($sourcedir)) && is_dir($boarddir . '/Sources'))
|
||||||
|
$sourcedir = $boarddir . '/Sources';
|
||||||
|
if (!is_dir(realpath($tasksdir)) && is_dir($sourcedir . '/tasks'))
|
||||||
|
$tasksdir = $sourcedir . '/tasks';
|
||||||
|
if (!is_dir(realpath($packagesdir)) && is_dir($boarddir . '/Packages'))
|
||||||
|
$packagesdir = $boarddir . '/Packages';
|
||||||
|
if (!is_dir(realpath($cachedir)) && is_dir($boarddir . '/cache'))
|
||||||
|
$cachedir = $boarddir . '/cache';
|
||||||
|
|
||||||
|
######### Legacy Settings #########
|
||||||
|
# UTF-8 is now the only character set supported in 2.1.
|
||||||
|
$db_character_set = 'utf8';
|
||||||
|
|
||||||
|
########## Error-Catching ##########
|
||||||
|
# Note: You shouldn't touch these settings.
|
||||||
|
if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php'))
|
||||||
|
include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php');
|
||||||
|
|
||||||
|
if (!isset($db_last_error))
|
||||||
|
{
|
||||||
|
// File does not exist so lets try to create it
|
||||||
|
file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php', '<' . '?' . "php\n" . '$db_last_error = 0;' . "\n" . '?' . '>');
|
||||||
|
$db_last_error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists(dirname(__FILE__) . '/install.php'))
|
||||||
|
{
|
||||||
|
$secure = false;
|
||||||
|
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
|
||||||
|
$secure = true;
|
||||||
|
elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
|
||||||
|
$secure = true;
|
||||||
|
|
||||||
|
if (basename($_SERVER['PHP_SELF']) != 'install.php')
|
||||||
|
{
|
||||||
|
header('location: http' . ($secure ? 's' : '') . '://' . (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']) . (strtr(dirname($_SERVER['PHP_SELF']), '\\', '/') == '/' ? '' : strtr(dirname($_SERVER['PHP_SELF']), '\\', '/')) . '/install.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
271
Settings_bak.php
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings file contains all of the basic settings that need to be present when a database/cache is not available.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
########## Maintenance ##########
|
||||||
|
/**
|
||||||
|
* The maintenance "mode"
|
||||||
|
* Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you'll have to make it 0 again manually!)
|
||||||
|
* 0 is default and disables maintenance mode.
|
||||||
|
*
|
||||||
|
* @var int 0, 1, 2
|
||||||
|
* @global int $maintenance
|
||||||
|
*/
|
||||||
|
$maintenance = 0;
|
||||||
|
/**
|
||||||
|
* Title for the Maintenance Mode message.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @global int $mtitle
|
||||||
|
*/
|
||||||
|
$mtitle = 'Maintenance Mode';
|
||||||
|
/**
|
||||||
|
* Description of why the forum is in maintenance mode.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @global string $mmessage
|
||||||
|
*/
|
||||||
|
$mmessage = 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!';
|
||||||
|
|
||||||
|
########## Forum Info ##########
|
||||||
|
/**
|
||||||
|
* The name of your forum.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$mbname = 'My Community';
|
||||||
|
/**
|
||||||
|
* The default language file set for the forum.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$language = 'english';
|
||||||
|
/**
|
||||||
|
* URL to your forum's folder. (without the trailing /!)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$boardurl = 'http://127.0.0.1/smf';
|
||||||
|
/**
|
||||||
|
* Email address to send emails from. (like noreply@yourdomain.com.)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$webmaster_email = 'noreply@myserver.com';
|
||||||
|
/**
|
||||||
|
* Name of the cookie to set for authentication.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cookiename = 'SMFCookie11';
|
||||||
|
|
||||||
|
########## Database Info ##########
|
||||||
|
/**
|
||||||
|
* The database type
|
||||||
|
* Default options: mysql, postgresql
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_type = 'mysql';
|
||||||
|
/**
|
||||||
|
* The database port
|
||||||
|
* 0 to use default port for the database type
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$db_port = 0;
|
||||||
|
/**
|
||||||
|
* The server to connect to (or a Unix socket)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_server = 'localhost';
|
||||||
|
/**
|
||||||
|
* The database name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_name = 'smf';
|
||||||
|
/**
|
||||||
|
* Database username
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_user = 'root';
|
||||||
|
/**
|
||||||
|
* Database password
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_passwd = '';
|
||||||
|
/**
|
||||||
|
* Database user for when connecting with SSI
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$ssi_db_user = '';
|
||||||
|
/**
|
||||||
|
* Database password for when connecting with SSI
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$ssi_db_passwd = '';
|
||||||
|
/**
|
||||||
|
* A prefix to put in front of your table names.
|
||||||
|
* This helps to prevent conflicts
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$db_prefix = 'smf_';
|
||||||
|
/**
|
||||||
|
* Use a persistent database connection
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$db_persist = false;
|
||||||
|
/**
|
||||||
|
* Send emails on database connection error
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$db_error_send = false;
|
||||||
|
/**
|
||||||
|
* Override the default behavior of the database layer for mb4 handling
|
||||||
|
* null keep the default behavior untouched
|
||||||
|
*
|
||||||
|
* @var null|bool
|
||||||
|
*/
|
||||||
|
$db_mb4 = null;
|
||||||
|
|
||||||
|
########## Cache Info ##########
|
||||||
|
/**
|
||||||
|
* Select a cache system. You want to leave this up to the cache area of the admin panel for
|
||||||
|
* proper detection of apc, memcached, output_cache, smf, or xcache
|
||||||
|
* (you can add more with a mod).
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cache_accelerator = '';
|
||||||
|
/**
|
||||||
|
* The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$cache_enable = 0;
|
||||||
|
/**
|
||||||
|
* This is only used for memcache / memcached. Should be a string of 'server:port,server:port'
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
$cache_memcached = '';
|
||||||
|
/**
|
||||||
|
* This is only for the 'smf' file cache system. It is the path to the cache directory.
|
||||||
|
* It is also recommended that you place this in /tmp/ if you are going to use this.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$cachedir = dirname(__FILE__) . '/cache';
|
||||||
|
|
||||||
|
########## Image Proxy ##########
|
||||||
|
# This is done entirely in Settings.php to avoid loading the DB while serving the images
|
||||||
|
/**
|
||||||
|
* Whether the proxy is enabled or not
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
$image_proxy_enabled = true;
|
||||||
|
/**
|
||||||
|
* Secret key to be used by the proxy
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$image_proxy_secret = 'smfisawesome';
|
||||||
|
/**
|
||||||
|
* Maximum file size (in KB) for individual files
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
$image_proxy_maxsize = 5192;
|
||||||
|
|
||||||
|
########## Directories/Files ##########
|
||||||
|
# Note: These directories do not have to be changed unless you move things.
|
||||||
|
/**
|
||||||
|
* The absolute path to the forum's folder. (not just '.'!)
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$boarddir = dirname(__FILE__);
|
||||||
|
/**
|
||||||
|
* Path to the Sources directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$sourcedir = dirname(__FILE__) . '/Sources';
|
||||||
|
/**
|
||||||
|
* Path to the Packages directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$packagesdir = dirname(__FILE__) . '/Packages';
|
||||||
|
/**
|
||||||
|
* Path to the tasks directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
$tasksdir = $sourcedir . '/tasks';
|
||||||
|
|
||||||
|
# Make sure the paths are correct... at least try to fix them.
|
||||||
|
if (!is_dir(realpath($boarddir)) && file_exists(dirname(__FILE__) . '/agreement.txt'))
|
||||||
|
$boarddir = dirname(__FILE__);
|
||||||
|
if (!is_dir(realpath($sourcedir)) && is_dir($boarddir . '/Sources'))
|
||||||
|
$sourcedir = $boarddir . '/Sources';
|
||||||
|
if (!is_dir(realpath($tasksdir)) && is_dir($sourcedir . '/tasks'))
|
||||||
|
$tasksdir = $sourcedir . '/tasks';
|
||||||
|
if (!is_dir(realpath($packagesdir)) && is_dir($boarddir . '/Packages'))
|
||||||
|
$packagesdir = $boarddir . '/Packages';
|
||||||
|
if (!is_dir(realpath($cachedir)) && is_dir($boarddir . '/cache'))
|
||||||
|
$cachedir = $boarddir . '/cache';
|
||||||
|
|
||||||
|
######### Legacy Settings #########
|
||||||
|
# UTF-8 is now the only character set supported in 2.1.
|
||||||
|
$db_character_set = 'utf8';
|
||||||
|
|
||||||
|
########## Error-Catching ##########
|
||||||
|
# Note: You shouldn't touch these settings.
|
||||||
|
if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php'))
|
||||||
|
include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php');
|
||||||
|
|
||||||
|
if (!isset($db_last_error))
|
||||||
|
{
|
||||||
|
// File does not exist so lets try to create it
|
||||||
|
file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . '/db_last_error.php', '<' . '?' . "php\n" . '$db_last_error = 0;' . "\n" . '?' . '>');
|
||||||
|
$db_last_error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists(dirname(__FILE__) . '/install.php'))
|
||||||
|
{
|
||||||
|
$secure = false;
|
||||||
|
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
|
||||||
|
$secure = true;
|
||||||
|
elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
|
||||||
|
$secure = true;
|
||||||
|
|
||||||
|
if (basename($_SERVER['PHP_SELF']) != 'install.php')
|
||||||
|
{
|
||||||
|
header('location: http' . ($secure ? 's' : '') . '://' . (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST']) . (strtr(dirname($_SERVER['PHP_SELF']), '\\', '/') == '/' ? '' : strtr(dirname($_SERVER['PHP_SELF']), '\\', '/')) . '/install.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
BIN
Smileys/alienine/afro.png
Normal file
After Width: | Height: | Size: 953 B |
BIN
Smileys/alienine/angel.png
Normal file
After Width: | Height: | Size: 902 B |
BIN
Smileys/alienine/angry.png
Normal file
After Width: | Height: | Size: 783 B |
BIN
Smileys/alienine/azn.png
Normal file
After Width: | Height: | Size: 788 B |
BIN
Smileys/alienine/blank.png
Normal file
After Width: | Height: | Size: 659 B |
BIN
Smileys/alienine/cheesy.png
Normal file
After Width: | Height: | Size: 816 B |
BIN
Smileys/alienine/cool.png
Normal file
After Width: | Height: | Size: 758 B |
BIN
Smileys/alienine/cry.png
Normal file
After Width: | Height: | Size: 839 B |
BIN
Smileys/alienine/embarrassed.png
Normal file
After Width: | Height: | Size: 845 B |
BIN
Smileys/alienine/evil.png
Normal file
After Width: | Height: | Size: 893 B |
BIN
Smileys/alienine/grin.png
Normal file
After Width: | Height: | Size: 773 B |
BIN
Smileys/alienine/huh.png
Normal file
After Width: | Height: | Size: 800 B |
9
Smileys/alienine/index.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Try to handle it with the upper level index.php. (it should know what to do.)
|
||||||
|
if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
|
||||||
|
include (dirname(dirname(__FILE__)) . '/index.php');
|
||||||
|
else
|
||||||
|
exit;
|
||||||
|
|
||||||
|
?>
|
BIN
Smileys/alienine/kiss.png
Normal file
After Width: | Height: | Size: 822 B |
BIN
Smileys/alienine/laugh.png
Normal file
After Width: | Height: | Size: 811 B |
BIN
Smileys/alienine/lipsrsealed.png
Normal file
After Width: | Height: | Size: 789 B |
BIN
Smileys/alienine/police.png
Normal file
After Width: | Height: | Size: 1,022 B |
BIN
Smileys/alienine/rolleyes.png
Normal file
After Width: | Height: | Size: 837 B |
BIN
Smileys/alienine/sad.png
Normal file
After Width: | Height: | Size: 783 B |
BIN
Smileys/alienine/shocked.png
Normal file
After Width: | Height: | Size: 862 B |
BIN
Smileys/alienine/smiley.png
Normal file
After Width: | Height: | Size: 796 B |
BIN
Smileys/alienine/tongue.png
Normal file
After Width: | Height: | Size: 794 B |
BIN
Smileys/alienine/undecided.png
Normal file
After Width: | Height: | Size: 825 B |
BIN
Smileys/alienine/wink.png
Normal file
After Width: | Height: | Size: 781 B |
BIN
Smileys/fugue/afro.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Smileys/fugue/angel.png
Normal file
After Width: | Height: | Size: 896 B |
BIN
Smileys/fugue/angry.png
Normal file
After Width: | Height: | Size: 805 B |
BIN
Smileys/fugue/azn.png
Normal file
After Width: | Height: | Size: 844 B |
BIN
Smileys/fugue/blank.png
Normal file
After Width: | Height: | Size: 769 B |
BIN
Smileys/fugue/cheesy.png
Normal file
After Width: | Height: | Size: 805 B |
BIN
Smileys/fugue/cool.png
Normal file
After Width: | Height: | Size: 879 B |
BIN
Smileys/fugue/cry.png
Normal file
After Width: | Height: | Size: 837 B |
BIN
Smileys/fugue/embarrassed.png
Normal file
After Width: | Height: | Size: 809 B |
BIN
Smileys/fugue/evil.png
Normal file
After Width: | Height: | Size: 877 B |
BIN
Smileys/fugue/grin.png
Normal file
After Width: | Height: | Size: 800 B |
BIN
Smileys/fugue/huh.png
Normal file
After Width: | Height: | Size: 818 B |
9
Smileys/fugue/index.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Try to handle it with the upper level index.php. (it should know what to do.)
|
||||||
|
if (file_exists(dirname(dirname(__FILE__)) . '/index.php'))
|
||||||
|
include (dirname(dirname(__FILE__)) . '/index.php');
|
||||||
|
else
|
||||||
|
exit;
|
||||||
|
|
||||||
|
?>
|
BIN
Smileys/fugue/kiss.png
Normal file
After Width: | Height: | Size: 897 B |
BIN
Smileys/fugue/laugh.png
Normal file
After Width: | Height: | Size: 797 B |
BIN
Smileys/fugue/lipsrsealed.png
Normal file
After Width: | Height: | Size: 828 B |
BIN
Smileys/fugue/police.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
Smileys/fugue/rolleyes.png
Normal file
After Width: | Height: | Size: 815 B |
BIN
Smileys/fugue/sad.png
Normal file
After Width: | Height: | Size: 798 B |
BIN
Smileys/fugue/shocked.png
Normal file
After Width: | Height: | Size: 787 B |
BIN
Smileys/fugue/smiley.png
Normal file
After Width: | Height: | Size: 823 B |
BIN
Smileys/fugue/tongue.png
Normal file
After Width: | Height: | Size: 786 B |
BIN
Smileys/fugue/undecided.png
Normal file
After Width: | Height: | Size: 787 B |
BIN
Smileys/fugue/wink.png
Normal file
After Width: | Height: | Size: 830 B |
18
Smileys/index.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is here solely to protect your Smileys directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Look for Settings.php....
|
||||||
|
if (file_exists(dirname(dirname(__FILE__)) . '/Settings.php'))
|
||||||
|
{
|
||||||
|
// Found it!
|
||||||
|
require(dirname(dirname(__FILE__)) . '/Settings.php');
|
||||||
|
header('location: ' . $boardurl);
|
||||||
|
}
|
||||||
|
// Can't find it... just forget it.
|
||||||
|
else
|
||||||
|
exit;
|
||||||
|
|
||||||
|
?>
|
971
Sources/Admin.php
Normal file
|
@ -0,0 +1,971 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file, unpredictable as this might be, handles basic administration.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main admin handling function.<br>
|
||||||
|
* It initialises all the basic context required for the admin center.<br>
|
||||||
|
* It passes execution onto the relevant admin section.<br>
|
||||||
|
* If the passed section is not found it shows the admin home page.
|
||||||
|
*/
|
||||||
|
function AdminMain()
|
||||||
|
{
|
||||||
|
global $txt, $context, $scripturl, $modSettings, $settings;
|
||||||
|
global $smcFunc, $sourcedir, $options, $boarddir;
|
||||||
|
|
||||||
|
// Load the language and templates....
|
||||||
|
loadLanguage('Admin');
|
||||||
|
loadTemplate('Admin');
|
||||||
|
loadJavaScriptFile('admin.js', array('minimize' => true), 'smf_admin');
|
||||||
|
loadCSSFile('admin.css', array(), 'smf_admin');
|
||||||
|
|
||||||
|
// No indexing evil stuff.
|
||||||
|
$context['robot_no_index'] = true;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Menu.php');
|
||||||
|
|
||||||
|
// Some preferences.
|
||||||
|
$context['admin_preferences'] = !empty($options['admin_preferences']) ? $smcFunc['json_decode']($options['admin_preferences'], true) : array();
|
||||||
|
|
||||||
|
/** @var array $admin_areas Defines the menu structure for the admin center. See {@link Subs-Menu.php Subs-Menu.php} for details! */
|
||||||
|
$admin_areas = array(
|
||||||
|
'forum' => array(
|
||||||
|
'title' => $txt['admin_main'],
|
||||||
|
'permission' => array('admin_forum', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_boards', 'manage_smileys', 'manage_attachments'),
|
||||||
|
'areas' => array(
|
||||||
|
'index' => array(
|
||||||
|
'label' => $txt['admin_center'],
|
||||||
|
'function' => 'AdminHome',
|
||||||
|
'icon' => 'administration',
|
||||||
|
),
|
||||||
|
'credits' => array(
|
||||||
|
'label' => $txt['support_credits_title'],
|
||||||
|
'function' => 'AdminHome',
|
||||||
|
'icon' => 'support',
|
||||||
|
),
|
||||||
|
'news' => array(
|
||||||
|
'label' => $txt['news_title'],
|
||||||
|
'file' => 'ManageNews.php',
|
||||||
|
'function' => 'ManageNews',
|
||||||
|
'icon' => 'news',
|
||||||
|
'permission' => array('edit_news', 'send_mail', 'admin_forum'),
|
||||||
|
'subsections' => array(
|
||||||
|
'editnews' => array($txt['admin_edit_news'], 'edit_news'),
|
||||||
|
'mailingmembers' => array($txt['admin_newsletters'], 'send_mail'),
|
||||||
|
'settings' => array($txt['settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'packages' => array(
|
||||||
|
'label' => $txt['package'],
|
||||||
|
'file' => 'Packages.php',
|
||||||
|
'function' => 'Packages',
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'icon' => 'packages',
|
||||||
|
'subsections' => array(
|
||||||
|
'browse' => array($txt['browse_packages']),
|
||||||
|
'packageget' => array($txt['download_packages'], 'url' => $scripturl . '?action=admin;area=packages;sa=packageget;get'),
|
||||||
|
'perms' => array($txt['package_file_perms']),
|
||||||
|
'options' => array($txt['package_settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'search' => array(
|
||||||
|
'function' => 'AdminSearch',
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'select' => 'index'
|
||||||
|
),
|
||||||
|
'adminlogoff' => array(
|
||||||
|
'label' => $txt['admin_logoff'],
|
||||||
|
'function' => 'AdminEndSession',
|
||||||
|
'enabled' => empty($modSettings['securityDisable']),
|
||||||
|
'icon' => 'exit',
|
||||||
|
),
|
||||||
|
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'config' => array(
|
||||||
|
'title' => $txt['admin_config'],
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'areas' => array(
|
||||||
|
'featuresettings' => array(
|
||||||
|
'label' => $txt['modSettings_title'],
|
||||||
|
'file' => 'ManageSettings.php',
|
||||||
|
'function' => 'ModifyFeatureSettings',
|
||||||
|
'icon' => 'features',
|
||||||
|
'subsections' => array(
|
||||||
|
'basic' => array($txt['mods_cat_features']),
|
||||||
|
'bbc' => array($txt['manageposts_bbc_settings']),
|
||||||
|
'layout' => array($txt['mods_cat_layout']),
|
||||||
|
'sig' => array($txt['signature_settings_short']),
|
||||||
|
'profile' => array($txt['custom_profile_shorttitle']),
|
||||||
|
'likes' => array($txt['likes']),
|
||||||
|
'mentions' => array($txt['mentions']),
|
||||||
|
'alerts' => array($txt['notifications']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'antispam' => array(
|
||||||
|
'label' => $txt['antispam_title'],
|
||||||
|
'file' => 'ManageSettings.php',
|
||||||
|
'function' => 'ModifyAntispamSettings',
|
||||||
|
'icon' => 'security',
|
||||||
|
),
|
||||||
|
'languages' => array(
|
||||||
|
'label' => $txt['language_configuration'],
|
||||||
|
'file' => 'ManageLanguages.php',
|
||||||
|
'function' => 'ManageLanguages',
|
||||||
|
'icon' => 'languages',
|
||||||
|
'subsections' => array(
|
||||||
|
'edit' => array($txt['language_edit']),
|
||||||
|
'add' => array($txt['language_add']),
|
||||||
|
'settings' => array($txt['language_settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'current_theme' => array(
|
||||||
|
'label' => $txt['theme_current_settings'],
|
||||||
|
'file' => 'Themes.php',
|
||||||
|
'function' => 'ThemesMain',
|
||||||
|
'custom_url' => $scripturl . '?action=admin;area=theme;sa=list;th=' . $settings['theme_id'],
|
||||||
|
'icon' => 'current_theme',
|
||||||
|
),
|
||||||
|
'theme' => array(
|
||||||
|
'label' => $txt['theme_admin'],
|
||||||
|
'file' => 'Themes.php',
|
||||||
|
'function' => 'ThemesMain',
|
||||||
|
'custom_url' => $scripturl . '?action=admin;area=theme',
|
||||||
|
'icon' => 'themes',
|
||||||
|
'subsections' => array(
|
||||||
|
'admin' => array($txt['themeadmin_admin_title']),
|
||||||
|
'list' => array($txt['themeadmin_list_title']),
|
||||||
|
'reset' => array($txt['themeadmin_reset_title']),
|
||||||
|
'edit' => array($txt['themeadmin_edit_title']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'modsettings' => array(
|
||||||
|
'label' => $txt['admin_modifications'],
|
||||||
|
'file' => 'ManageSettings.php',
|
||||||
|
'function' => 'ModifyModSettings',
|
||||||
|
'icon' => 'modifications',
|
||||||
|
'subsections' => array(
|
||||||
|
'general' => array($txt['mods_cat_modifications_misc']),
|
||||||
|
// Mod Authors for a "ADD AFTER" on this line. Ensure you end your change with a comma. For example:
|
||||||
|
// 'shout' => array($txt['shout']),
|
||||||
|
// Note the comma!! The setting with automatically appear with the first mod to be added.
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'layout' => array(
|
||||||
|
'title' => $txt['layout_controls'],
|
||||||
|
'permission' => array('manage_boards', 'admin_forum', 'manage_smileys', 'manage_attachments', 'moderate_forum'),
|
||||||
|
'areas' => array(
|
||||||
|
'manageboards' => array(
|
||||||
|
'label' => $txt['admin_boards'],
|
||||||
|
'file' => 'ManageBoards.php',
|
||||||
|
'function' => 'ManageBoards',
|
||||||
|
'icon' => 'boards',
|
||||||
|
'permission' => array('manage_boards'),
|
||||||
|
'subsections' => array(
|
||||||
|
'main' => array($txt['boards_edit']),
|
||||||
|
'newcat' => array($txt['mboards_new_cat']),
|
||||||
|
'settings' => array($txt['settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'postsettings' => array(
|
||||||
|
'label' => $txt['manageposts'],
|
||||||
|
'file' => 'ManagePosts.php',
|
||||||
|
'function' => 'ManagePostSettings',
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'icon' => 'posts',
|
||||||
|
'subsections' => array(
|
||||||
|
'posts' => array($txt['manageposts_settings']),
|
||||||
|
'censor' => array($txt['admin_censored_words']),
|
||||||
|
'topics' => array($txt['manageposts_topic_settings']),
|
||||||
|
'drafts' => array($txt['manage_drafts']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'managecalendar' => array(
|
||||||
|
'label' => $txt['manage_calendar'],
|
||||||
|
'file' => 'ManageCalendar.php',
|
||||||
|
'function' => 'ManageCalendar',
|
||||||
|
'icon' => 'calendar',
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'inactive' => empty($modSettings['cal_enabled']),
|
||||||
|
'subsections' => empty($modSettings['cal_enabled']) ? array() : array(
|
||||||
|
'holidays' => array($txt['manage_holidays'], 'admin_forum'),
|
||||||
|
'settings' => array($txt['calendar_settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'managesearch' => array(
|
||||||
|
'label' => $txt['manage_search'],
|
||||||
|
'file' => 'ManageSearch.php',
|
||||||
|
'function' => 'ManageSearch',
|
||||||
|
'icon' => 'search',
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'subsections' => array(
|
||||||
|
'weights' => array($txt['search_weights']),
|
||||||
|
'method' => array($txt['search_method']),
|
||||||
|
'settings' => array($txt['settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'smileys' => array(
|
||||||
|
'label' => $txt['smileys_manage'],
|
||||||
|
'file' => 'ManageSmileys.php',
|
||||||
|
'function' => 'ManageSmileys',
|
||||||
|
'icon' => 'smiley',
|
||||||
|
'permission' => array('manage_smileys'),
|
||||||
|
'subsections' => array(
|
||||||
|
'editsets' => array($txt['smiley_sets']),
|
||||||
|
'addsmiley' => array($txt['smileys_add'], 'enabled' => !empty($modSettings['smiley_enable'])),
|
||||||
|
'editsmileys' => array($txt['smileys_edit'], 'enabled' => !empty($modSettings['smiley_enable'])),
|
||||||
|
'setorder' => array($txt['smileys_set_order'], 'enabled' => !empty($modSettings['smiley_enable'])),
|
||||||
|
'editicons' => array($txt['icons_edit_message_icons'], 'enabled' => !empty($modSettings['messageIcons_enable'])),
|
||||||
|
'settings' => array($txt['settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'manageattachments' => array(
|
||||||
|
'label' => $txt['attachments_avatars'],
|
||||||
|
'file' => 'ManageAttachments.php',
|
||||||
|
'function' => 'ManageAttachments',
|
||||||
|
'icon' => 'attachment',
|
||||||
|
'permission' => array('manage_attachments'),
|
||||||
|
'subsections' => array(
|
||||||
|
'browse' => array($txt['attachment_manager_browse']),
|
||||||
|
'attachments' => array($txt['attachment_manager_settings']),
|
||||||
|
'avatars' => array($txt['attachment_manager_avatar_settings']),
|
||||||
|
'attachpaths' => array($txt['attach_directories']),
|
||||||
|
'maintenance' => array($txt['attachment_manager_maintenance']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'sengines' => array(
|
||||||
|
'label' => $txt['search_engines'],
|
||||||
|
'inactive' => empty($modSettings['spider_mode']),
|
||||||
|
'file' => 'ManageSearchEngines.php',
|
||||||
|
'icon' => 'engines',
|
||||||
|
'function' => 'SearchEngines',
|
||||||
|
'permission' => 'admin_forum',
|
||||||
|
'subsections' => empty($modSettings['spider_mode']) ? array() : array(
|
||||||
|
'stats' => array($txt['spider_stats']),
|
||||||
|
'logs' => array($txt['spider_logs']),
|
||||||
|
'spiders' => array($txt['spiders']),
|
||||||
|
'settings' => array($txt['settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'members' => array(
|
||||||
|
'title' => $txt['admin_manage_members'],
|
||||||
|
'permission' => array('moderate_forum', 'manage_membergroups', 'manage_bans', 'manage_permissions', 'admin_forum'),
|
||||||
|
'areas' => array(
|
||||||
|
'viewmembers' => array(
|
||||||
|
'label' => $txt['admin_users'],
|
||||||
|
'file' => 'ManageMembers.php',
|
||||||
|
'function' => 'ViewMembers',
|
||||||
|
'icon' => 'members',
|
||||||
|
'permission' => array('moderate_forum'),
|
||||||
|
'subsections' => array(
|
||||||
|
'all' => array($txt['view_all_members']),
|
||||||
|
'search' => array($txt['mlist_search']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'membergroups' => array(
|
||||||
|
'label' => $txt['admin_groups'],
|
||||||
|
'file' => 'ManageMembergroups.php',
|
||||||
|
'function' => 'ModifyMembergroups',
|
||||||
|
'icon' => 'membergroups',
|
||||||
|
'permission' => array('manage_membergroups'),
|
||||||
|
'subsections' => array(
|
||||||
|
'index' => array($txt['membergroups_edit_groups'], 'manage_membergroups'),
|
||||||
|
'add' => array($txt['membergroups_new_group'], 'manage_membergroups'),
|
||||||
|
'settings' => array($txt['settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'permissions' => array(
|
||||||
|
'label' => $txt['edit_permissions'],
|
||||||
|
'file' => 'ManagePermissions.php',
|
||||||
|
'function' => 'ModifyPermissions',
|
||||||
|
'icon' => 'permissions',
|
||||||
|
'permission' => array('manage_permissions'),
|
||||||
|
'subsections' => array(
|
||||||
|
'index' => array($txt['permissions_groups'], 'manage_permissions'),
|
||||||
|
'board' => array($txt['permissions_boards'], 'manage_permissions'),
|
||||||
|
'profiles' => array($txt['permissions_profiles'], 'manage_permissions'),
|
||||||
|
'postmod' => array($txt['permissions_post_moderation'], 'manage_permissions'),
|
||||||
|
'settings' => array($txt['settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'regcenter' => array(
|
||||||
|
'label' => $txt['registration_center'],
|
||||||
|
'file' => 'ManageRegistration.php',
|
||||||
|
'function' => 'RegCenter',
|
||||||
|
'icon' => 'regcenter',
|
||||||
|
'permission' => array('admin_forum', 'moderate_forum'),
|
||||||
|
'subsections' => array(
|
||||||
|
'register' => array($txt['admin_browse_register_new'], 'moderate_forum'),
|
||||||
|
'agreement' => array($txt['registration_agreement'], 'admin_forum'),
|
||||||
|
'policy' => array($txt['privacy_policy'], 'admin_forum'),
|
||||||
|
'reservednames' => array($txt['admin_reserved_set'], 'admin_forum'),
|
||||||
|
'settings' => array($txt['settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'warnings' => array(
|
||||||
|
'label' => $txt['warnings'],
|
||||||
|
'file' => 'ManageSettings.php',
|
||||||
|
'function' => 'ModifyWarningSettings',
|
||||||
|
'icon' => 'warning',
|
||||||
|
'inactive' => $modSettings['warning_settings'][0] == 0,
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
),
|
||||||
|
'ban' => array(
|
||||||
|
'label' => $txt['ban_title'],
|
||||||
|
'file' => 'ManageBans.php',
|
||||||
|
'function' => 'Ban',
|
||||||
|
'icon' => 'ban',
|
||||||
|
'permission' => 'manage_bans',
|
||||||
|
'subsections' => array(
|
||||||
|
'list' => array($txt['ban_edit_list']),
|
||||||
|
'add' => array($txt['ban_add_new']),
|
||||||
|
'browse' => array($txt['ban_trigger_browse']),
|
||||||
|
'log' => array($txt['ban_log']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'paidsubscribe' => array(
|
||||||
|
'label' => $txt['paid_subscriptions'],
|
||||||
|
'inactive' => empty($modSettings['paid_enabled']),
|
||||||
|
'file' => 'ManagePaid.php',
|
||||||
|
'icon' => 'paid',
|
||||||
|
'function' => 'ManagePaidSubscriptions',
|
||||||
|
'permission' => 'admin_forum',
|
||||||
|
'subsections' => empty($modSettings['paid_enabled']) ? array() : array(
|
||||||
|
'view' => array($txt['paid_subs_view']),
|
||||||
|
'settings' => array($txt['settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'maintenance' => array(
|
||||||
|
'title' => $txt['admin_maintenance'],
|
||||||
|
'permission' => array('admin_forum'),
|
||||||
|
'areas' => array(
|
||||||
|
'serversettings' => array(
|
||||||
|
'label' => $txt['admin_server_settings'],
|
||||||
|
'file' => 'ManageServer.php',
|
||||||
|
'function' => 'ModifySettings',
|
||||||
|
'icon' => 'server',
|
||||||
|
'subsections' => array(
|
||||||
|
'general' => array($txt['general_settings']),
|
||||||
|
'database' => array($txt['database_settings']),
|
||||||
|
'cookie' => array($txt['cookies_sessions_settings']),
|
||||||
|
'security' => array($txt['security_settings']),
|
||||||
|
'cache' => array($txt['caching_settings']),
|
||||||
|
'export' => array($txt['export_settings']),
|
||||||
|
'loads' => array($txt['load_balancing_settings']),
|
||||||
|
'phpinfo' => array($txt['phpinfo_settings']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'maintain' => array(
|
||||||
|
'label' => $txt['maintain_title'],
|
||||||
|
'file' => 'ManageMaintenance.php',
|
||||||
|
'icon' => 'maintain',
|
||||||
|
'function' => 'ManageMaintenance',
|
||||||
|
'subsections' => array(
|
||||||
|
'routine' => array($txt['maintain_sub_routine'], 'admin_forum'),
|
||||||
|
'database' => array($txt['maintain_sub_database'], 'admin_forum'),
|
||||||
|
'members' => array($txt['maintain_sub_members'], 'admin_forum'),
|
||||||
|
'topics' => array($txt['maintain_sub_topics'], 'admin_forum'),
|
||||||
|
'hooks' => array($txt['hooks_title_list'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'scheduledtasks' => array(
|
||||||
|
'label' => $txt['maintain_tasks'],
|
||||||
|
'file' => 'ManageScheduledTasks.php',
|
||||||
|
'icon' => 'scheduled',
|
||||||
|
'function' => 'ManageScheduledTasks',
|
||||||
|
'subsections' => array(
|
||||||
|
'tasks' => array($txt['maintain_tasks'], 'admin_forum'),
|
||||||
|
'tasklog' => array($txt['scheduled_log'], 'admin_forum'),
|
||||||
|
'settings' => array($txt['scheduled_tasks_settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'mailqueue' => array(
|
||||||
|
'label' => $txt['mailqueue_title'],
|
||||||
|
'file' => 'ManageMail.php',
|
||||||
|
'function' => 'ManageMail',
|
||||||
|
'icon' => 'mail',
|
||||||
|
'subsections' => array(
|
||||||
|
'browse' => array($txt['mailqueue_browse'], 'admin_forum'),
|
||||||
|
'settings' => array($txt['mailqueue_settings'], 'admin_forum'),
|
||||||
|
'test' => array($txt['mailqueue_test'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'reports' => array(
|
||||||
|
'label' => $txt['generate_reports'],
|
||||||
|
'file' => 'Reports.php',
|
||||||
|
'function' => 'ReportsMain',
|
||||||
|
'icon' => 'reports',
|
||||||
|
),
|
||||||
|
'logs' => array(
|
||||||
|
'label' => $txt['logs'],
|
||||||
|
'function' => 'AdminLogs',
|
||||||
|
'icon' => 'logs',
|
||||||
|
'subsections' => array(
|
||||||
|
'errorlog' => array($txt['errorlog'], 'admin_forum', 'enabled' => !empty($modSettings['enableErrorLogging']), 'url' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc'),
|
||||||
|
'adminlog' => array($txt['admin_log'], 'admin_forum', 'enabled' => !empty($modSettings['adminlog_enabled'])),
|
||||||
|
'modlog' => array($txt['moderation_log'], 'admin_forum', 'enabled' => !empty($modSettings['modlog_enabled'])),
|
||||||
|
'banlog' => array($txt['ban_log'], 'manage_bans'),
|
||||||
|
'spiderlog' => array($txt['spider_logs'], 'admin_forum', 'enabled' => !empty($modSettings['spider_mode'])),
|
||||||
|
'tasklog' => array($txt['scheduled_log'], 'admin_forum'),
|
||||||
|
'settings' => array($txt['log_settings'], 'admin_forum'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'repairboards' => array(
|
||||||
|
'label' => $txt['admin_repair'],
|
||||||
|
'file' => 'RepairBoards.php',
|
||||||
|
'function' => 'RepairBoards',
|
||||||
|
'select' => 'maintain',
|
||||||
|
'hidden' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Any files to include for administration?
|
||||||
|
if (!empty($modSettings['integrate_admin_include']))
|
||||||
|
{
|
||||||
|
$admin_includes = explode(',', $modSettings['integrate_admin_include']);
|
||||||
|
foreach ($admin_includes as $include)
|
||||||
|
{
|
||||||
|
$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
|
||||||
|
if (file_exists($include))
|
||||||
|
require_once($include);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the administrator has a valid session...
|
||||||
|
validateSession();
|
||||||
|
|
||||||
|
// Actually create the menu!
|
||||||
|
$admin_include_data = createMenu($admin_areas, array('do_big_icons' => true));
|
||||||
|
unset($admin_areas);
|
||||||
|
|
||||||
|
// Nothing valid?
|
||||||
|
if ($admin_include_data == false)
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
// Build the link tree.
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=admin',
|
||||||
|
'name' => $txt['admin_center'],
|
||||||
|
);
|
||||||
|
if (isset($admin_include_data['current_area']) && $admin_include_data['current_area'] != 'index')
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=admin;area=' . $admin_include_data['current_area'] . ';' . $context['session_var'] . '=' . $context['session_id'],
|
||||||
|
'name' => $admin_include_data['label'],
|
||||||
|
);
|
||||||
|
if (!empty($admin_include_data['current_subsection']) && $admin_include_data['subsections'][$admin_include_data['current_subsection']][0] != $admin_include_data['label'])
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=admin;area=' . $admin_include_data['current_area'] . ';sa=' . $admin_include_data['current_subsection'] . ';' . $context['session_var'] . '=' . $context['session_id'],
|
||||||
|
'name' => $admin_include_data['subsections'][$admin_include_data['current_subsection']][0],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make a note of the Unique ID for this menu.
|
||||||
|
$context['admin_menu_id'] = $context['max_menu_id'];
|
||||||
|
$context['admin_menu_name'] = 'menu_data_' . $context['admin_menu_id'];
|
||||||
|
|
||||||
|
// Where in the admin are we?
|
||||||
|
$context['admin_area'] = $admin_include_data['current_area'];
|
||||||
|
|
||||||
|
// Now - finally - call the right place!
|
||||||
|
if (isset($admin_include_data['file']))
|
||||||
|
require_once($sourcedir . '/' . $admin_include_data['file']);
|
||||||
|
|
||||||
|
// Get the right callable.
|
||||||
|
$call = call_helper($admin_include_data['function'], true);
|
||||||
|
|
||||||
|
// Is it valid?
|
||||||
|
if (!empty($call))
|
||||||
|
call_user_func($call);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main administration section.
|
||||||
|
* It prepares all the data necessary for the administration front page.
|
||||||
|
* It uses the Admin template along with the admin sub template.
|
||||||
|
* It requires the moderate_forum, manage_membergroups, manage_bans,
|
||||||
|
* admin_forum, manage_permissions, manage_attachments, manage_smileys,
|
||||||
|
* manage_boards, edit_news, or send_mail permission.
|
||||||
|
* It uses the index administrative area.
|
||||||
|
* It can be found by going to ?action=admin.
|
||||||
|
*/
|
||||||
|
function AdminHome()
|
||||||
|
{
|
||||||
|
global $sourcedir, $txt, $scripturl, $context, $user_info;
|
||||||
|
|
||||||
|
// You have to be able to do at least one of the below to see this page.
|
||||||
|
isAllowedTo(array('admin_forum', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_boards', 'manage_smileys', 'manage_attachments'));
|
||||||
|
|
||||||
|
// Find all of this forum's administrators...
|
||||||
|
require_once($sourcedir . '/Subs-Membergroups.php');
|
||||||
|
if (listMembergroupMembers_Href($context['administrators'], 1, 32) && allowedTo('manage_membergroups'))
|
||||||
|
{
|
||||||
|
// Add a 'more'-link if there are more than 32.
|
||||||
|
$context['more_admins_link'] = '<a href="' . $scripturl . '?action=moderate;area=viewgroups;sa=members;group=1">' . $txt['more'] . '</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the credits stuff.
|
||||||
|
require_once($sourcedir . '/Who.php');
|
||||||
|
Credits(true);
|
||||||
|
|
||||||
|
// This makes it easier to get the latest news with your time format.
|
||||||
|
$context['time_format'] = urlencode($user_info['time_format']);
|
||||||
|
$context['forum_version'] = SMF_FULL_VERSION;
|
||||||
|
|
||||||
|
// Get a list of current server versions.
|
||||||
|
require_once($sourcedir . '/Subs-Admin.php');
|
||||||
|
$checkFor = array(
|
||||||
|
'gd',
|
||||||
|
'imagemagick',
|
||||||
|
'db_server',
|
||||||
|
'apcu',
|
||||||
|
'memcacheimplementation',
|
||||||
|
'memcachedimplementation',
|
||||||
|
'postgres',
|
||||||
|
'sqlite',
|
||||||
|
'zend',
|
||||||
|
'filebased',
|
||||||
|
'php',
|
||||||
|
'server',
|
||||||
|
);
|
||||||
|
$context['current_versions'] = getServerVersions($checkFor);
|
||||||
|
|
||||||
|
$context['can_admin'] = allowedTo('admin_forum');
|
||||||
|
|
||||||
|
$context['sub_template'] = $context['admin_area'] == 'credits' ? 'credits' : 'admin';
|
||||||
|
$context['page_title'] = $context['admin_area'] == 'credits' ? $txt['support_credits_title'] : $txt['admin_center'];
|
||||||
|
if ($context['admin_area'] != 'credits')
|
||||||
|
$context[$context['admin_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['admin_center'],
|
||||||
|
'help' => '',
|
||||||
|
'description' => '<strong>' . $txt['hello_guest'] . ' ' . $context['user']['name'] . '!</strong>
|
||||||
|
' . sprintf($txt['admin_main_welcome'], $txt['admin_center'], $txt['help'], $txt['help']),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lastly, fill in the blanks in the support resources paragraphs.
|
||||||
|
$txt['support_resources_p1'] = sprintf($txt['support_resources_p1'],
|
||||||
|
'https://wiki.simplemachines.org/',
|
||||||
|
'https://wiki.simplemachines.org/smf/features2',
|
||||||
|
'https://wiki.simplemachines.org/smf/options2',
|
||||||
|
'https://wiki.simplemachines.org/smf/themes2',
|
||||||
|
'https://wiki.simplemachines.org/smf/packages2'
|
||||||
|
);
|
||||||
|
$txt['support_resources_p2'] = sprintf($txt['support_resources_p2'],
|
||||||
|
'https://www.simplemachines.org/community/',
|
||||||
|
'https://www.simplemachines.org/redirect/english_support',
|
||||||
|
'https://www.simplemachines.org/redirect/international_support_boards',
|
||||||
|
'https://www.simplemachines.org/redirect/smf_support',
|
||||||
|
'https://www.simplemachines.org/redirect/customize_support'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($context['admin_area'] == 'admin')
|
||||||
|
loadJavaScriptFile('admin.js', array('defer' => false, 'minimize' => true), 'smf_admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get one of the admin information files from Simple Machines.
|
||||||
|
*/
|
||||||
|
function DisplayAdminFile()
|
||||||
|
{
|
||||||
|
global $context, $modSettings, $smcFunc;
|
||||||
|
|
||||||
|
setMemoryLimit('32M');
|
||||||
|
|
||||||
|
if (empty($_REQUEST['filename']) || !is_string($_REQUEST['filename']))
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
// Strip off the forum cache part or we won't find it...
|
||||||
|
$_REQUEST['filename'] = str_replace($context['browser_cache'], '', $_REQUEST['filename']);
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT data, filetype
|
||||||
|
FROM {db_prefix}admin_info_files
|
||||||
|
WHERE filename = {string:current_filename}
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'current_filename' => $_REQUEST['filename'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 0)
|
||||||
|
fatal_lang_error('admin_file_not_found', true, array($_REQUEST['filename']), 404);
|
||||||
|
|
||||||
|
list ($file_data, $filetype) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// @todo Temp
|
||||||
|
// Figure out if sesc is still being used.
|
||||||
|
if (strpos($file_data, ';sesc=') !== false && $filetype == 'text/javascript')
|
||||||
|
$file_data = '
|
||||||
|
if (!(\'smfForum_sessionvar\' in window))
|
||||||
|
window.smfForum_sessionvar = \'sesc\';
|
||||||
|
' . strtr($file_data, array(';sesc=' => ';\' + window.smfForum_sessionvar + \'='));
|
||||||
|
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
// Lets make sure we aren't going to output anything nasty.
|
||||||
|
@ob_end_clean();
|
||||||
|
if (!empty($modSettings['enableCompressedOutput']))
|
||||||
|
@ob_start('ob_gzhandler');
|
||||||
|
else
|
||||||
|
@ob_start();
|
||||||
|
|
||||||
|
// Make sure they know what type of file we are.
|
||||||
|
header('content-type: ' . $filetype);
|
||||||
|
echo $file_data;
|
||||||
|
obExit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function allocates out all the search stuff.
|
||||||
|
*/
|
||||||
|
function AdminSearch()
|
||||||
|
{
|
||||||
|
global $txt, $context, $smcFunc, $sourcedir;
|
||||||
|
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
// What can we search for?
|
||||||
|
$subActions = array(
|
||||||
|
'internal' => 'AdminSearchInternal',
|
||||||
|
'online' => 'AdminSearchOM',
|
||||||
|
'member' => 'AdminSearchMember',
|
||||||
|
);
|
||||||
|
|
||||||
|
$context['search_type'] = !isset($_REQUEST['search_type']) || !isset($subActions[$_REQUEST['search_type']]) ? 'internal' : $_REQUEST['search_type'];
|
||||||
|
$context['search_term'] = isset($_REQUEST['search_term']) ? $smcFunc['htmlspecialchars']($_REQUEST['search_term'], ENT_QUOTES) : '';
|
||||||
|
|
||||||
|
$context['sub_template'] = 'admin_search_results';
|
||||||
|
$context['page_title'] = $txt['admin_search_results'];
|
||||||
|
|
||||||
|
// Keep track of what the admin wants.
|
||||||
|
if (empty($context['admin_preferences']['sb']) || $context['admin_preferences']['sb'] != $context['search_type'])
|
||||||
|
{
|
||||||
|
$context['admin_preferences']['sb'] = $context['search_type'];
|
||||||
|
|
||||||
|
// Update the preferences.
|
||||||
|
require_once($sourcedir . '/Subs-Admin.php');
|
||||||
|
updateAdminPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim($context['search_term']) == '')
|
||||||
|
$context['search_results'] = array();
|
||||||
|
else
|
||||||
|
call_helper($subActions[$context['search_type']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complicated but relatively quick internal search.
|
||||||
|
*/
|
||||||
|
function AdminSearchInternal()
|
||||||
|
{
|
||||||
|
global $context, $txt, $helptxt, $scripturl, $sourcedir;
|
||||||
|
|
||||||
|
// Try to get some more memory.
|
||||||
|
setMemoryLimit('128M');
|
||||||
|
|
||||||
|
// Load a lot of language files.
|
||||||
|
$language_files = array(
|
||||||
|
'Help', 'ManageMail', 'ManageSettings', 'ManageCalendar', 'ManageBoards', 'ManagePaid', 'ManagePermissions', 'Search',
|
||||||
|
'Login', 'ManageSmileys', 'Drafts',
|
||||||
|
);
|
||||||
|
|
||||||
|
// All the files we need to include.
|
||||||
|
$include_files = array(
|
||||||
|
'ManageSettings', 'ManageBoards', 'ManageNews', 'ManageAttachments', 'ManageCalendar', 'ManageMail', 'ManagePaid', 'ManagePermissions',
|
||||||
|
'ManagePosts', 'ManageRegistration', 'ManageSearch', 'ManageSearchEngines', 'ManageServer', 'ManageSmileys', 'ManageLanguages',
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is a special array of functions that contain setting data - we query all these to simply pull all setting bits!
|
||||||
|
$settings_search = array(
|
||||||
|
array('ModifyBasicSettings', 'area=featuresettings;sa=basic'),
|
||||||
|
array('ModifyBBCSettings', 'area=featuresettings;sa=bbc'),
|
||||||
|
array('ModifyLayoutSettings', 'area=featuresettings;sa=layout'),
|
||||||
|
array('ModifyLikesSettings', 'area=featuresettings;sa=likes'),
|
||||||
|
array('ModifyMentionsSettings', 'area=featuresettings;sa=mentions'),
|
||||||
|
array('ModifySignatureSettings', 'area=featuresettings;sa=sig'),
|
||||||
|
array('ModifyAntispamSettings', 'area=antispam'),
|
||||||
|
array('ModifyWarningSettings', 'area=warnings'),
|
||||||
|
array('ModifyGeneralModSettings', 'area=modsettings;sa=general'),
|
||||||
|
// Mod authors if you want to be "real freaking good" then add any setting pages for your mod BELOW this line!
|
||||||
|
array('ManageAttachmentSettings', 'area=manageattachments;sa=attachments'),
|
||||||
|
array('ManageAvatarSettings', 'area=manageattachments;sa=avatars'),
|
||||||
|
array('ModifyCalendarSettings', 'area=managecalendar;sa=settings'),
|
||||||
|
array('EditBoardSettings', 'area=manageboards;sa=settings'),
|
||||||
|
array('ModifyMailSettings', 'area=mailqueue;sa=settings'),
|
||||||
|
array('ModifyNewsSettings', 'area=news;sa=settings'),
|
||||||
|
array('GeneralPermissionSettings', 'area=permissions;sa=settings'),
|
||||||
|
array('ModifyPostSettings', 'area=postsettings;sa=posts'),
|
||||||
|
array('ModifyTopicSettings', 'area=postsettings;sa=topics'),
|
||||||
|
array('ModifyDraftSettings', 'area=postsettings;sa=drafts'),
|
||||||
|
array('EditSearchSettings', 'area=managesearch;sa=settings'),
|
||||||
|
array('EditSmileySettings', 'area=smileys;sa=settings'),
|
||||||
|
array('ModifyGeneralSettings', 'area=serversettings;sa=general'),
|
||||||
|
array('ModifyDatabaseSettings', 'area=serversettings;sa=database'),
|
||||||
|
array('ModifyCookieSettings', 'area=serversettings;sa=cookie'),
|
||||||
|
array('ModifyGeneralSecuritySettings', 'area=serversettings;sa=security'),
|
||||||
|
array('ModifyCacheSettings', 'area=serversettings;sa=cache'),
|
||||||
|
array('ModifyLanguageSettings', 'area=languages;sa=settings'),
|
||||||
|
array('ModifyRegistrationSettings', 'area=regcenter;sa=settings'),
|
||||||
|
array('ManageSearchEngineSettings', 'area=sengines;sa=settings'),
|
||||||
|
array('ModifySubscriptionSettings', 'area=paidsubscribe;sa=settings'),
|
||||||
|
array('ModifyLogSettings', 'area=logs;sa=settings'),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_admin_search', array(&$language_files, &$include_files, &$settings_search));
|
||||||
|
|
||||||
|
loadLanguage(implode('+', $language_files));
|
||||||
|
|
||||||
|
foreach ($include_files as $file)
|
||||||
|
require_once($sourcedir . '/' . $file . '.php');
|
||||||
|
|
||||||
|
/* This is the huge array that defines everything... it's a huge array of items formatted as follows:
|
||||||
|
0 = Language index (Can be array of indexes) to search through for this setting.
|
||||||
|
1 = URL for this indexes page.
|
||||||
|
2 = Help index for help associated with this item (If different from 0)
|
||||||
|
*/
|
||||||
|
|
||||||
|
$search_data = array(
|
||||||
|
// All the major sections of the forum.
|
||||||
|
'sections' => array(
|
||||||
|
),
|
||||||
|
'settings' => array(
|
||||||
|
array('COPPA', 'area=regcenter;sa=settings'),
|
||||||
|
array('CAPTCHA', 'area=antispam'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go through the admin menu structure trying to find suitably named areas!
|
||||||
|
foreach ($context[$context['admin_menu_name']]['sections'] as $section)
|
||||||
|
{
|
||||||
|
foreach ($section['areas'] as $menu_key => $menu_item)
|
||||||
|
{
|
||||||
|
$search_data['sections'][] = array($menu_item['label'], 'area=' . $menu_key);
|
||||||
|
if (!empty($menu_item['subsections']))
|
||||||
|
foreach ($menu_item['subsections'] as $key => $sublabel)
|
||||||
|
{
|
||||||
|
if (isset($sublabel['label']))
|
||||||
|
$search_data['sections'][] = array($sublabel['label'], 'area=' . $menu_key . ';sa=' . $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($settings_search as $setting_area)
|
||||||
|
{
|
||||||
|
// Get a list of their variables.
|
||||||
|
$config_vars = call_user_func($setting_area[0], true);
|
||||||
|
|
||||||
|
foreach ($config_vars as $var)
|
||||||
|
if (!empty($var[1]) && !in_array($var[0], array('permissions', 'switch', 'desc')))
|
||||||
|
$search_data['settings'][] = array($var[(isset($var[2]) && in_array($var[2], array('file', 'db'))) ? 0 : 1], $setting_area[1], 'alttxt' => (isset($var[2]) && in_array($var[2], array('file', 'db'))) || isset($var[3]) ? (in_array($var[2], array('file', 'db')) ? $var[1] : $var[3]) : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['page_title'] = $txt['admin_search_results'];
|
||||||
|
$context['search_results'] = array();
|
||||||
|
|
||||||
|
$search_term = strtolower(un_htmlspecialchars($context['search_term']));
|
||||||
|
// Go through all the search data trying to find this text!
|
||||||
|
foreach ($search_data as $section => $data)
|
||||||
|
{
|
||||||
|
foreach ($data as $item)
|
||||||
|
{
|
||||||
|
$found = false;
|
||||||
|
if (!is_array($item[0]))
|
||||||
|
$item[0] = array($item[0]);
|
||||||
|
foreach ($item[0] as $term)
|
||||||
|
{
|
||||||
|
if (stripos($term, $search_term) !== false || (isset($txt[$term]) && stripos($txt[$term], $search_term) !== false) || (isset($txt['setting_' . $term]) && stripos($txt['setting_' . $term], $search_term) !== false))
|
||||||
|
{
|
||||||
|
$found = $term;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($found)
|
||||||
|
{
|
||||||
|
// Format the name - and remove any descriptions the entry may have.
|
||||||
|
$name = isset($txt[$found]) ? $txt[$found] : (isset($txt['setting_' . $found]) ? $txt['setting_' . $found] : (!empty($item['alttxt']) ? $item['alttxt'] : $found));
|
||||||
|
$name = preg_replace('~<(?:div|span)\sclass="smalltext">.+?</(?:div|span)>~', '', $name);
|
||||||
|
|
||||||
|
$context['search_results'][] = array(
|
||||||
|
'url' => (substr($item[1], 0, 4) == 'area' ? $scripturl . '?action=admin;' . $item[1] : $item[1]) . ';' . $context['session_var'] . '=' . $context['session_id'] . ((substr($item[1], 0, 4) == 'area' && $section == 'settings' ? '#' . $item[0][0] : '')),
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $section,
|
||||||
|
'help' => shorten_subject(isset($item[2]) ? strip_tags($helptxt[$item[2]]) : (isset($helptxt[$found]) ? strip_tags($helptxt[$found]) : ''), 255),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All this does is pass through to manage members.
|
||||||
|
* {@see ViewMembers()}
|
||||||
|
*/
|
||||||
|
function AdminSearchMember()
|
||||||
|
{
|
||||||
|
global $context, $sourcedir;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/ManageMembers.php');
|
||||||
|
$_REQUEST['sa'] = 'query';
|
||||||
|
|
||||||
|
$_POST['membername'] = un_htmlspecialchars($context['search_term']);
|
||||||
|
$_POST['types'] = '';
|
||||||
|
|
||||||
|
ViewMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file allows the user to search the SM online manual for a little of help.
|
||||||
|
*/
|
||||||
|
function AdminSearchOM()
|
||||||
|
{
|
||||||
|
global $context, $sourcedir;
|
||||||
|
|
||||||
|
$context['doc_apiurl'] = 'https://wiki.simplemachines.org/api.php';
|
||||||
|
$context['doc_scripturl'] = 'https://wiki.simplemachines.org/smf/';
|
||||||
|
|
||||||
|
// Set all the parameters search might expect.
|
||||||
|
$postVars = explode(' ', $context['search_term']);
|
||||||
|
|
||||||
|
// Encode the search data.
|
||||||
|
foreach ($postVars as $k => $v)
|
||||||
|
$postVars[$k] = urlencode($v);
|
||||||
|
|
||||||
|
// This is what we will send.
|
||||||
|
$postVars = implode('+', $postVars);
|
||||||
|
|
||||||
|
// Get the results from the doc site.
|
||||||
|
// Demo URL:
|
||||||
|
// https://wiki.simplemachines.org/api.php?action=query&list=search&srprop=timestamp|snippet&format=xml&srwhat=text&srsearch=template+eval
|
||||||
|
$search_results = fetch_web_data($context['doc_apiurl'] . '?action=query&list=search&srprop=timestamp|snippet&format=xml&srwhat=text&srsearch=' . $postVars);
|
||||||
|
|
||||||
|
// If we didn't get any xml back we are in trouble - perhaps the doc site is overloaded?
|
||||||
|
if (!$search_results || preg_match('~<' . '\?xml\sversion="\d+\.\d+"\?' . '>\s*(<api\b[^>]*>.+?</api>)~is', $search_results, $matches) != true)
|
||||||
|
fatal_lang_error('cannot_connect_doc_site');
|
||||||
|
|
||||||
|
$search_results = $matches[1];
|
||||||
|
|
||||||
|
// Otherwise we simply walk through the XML and stick it in context for display.
|
||||||
|
$context['search_results'] = array();
|
||||||
|
require_once($sourcedir . '/Class-Package.php');
|
||||||
|
|
||||||
|
// Get the results loaded into an array for processing!
|
||||||
|
$results = new xmlArray($search_results, false);
|
||||||
|
|
||||||
|
// Move through the api layer.
|
||||||
|
if (!$results->exists('api'))
|
||||||
|
fatal_lang_error('cannot_connect_doc_site');
|
||||||
|
|
||||||
|
// Are there actually some results?
|
||||||
|
if ($results->exists('api/query/search/p'))
|
||||||
|
{
|
||||||
|
$relevance = 0;
|
||||||
|
foreach ($results->set('api/query/search/p') as $result)
|
||||||
|
{
|
||||||
|
$context['search_results'][$result->fetch('@title')] = array(
|
||||||
|
'title' => $result->fetch('@title'),
|
||||||
|
'relevance' => $relevance++,
|
||||||
|
'snippet' => str_replace('class=\'searchmatch\'', 'class="highlight"', un_htmlspecialchars($result->fetch('@snippet'))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function decides which log to load.
|
||||||
|
*/
|
||||||
|
function AdminLogs()
|
||||||
|
{
|
||||||
|
global $sourcedir, $context, $txt, $scripturl, $modSettings;
|
||||||
|
|
||||||
|
// These are the logs they can load.
|
||||||
|
$log_functions = array(
|
||||||
|
'errorlog' => array('ManageErrors.php', 'ViewErrorLog'),
|
||||||
|
'adminlog' => array('Modlog.php', 'ViewModlog', 'disabled' => empty($modSettings['adminlog_enabled'])),
|
||||||
|
'modlog' => array('Modlog.php', 'ViewModlog', 'disabled' => empty($modSettings['modlog_enabled'])),
|
||||||
|
'banlog' => array('ManageBans.php', 'BanLog'),
|
||||||
|
'spiderlog' => array('ManageSearchEngines.php', 'SpiderLogs'),
|
||||||
|
'tasklog' => array('ManageScheduledTasks.php', 'TaskLog'),
|
||||||
|
'settings' => array('ManageSettings.php', 'ModifyLogSettings'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If it's not got a sa set it must have come here for first time, pretend error log should be reversed.
|
||||||
|
if (!isset($_REQUEST['sa']))
|
||||||
|
$_REQUEST['desc'] = true;
|
||||||
|
|
||||||
|
// Setup some tab stuff.
|
||||||
|
$context[$context['admin_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['logs'],
|
||||||
|
'help' => '',
|
||||||
|
'description' => $txt['maintain_info'],
|
||||||
|
'tabs' => array(
|
||||||
|
'errorlog' => array(
|
||||||
|
'url' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
|
||||||
|
'description' => sprintf($txt['errorlog_desc'], $txt['remove']),
|
||||||
|
),
|
||||||
|
'adminlog' => array(
|
||||||
|
'description' => $txt['admin_log_desc'],
|
||||||
|
),
|
||||||
|
'modlog' => array(
|
||||||
|
'description' => $txt['moderation_log_desc'],
|
||||||
|
),
|
||||||
|
'banlog' => array(
|
||||||
|
'description' => $txt['ban_log_description'],
|
||||||
|
),
|
||||||
|
'spiderlog' => array(
|
||||||
|
'description' => $txt['spider_log_desc'],
|
||||||
|
),
|
||||||
|
'tasklog' => array(
|
||||||
|
'description' => $txt['scheduled_log_desc'],
|
||||||
|
),
|
||||||
|
'settings' => array(
|
||||||
|
'description' => $txt['log_settings_desc'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_manage_logs', array(&$log_functions));
|
||||||
|
|
||||||
|
$subAction = isset($_REQUEST['sa']) && isset($log_functions[$_REQUEST['sa']]) && empty($log_functions[$_REQUEST['sa']]['disabled']) ? $_REQUEST['sa'] : 'errorlog';
|
||||||
|
|
||||||
|
require_once($sourcedir . '/' . $log_functions[$subAction][0]);
|
||||||
|
call_helper($log_functions[$subAction][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This ends a admin session, requiring authentication to access the ACP again.
|
||||||
|
*/
|
||||||
|
function AdminEndSession()
|
||||||
|
{
|
||||||
|
// This is so easy!
|
||||||
|
unset($_SESSION['admin_time']);
|
||||||
|
|
||||||
|
// Clean any admin tokens as well.
|
||||||
|
foreach ($_SESSION['token'] as $key => $token)
|
||||||
|
if (strpos($key, '-admin') !== false)
|
||||||
|
unset($_SESSION['token'][$key]);
|
||||||
|
|
||||||
|
redirectexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
184
Sources/Agreement.php
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file handles the user and privacy policy agreements.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/* The purpose of this file is to show the user an updated registration
|
||||||
|
agreement, and get them to agree to it.
|
||||||
|
|
||||||
|
bool prepareAgreementContext()
|
||||||
|
// !!!
|
||||||
|
|
||||||
|
bool canRequireAgreement()
|
||||||
|
// !!!
|
||||||
|
|
||||||
|
bool canRequirePrivacyPolicy()
|
||||||
|
// !!!
|
||||||
|
|
||||||
|
void Agreement()
|
||||||
|
- Show the new registration agreement
|
||||||
|
|
||||||
|
void AcceptAgreement()
|
||||||
|
- Called when they actually accept the agreement
|
||||||
|
- Save the date of the current agreement to the members database table
|
||||||
|
- Redirect back to wherever they came from
|
||||||
|
*/
|
||||||
|
|
||||||
|
function prepareAgreementContext()
|
||||||
|
{
|
||||||
|
global $boarddir, $context, $language, $modSettings, $user_info;
|
||||||
|
|
||||||
|
// What, if anything, do they need to accept?
|
||||||
|
$context['can_accept_agreement'] = !empty($modSettings['requireAgreement']) && canRequireAgreement();
|
||||||
|
$context['can_accept_privacy_policy'] = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy();
|
||||||
|
$context['accept_doc'] = $context['can_accept_agreement'] || $context['can_accept_privacy_policy'];
|
||||||
|
|
||||||
|
if (!$context['accept_doc'] || $context['can_accept_agreement'])
|
||||||
|
{
|
||||||
|
// Grab the agreement.
|
||||||
|
// Have we got a localized one?
|
||||||
|
if (file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt'))
|
||||||
|
$context['agreement_file'] = $boarddir . '/agreement.' . $user_info['language'] . '.txt';
|
||||||
|
elseif (file_exists($boarddir . '/agreement.txt'))
|
||||||
|
$context['agreement_file'] = $boarddir . '/agreement.txt';
|
||||||
|
|
||||||
|
if (!empty($context['agreement_file']))
|
||||||
|
{
|
||||||
|
$cache_id = strtr($context['agreement_file'], array($boarddir => '', '.txt' => '', '.' => '_'));
|
||||||
|
$context['agreement'] = parse_bbc(file_get_contents($context['agreement_file']), true, $cache_id);
|
||||||
|
}
|
||||||
|
elseif ($context['can_accept_agreement'])
|
||||||
|
fatal_lang_error('error_no_agreement', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$context['accept_doc'] || $context['can_accept_privacy_policy'])
|
||||||
|
{
|
||||||
|
// Have we got a localized policy?
|
||||||
|
if (!empty($modSettings['policy_' . $user_info['language']]))
|
||||||
|
$context['privacy_policy'] = parse_bbc($modSettings['policy_' . $user_info['language']]);
|
||||||
|
elseif (!empty($modSettings['policy_' . $language]))
|
||||||
|
$context['privacy_policy'] = parse_bbc($modSettings['policy_' . $language]);
|
||||||
|
// Then I guess we've got nothing
|
||||||
|
elseif ($context['can_accept_privacy_policy'])
|
||||||
|
fatal_lang_error('error_no_privacy_policy', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canRequireAgreement()
|
||||||
|
{
|
||||||
|
global $boarddir, $context, $modSettings, $options, $user_info;
|
||||||
|
|
||||||
|
// Guests can't agree
|
||||||
|
if (!empty($user_info['is_guest']) || empty($modSettings['requireAgreement']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$agreement_lang = file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt') ? $user_info['language'] : 'default';
|
||||||
|
|
||||||
|
if (empty($modSettings['agreement_updated_' . $agreement_lang]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$context['agreement_accepted_date'] = empty($options['agreement_accepted']) ? 0 : $options['agreement_accepted'];
|
||||||
|
|
||||||
|
// A new timestamp means that there are new changes to the registration agreement and must therefore be shown.
|
||||||
|
return empty($options['agreement_accepted']) || $modSettings['agreement_updated_' . $agreement_lang] > $options['agreement_accepted'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function canRequirePrivacyPolicy()
|
||||||
|
{
|
||||||
|
global $modSettings, $options, $user_info, $language, $context;
|
||||||
|
|
||||||
|
if (!empty($user_info['is_guest']) || empty($modSettings['requirePolicyAgreement']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$policy_lang = !empty($modSettings['policy_' . $user_info['language']]) ? $user_info['language'] : $language;
|
||||||
|
|
||||||
|
if (empty($modSettings['policy_updated_' . $policy_lang]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$context['privacy_policy_accepted_date'] = empty($options['policy_accepted']) ? 0 : $options['policy_accepted'];
|
||||||
|
|
||||||
|
return empty($options['policy_accepted']) || $modSettings['policy_updated_' . $policy_lang] > $options['policy_accepted'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's tell them there's a new agreement
|
||||||
|
function Agreement()
|
||||||
|
{
|
||||||
|
global $context, $modSettings, $scripturl, $smcFunc, $txt;
|
||||||
|
|
||||||
|
prepareAgreementContext();
|
||||||
|
|
||||||
|
loadLanguage('Agreement');
|
||||||
|
loadTemplate('Agreement');
|
||||||
|
|
||||||
|
$page_title = '';
|
||||||
|
if (!empty($context['agreement']) && !empty($context['privacy_policy']))
|
||||||
|
$page_title = $txt['agreement_and_privacy_policy'];
|
||||||
|
elseif (!empty($context['agreement']))
|
||||||
|
$page_title = $txt['agreement'];
|
||||||
|
elseif (!empty($context['privacy_policy']))
|
||||||
|
$page_title = $txt['privacy_policy'];
|
||||||
|
|
||||||
|
$context['page_title'] = $page_title;
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=agreement',
|
||||||
|
'name' => $context['page_title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($_SESSION['old_url']))
|
||||||
|
$_SESSION['redirect_url'] = $_SESSION['old_url'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// I solemly swear to no longer chase squirrels.
|
||||||
|
function AcceptAgreement()
|
||||||
|
{
|
||||||
|
global $context, $modSettings, $smcFunc, $user_info;
|
||||||
|
|
||||||
|
$can_accept_agreement = !empty($modSettings['requireAgreement']) && canRequireAgreement();
|
||||||
|
$can_accept_privacy_policy = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy();
|
||||||
|
|
||||||
|
if ($can_accept_agreement || $can_accept_privacy_policy)
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
if ($can_accept_agreement)
|
||||||
|
{
|
||||||
|
$smcFunc['db_insert']('replace',
|
||||||
|
'{db_prefix}themes',
|
||||||
|
array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
|
||||||
|
array($user_info['id'], 1, 'agreement_accepted', time()),
|
||||||
|
array('id_member', 'id_theme', 'variable')
|
||||||
|
);
|
||||||
|
logAction('agreement_accepted', array('applicator' => $user_info['id']), 'user');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($can_accept_privacy_policy)
|
||||||
|
{
|
||||||
|
$smcFunc['db_insert']('replace',
|
||||||
|
'{db_prefix}themes',
|
||||||
|
array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
|
||||||
|
array($user_info['id'], 1, 'policy_accepted', time()),
|
||||||
|
array('id_member', 'id_theme', 'variable')
|
||||||
|
);
|
||||||
|
logAction('policy_accepted', array('applicator' => $user_info['id']), 'user');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to chasing those squirrels, er, viewing those memes.
|
||||||
|
redirectexit(!empty($_SESSION['redirect_url']) ? $_SESSION['redirect_url'] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
538
Sources/Attachments.php
Normal file
|
@ -0,0 +1,538 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains handling attachments.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Attachments
|
||||||
|
*
|
||||||
|
* This class handles adding/deleting attachments
|
||||||
|
*/
|
||||||
|
class Attachments
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int $_msg The ID of the message this attachment is associated with
|
||||||
|
*/
|
||||||
|
protected $_msg = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int|null $_board The ID of the board this attachment's post is in or null if it's not set
|
||||||
|
*/
|
||||||
|
protected $_board = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|bool $_attachmentUploadDir An array of info about attachment upload directories or false
|
||||||
|
*/
|
||||||
|
protected $_attachmentUploadDir = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $_attchDir The path to the current attachment directory
|
||||||
|
*/
|
||||||
|
protected $_attchDir = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int $_currentAttachmentUploadDir ID of the current attachment directory
|
||||||
|
*/
|
||||||
|
protected $_currentAttachmentUploadDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool $_canPostAttachment Whether or not an attachment can be posted
|
||||||
|
*/
|
||||||
|
protected $_canPostAttachment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_generalErrors An array of information about any errors that occurred
|
||||||
|
*/
|
||||||
|
protected $_generalErrors = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var mixed $_initialError Not used?
|
||||||
|
*/
|
||||||
|
protected $_initialError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_attachments Not used?
|
||||||
|
*/
|
||||||
|
protected $_attachments = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_attachResults An array of information about the results of each file
|
||||||
|
*/
|
||||||
|
protected $_attachResults = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_attachSuccess An array of information about successful attachments
|
||||||
|
*/
|
||||||
|
protected $_attachSuccess = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_response An array of response information. @used-by \sendResponse() when adding attachments
|
||||||
|
*/
|
||||||
|
protected $_response = array(
|
||||||
|
'error' => true,
|
||||||
|
'data' => array(),
|
||||||
|
'extra' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_subActions An array of all valid sub-actions
|
||||||
|
*/
|
||||||
|
protected $_subActions = array(
|
||||||
|
'add',
|
||||||
|
'delete',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|bool $_sa The current sub-action, or false if there isn't one
|
||||||
|
*/
|
||||||
|
protected $_sa = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attachments constructor.
|
||||||
|
*
|
||||||
|
* Sets up some initial information - the message ID, board, current attachment upload dir, etc.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
global $modSettings, $context;
|
||||||
|
|
||||||
|
$this->_msg = (int) !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0;
|
||||||
|
$this->_board = (int) !empty($_REQUEST['board']) ? $_REQUEST['board'] : null;
|
||||||
|
|
||||||
|
$this->_currentAttachmentUploadDir = $modSettings['currentAttachmentUploadDir'];
|
||||||
|
|
||||||
|
$this->_attachmentUploadDir = $modSettings['attachmentUploadDir'];
|
||||||
|
|
||||||
|
$this->_attchDir = $context['attach_dir'] = $this->_attachmentUploadDir[$modSettings['currentAttachmentUploadDir']];
|
||||||
|
|
||||||
|
$this->_canPostAttachment = $context['can_post_attachment'] = !empty($modSettings['attachmentEnable']) && $modSettings['attachmentEnable'] == 1 && (allowedTo('post_attachment', $this->_board) || ($modSettings['postmod_active'] && allowedTo('post_unapproved_attachments', $this->_board)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles calling the appropriate function based on the sub-action
|
||||||
|
*/
|
||||||
|
public function call()
|
||||||
|
{
|
||||||
|
global $smcFunc, $sourcedir;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Attachments.php');
|
||||||
|
|
||||||
|
// Need this. For reasons...
|
||||||
|
loadLanguage('Post');
|
||||||
|
|
||||||
|
$this->_sa = !empty($_REQUEST['sa']) ? $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($_REQUEST['sa'])) : false;
|
||||||
|
|
||||||
|
if ($this->_canPostAttachment && $this->_sa && in_array($this->_sa, $this->_subActions))
|
||||||
|
$this->{$this->_sa}();
|
||||||
|
|
||||||
|
// Just send a generic message.
|
||||||
|
else
|
||||||
|
$this->setResponse(array(
|
||||||
|
'text' => $this->_sa == 'add' ? 'attach_error_title' : 'attached_file_deleted_error',
|
||||||
|
'type' => 'error',
|
||||||
|
'data' => false,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Back to the future, oh, to the browser!
|
||||||
|
$this->sendResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles deleting the attachment
|
||||||
|
*/
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
global $sourcedir;
|
||||||
|
|
||||||
|
// Need this, don't ask why just nod your head.
|
||||||
|
require_once($sourcedir . '/ManageAttachments.php');
|
||||||
|
|
||||||
|
$attachID = !empty($_REQUEST['attach']) && is_numeric($_REQUEST['attach']) ? (int) $_REQUEST['attach'] : 0;
|
||||||
|
|
||||||
|
// Need something to work with.
|
||||||
|
if (!$attachID || (!empty($_SESSION['already_attached']) && !isset($_SESSION['already_attached'][$attachID])))
|
||||||
|
return $this->setResponse(array(
|
||||||
|
'text' => 'attached_file_deleted_error',
|
||||||
|
'type' => 'error',
|
||||||
|
'data' => false,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Lets pass some params and see what happens :P
|
||||||
|
$affectedMessage = removeAttachments(array('id_attach' => $attachID), '', true, true);
|
||||||
|
|
||||||
|
// Gotta also remove the attachment from the session var.
|
||||||
|
unset($_SESSION['already_attached'][$attachID]);
|
||||||
|
|
||||||
|
// $affectedMessage returns an empty array array(0) which php treats as non empty... awesome...
|
||||||
|
$this->setResponse(array(
|
||||||
|
'text' => !empty($affectedMessage) ? 'attached_file_deleted' : 'attached_file_deleted_error',
|
||||||
|
'type' => !empty($affectedMessage) ? 'info' : 'warning',
|
||||||
|
'data' => $affectedMessage,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles adding an attachment
|
||||||
|
*/
|
||||||
|
public function add()
|
||||||
|
{
|
||||||
|
// You gotta be able to post attachments.
|
||||||
|
if (!$this->_canPostAttachment)
|
||||||
|
return $this->setResponse(array(
|
||||||
|
'text' => 'attached_file_cannot',
|
||||||
|
'type' => 'error',
|
||||||
|
'data' => false,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Process them at once!
|
||||||
|
$this->processAttachments();
|
||||||
|
|
||||||
|
// The attachments was created and moved the the right folder, time to update the DB.
|
||||||
|
if (!empty($_SESSION['temp_attachments']))
|
||||||
|
$this->createAttach();
|
||||||
|
|
||||||
|
// Set the response.
|
||||||
|
$this->setResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments']
|
||||||
|
*/
|
||||||
|
protected function processAttachments()
|
||||||
|
{
|
||||||
|
global $context, $modSettings, $smcFunc, $user_info, $txt;
|
||||||
|
|
||||||
|
if (!isset($_FILES['attachment']['name']))
|
||||||
|
$_FILES['attachment']['tmp_name'] = array();
|
||||||
|
|
||||||
|
// If there are attachments, calculate the total size and how many.
|
||||||
|
$context['attachments']['total_size'] = 0;
|
||||||
|
$context['attachments']['quantity'] = 0;
|
||||||
|
|
||||||
|
// If this isn't a new post, check the current attachments.
|
||||||
|
if (isset($_REQUEST['msg']))
|
||||||
|
{
|
||||||
|
$context['attachments']['quantity'] = count($context['current_attachments']);
|
||||||
|
foreach ($context['current_attachments'] as $attachment)
|
||||||
|
$context['attachments']['total_size'] += $attachment['size'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bit of house keeping first.
|
||||||
|
if (!empty($_SESSION['temp_attachments']) && count($_SESSION['temp_attachments']) == 1)
|
||||||
|
unset($_SESSION['temp_attachments']);
|
||||||
|
|
||||||
|
// Our infamous SESSION var, we are gonna have soo much fun with it!
|
||||||
|
if (!isset($_SESSION['temp_attachments']))
|
||||||
|
$_SESSION['temp_attachments'] = array();
|
||||||
|
|
||||||
|
// Make sure we're uploading to the right place.
|
||||||
|
if (!empty($modSettings['automanage_attachments']))
|
||||||
|
automanage_attachments_check_directory();
|
||||||
|
|
||||||
|
// Is the attachments folder actually there?
|
||||||
|
if (!empty($context['dir_creation_error']))
|
||||||
|
$this->_generalErrors[] = $context['dir_creation_error'];
|
||||||
|
|
||||||
|
// The current attach folder ha some issues...
|
||||||
|
elseif (!is_dir($this->_attchDir))
|
||||||
|
{
|
||||||
|
$this->_generalErrors[] = 'attach_folder_warning';
|
||||||
|
log_error(sprintf($txt['attach_folder_admin_warning'], $this->_attchDir), 'critical');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't a new post, check the current attachments.
|
||||||
|
if (empty($this->_generalErrors) && $this->_msg)
|
||||||
|
{
|
||||||
|
$context['attachments'] = array();
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*), SUM(size)
|
||||||
|
FROM {db_prefix}attachments
|
||||||
|
WHERE id_msg = {int:id_msg}
|
||||||
|
AND attachment_type = {int:attachment_type}',
|
||||||
|
array(
|
||||||
|
'id_msg' => (int) $this->_msg,
|
||||||
|
'attachment_type' => 0,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
$context['attachments'] = array(
|
||||||
|
'quantity' => 0,
|
||||||
|
'total_size' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for other general errors here.
|
||||||
|
|
||||||
|
// If we have an initial error, delete the files.
|
||||||
|
if (!empty($this->_generalErrors))
|
||||||
|
{
|
||||||
|
// And delete the files 'cos they ain't going nowhere.
|
||||||
|
foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
|
||||||
|
if (file_exists($_FILES['attachment']['tmp_name'][$n]))
|
||||||
|
unlink($_FILES['attachment']['tmp_name'][$n]);
|
||||||
|
|
||||||
|
$_FILES['attachment']['tmp_name'] = array();
|
||||||
|
|
||||||
|
// No point in going further with this.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
|
||||||
|
foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
|
||||||
|
{
|
||||||
|
if ($_FILES['attachment']['name'][$n] == '')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// First, let's first check for PHP upload errors.
|
||||||
|
$errors = array();
|
||||||
|
if (!empty($_FILES['attachment']['error'][$n]))
|
||||||
|
{
|
||||||
|
if ($_FILES['attachment']['error'][$n] == 2)
|
||||||
|
$errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
|
||||||
|
|
||||||
|
else
|
||||||
|
log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
|
||||||
|
|
||||||
|
// Log this one, because...
|
||||||
|
if ($_FILES['attachment']['error'][$n] == 6)
|
||||||
|
log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
|
||||||
|
|
||||||
|
// Weird, no errors were cached, still fill out a generic one.
|
||||||
|
if (empty($errors))
|
||||||
|
$errors[] = 'attach_php_error';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to move and rename the file before doing any more checks on it.
|
||||||
|
$attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
|
||||||
|
$destName = $this->_attchDir . '/' . $attachID;
|
||||||
|
|
||||||
|
// No errors, YAY!
|
||||||
|
if (empty($errors))
|
||||||
|
{
|
||||||
|
// The reported MIME type of the attachment might not be reliable.
|
||||||
|
$detected_mime_type = get_mime_type($_FILES['attachment']['tmp_name'][$n], true);
|
||||||
|
if ($detected_mime_type !== false)
|
||||||
|
$_FILES['attachment']['type'][$n] = $detected_mime_type;
|
||||||
|
|
||||||
|
$_SESSION['temp_attachments'][$attachID] = array(
|
||||||
|
'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
|
||||||
|
'tmp_name' => $destName,
|
||||||
|
'size' => $_FILES['attachment']['size'][$n],
|
||||||
|
'type' => $_FILES['attachment']['type'][$n],
|
||||||
|
'id_folder' => $modSettings['currentAttachmentUploadDir'],
|
||||||
|
'errors' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Move the file to the attachments folder with a temp name for now.
|
||||||
|
if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
|
||||||
|
smf_chmod($destName, 0644);
|
||||||
|
|
||||||
|
// This is madness!!
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// File couldn't be moved.
|
||||||
|
$_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
|
||||||
|
if (file_exists($_FILES['attachment']['tmp_name'][$n]))
|
||||||
|
unlink($_FILES['attachment']['tmp_name'][$n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill up a nice array with some data from the file and the errors encountered so far.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$_SESSION['temp_attachments'][$attachID] = array(
|
||||||
|
'name' => $smcFunc['htmlspecialchars'](basename($_FILES['attachment']['name'][$n])),
|
||||||
|
'tmp_name' => $destName,
|
||||||
|
'errors' => $errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_exists($_FILES['attachment']['tmp_name'][$n]))
|
||||||
|
unlink($_FILES['attachment']['tmp_name'][$n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no errors to this point. We still do need to apply some additional checks before we are finished.
|
||||||
|
if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
|
||||||
|
attachmentChecks($attachID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mod authors, finally a hook to hang an alternate attachment upload system upon
|
||||||
|
// Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
|
||||||
|
// Populate $_SESSION['temp_attachments'][$attachID] with the following:
|
||||||
|
// name => The file name
|
||||||
|
// tmp_name => Path to the temp file ($this->_attchDir . '/' . $attachID).
|
||||||
|
// size => File size (required).
|
||||||
|
// type => MIME type (optional if not available on upload).
|
||||||
|
// id_folder => $modSettings['currentAttachmentUploadDir']
|
||||||
|
// errors => An array of errors (use the index of the $txt variable for that error).
|
||||||
|
// Template changes can be done using "integrate_upload_template".
|
||||||
|
call_integration_hook('integrate_attachment_upload', array());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually attaches the file
|
||||||
|
*/
|
||||||
|
protected function createAttach()
|
||||||
|
{
|
||||||
|
global $txt, $user_info, $modSettings;
|
||||||
|
|
||||||
|
// Create an empty session var to keep track of all the files we attached.
|
||||||
|
if (!isset($_SESSION['already_attached']))
|
||||||
|
$_SESSION['already_attached'] = array();
|
||||||
|
|
||||||
|
foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
|
||||||
|
{
|
||||||
|
$attachmentOptions = array(
|
||||||
|
'post' => $this->_msg,
|
||||||
|
'poster' => $user_info['id'],
|
||||||
|
'name' => $attachment['name'],
|
||||||
|
'tmp_name' => $attachment['tmp_name'],
|
||||||
|
'size' => isset($attachment['size']) ? $attachment['size'] : 0,
|
||||||
|
'mime_type' => isset($attachment['type']) ? $attachment['type'] : '',
|
||||||
|
'id_folder' => isset($attachment['id_folder']) ? $attachment['id_folder'] : $modSettings['currentAttachmentUploadDir'],
|
||||||
|
'approved' => !$modSettings['postmod_active'] || allowedTo('post_attachment'),
|
||||||
|
'errors' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($attachment['errors']))
|
||||||
|
{
|
||||||
|
if (createAttachment($attachmentOptions))
|
||||||
|
{
|
||||||
|
// Avoid JS getting confused.
|
||||||
|
$attachmentOptions['attachID'] = $attachmentOptions['id'];
|
||||||
|
unset($attachmentOptions['id']);
|
||||||
|
|
||||||
|
$_SESSION['already_attached'][$attachmentOptions['attachID']] = $attachmentOptions['attachID'];
|
||||||
|
|
||||||
|
if (!empty($attachmentOptions['thumb']))
|
||||||
|
$_SESSION['already_attached'][$attachmentOptions['thumb']] = $attachmentOptions['thumb'];
|
||||||
|
|
||||||
|
if ($this->_msg)
|
||||||
|
assignAttachments($_SESSION['already_attached'], $this->_msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Sort out the errors for display and delete any associated files.
|
||||||
|
$log_these = array('attachments_no_create', 'attachments_no_write', 'attach_timeout', 'ran_out_of_space', 'cant_access_upload_path', 'attach_0_byte_file');
|
||||||
|
|
||||||
|
foreach ($attachment['errors'] as $error)
|
||||||
|
{
|
||||||
|
$attachmentOptions['errors'][] = sprintf($txt['attach_warning'], $attachment['name']);
|
||||||
|
|
||||||
|
if (!is_array($error))
|
||||||
|
{
|
||||||
|
$attachmentOptions['errors'][] = $txt[$error];
|
||||||
|
if (in_array($error, $log_these))
|
||||||
|
log_error($attachment['name'] . ': ' . $txt[$error], 'critical');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$attachmentOptions['errors'][] = vsprintf($txt[$error[0]], (array) $error[1]);
|
||||||
|
}
|
||||||
|
if (file_exists($attachment['tmp_name']))
|
||||||
|
unlink($attachment['tmp_name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You don't need to know.
|
||||||
|
unset($attachmentOptions['tmp_name']);
|
||||||
|
unset($attachmentOptions['destination']);
|
||||||
|
|
||||||
|
// Regardless of errors, pass the results.
|
||||||
|
$this->_attachResults[] = $attachmentOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp save this on the db.
|
||||||
|
if (!empty($_SESSION['already_attached']))
|
||||||
|
$this->_attachSuccess = $_SESSION['already_attached'];
|
||||||
|
|
||||||
|
unset($_SESSION['temp_attachments']);
|
||||||
|
|
||||||
|
// Allow user to see previews for all of this post's attachments, even if the post hasn't been submitted yet.
|
||||||
|
if (!isset($_SESSION['attachments_can_preview']))
|
||||||
|
$_SESSION['attachments_can_preview'] = array();
|
||||||
|
if (!empty($_SESSION['already_attached']))
|
||||||
|
$_SESSION['attachments_can_preview'] += array_fill_keys(array_keys($_SESSION['already_attached']), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the response information
|
||||||
|
*
|
||||||
|
* @param array $data Data for the response if we're not adding an attachment
|
||||||
|
*/
|
||||||
|
protected function setResponse($data = array())
|
||||||
|
{
|
||||||
|
global $txt;
|
||||||
|
|
||||||
|
// Some default values in case something is missed or neglected :P
|
||||||
|
$this->_response = array(
|
||||||
|
'text' => 'attach_php_error',
|
||||||
|
'type' => 'error',
|
||||||
|
'data' => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Adding needs some VIP treatment.
|
||||||
|
if ($this->_sa == 'add')
|
||||||
|
{
|
||||||
|
// Is there any generic errors? made some sense out of them!
|
||||||
|
if ($this->_generalErrors)
|
||||||
|
foreach ($this->_generalErrors as $k => $v)
|
||||||
|
$this->_generalErrors[$k] = (is_array($v) ? vsprintf($txt[$v[0]], (array) $v[1]) : $txt[$v]);
|
||||||
|
|
||||||
|
// Gotta urlencode the filename.
|
||||||
|
if ($this->_attachResults)
|
||||||
|
foreach ($this->_attachResults as $k => $v)
|
||||||
|
$this->_attachResults[$k]['name'] = urlencode($this->_attachResults[$k]['name']);
|
||||||
|
|
||||||
|
$this->_response = array(
|
||||||
|
'files' => $this->_attachResults ? $this->_attachResults : false,
|
||||||
|
'generalErrors' => $this->_generalErrors ? $this->_generalErrors : false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rest of us mere mortals gets no special treatment...
|
||||||
|
elseif (!empty($data))
|
||||||
|
if (!empty($data['text']) && !empty($txt[$data['text']]))
|
||||||
|
$this->_response['text'] = $txt[$data['text']];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the response data
|
||||||
|
*/
|
||||||
|
protected function sendResponse()
|
||||||
|
{
|
||||||
|
global $smcFunc, $modSettings, $context;
|
||||||
|
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
if (!empty($modSettings['enableCompressedOutput']))
|
||||||
|
@ob_start('ob_gzhandler');
|
||||||
|
else
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
// Set the header.
|
||||||
|
header('content-type: application/json; charset=' . $context['character_set'] . '');
|
||||||
|
|
||||||
|
echo $smcFunc['json_encode']($this->_response ? $this->_response : array());
|
||||||
|
|
||||||
|
// Done.
|
||||||
|
obExit(false);
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
150
Sources/BoardIndex.php
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The single function this file contains is used to display the main
|
||||||
|
* board index.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function shows the board index.
|
||||||
|
* It uses the BoardIndex template, and main sub template.
|
||||||
|
* It updates the most online statistics.
|
||||||
|
* It is accessed by ?action=boardindex.
|
||||||
|
*/
|
||||||
|
function BoardIndex()
|
||||||
|
{
|
||||||
|
global $txt, $user_info, $sourcedir, $modSettings, $context, $settings, $scripturl;
|
||||||
|
|
||||||
|
loadTemplate('BoardIndex');
|
||||||
|
$context['template_layers'][] = 'boardindex_outer';
|
||||||
|
|
||||||
|
// Set a canonical URL for this page.
|
||||||
|
$context['canonical_url'] = $scripturl;
|
||||||
|
|
||||||
|
// Do not let search engines index anything if there is a random thing in $_GET.
|
||||||
|
if (!empty($_GET))
|
||||||
|
$context['robot_no_index'] = true;
|
||||||
|
|
||||||
|
// Retrieve the categories and boards.
|
||||||
|
require_once($sourcedir . '/Subs-BoardIndex.php');
|
||||||
|
$boardIndexOptions = array(
|
||||||
|
'include_categories' => true,
|
||||||
|
'base_level' => 0,
|
||||||
|
'parent_id' => 0,
|
||||||
|
'set_latest_post' => true,
|
||||||
|
'countChildPosts' => !empty($modSettings['countChildPosts']),
|
||||||
|
);
|
||||||
|
$context['categories'] = getBoardIndex($boardIndexOptions);
|
||||||
|
|
||||||
|
// Now set up for the info center.
|
||||||
|
$context['info_center'] = array();
|
||||||
|
|
||||||
|
// Retrieve the latest posts if the theme settings require it.
|
||||||
|
if (!empty($settings['number_recent_posts']))
|
||||||
|
{
|
||||||
|
if ($settings['number_recent_posts'] > 1)
|
||||||
|
{
|
||||||
|
$latestPostOptions = array(
|
||||||
|
'number_posts' => $settings['number_recent_posts'],
|
||||||
|
);
|
||||||
|
$context['latest_posts'] = cache_quick_get('boardindex-latest_posts:' . md5($user_info['query_wanna_see_board'] . $user_info['language']), 'Subs-Recent.php', 'cache_getLastPosts', array($latestPostOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($context['latest_posts']) || !empty($context['latest_post']))
|
||||||
|
$context['info_center'][] = array(
|
||||||
|
'tpl' => 'recent',
|
||||||
|
'txt' => 'recent_posts',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the calendar?
|
||||||
|
if (!empty($modSettings['cal_enabled']) && allowedTo('calendar_view'))
|
||||||
|
{
|
||||||
|
// Retrieve the calendar data (events, birthdays, holidays).
|
||||||
|
$eventOptions = array(
|
||||||
|
'include_holidays' => $modSettings['cal_showholidays'] > 1,
|
||||||
|
'include_birthdays' => $modSettings['cal_showbdays'] > 1,
|
||||||
|
'include_events' => $modSettings['cal_showevents'] > 1,
|
||||||
|
'num_days_shown' => empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'],
|
||||||
|
);
|
||||||
|
$context += cache_quick_get('calendar_index_offset_' . $user_info['time_offset'], 'Subs-Calendar.php', 'cache_getRecentEvents', array($eventOptions));
|
||||||
|
|
||||||
|
// Whether one or multiple days are shown on the board index.
|
||||||
|
$context['calendar_only_today'] = $modSettings['cal_days_for_index'] == 1;
|
||||||
|
|
||||||
|
// This is used to show the "how-do-I-edit" help.
|
||||||
|
$context['calendar_can_edit'] = allowedTo('calendar_edit_any');
|
||||||
|
|
||||||
|
if (!empty($context['show_calendar']))
|
||||||
|
$context['info_center'][] = array(
|
||||||
|
'tpl' => 'calendar',
|
||||||
|
'txt' => $context['calendar_only_today'] ? 'calendar_today' : 'calendar_upcoming',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And stats.
|
||||||
|
$context['show_stats'] = allowedTo('view_stats') && !empty($modSettings['trackStats']);
|
||||||
|
if ($settings['show_stats_index'])
|
||||||
|
$context['info_center'][] = array(
|
||||||
|
'tpl' => 'stats',
|
||||||
|
'txt' => 'forum_stats',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now the online stuff
|
||||||
|
require_once($sourcedir . '/Subs-MembersOnline.php');
|
||||||
|
$membersOnlineOptions = array(
|
||||||
|
'show_hidden' => allowedTo('moderate_forum'),
|
||||||
|
'sort' => 'log_time',
|
||||||
|
'reverse_sort' => true,
|
||||||
|
);
|
||||||
|
$context += getMembersOnlineStats($membersOnlineOptions);
|
||||||
|
$context['show_buddies'] = !empty($user_info['buddies']);
|
||||||
|
$context['show_who'] = allowedTo('who_view') && !empty($modSettings['who_enabled']);
|
||||||
|
$context['info_center'][] = array(
|
||||||
|
'tpl' => 'online',
|
||||||
|
'txt' => 'online_users',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Track most online statistics? (Subs-MembersOnline.php)
|
||||||
|
if (!empty($modSettings['trackStats']))
|
||||||
|
trackStatsUsersOnline($context['num_guests'] + $context['num_users_online']);
|
||||||
|
|
||||||
|
// Are we showing all membergroups on the board index?
|
||||||
|
if (!empty($settings['show_group_key']))
|
||||||
|
$context['membergroups'] = cache_quick_get('membergroup_list', 'Subs-Membergroups.php', 'cache_getMembergroupList', array());
|
||||||
|
|
||||||
|
// And back to normality.
|
||||||
|
$context['page_title'] = sprintf($txt['forum_index'], $context['forum_name']);
|
||||||
|
|
||||||
|
// Mark read button
|
||||||
|
$context['mark_read_button'] = array(
|
||||||
|
'markread' => array('text' => 'mark_as_read', 'image' => 'markread.png', 'custom' => 'data-confirm="' . $txt['are_sure_mark_read'] . '"', 'class' => 'you_sure', 'url' => $scripturl . '?action=markasread;sa=all;' . $context['session_var'] . '=' . $context['session_id']),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Replace the collapse and expand default alts.
|
||||||
|
addJavaScriptVar('smf_expandAlt', $txt['show_category'], true);
|
||||||
|
addJavaScriptVar('smf_collapseAlt', $txt['hide_category'], true);
|
||||||
|
|
||||||
|
// Allow mods to add additional buttons here
|
||||||
|
call_integration_hook('integrate_mark_read_button');
|
||||||
|
|
||||||
|
if (!empty($settings['show_newsfader']))
|
||||||
|
{
|
||||||
|
loadJavaScriptFile('slippry.min.js', array(), 'smf_jquery_slippry');
|
||||||
|
loadCSSFile('slider.min.css', array(), 'smf_jquery_slider');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
96
Sources/Cache/APIs/Apcu.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class Apcu extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
$supported = function_exists('apcu_fetch') && function_exists('apcu_store');
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
$value = apcu_fetch($key . 'smf');
|
||||||
|
|
||||||
|
return !empty($value) ? $value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
// An extended key is needed to counteract a bug in APC.
|
||||||
|
if ($value === null)
|
||||||
|
return apcu_delete($key . 'smf');
|
||||||
|
|
||||||
|
else
|
||||||
|
return apcu_store($key . 'smf', $value, $ttl !== null ? $ttl : $this->ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return apcu_clear_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
return phpversion('apcu');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
274
Sources/Cache/APIs/FileBased.php
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use GlobIterator;
|
||||||
|
use FilesystemIterator;
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class FileBased extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The path to the current $cachedir directory.
|
||||||
|
*/
|
||||||
|
private $cachedir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Set our default cachedir.
|
||||||
|
$this->setCachedir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
$supported = is_writable($this->cachedir);
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readFile($file)
|
||||||
|
{
|
||||||
|
if (($fp = @fopen($file, 'rb')) !== false)
|
||||||
|
{
|
||||||
|
if (!flock($fp, LOCK_SH))
|
||||||
|
{
|
||||||
|
fclose($fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$string = '';
|
||||||
|
while (!feof($fp))
|
||||||
|
$string .= fread($fp, 8192);
|
||||||
|
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeFile($file, $string)
|
||||||
|
{
|
||||||
|
if (($fp = fopen($file, 'cb')) !== false)
|
||||||
|
{
|
||||||
|
if (!flock($fp, LOCK_EX))
|
||||||
|
{
|
||||||
|
fclose($fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ftruncate($fp, 0);
|
||||||
|
$bytes = 0;
|
||||||
|
$pieces = str_split($string, 8192);
|
||||||
|
foreach ($pieces as $piece)
|
||||||
|
{
|
||||||
|
if (($val = fwrite($fp, $piece, 8192)) !== false)
|
||||||
|
$bytes += $val;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fflush($fp);
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
return $bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$file = sprintf('%s/data_%s.cache',
|
||||||
|
$this->cachedir,
|
||||||
|
$this->prefix . strtr($key, ':/', '-_')
|
||||||
|
);
|
||||||
|
|
||||||
|
// SMF Data returns $value and $expired. $expired has a unix timestamp of when this expires.
|
||||||
|
if (file_exists($file) && ($raw = $this->readFile($file)) !== false)
|
||||||
|
{
|
||||||
|
if (($value = smf_json_decode($raw, true, false)) !== array() && isset($value['expiration']) && $value['expiration'] >= time())
|
||||||
|
return $value['value'];
|
||||||
|
else
|
||||||
|
@unlink($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$file = sprintf('%s/data_%s.cache',
|
||||||
|
$this->cachedir,
|
||||||
|
$this->prefix . strtr($key, ':/', '-_')
|
||||||
|
);
|
||||||
|
$ttl = $ttl !== null ? $ttl : $this->ttl;
|
||||||
|
|
||||||
|
if ($value === null)
|
||||||
|
@unlink($file);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$cache_data = json_encode(
|
||||||
|
array(
|
||||||
|
'expiration' => time() + $ttl,
|
||||||
|
'value' => $value
|
||||||
|
),
|
||||||
|
JSON_NUMERIC_CHECK
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write out the cache file, check that the cache write was successful; all the data must be written
|
||||||
|
// If it fails due to low diskspace, or other, remove the cache file
|
||||||
|
if ($this->writeFile($file, $cache_data) !== strlen($cache_data))
|
||||||
|
{
|
||||||
|
@unlink($file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
// No directory = no game.
|
||||||
|
if (!is_dir($this->cachedir))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove the files in SMF's own disk cache, if any
|
||||||
|
$files = new GlobIterator($this->cachedir . '/' . $type . '*.cache', FilesystemIterator::NEW_CURRENT_AND_KEY);
|
||||||
|
|
||||||
|
foreach ($files as $file => $info)
|
||||||
|
unlink($this->cachedir . '/' . $file);
|
||||||
|
|
||||||
|
// Make this invalid.
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function invalidateCache()
|
||||||
|
{
|
||||||
|
// We don't worry about $cachedir here, since the key is based on the real $cachedir.
|
||||||
|
parent::invalidateCache();
|
||||||
|
|
||||||
|
// Since SMF is file based, be sure to clear the statcache.
|
||||||
|
clearstatcache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cacheSettings(array &$config_vars)
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
$class_name = $this->getImplementationClassKeyName();
|
||||||
|
$class_name_txt_key = strtolower($class_name);
|
||||||
|
|
||||||
|
$config_vars[] = $txt['cache_'. $class_name_txt_key .'_settings'];
|
||||||
|
$config_vars[] = array('cachedir', $txt['cachedir'], 'file', 'text', 36, 'cache_cachedir');
|
||||||
|
|
||||||
|
if (!isset($context['settings_post_javascript']))
|
||||||
|
$context['settings_post_javascript'] = '';
|
||||||
|
|
||||||
|
if (empty($context['settings_not_writable']))
|
||||||
|
$context['settings_post_javascript'] .= '
|
||||||
|
$("#cache_accelerator").change(function (e) {
|
||||||
|
var cache_type = e.currentTarget.value;
|
||||||
|
$("#cachedir").prop("disabled", cache_type != "'. $class_name .'");
|
||||||
|
});';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the $cachedir or uses the SMF default $cachedir..
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $dir A valid path
|
||||||
|
* @return boolean If this was successful or not.
|
||||||
|
*/
|
||||||
|
public function setCachedir($dir = null)
|
||||||
|
{
|
||||||
|
global $cachedir;
|
||||||
|
|
||||||
|
// If its invalid, use SMF's.
|
||||||
|
if (is_null($dir) || !is_writable($dir))
|
||||||
|
$this->cachedir = $cachedir;
|
||||||
|
|
||||||
|
else
|
||||||
|
$this->cachedir = $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current $cachedir.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the value of $ttl.
|
||||||
|
*/
|
||||||
|
public function getCachedir()
|
||||||
|
{
|
||||||
|
return $this->cachedir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
return SMF_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
193
Sources/Cache/APIs/MemcacheImplementation.php
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use Memcache;
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class MemcacheImplementation extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
const CLASS_KEY = 'cache_memcached';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Memcache The memcache instance.
|
||||||
|
*/
|
||||||
|
private $memcache = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
global $cache_memcached;
|
||||||
|
|
||||||
|
$supported = class_exists('Memcache');
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported && !empty($cache_memcached);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
global $db_persist, $cache_memcached;
|
||||||
|
|
||||||
|
$this->memcache = new Memcache();
|
||||||
|
|
||||||
|
$servers = explode(',', $cache_memcached);
|
||||||
|
$port = 0;
|
||||||
|
|
||||||
|
// Don't try more times than we have servers!
|
||||||
|
$connected = false;
|
||||||
|
$level = 0;
|
||||||
|
|
||||||
|
// We should keep trying if a server times out, but only for the amount of servers we have.
|
||||||
|
while (!$connected && $level < count($servers))
|
||||||
|
{
|
||||||
|
++$level;
|
||||||
|
|
||||||
|
$server = trim($servers[array_rand($servers)]);
|
||||||
|
|
||||||
|
// No server, can't connect to this.
|
||||||
|
if (empty($server))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Normal host names do not contain slashes, while e.g. unix sockets do. Assume alternative transport pipe with port 0.
|
||||||
|
if (strpos($server, '/') !== false)
|
||||||
|
$host = $server;
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$server = explode(':', $server);
|
||||||
|
$host = $server[0];
|
||||||
|
$port = isset($server[1]) ? $server[1] : 11211;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't wait too long: yes, we want the server, but we might be able to run the query faster!
|
||||||
|
if (empty($db_persist))
|
||||||
|
$connected = $this->memcache->connect($host, $port);
|
||||||
|
|
||||||
|
else
|
||||||
|
$connected = $this->memcache->pconnect($host, $port);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
$value = $this->memcache->get($key);
|
||||||
|
|
||||||
|
// $value should return either data or false (from failure, key not found or empty array).
|
||||||
|
if ($value === false)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
return $this->memcache->set($key, $value, 0, $ttl !== null ? $ttl : $this->ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function quit()
|
||||||
|
{
|
||||||
|
return $this->memcache->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return $this->memcache->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cacheSettings(array &$config_vars)
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
if (!in_array($txt[self::CLASS_KEY .'_settings'], $config_vars))
|
||||||
|
{
|
||||||
|
$config_vars[] = $txt[self::CLASS_KEY .'_settings'];
|
||||||
|
$config_vars[] = array(
|
||||||
|
self::CLASS_KEY,
|
||||||
|
$txt[self::CLASS_KEY .'_servers'],
|
||||||
|
'file',
|
||||||
|
'text',
|
||||||
|
0,
|
||||||
|
'subtext' => $txt[self::CLASS_KEY .'_servers_subtext']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($context['settings_post_javascript']))
|
||||||
|
$context['settings_post_javascript'] = '';
|
||||||
|
|
||||||
|
if (empty($context['settings_not_writable']))
|
||||||
|
$context['settings_post_javascript'] .= '
|
||||||
|
$("#cache_accelerator").change(function (e) {
|
||||||
|
var cache_type = e.currentTarget.value;
|
||||||
|
$("#'. self::CLASS_KEY .'").prop("disabled", cache_type != "MemcacheImplementation" && cache_type != "MemcachedImplementation");
|
||||||
|
});';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
if (!is_object($this->memcache))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// This gets called in Subs-Admin getServerVersions when loading up support information. If we can't get a connection, return nothing.
|
||||||
|
$result = $this->memcache->getVersion();
|
||||||
|
|
||||||
|
if (!empty($result))
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
213
Sources/Cache/APIs/MemcachedImplementation.php
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use Memcached;
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class MemcachedImplementation extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
const CLASS_KEY = 'cache_memcached';
|
||||||
|
|
||||||
|
/** @var Memcached The memcache instance. */
|
||||||
|
private $memcached = null;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private $servers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
global $cache_memcached;
|
||||||
|
|
||||||
|
$this->servers = array_map(
|
||||||
|
function($server)
|
||||||
|
{
|
||||||
|
if (strpos($server, '/') !== false)
|
||||||
|
return array($server, 0);
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$server = explode(':', $server);
|
||||||
|
return array($server[0], isset($server[1]) ? (int) $server[1] : 11211);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
explode(',', $cache_memcached)
|
||||||
|
);
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
global $cache_memcached;
|
||||||
|
|
||||||
|
$supported = class_exists('Memcached');
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported && !empty($cache_memcached);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
$this->memcached = new Memcached;
|
||||||
|
|
||||||
|
return $this->addServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add memcached servers.
|
||||||
|
*
|
||||||
|
* Don't add servers if they already exist. Ideal for persistent connections.
|
||||||
|
*
|
||||||
|
* @return bool True if there are servers in the daemon, false if not.
|
||||||
|
*/
|
||||||
|
protected function addServers()
|
||||||
|
{
|
||||||
|
$currentServers = $this->memcached->getServerList();
|
||||||
|
$retVal = !empty($currentServers);
|
||||||
|
foreach ($this->servers as $server)
|
||||||
|
{
|
||||||
|
// Figure out if we have this server or not
|
||||||
|
$foundServer = false;
|
||||||
|
foreach ($currentServers as $currentServer)
|
||||||
|
{
|
||||||
|
if ($server[0] == $currentServer['host'] && $server[1] == $currentServer['port'])
|
||||||
|
{
|
||||||
|
$foundServer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found it?
|
||||||
|
if (empty($foundServer))
|
||||||
|
$retVal |= $this->memcached->addServer($server[0], $server[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
$value = $this->memcached->get($key);
|
||||||
|
|
||||||
|
// $value should return either data or false (from failure, key not found or empty array).
|
||||||
|
if ($value === false)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
return $this->memcached->set($key, $value, $ttl !== null ? $ttl : $this->ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
// Memcached accepts a delay parameter, always use 0 (instant).
|
||||||
|
return $this->memcached->flush(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function quit()
|
||||||
|
{
|
||||||
|
return $this->memcached->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cacheSettings(array &$config_vars)
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
if (!in_array($txt[self::CLASS_KEY .'_settings'], $config_vars))
|
||||||
|
{
|
||||||
|
$config_vars[] = $txt[self::CLASS_KEY .'_settings'];
|
||||||
|
$config_vars[] = array(
|
||||||
|
self::CLASS_KEY,
|
||||||
|
$txt[self::CLASS_KEY .'_servers'],
|
||||||
|
'file',
|
||||||
|
'text',
|
||||||
|
0,
|
||||||
|
'subtext' => $txt[self::CLASS_KEY .'_servers_subtext']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($context['settings_post_javascript']))
|
||||||
|
$context['settings_post_javascript'] = '';
|
||||||
|
|
||||||
|
if (empty($context['settings_not_writable']))
|
||||||
|
$context['settings_post_javascript'] .= '
|
||||||
|
$("#cache_accelerator").change(function (e) {
|
||||||
|
var cache_type = e.currentTarget.value;
|
||||||
|
$("#'. self::CLASS_KEY .'").prop("disabled", cache_type != "MemcacheImplementation" && cache_type != "MemcachedImplementation");
|
||||||
|
});';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
if (!is_object($this->memcached))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// This gets called in Subs-Admin getServerVersions when loading up support information. If we can't get a connection, return nothing.
|
||||||
|
$result = $this->memcached->getVersion();
|
||||||
|
|
||||||
|
if (!empty($result))
|
||||||
|
return current($result);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
220
Sources/Cache/APIs/Postgres.php
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class Postgres extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $db_prefix;
|
||||||
|
|
||||||
|
/** @var resource result of pg_connect. */
|
||||||
|
private $db_connection;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
global $db_prefix, $db_connection;
|
||||||
|
|
||||||
|
$this->db_prefix = $db_prefix;
|
||||||
|
$this->db_connection = $db_connection;
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
$result = pg_query_params($this->db_connection, 'SELECT 1
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE schemaname = $1
|
||||||
|
AND tablename = $2',
|
||||||
|
array(
|
||||||
|
'public',
|
||||||
|
$this->db_prefix . 'cache',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pg_affected_rows($result) === 0)
|
||||||
|
pg_query($this->db_connection, 'CREATE UNLOGGED TABLE ' . $this->db_prefix . 'cache (key text, value text, ttl bigint, PRIMARY KEY (key))');
|
||||||
|
|
||||||
|
$this->prepareQueries(
|
||||||
|
array(
|
||||||
|
'smf_cache_get_data',
|
||||||
|
'smf_cache_put_data',
|
||||||
|
'smf_cache_delete_data',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'SELECT value FROM ' . $this->db_prefix . 'cache WHERE key = $1 AND ttl >= $2 LIMIT 1',
|
||||||
|
'INSERT INTO ' . $this->db_prefix . 'cache(key,value,ttl) VALUES($1,$2,$3)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = $2, ttl = $3',
|
||||||
|
'DELETE FROM ' . $this->db_prefix . 'cache WHERE key = $1',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a prepared SQL statement, ensuring that it's not done twice.
|
||||||
|
*
|
||||||
|
* @param array $stmtnames
|
||||||
|
* @param array $queries
|
||||||
|
*/
|
||||||
|
private function prepareQueries(array $stmtnames, array $queries)
|
||||||
|
{
|
||||||
|
$result = pg_query_params(
|
||||||
|
$this->db_connection,
|
||||||
|
'SELECT name FROM pg_prepared_statements WHERE name = ANY ($1)',
|
||||||
|
array('{' . implode(', ', $stmtnames) . '}')
|
||||||
|
);
|
||||||
|
|
||||||
|
$arr = pg_num_rows($result) == 0 ? array() : array_map(
|
||||||
|
function($el)
|
||||||
|
{
|
||||||
|
return $el['name'];
|
||||||
|
},
|
||||||
|
pg_fetch_all($result)
|
||||||
|
);
|
||||||
|
foreach ($stmtnames as $idx => $stmtname)
|
||||||
|
if (!in_array($stmtname, $arr))
|
||||||
|
pg_prepare($this->db_connection, $stmtname, $queries[$idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if ($smcFunc['db_title'] !== POSTGRE_TITLE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$result = pg_query($this->db_connection, 'SHOW server_version_num');
|
||||||
|
$res = pg_fetch_assoc($result);
|
||||||
|
|
||||||
|
if ($res['server_version_num'] < 90500)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return $test ? true : parent::isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$result = pg_execute($this->db_connection, 'smf_cache_get_data', array($key, time()));
|
||||||
|
|
||||||
|
if (pg_affected_rows($result) === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
$res = pg_fetch_assoc($result);
|
||||||
|
|
||||||
|
return $res['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$ttl = time() + (int) ($ttl !== null ? $ttl : $this->ttl);
|
||||||
|
|
||||||
|
if ($value === null)
|
||||||
|
$result = pg_execute($this->db_connection, 'smf_cache_delete_data', array($key));
|
||||||
|
else
|
||||||
|
$result = pg_execute($this->db_connection, 'smf_cache_put_data', array($key, $value, $ttl));
|
||||||
|
|
||||||
|
return pg_affected_rows($result) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
if ($type == 'expired')
|
||||||
|
pg_query($this->db_connection, 'DELETE FROM ' . $this->db_prefix . 'cache WHERE ttl < ' . time() . ';');
|
||||||
|
else
|
||||||
|
pg_query($this->db_connection, 'TRUNCATE ' . $this->db_prefix . 'cache');
|
||||||
|
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
return pg_version($this->db_connection)['server'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function housekeeping()
|
||||||
|
{
|
||||||
|
$this->createTempTable();
|
||||||
|
$this->cleanCache();
|
||||||
|
$this->retrieveData();
|
||||||
|
$this->deleteTempTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the temp table of valid data.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function createTempTable()
|
||||||
|
{
|
||||||
|
pg_query($this->db_connection, 'CREATE LOCAL TEMP TABLE IF NOT EXISTS ' . $this->db_prefix . 'cache_tmp AS SELECT * FROM ' . $this->db_prefix . 'cache WHERE ttl >= ' . time());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the temp table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function deleteTempTable()
|
||||||
|
{
|
||||||
|
pg_query($this->db_connection, 'DROP TABLE IF EXISTS ' . $this->db_prefix . 'cache_tmp');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the valid data from temp table.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function retrieveData()
|
||||||
|
{
|
||||||
|
pg_query($this->db_connection, 'INSERT INTO ' . $this->db_prefix . 'cache SELECT * FROM ' . $this->db_prefix . 'cache_tmp ON CONFLICT DO NOTHING');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
207
Sources/Cache/APIs/Sqlite.php
Executable file
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
use SQLite3;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class Sqlite extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The path to the current $cachedir directory.
|
||||||
|
*/
|
||||||
|
private $cachedir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SQLite3
|
||||||
|
*/
|
||||||
|
private $cacheDB = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Set our default cachedir.
|
||||||
|
$this->setCachedir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
$database = $this->cachedir . '/' . 'SQLite3Cache.db3';
|
||||||
|
$this->cacheDB = new SQLite3($database);
|
||||||
|
$this->cacheDB->busyTimeout(1000);
|
||||||
|
if (filesize($database) == 0)
|
||||||
|
{
|
||||||
|
$this->cacheDB->exec('CREATE TABLE cache (key text unique, value blob, ttl int);');
|
||||||
|
$this->cacheDB->exec('CREATE INDEX ttls ON cache(ttl);');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
$supported = class_exists("SQLite3") && is_writable($this->cachedir);
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$query = 'SELECT value FROM cache WHERE key = \'' . $this->cacheDB->escapeString($key) . '\' AND ttl >= ' . time() . ' LIMIT 1';
|
||||||
|
$result = $this->cacheDB->query($query);
|
||||||
|
|
||||||
|
$value = null;
|
||||||
|
while ($res = $result->fetchArray(SQLITE3_ASSOC))
|
||||||
|
$value = $res['value'];
|
||||||
|
|
||||||
|
return !empty($value) ? $value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$ttl = time() + (int) ($ttl !== null ? $ttl : $this->ttl);
|
||||||
|
if ($value === null)
|
||||||
|
$query = 'DELETE FROM cache WHERE key = \'' . $this->cacheDB->escapeString($key) . '\';';
|
||||||
|
else
|
||||||
|
$query = 'REPLACE INTO cache VALUES (\'' . $this->cacheDB->escapeString($key) . '\', \'' . $this->cacheDB->escapeString($value) . '\', ' . $ttl . ');';
|
||||||
|
$result = $this->cacheDB->exec($query);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
if ($type == 'expired')
|
||||||
|
$query = 'DELETE FROM cache WHERE ttl < ' . time() . ';';
|
||||||
|
else
|
||||||
|
$query = 'DELETE FROM cache;';
|
||||||
|
|
||||||
|
$result = $this->cacheDB->exec($query);
|
||||||
|
|
||||||
|
$query = 'VACUUM;';
|
||||||
|
$this->cacheDB->exec($query);
|
||||||
|
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cacheSettings(array &$config_vars)
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
$class_name = $this->getImplementationClassKeyName();
|
||||||
|
$class_name_txt_key = strtolower($class_name);
|
||||||
|
|
||||||
|
$config_vars[] = $txt['cache_'. $class_name_txt_key .'_settings'];
|
||||||
|
$config_vars[] = array(
|
||||||
|
'cachedir_'. $class_name_txt_key,
|
||||||
|
$txt['cachedir_'. $class_name_txt_key],
|
||||||
|
'file',
|
||||||
|
'text',
|
||||||
|
36,
|
||||||
|
'cache_'. $class_name_txt_key .'_cachedir',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isset($context['settings_post_javascript']))
|
||||||
|
$context['settings_post_javascript'] = '';
|
||||||
|
|
||||||
|
if (empty($context['settings_not_writable']))
|
||||||
|
$context['settings_post_javascript'] .= '
|
||||||
|
$("#cache_accelerator").change(function (e) {
|
||||||
|
var cache_type = e.currentTarget.value;
|
||||||
|
$("#cachedir_'. $class_name_txt_key .'").prop("disabled", cache_type != "'. $class_name .'");
|
||||||
|
});';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the $cachedir or uses the SMF default $cachedir..
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*
|
||||||
|
* @param string $dir A valid path
|
||||||
|
*
|
||||||
|
* @return boolean If this was successful or not.
|
||||||
|
*/
|
||||||
|
public function setCachedir($dir = null)
|
||||||
|
{
|
||||||
|
global $cachedir, $cachedir_sqlite, $sourcedir;
|
||||||
|
|
||||||
|
// If its invalid, use SMF's.
|
||||||
|
if (!isset($dir) || !is_writable($dir))
|
||||||
|
{
|
||||||
|
if (!isset($cachedir_sqlite) || !is_writable($cachedir_sqlite))
|
||||||
|
{
|
||||||
|
$cachedir_sqlite = $cachedir;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Admin.php');
|
||||||
|
updateSettingsFile(array('cachedir_sqlite' => $cachedir_sqlite));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cachedir = $cachedir_sqlite;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->cachedir = $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
if (null == $this->cacheDB)
|
||||||
|
$this->connect();
|
||||||
|
|
||||||
|
return $this->cacheDB->version()['versionString'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function housekeeping()
|
||||||
|
{
|
||||||
|
$this->cleanCache('expired');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
95
Sources/Cache/APIs/Zend.php
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache\APIs;
|
||||||
|
|
||||||
|
use SMF\Cache\CacheApi;
|
||||||
|
use SMF\Cache\CacheApiInterface;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our Cache API class
|
||||||
|
*
|
||||||
|
* @package CacheAPI
|
||||||
|
*/
|
||||||
|
class Zend extends CacheApi implements CacheApiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
$supported = function_exists('zend_shm_cache_fetch') || function_exists('output_cache_get');
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return $supported;
|
||||||
|
|
||||||
|
return parent::isSupported() && $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
// Zend's pricey stuff.
|
||||||
|
if (function_exists('zend_shm_cache_fetch'))
|
||||||
|
return zend_shm_cache_fetch('SMF::' . $key);
|
||||||
|
|
||||||
|
elseif (function_exists('output_cache_get'))
|
||||||
|
return output_cache_get($key, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null)
|
||||||
|
{
|
||||||
|
$key = $this->prefix . strtr($key, ':/', '-_');
|
||||||
|
|
||||||
|
if (function_exists('zend_shm_cache_store'))
|
||||||
|
return zend_shm_cache_store('SMF::' . $key, $value, $ttl);
|
||||||
|
|
||||||
|
elseif (function_exists('output_cache_put'))
|
||||||
|
return output_cache_put($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '')
|
||||||
|
{
|
||||||
|
$this->invalidateCache();
|
||||||
|
|
||||||
|
return zend_shm_cache_clear('SMF');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
return zend_version();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
252
Sources/Cache/CacheApi.php
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
abstract class CacheApi
|
||||||
|
{
|
||||||
|
const APIS_FOLDER = 'APIs';
|
||||||
|
const APIS_NAMESPACE = 'SMF\Cache\APIs\\';
|
||||||
|
const APIS_DEFAULT = 'FileBased';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The maximum SMF version that this will work with.
|
||||||
|
*/
|
||||||
|
protected $version_compatible = '2.1.999';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The minimum SMF version that this will work with.
|
||||||
|
*/
|
||||||
|
protected $min_smf_version = '2.1 RC1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The prefix for all keys.
|
||||||
|
*/
|
||||||
|
protected $prefix = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int The default TTL.
|
||||||
|
*/
|
||||||
|
protected $ttl = 120;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does basic setup of a cache method when we create the object but before we call connect.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->setPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we can use the cache method performed by this API.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param bool $test Test if this is supported or enabled.
|
||||||
|
* @return bool Whether or not the cache is supported
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false)
|
||||||
|
{
|
||||||
|
global $cache_enable;
|
||||||
|
|
||||||
|
if ($test)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !empty($cache_enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cache prefix.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $prefix The prefix to use.
|
||||||
|
* If empty, the prefix will be generated automatically.
|
||||||
|
* @return bool If this was successful or not.
|
||||||
|
*/
|
||||||
|
public function setPrefix($prefix = '')
|
||||||
|
{
|
||||||
|
global $boardurl, $cachedir, $boarddir;
|
||||||
|
|
||||||
|
if (!is_string($prefix))
|
||||||
|
$prefix = '';
|
||||||
|
|
||||||
|
// Use the supplied prefix, if there is one.
|
||||||
|
if (!empty($prefix))
|
||||||
|
{
|
||||||
|
$this->prefix = $prefix;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally the prefix should reflect the last time the cache was reset.
|
||||||
|
if (!empty($cachedir) && file_exists($cachedir . '/index.php'))
|
||||||
|
{
|
||||||
|
$mtime = filemtime($cachedir . '/index.php');
|
||||||
|
}
|
||||||
|
// Fall back to the last time that Settings.php was updated.
|
||||||
|
elseif (!empty($boarddir) && file_exists($boarddir . '/Settings.php'))
|
||||||
|
{
|
||||||
|
$mtime = filemtime($boarddir . '/Settings.php');
|
||||||
|
}
|
||||||
|
// This should never happen, but just in case...
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$mtime = filemtime(realpath($_SERVER['SCRIPT_FILENAME']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->prefix = md5($boardurl . $mtime) . '-SMF-';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the prefix as defined from set or the default.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the value of $key.
|
||||||
|
*/
|
||||||
|
public function getPrefix()
|
||||||
|
{
|
||||||
|
return $this->prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a default Time To Live, if this isn't specified we let the class define it.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param int $ttl The default TTL
|
||||||
|
* @return bool If this was successful or not.
|
||||||
|
*/
|
||||||
|
public function setDefaultTTL($ttl = 120)
|
||||||
|
{
|
||||||
|
$this->ttl = $ttl;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the TTL as defined from set or the default.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return int the value of $ttl.
|
||||||
|
*/
|
||||||
|
public function getDefaultTTL()
|
||||||
|
{
|
||||||
|
return $this->ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate all cached data.
|
||||||
|
*
|
||||||
|
* @return bool Whether or not we could invalidate the cache.
|
||||||
|
*/
|
||||||
|
public function invalidateCache()
|
||||||
|
{
|
||||||
|
global $cachedir;
|
||||||
|
|
||||||
|
// Invalidate cache, to be sure!
|
||||||
|
// ... as long as index.php can be modified, anyway.
|
||||||
|
if (is_writable($cachedir . '/' . 'index.php'))
|
||||||
|
@touch($cachedir . '/' . 'index.php');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes connections to the cache method.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool Whether the connections were closed.
|
||||||
|
*/
|
||||||
|
public function quit()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify custom settings that the cache API supports.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $config_vars Additional config_vars, see ManageSettings.php for usage.
|
||||||
|
*/
|
||||||
|
public function cacheSettings(array &$config_vars)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the latest version of SMF this is compatible with.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the value of $key.
|
||||||
|
*/
|
||||||
|
public function getCompatibleVersion()
|
||||||
|
{
|
||||||
|
return $this->version_compatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the min version that we support.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the value of $key.
|
||||||
|
*/
|
||||||
|
public function getMinimumVersion()
|
||||||
|
{
|
||||||
|
return $this->min_smf_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the Version of the Caching API.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the value of $key.
|
||||||
|
*/
|
||||||
|
public function getVersion()
|
||||||
|
{
|
||||||
|
return $this->min_smf_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run housekeeping of this cache
|
||||||
|
* exp. clean up old data or do optimization
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function housekeeping()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the class identifier of the current caching API implementation.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the unique identifier for the current class implementation.
|
||||||
|
*/
|
||||||
|
public function getImplementationClassKeyName()
|
||||||
|
{
|
||||||
|
$class_name = get_class($this);
|
||||||
|
|
||||||
|
if ($position = strrpos($class_name, '\\'))
|
||||||
|
return substr($class_name, $position + 1);
|
||||||
|
|
||||||
|
else
|
||||||
|
return get_class($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
82
Sources/Cache/CacheApiInterface.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SMF\Cache;
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
|
||||||
|
interface CacheApiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks whether we can use the cache method performed by this API.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param bool $test Test if this is supported or enabled.
|
||||||
|
* @return bool Whether or not the cache is supported
|
||||||
|
*/
|
||||||
|
public function isSupported($test = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to the cache method. This defines our $key. If this fails, we return false, otherwise we return true.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool Whether or not the cache method was connected to.
|
||||||
|
*/
|
||||||
|
public function connect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an item from the cache.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $key The key to use, the prefix is applied to the key name.
|
||||||
|
* @param int $ttl Overrides the default TTL. Not really used anymore,
|
||||||
|
* but is kept for backwards compatibility.
|
||||||
|
* @return mixed The result from the cache, if there is no data or it is invalid, we return null.
|
||||||
|
* @todo Seperate existence checking into its own method
|
||||||
|
*/
|
||||||
|
public function getData($key, $ttl = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a value, regardless of whether or not the key already exists (in
|
||||||
|
* which case it will overwrite the existing value for that key).
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $key The key to use, the prefix is applied to the key name.
|
||||||
|
* @param mixed $value The data we wish to save. Use null to delete.
|
||||||
|
* @param int $ttl How long (in seconds) the data should be cached for.
|
||||||
|
* The default TTL will be used if this is null.
|
||||||
|
* @return bool Whether or not we could save this to the cache.
|
||||||
|
* @todo Seperate deletion into its own method
|
||||||
|
*/
|
||||||
|
public function putData($key, $value, $ttl = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean out the cache.
|
||||||
|
*
|
||||||
|
* @param string $type If supported, the type of cache to clear, blank/data or user.
|
||||||
|
* @return bool Whether or not we could clean the cache.
|
||||||
|
*/
|
||||||
|
public function cleanCache($type = '');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the class identifier of the current caching API implementation.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return string the unique identifier for the current class implementation.
|
||||||
|
*/
|
||||||
|
public function getImplementationClassKeyName();
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
736
Sources/Calendar.php
Normal file
|
@ -0,0 +1,736 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file has only one real task, showing the calendar.
|
||||||
|
* Original module by Aaron O'Neil - aaron@mud-master.com
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the calendar.
|
||||||
|
* It loads the specified month's events, holidays, and birthdays.
|
||||||
|
* It requires the calendar_view permission.
|
||||||
|
* It depends on the cal_enabled setting, and many of the other cal_ settings.
|
||||||
|
* It uses the calendar_start_day theme option. (Monday/Sunday)
|
||||||
|
* It uses the main sub template in the Calendar template.
|
||||||
|
* It goes to the month and year passed in 'month' and 'year' by get or post.
|
||||||
|
* It is accessed through ?action=calendar.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function CalendarMain()
|
||||||
|
{
|
||||||
|
global $txt, $context, $modSettings, $scripturl, $options, $sourcedir, $user_info, $smcFunc;
|
||||||
|
|
||||||
|
// Permissions, permissions, permissions.
|
||||||
|
isAllowedTo('calendar_view');
|
||||||
|
|
||||||
|
// Some global template resources.
|
||||||
|
$context['calendar_resources'] = array(
|
||||||
|
'min_year' => $modSettings['cal_minyear'],
|
||||||
|
'max_year' => $modSettings['cal_maxyear'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Doing something other than calendar viewing?
|
||||||
|
$subActions = array(
|
||||||
|
'ical' => 'iCalDownload',
|
||||||
|
'post' => 'CalendarPost',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($_GET['sa']) && isset($subActions[$_GET['sa']]))
|
||||||
|
return call_helper($subActions[$_GET['sa']]);
|
||||||
|
|
||||||
|
// You can't do anything if the calendar is off.
|
||||||
|
if (empty($modSettings['cal_enabled']))
|
||||||
|
fatal_lang_error('calendar_off', false);
|
||||||
|
|
||||||
|
// This is gonna be needed...
|
||||||
|
loadTemplate('Calendar');
|
||||||
|
loadCSSFile('calendar.css', array('force_current' => false, 'validate' => true, 'rtl' => 'calendar.rtl.css'), 'smf_calendar');
|
||||||
|
|
||||||
|
// Did the specify an individual event ID? If so, let's splice the year/month in to what we would otherwise be doing.
|
||||||
|
if (isset($_GET['event']))
|
||||||
|
{
|
||||||
|
$evid = (int) $_GET['event'];
|
||||||
|
if ($evid > 0)
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT start_date
|
||||||
|
FROM {db_prefix}calendar
|
||||||
|
WHERE id_event = {int:event_id}',
|
||||||
|
array(
|
||||||
|
'event_id' => $evid,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$_REQUEST['start_date'] = $row['start_date'];
|
||||||
|
|
||||||
|
// We might use this later.
|
||||||
|
$context['selected_event'] = $evid;
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
unset ($_GET['event']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the page title to mention the calendar ;).
|
||||||
|
$context['page_title'] = $txt['calendar'];
|
||||||
|
|
||||||
|
// Ensure a default view is defined
|
||||||
|
if (empty($options['calendar_default_view']))
|
||||||
|
$options['calendar_default_view'] = 'viewlist';
|
||||||
|
|
||||||
|
// What view do we want?
|
||||||
|
if (isset($_GET['viewweek']))
|
||||||
|
$context['calendar_view'] = 'viewweek';
|
||||||
|
elseif (isset($_GET['viewmonth']))
|
||||||
|
$context['calendar_view'] = 'viewmonth';
|
||||||
|
elseif (isset($_GET['viewlist']))
|
||||||
|
$context['calendar_view'] = 'viewlist';
|
||||||
|
else
|
||||||
|
$context['calendar_view'] = $options['calendar_default_view'];
|
||||||
|
|
||||||
|
// Don't let search engines index the non-default calendar pages
|
||||||
|
if ($context['calendar_view'] !== $options['calendar_default_view'])
|
||||||
|
$context['robot_no_index'] = true;
|
||||||
|
|
||||||
|
// Get the current day of month...
|
||||||
|
require_once($sourcedir . '/Subs-Calendar.php');
|
||||||
|
$today = getTodayInfo();
|
||||||
|
|
||||||
|
// Need a start date for all views
|
||||||
|
if (!empty($_REQUEST['start_date']))
|
||||||
|
{
|
||||||
|
$start_parsed = date_parse(str_replace(',', '', convertDateToEnglish($_REQUEST['start_date'])));
|
||||||
|
if (empty($start_parsed['error_count']) && empty($start_parsed['warning_count']))
|
||||||
|
{
|
||||||
|
$_REQUEST['year'] = $start_parsed['year'];
|
||||||
|
$_REQUEST['month'] = $start_parsed['month'];
|
||||||
|
$_REQUEST['day'] = $start_parsed['day'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$year = !empty($_REQUEST['year']) ? (int) $_REQUEST['year'] : $today['year'];
|
||||||
|
$month = !empty($_REQUEST['month']) ? (int) $_REQUEST['month'] : $today['month'];
|
||||||
|
$day = !empty($_REQUEST['day']) ? (int) $_REQUEST['day'] : (!empty($_REQUEST['month']) ? 1 : $today['day']);
|
||||||
|
|
||||||
|
$start_object = checkdate($month, $day, $year) === true ? date_create(implode('-', array($year, $month, $day)) . ' ' . getUserTimezone()) : date_create(implode('-', array($today['year'], $today['month'], $today['day'])) . ' ' . getUserTimezone());
|
||||||
|
|
||||||
|
// Need an end date for the list view
|
||||||
|
if (!empty($_REQUEST['end_date']))
|
||||||
|
{
|
||||||
|
$end_parsed = date_parse(str_replace(',', '', convertDateToEnglish($_REQUEST['end_date'])));
|
||||||
|
if (empty($end_parsed['error_count']) && empty($end_parsed['warning_count']))
|
||||||
|
{
|
||||||
|
$_REQUEST['end_year'] = $end_parsed['year'];
|
||||||
|
$_REQUEST['end_month'] = $end_parsed['month'];
|
||||||
|
$_REQUEST['end_day'] = $end_parsed['day'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$end_year = !empty($_REQUEST['end_year']) ? (int) $_REQUEST['end_year'] : null;
|
||||||
|
$end_month = !empty($_REQUEST['end_month']) ? (int) $_REQUEST['end_month'] : null;
|
||||||
|
$end_day = !empty($_REQUEST['end_day']) ? (int) $_REQUEST['end_day'] : null;
|
||||||
|
|
||||||
|
$end_object = null;
|
||||||
|
|
||||||
|
if (isset($end_month, $end_day, $end_year) && checkdate($end_month, $end_day, $end_year))
|
||||||
|
{
|
||||||
|
$end_object = date_create(implode('-', array($end_year, $end_month, $end_day)) . ' ' . getUserTimezone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($end_object) || $start_object >= $end_object)
|
||||||
|
{
|
||||||
|
$num_days_shown = empty($modSettings['cal_days_for_index']) || $modSettings['cal_days_for_index'] < 1 ? 1 : $modSettings['cal_days_for_index'];
|
||||||
|
|
||||||
|
$end_object = date_create(date_format($start_object, 'Y-m-d') . ' ' . getUserTimezone());
|
||||||
|
|
||||||
|
date_add($end_object, date_interval_create_from_date_string($num_days_shown . ' days'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$curPage = array(
|
||||||
|
'year' => date_format($start_object, 'Y'),
|
||||||
|
'month' => date_format($start_object, 'n'),
|
||||||
|
'day' => date_format($start_object, 'j'),
|
||||||
|
'start_date' => date_format($start_object, 'Y-m-d'),
|
||||||
|
'end_year' => date_format($end_object, 'Y'),
|
||||||
|
'end_month' => date_format($end_object, 'n'),
|
||||||
|
'end_day' => date_format($end_object, 'j'),
|
||||||
|
'end_date' => date_format($end_object, 'Y-m-d'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure the year and month are in valid ranges.
|
||||||
|
if ($curPage['month'] < 1 || $curPage['month'] > 12)
|
||||||
|
fatal_lang_error('invalid_month', false);
|
||||||
|
if ($curPage['year'] < $modSettings['cal_minyear'] || $curPage['year'] > $modSettings['cal_maxyear'])
|
||||||
|
fatal_lang_error('invalid_year', false);
|
||||||
|
// If we have a day clean that too.
|
||||||
|
if ($context['calendar_view'] != 'viewmonth')
|
||||||
|
{
|
||||||
|
$isValid = checkdate($curPage['month'], $curPage['day'], $curPage['year']);
|
||||||
|
if (!$isValid)
|
||||||
|
fatal_lang_error('invalid_day', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all the context information needed to show the calendar grid.
|
||||||
|
$calendarOptions = array(
|
||||||
|
'start_day' => !empty($options['calendar_start_day']) ? $options['calendar_start_day'] : 0,
|
||||||
|
'show_birthdays' => in_array($modSettings['cal_showbdays'], array(1, 2)),
|
||||||
|
'show_events' => in_array($modSettings['cal_showevents'], array(1, 2)),
|
||||||
|
'show_holidays' => in_array($modSettings['cal_showholidays'], array(1, 2)),
|
||||||
|
'show_week_num' => true,
|
||||||
|
'short_day_titles' => !empty($modSettings['cal_short_days']),
|
||||||
|
'short_month_titles' => !empty($modSettings['cal_short_months']),
|
||||||
|
'show_next_prev' => !empty($modSettings['cal_prev_next_links']),
|
||||||
|
'show_week_links' => isset($modSettings['cal_week_links']) ? $modSettings['cal_week_links'] : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load up the main view.
|
||||||
|
if ($context['calendar_view'] == 'viewlist')
|
||||||
|
$context['calendar_grid_main'] = getCalendarList($curPage['start_date'], $curPage['end_date'], $calendarOptions);
|
||||||
|
elseif ($context['calendar_view'] == 'viewweek')
|
||||||
|
$context['calendar_grid_main'] = getCalendarWeek($curPage['start_date'], $calendarOptions);
|
||||||
|
else
|
||||||
|
$context['calendar_grid_main'] = getCalendarGrid($curPage['start_date'], $calendarOptions);
|
||||||
|
|
||||||
|
// Load up the previous and next months.
|
||||||
|
$context['calendar_grid_current'] = getCalendarGrid($curPage['start_date'], $calendarOptions, false, false);
|
||||||
|
|
||||||
|
// Only show previous month if it isn't pre-January of the min-year
|
||||||
|
if ($context['calendar_grid_current']['previous_calendar']['year'] > $modSettings['cal_minyear'] || $curPage['month'] != 1)
|
||||||
|
$context['calendar_grid_prev'] = getCalendarGrid($context['calendar_grid_current']['previous_calendar']['start_date'], $calendarOptions, true, false);
|
||||||
|
|
||||||
|
// Only show next month if it isn't post-December of the max-year
|
||||||
|
if ($context['calendar_grid_current']['next_calendar']['year'] < $modSettings['cal_maxyear'] || $curPage['month'] != 12)
|
||||||
|
$context['calendar_grid_next'] = getCalendarGrid($context['calendar_grid_current']['next_calendar']['start_date'], $calendarOptions, false, false);
|
||||||
|
|
||||||
|
// Basic template stuff.
|
||||||
|
$context['allow_calendar_event'] = allowedTo('calendar_post');
|
||||||
|
|
||||||
|
// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
|
||||||
|
if ($context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
|
||||||
|
{
|
||||||
|
$boards_can_post = boardsAllowedTo('post_new');
|
||||||
|
$context['allow_calendar_event'] &= !empty($boards_can_post);
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['can_post'] = $context['allow_calendar_event'];
|
||||||
|
$context['current_day'] = $curPage['day'];
|
||||||
|
$context['current_month'] = $curPage['month'];
|
||||||
|
$context['current_year'] = $curPage['year'];
|
||||||
|
$context['show_all_birthdays'] = isset($_GET['showbd']);
|
||||||
|
$context['blocks_disabled'] = !empty($modSettings['cal_disable_prev_next']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Set the page title to mention the month or week, too
|
||||||
|
if ($context['calendar_view'] != 'viewlist')
|
||||||
|
$context['page_title'] .= ' - ' . ($context['calendar_view'] == 'viewweek' ? $context['calendar_grid_main']['week_title'] : $txt['months_titles'][$context['current_month']] . ' ' . $context['current_year']);
|
||||||
|
|
||||||
|
// Load up the linktree!
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=calendar',
|
||||||
|
'name' => $txt['calendar']
|
||||||
|
);
|
||||||
|
// Add the current month to the linktree.
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=calendar;year=' . $context['current_year'] . ';month=' . $context['current_month'],
|
||||||
|
'name' => $txt['months_titles'][$context['current_month']] . ' ' . $context['current_year']
|
||||||
|
);
|
||||||
|
// If applicable, add the current week to the linktree.
|
||||||
|
if ($context['calendar_view'] == 'viewweek')
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=calendar;viewweek;year=' . $context['current_year'] . ';month=' . $context['current_month'] . ';day=' . $context['current_day'],
|
||||||
|
'name' => $context['calendar_grid_main']['week_title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the calendar button array.
|
||||||
|
$context['calendar_buttons'] = array();
|
||||||
|
|
||||||
|
if ($context['can_post'])
|
||||||
|
$context['calendar_buttons']['post_event'] = array('text' => 'calendar_post_event', 'image' => 'calendarpe.png', 'url' => $scripturl . '?action=calendar;sa=post;month=' . $context['current_month'] . ';year=' . $context['current_year'] . ';' . $context['session_var'] . '=' . $context['session_id']);
|
||||||
|
|
||||||
|
// Allow mods to add additional buttons here
|
||||||
|
call_integration_hook('integrate_calendar_buttons');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function processes posting/editing/deleting a calendar event.
|
||||||
|
*
|
||||||
|
* - calls {@link Post.php|Post() Post()} function if event is linked to a post.
|
||||||
|
* - calls {@link Subs-Calendar.php|insertEvent() insertEvent()} to insert the event if not linked to post.
|
||||||
|
*
|
||||||
|
* It requires the calendar_post permission to use.
|
||||||
|
* It uses the event_post sub template in the Calendar template.
|
||||||
|
* It is accessed with ?action=calendar;sa=post.
|
||||||
|
*/
|
||||||
|
function CalendarPost()
|
||||||
|
{
|
||||||
|
global $context, $txt, $user_info, $sourcedir, $scripturl;
|
||||||
|
global $modSettings, $topic, $smcFunc;
|
||||||
|
|
||||||
|
// Well - can they?
|
||||||
|
isAllowedTo('calendar_post');
|
||||||
|
|
||||||
|
// We need these for all kinds of useful functions.
|
||||||
|
require_once($sourcedir . '/Subs-Calendar.php');
|
||||||
|
require_once($sourcedir . '/Subs.php');
|
||||||
|
|
||||||
|
// Cast this for safety...
|
||||||
|
if (isset($_REQUEST['eventid']))
|
||||||
|
$_REQUEST['eventid'] = (int) $_REQUEST['eventid'];
|
||||||
|
|
||||||
|
// We want a fairly compact version of the time, but as close as possible to the user's settings.
|
||||||
|
$time_string = strtr(get_date_or_time_format('time'), array(
|
||||||
|
'%I' => '%l',
|
||||||
|
'%H' => '%k',
|
||||||
|
'%S' => '',
|
||||||
|
'%r' => '%l:%M %p',
|
||||||
|
'%R' => '%k:%M',
|
||||||
|
'%T' => '%l:%M',
|
||||||
|
));
|
||||||
|
|
||||||
|
$time_string = preg_replace('~:(?=\s|$|%[pPzZ])~', '', $time_string);
|
||||||
|
|
||||||
|
// Submitting?
|
||||||
|
if (isset($_POST[$context['session_var']], $_REQUEST['eventid']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
// Validate the post...
|
||||||
|
if (!isset($_POST['link_to_board']))
|
||||||
|
validateEventPost();
|
||||||
|
|
||||||
|
// If you're not allowed to edit any events, you have to be the poster.
|
||||||
|
if ($_REQUEST['eventid'] > 0 && !allowedTo('calendar_edit_any'))
|
||||||
|
isAllowedTo('calendar_edit_' . (!empty($user_info['id']) && getEventPoster($_REQUEST['eventid']) == $user_info['id'] ? 'own' : 'any'));
|
||||||
|
|
||||||
|
// New - and directing?
|
||||||
|
if (isset($_POST['link_to_board']) || empty($modSettings['cal_allow_unlinked']))
|
||||||
|
{
|
||||||
|
$_REQUEST['calendar'] = 1;
|
||||||
|
require_once($sourcedir . '/Post.php');
|
||||||
|
return Post();
|
||||||
|
}
|
||||||
|
// New...
|
||||||
|
elseif ($_REQUEST['eventid'] == -1)
|
||||||
|
{
|
||||||
|
$eventOptions = array(
|
||||||
|
'board' => 0,
|
||||||
|
'topic' => 0,
|
||||||
|
'title' => $smcFunc['substr']($_REQUEST['evtitle'], 0, 100),
|
||||||
|
'location' => $smcFunc['substr']($_REQUEST['event_location'], 0, 255),
|
||||||
|
'member' => $user_info['id'],
|
||||||
|
);
|
||||||
|
insertEvent($eventOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting...
|
||||||
|
elseif (isset($_REQUEST['deleteevent']))
|
||||||
|
removeEvent($_REQUEST['eventid']);
|
||||||
|
|
||||||
|
// ... or just update it?
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$eventOptions = array(
|
||||||
|
'title' => $smcFunc['substr']($_REQUEST['evtitle'], 0, 100),
|
||||||
|
'location' => $smcFunc['substr']($_REQUEST['event_location'], 0, 255),
|
||||||
|
);
|
||||||
|
modifyEvent($_REQUEST['eventid'], $eventOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings(array(
|
||||||
|
'calendar_updated' => time(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// No point hanging around here now...
|
||||||
|
if (isset($_POST['start_date']))
|
||||||
|
{
|
||||||
|
$d = date_parse($_POST['start_date']);
|
||||||
|
$year = $d['year'];
|
||||||
|
$month = $d['month'];
|
||||||
|
$day = $d['day'];
|
||||||
|
}
|
||||||
|
elseif (isset($_POST['start_datetime']))
|
||||||
|
{
|
||||||
|
$d = date_parse($_POST['start_datetime']);
|
||||||
|
$year = $d['year'];
|
||||||
|
$month = $d['month'];
|
||||||
|
$day = $d['day'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$today = getdate();
|
||||||
|
$year = isset($_POST['year']) ? $_POST['year'] : $today['year'];
|
||||||
|
$month = isset($_POST['month']) ? $_POST['month'] : $today['mon'];
|
||||||
|
$day = isset($_POST['day']) ? $_POST['day'] : $today['mday'];
|
||||||
|
}
|
||||||
|
redirectexit($scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not enabled... we are not enabled.
|
||||||
|
if (empty($modSettings['cal_allow_unlinked']) && empty($_REQUEST['eventid']))
|
||||||
|
{
|
||||||
|
$_REQUEST['calendar'] = 1;
|
||||||
|
require_once($sourcedir . '/Post.php');
|
||||||
|
return Post();
|
||||||
|
}
|
||||||
|
|
||||||
|
// New?
|
||||||
|
if (!isset($_REQUEST['eventid']))
|
||||||
|
{
|
||||||
|
$context['event'] = array(
|
||||||
|
'boards' => array(),
|
||||||
|
'board' => 0,
|
||||||
|
'new' => 1,
|
||||||
|
'eventid' => -1,
|
||||||
|
'title' => '',
|
||||||
|
'location' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
$eventDatetimes = getNewEventDatetimes();
|
||||||
|
$context['event'] = array_merge($context['event'], $eventDatetimes);
|
||||||
|
|
||||||
|
$context['event']['last_day'] = (int) smf_strftime('%d', mktime(0, 0, 0, $context['event']['month'] == 12 ? 1 : $context['event']['month'] + 1, 0, $context['event']['month'] == 12 ? $context['event']['year'] + 1 : $context['event']['year']));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['event'] = getEventProperties($_REQUEST['eventid']);
|
||||||
|
|
||||||
|
if ($context['event'] === false)
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
// If it has a board, then they should be editing it within the topic.
|
||||||
|
if (!empty($context['event']['topic']['id']) && !empty($context['event']['topic']['first_msg']))
|
||||||
|
{
|
||||||
|
// We load the board up, for a check on the board access rights...
|
||||||
|
$topic = $context['event']['topic']['id'];
|
||||||
|
loadBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the user is allowed to edit this event.
|
||||||
|
if ($context['event']['member'] != $user_info['id'])
|
||||||
|
isAllowedTo('calendar_edit_any');
|
||||||
|
elseif (!allowedTo('calendar_edit_any'))
|
||||||
|
isAllowedTo('calendar_edit_own');
|
||||||
|
}
|
||||||
|
|
||||||
|
// An all day event? Set up some nice defaults in case the user wants to change that
|
||||||
|
if ($context['event']['allday'] == true)
|
||||||
|
{
|
||||||
|
$context['event']['tz'] = getUserTimezone();
|
||||||
|
$context['event']['start_time'] = timeformat(time(), $time_string);
|
||||||
|
$context['event']['end_time'] = timeformat(time() + 3600, $time_string);
|
||||||
|
}
|
||||||
|
// Otherwise, just adjust these to look nice on the input form
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['event']['start_time'] = $context['event']['start_time_orig'];
|
||||||
|
$context['event']['end_time'] = $context['event']['end_time_orig'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need this so the user can select a timezone for the event.
|
||||||
|
$context['all_timezones'] = smf_list_timezones($context['event']['start_date']);
|
||||||
|
|
||||||
|
// If the event's timezone is not in SMF's standard list of time zones, try to fix it.
|
||||||
|
if (!isset($context['all_timezones'][$context['event']['tz']]))
|
||||||
|
{
|
||||||
|
$later = strtotime('@' . $context['event']['start_timestamp'] . ' + 1 year');
|
||||||
|
$tzinfo = timezone_transitions_get(timezone_open($context['event']['tz']), $context['event']['start_timestamp'], $later);
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($context['all_timezones'] as $possible_tzid => $dummy)
|
||||||
|
{
|
||||||
|
// Ignore the "-----" option
|
||||||
|
if (empty($possible_tzid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$possible_tzinfo = timezone_transitions_get(timezone_open($possible_tzid), $context['event']['start_timestamp'], $later);
|
||||||
|
|
||||||
|
if ($tzinfo === $possible_tzinfo)
|
||||||
|
{
|
||||||
|
$context['event']['tz'] = $possible_tzid;
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hm. That's weird. Well, just prepend it to the list and let the user deal with it.
|
||||||
|
if (!$found)
|
||||||
|
{
|
||||||
|
$d = date_create($context['event']['start_datetime'] . ' ' . $context['event']['tz']);
|
||||||
|
$context['all_timezones'] = array($context['event']['tz'] => '[UTC' . date_format($d, 'P') . '] - ' . $context['event']['tz']) + $context['all_timezones'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get list of boards that can be posted in.
|
||||||
|
$boards = boardsAllowedTo('post_new');
|
||||||
|
if (empty($boards))
|
||||||
|
{
|
||||||
|
// You can post new events but can't link them to anything...
|
||||||
|
$context['event']['categories'] = array();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Load the list of boards and categories in the context.
|
||||||
|
require_once($sourcedir . '/Subs-MessageIndex.php');
|
||||||
|
$boardListOptions = array(
|
||||||
|
'included_boards' => in_array(0, $boards) ? null : $boards,
|
||||||
|
'not_redirection' => true,
|
||||||
|
'use_permissions' => true,
|
||||||
|
'selected_board' => $modSettings['cal_defaultboard'],
|
||||||
|
);
|
||||||
|
$context['event']['categories'] = getBoardList($boardListOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template, sub template, etc.
|
||||||
|
loadTemplate('Calendar');
|
||||||
|
$context['sub_template'] = 'event_post';
|
||||||
|
|
||||||
|
$context['page_title'] = isset($_REQUEST['eventid']) ? $txt['calendar_edit'] : $txt['calendar_post_event'];
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'name' => $context['page_title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
loadDatePicker('#event_time_input .date_input');
|
||||||
|
loadTimePicker('#event_time_input .time_input', $time_string);
|
||||||
|
loadDatePair('#event_time_input', 'date_input', 'time_input');
|
||||||
|
addInlineJavaScript('
|
||||||
|
$("#allday").click(function(){
|
||||||
|
$("#start_time").attr("disabled", this.checked);
|
||||||
|
$("#end_time").attr("disabled", this.checked);
|
||||||
|
$("#tz").attr("disabled", this.checked);
|
||||||
|
});', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function offers up a download of an event in iCal 2.0 format.
|
||||||
|
*
|
||||||
|
* Follows the conventions in {@link https://tools.ietf.org/html/rfc5546 RFC5546}
|
||||||
|
* Sets events as all day events since we don't have hourly events
|
||||||
|
* Will honor and set multi day events
|
||||||
|
* Sets a sequence number if the event has been modified
|
||||||
|
*
|
||||||
|
* @todo .... allow for week or month export files as well?
|
||||||
|
*/
|
||||||
|
function iCalDownload()
|
||||||
|
{
|
||||||
|
global $smcFunc, $sourcedir, $modSettings, $webmaster_email, $mbname;
|
||||||
|
|
||||||
|
// You can't export if the calendar export feature is off.
|
||||||
|
if (empty($modSettings['cal_export']))
|
||||||
|
fatal_lang_error('calendar_export_off', false);
|
||||||
|
|
||||||
|
// Goes without saying that this is required.
|
||||||
|
if (!isset($_REQUEST['eventid']))
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
// This is kinda wanted.
|
||||||
|
require_once($sourcedir . '/Subs-Calendar.php');
|
||||||
|
|
||||||
|
// Load up the event in question and check it exists.
|
||||||
|
$event = getEventProperties($_REQUEST['eventid']);
|
||||||
|
|
||||||
|
if ($event === false)
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
// Check the title isn't too long - iCal requires some formatting if so.
|
||||||
|
$title = str_split($event['title'], 30);
|
||||||
|
foreach ($title as $id => $line)
|
||||||
|
{
|
||||||
|
if ($id != 0)
|
||||||
|
$title[$id] = ' ' . $title[$id];
|
||||||
|
$title[$id] .= "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the dates.
|
||||||
|
$datestamp = date('Ymd\THis\Z', time());
|
||||||
|
$start_date = date_create($event['start_date'] . (isset($event['start_time']) ? ' ' . $event['start_time'] : '') . (isset($event['tz']) ? ' ' . $event['tz'] : ''));
|
||||||
|
$end_date = date_create($event['end_date'] . (isset($event['end_time']) ? ' ' . $event['end_time'] : '') . (isset($event['tz']) ? ' ' . $event['tz'] : ''));
|
||||||
|
|
||||||
|
if (!empty($event['start_time']))
|
||||||
|
{
|
||||||
|
$datestart = date_format($start_date, 'Ymd\THis');
|
||||||
|
$dateend = date_format($end_date, 'Ymd\THis');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$datestart = date_format($start_date, 'Ymd');
|
||||||
|
|
||||||
|
date_add($end_date, date_interval_create_from_date_string('1 day'));
|
||||||
|
$dateend = date_format($end_date, 'Ymd');
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is what we will be sending later
|
||||||
|
$filecontents = '';
|
||||||
|
$filecontents .= 'BEGIN:VCALENDAR' . "\n";
|
||||||
|
$filecontents .= 'METHOD:PUBLISH' . "\n";
|
||||||
|
$filecontents .= 'PRODID:-//SimpleMachines//' . SMF_FULL_VERSION . '//EN' . "\n";
|
||||||
|
$filecontents .= 'VERSION:2.0' . "\n";
|
||||||
|
$filecontents .= 'BEGIN:VEVENT' . "\n";
|
||||||
|
// @TODO - Should be the members email who created the event rather than $webmaster_email.
|
||||||
|
$filecontents .= 'ORGANIZER;CN="' . $event['realname'] . '":MAILTO:' . $webmaster_email . "\n";
|
||||||
|
$filecontents .= 'DTSTAMP:' . $datestamp . "\n";
|
||||||
|
$filecontents .= 'DTSTART' . (!empty($event['start_time']) ? ';TZID=' . $event['tz'] : ';VALUE=DATE') . ':' . $datestart . "\n";
|
||||||
|
|
||||||
|
// event has a duration
|
||||||
|
if ($event['start_iso_gmdate'] != $event['end_iso_gmdate'])
|
||||||
|
$filecontents .= 'DTEND' . (!empty($event['end_time']) ? ';TZID=' . $event['tz'] : ';VALUE=DATE') . ':' . $dateend . "\n";
|
||||||
|
|
||||||
|
// event has changed? advance the sequence for this UID
|
||||||
|
if ($event['sequence'] > 0)
|
||||||
|
$filecontents .= 'SEQUENCE:' . $event['sequence'] . "\n";
|
||||||
|
|
||||||
|
if (!empty($event['location']))
|
||||||
|
$filecontents .= 'LOCATION:' . str_replace(',', '\,', $event['location']) . "\n";
|
||||||
|
|
||||||
|
$filecontents .= 'SUMMARY:' . implode('', $title);
|
||||||
|
$filecontents .= 'UID:' . $event['eventid'] . '@' . str_replace(' ', '-', $mbname) . "\n";
|
||||||
|
$filecontents .= 'END:VEVENT' . "\n";
|
||||||
|
$filecontents .= 'END:VCALENDAR';
|
||||||
|
|
||||||
|
// Send some standard headers.
|
||||||
|
ob_end_clean();
|
||||||
|
if (!empty($modSettings['enableCompressedOutput']))
|
||||||
|
@ob_start('ob_gzhandler');
|
||||||
|
else
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
// Send the file headers
|
||||||
|
header('pragma: ');
|
||||||
|
header('cache-control: no-cache');
|
||||||
|
if (!isBrowser('gecko'))
|
||||||
|
header('content-transfer-encoding: binary');
|
||||||
|
header('expires: ' . gmdate('D, d M Y H:i:s', time() + 525600 * 60) . ' GMT');
|
||||||
|
header('last-modified: ' . gmdate('D, d M Y H:i:s', time()) . 'GMT');
|
||||||
|
header('accept-ranges: bytes');
|
||||||
|
header('connection: close');
|
||||||
|
header('content-disposition: attachment; filename="' . $event['title'] . '.ics"');
|
||||||
|
if (empty($modSettings['enableCompressedOutput']))
|
||||||
|
header('content-length: ' . $smcFunc['strlen']($filecontents));
|
||||||
|
|
||||||
|
// This is a calendar item!
|
||||||
|
header('content-type: text/calendar');
|
||||||
|
|
||||||
|
// Chuck out the card.
|
||||||
|
echo $filecontents;
|
||||||
|
|
||||||
|
// Off we pop - lovely!
|
||||||
|
obExit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nothing to see here. Move along.
|
||||||
|
*/
|
||||||
|
function clock()
|
||||||
|
{
|
||||||
|
global $smcFunc, $settings, $context, $scripturl;
|
||||||
|
|
||||||
|
$context['onimg'] = $settings['images_url'] . '/bbc/bbc_hoverbg.png';
|
||||||
|
$context['offimg'] = $settings['images_url'] . '/bbc/bbc_bg.png';
|
||||||
|
|
||||||
|
$context['page_title'] = 'Anyone know what time it is?';
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=clock',
|
||||||
|
'name' => 'Clock',
|
||||||
|
);
|
||||||
|
$context['robot_no_index'] = true;
|
||||||
|
|
||||||
|
$omfg = isset($_REQUEST['omfg']);
|
||||||
|
$bcd = !isset($_REQUEST['rb']) && !isset($_REQUEST['omfg']) && !isset($_REQUEST['time']);
|
||||||
|
|
||||||
|
loadTemplate('Calendar');
|
||||||
|
|
||||||
|
if ($bcd)
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'bcd';
|
||||||
|
$context['linktree'][] = array('url' => $scripturl . '?action=clock;bcd', 'name' => 'BCD');
|
||||||
|
$context['clockicons'] = $smcFunc['json_decode'](base64_decode('eyJoMSI6WzIsMV0sImgyIjpbOCw0LDIsMV0sIm0xIjpbNCwyLDFdLCJtMiI6WzgsNCwyLDFdLCJzMSI6WzQsMiwxXSwiczIiOls4LDQsMiwxXX0='), true);
|
||||||
|
}
|
||||||
|
elseif (!$omfg && !isset($_REQUEST['time']))
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'hms';
|
||||||
|
$context['linktree'][] = array('url' => $scripturl . '?action=clock', 'name' => 'Binary');
|
||||||
|
$context['clockicons'] = $smcFunc['json_decode'](base64_decode('eyJoIjpbMTYsOCw0LDIsMV0sIm0iOlszMiwxNiw4LDQsMiwxXSwicyI6WzMyLDE2LDgsNCwyLDFdfQ'), true);
|
||||||
|
}
|
||||||
|
elseif ($omfg)
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'omfg';
|
||||||
|
$context['linktree'][] = array('url' => $scripturl . '?action=clock;omfg', 'name' => 'OMFG');
|
||||||
|
$context['clockicons'] = $smcFunc['json_decode'](base64_decode('eyJ5ZWFyIjpbNjQsMzIsMTYsOCw0LDIsMV0sIm1vbnRoIjpbOCw0LDIsMV0sImRheSI6WzE2LDgsNCwyLDFdLCJob3VyIjpbMTYsOCw0LDIsMV0sIm1pbiI6WzMyLDE2LDgsNCwyLDFdLCJzZWMiOlszMiwxNiw4LDQsMiwxXX0='), true);
|
||||||
|
}
|
||||||
|
elseif (isset($_REQUEST['time']))
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'thetime';
|
||||||
|
$time = getdate($_REQUEST['time'] == 'now' ? time() : (int) $_REQUEST['time']);
|
||||||
|
$context['linktree'][] = array('url' => $scripturl . '?action=clock;time=' . $_REQUEST['time'], 'name' => 'Requested Time');
|
||||||
|
$context['clockicons'] = array(
|
||||||
|
'year' => array(
|
||||||
|
64 => false,
|
||||||
|
32 => false,
|
||||||
|
16 => false,
|
||||||
|
8 => false,
|
||||||
|
4 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
'month' => array(
|
||||||
|
8 => false,
|
||||||
|
4 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
'day' => array(
|
||||||
|
16 => false,
|
||||||
|
4 => false,
|
||||||
|
8 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
'hour' => array(
|
||||||
|
32 => false,
|
||||||
|
16 => false,
|
||||||
|
8 => false,
|
||||||
|
4 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
'min' => array(
|
||||||
|
32 => false,
|
||||||
|
16 => false,
|
||||||
|
8 => false,
|
||||||
|
4 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
'sec' => array(
|
||||||
|
32 => false,
|
||||||
|
16 => false,
|
||||||
|
8 => false,
|
||||||
|
4 => false,
|
||||||
|
2 => false,
|
||||||
|
1 => false
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($context['clockicons'] as $t => $vs)
|
||||||
|
foreach ($vs as $v => $dumb)
|
||||||
|
{
|
||||||
|
if ($$t >= $v)
|
||||||
|
{
|
||||||
|
$$t -= $v;
|
||||||
|
$context['clockicons'][$t][$v] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
462
Sources/Class-BrowserDetect.php
Normal file
|
@ -0,0 +1,462 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class browser_detector
|
||||||
|
* This class is an experiment for the job of correctly detecting browsers and settings needed for them.
|
||||||
|
* - Detects the following browsers
|
||||||
|
* - Opera, Webkit, Firefox, Web_tv, Konqueror, IE, Gecko
|
||||||
|
* - Webkit variants: Chrome, iphone, blackberry, android, safari, ipad, ipod
|
||||||
|
* - Opera Versions: 6, 7, 8 ... 10 ... and mobile mini and mobi
|
||||||
|
* - Firefox Versions: 1, 2, 3 .... 11 ...
|
||||||
|
* - Chrome Versions: 1 ... 18 ...
|
||||||
|
* - IE Versions: 4, 5, 5.5, 6, 7, 8, 9, 10 ... mobile and Mac
|
||||||
|
* - MS Edge
|
||||||
|
* - Nokia
|
||||||
|
*/
|
||||||
|
class browser_detector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array Holds all the browser information. Its contents will be placed into $context['browser']
|
||||||
|
*/
|
||||||
|
private $_browsers = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var boolean Whether or not this might be a mobile device
|
||||||
|
*/
|
||||||
|
private $_is_mobile = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main method of this class, you know the one that does the job: detect the thing.
|
||||||
|
* - determines the user agent (browser) as best it can.
|
||||||
|
*/
|
||||||
|
function detectBrowser()
|
||||||
|
{
|
||||||
|
global $context, $user_info;
|
||||||
|
|
||||||
|
// Init
|
||||||
|
$this->_browsers = array();
|
||||||
|
$this->_is_mobile = false;
|
||||||
|
|
||||||
|
// Initialize some values we'll set differently if necessary...
|
||||||
|
$this->_browsers['needs_size_fix'] = false;
|
||||||
|
|
||||||
|
// One at a time, one at a time, and in this order too
|
||||||
|
if ($this->isOpera())
|
||||||
|
$this->setupOpera();
|
||||||
|
// Meh...
|
||||||
|
elseif ($this->isEdge())
|
||||||
|
$this->setupEdge();
|
||||||
|
// Them webkits need to be set up too
|
||||||
|
elseif ($this->isWebkit())
|
||||||
|
$this->setupWebkit();
|
||||||
|
// We may have work to do on Firefox...
|
||||||
|
elseif ($this->isFirefox())
|
||||||
|
$this->setupFirefox();
|
||||||
|
// Old friend, old frenemy
|
||||||
|
elseif ($this->isIe())
|
||||||
|
$this->setupIe();
|
||||||
|
|
||||||
|
// Just a few mobile checks
|
||||||
|
$this->isOperaMini();
|
||||||
|
$this->isOperaMobi();
|
||||||
|
|
||||||
|
// IE11 seems to be fine by itself without being lumped into the "is_ie" category
|
||||||
|
$this->isIe11();
|
||||||
|
|
||||||
|
// Be you robot or human?
|
||||||
|
if ($user_info['possibly_robot'])
|
||||||
|
{
|
||||||
|
// This isn't meant to be reliable, it's just meant to catch most bots to prevent PHPSESSID from showing up.
|
||||||
|
$this->_browsers['possibly_robot'] = !empty($user_info['possibly_robot']);
|
||||||
|
|
||||||
|
// Robots shouldn't be logging in or registering. So, they aren't a bot. Better to be wrong than sorry (or people won't be able to log in!), anyway.
|
||||||
|
if ((isset($_REQUEST['action']) && in_array($_REQUEST['action'], array('login', 'login2', 'register', 'signup'))) || !$user_info['is_guest'])
|
||||||
|
$this->_browsers['possibly_robot'] = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->_browsers['possibly_robot'] = false;
|
||||||
|
|
||||||
|
// Fill out the historical array as needed to support old mods that don't use isBrowser
|
||||||
|
$this->fillInformation();
|
||||||
|
|
||||||
|
// Make it easy to check if the browser is on a mobile device.
|
||||||
|
$this->_browsers['is_mobile'] = $this->_is_mobile;
|
||||||
|
|
||||||
|
// Last step ...
|
||||||
|
$this->setupBrowserPriority();
|
||||||
|
|
||||||
|
// Now see what you've done!
|
||||||
|
$context['browser'] = $this->_browsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Opera or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is Opera
|
||||||
|
*/
|
||||||
|
function isOpera()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_opera']))
|
||||||
|
$this->_browsers['is_opera'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') !== false;
|
||||||
|
return $this->_browsers['is_opera'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is IE or not
|
||||||
|
*
|
||||||
|
* @return boolean true Whether or not the browser is IE
|
||||||
|
*/
|
||||||
|
function isIe()
|
||||||
|
{
|
||||||
|
// I'm IE, Yes I'm the real IE; All you other IEs are just imitating.
|
||||||
|
if (!isset($this->_browsers['is_ie']))
|
||||||
|
$this->_browsers['is_ie'] = !$this->isOpera() && !$this->isGecko() && !$this->isWebTv() && preg_match('~MSIE \d+~', $_SERVER['HTTP_USER_AGENT']) === 1;
|
||||||
|
return $this->_browsers['is_ie'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is IE11 or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not the browser is IE11
|
||||||
|
*/
|
||||||
|
function isIe11()
|
||||||
|
{
|
||||||
|
// IE11 is a bit different than earlier versions
|
||||||
|
// The isGecko() part is to ensure we get this right...
|
||||||
|
if (!isset($this->_browsers['is_ie11']))
|
||||||
|
$this->_browsers['is_ie11'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Trident') !== false && $this->isGecko();
|
||||||
|
return $this->_browsers['is_ie11'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Edge or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not the browser is Edge
|
||||||
|
*/
|
||||||
|
function isEdge()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_edge']))
|
||||||
|
$this->_browsers['is_edge'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Edge') !== false;
|
||||||
|
return $this->_browsers['is_edge'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is a Webkit based one or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is a Webkit-based browser
|
||||||
|
*/
|
||||||
|
function isWebkit()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_webkit']))
|
||||||
|
$this->_browsers['is_webkit'] = strpos($_SERVER['HTTP_USER_AGENT'], 'AppleWebKit') !== false;
|
||||||
|
return $this->_browsers['is_webkit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Firefox or one of its variants
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is Firefox (or one of its variants)
|
||||||
|
*/
|
||||||
|
function isFirefox()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_firefox']))
|
||||||
|
$this->_browsers['is_firefox'] = preg_match('~(?:Firefox|Ice[wW]easel|IceCat|Shiretoko|Minefield)/~', $_SERVER['HTTP_USER_AGENT']) === 1 && $this->isGecko();
|
||||||
|
return $this->_browsers['is_firefox'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is WebTv or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is WebTV
|
||||||
|
*/
|
||||||
|
function isWebTv()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_web_tv']))
|
||||||
|
$this->_browsers['is_web_tv'] = strpos($_SERVER['HTTP_USER_AGENT'], 'WebTV') !== false;
|
||||||
|
return $this->_browsers['is_web_tv'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is konqueror or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is Konqueror
|
||||||
|
*/
|
||||||
|
function isKonqueror()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_konqueror']))
|
||||||
|
$this->_browsers['is_konqueror'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Konqueror') !== false;
|
||||||
|
return $this->_browsers['is_konqueror'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Gecko or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is a Gecko-based browser
|
||||||
|
*/
|
||||||
|
function isGecko()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_gecko']))
|
||||||
|
$this->_browsers['is_gecko'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Gecko') !== false && !$this->isWebkit() && !$this->isKonqueror();
|
||||||
|
return $this->_browsers['is_gecko'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Opera Mini or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is Opera Mini
|
||||||
|
*/
|
||||||
|
function isOperaMini()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_opera_mini']))
|
||||||
|
$this->_browsers['is_opera_mini'] = (isset($_SERVER['HTTP_X_OPERAMINI_PHONE_UA']) || stripos($_SERVER['HTTP_USER_AGENT'], 'opera mini') !== false);
|
||||||
|
if ($this->_browsers['is_opera_mini'])
|
||||||
|
$this->_is_mobile = true;
|
||||||
|
return $this->_browsers['is_opera_mini'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the browser is Opera Mobile or not
|
||||||
|
*
|
||||||
|
* @return boolean Whether or not this is Opera Mobile
|
||||||
|
*/
|
||||||
|
function isOperaMobi()
|
||||||
|
{
|
||||||
|
if (!isset($this->_browsers['is_opera_mobi']))
|
||||||
|
$this->_browsers['is_opera_mobi'] = stripos($_SERVER['HTTP_USER_AGENT'], 'opera mobi') !== false;
|
||||||
|
if ($this->_browsers['is_opera_mobi'])
|
||||||
|
$this->_is_mobile = true;
|
||||||
|
return $this->_browsers['is_opera_mini'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect Safari / Chrome / iP[ao]d / iPhone / Android / Blackberry from webkit.
|
||||||
|
* - set the browser version for Safari and Chrome
|
||||||
|
* - set the mobile flag for mobile based useragents
|
||||||
|
*/
|
||||||
|
private function setupWebkit()
|
||||||
|
{
|
||||||
|
$this->_browsers += array(
|
||||||
|
'is_chrome' => strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') !== false,
|
||||||
|
'is_iphone' => (strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'iPod') !== false) && strpos($_SERVER['HTTP_USER_AGENT'], 'iPad') === false,
|
||||||
|
'is_blackberry' => stripos($_SERVER['HTTP_USER_AGENT'], 'BlackBerry') !== false || strpos($_SERVER['HTTP_USER_AGENT'], 'PlayBook') !== false,
|
||||||
|
'is_android' => strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false,
|
||||||
|
'is_nokia' => strpos($_SERVER['HTTP_USER_AGENT'], 'SymbianOS') !== false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// blackberry, playbook, iphone, nokia, android and ipods set a mobile flag
|
||||||
|
if ($this->_browsers['is_iphone'] || $this->_browsers['is_blackberry'] || $this->_browsers['is_android'] || $this->_browsers['is_nokia'])
|
||||||
|
$this->_is_mobile = true;
|
||||||
|
|
||||||
|
// @todo what to do with the blaPad? ... for now leave it detected as Safari ...
|
||||||
|
$this->_browsers['is_safari'] = strpos($_SERVER['HTTP_USER_AGENT'], 'Safari') !== false && !$this->_browsers['is_chrome'] && !$this->_browsers['is_iphone'];
|
||||||
|
$this->_browsers['is_ipad'] = strpos($_SERVER['HTTP_USER_AGENT'], 'iPad') !== false;
|
||||||
|
|
||||||
|
// if Chrome, get the major version
|
||||||
|
if ($this->_browsers['is_chrome'])
|
||||||
|
{
|
||||||
|
if (preg_match('~chrome[/]([0-9][0-9]?[.])~i', $_SERVER['HTTP_USER_AGENT'], $match) === 1)
|
||||||
|
$this->_browsers['is_chrome' . (int) $match[1]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// or if Safari get its major version
|
||||||
|
if ($this->_browsers['is_safari'])
|
||||||
|
{
|
||||||
|
if (preg_match('~version/?(.*)safari.*~i', $_SERVER['HTTP_USER_AGENT'], $match) === 1)
|
||||||
|
$this->_browsers['is_safari' . (int) trim($match[1])] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional IE checks and settings.
|
||||||
|
* - determines the version of the IE browser in use
|
||||||
|
* - detects ie4 onward
|
||||||
|
* - attempts to distinguish between IE and IE in compatibility view
|
||||||
|
* - checks for old IE on macs as well, since we can
|
||||||
|
*/
|
||||||
|
private function setupIe()
|
||||||
|
{
|
||||||
|
$this->_browsers['is_ie_compat_view'] = false;
|
||||||
|
|
||||||
|
// get the version of the browser from the msie tag
|
||||||
|
if (preg_match('~MSIE\s?([0-9][0-9]?.[0-9])~i', $_SERVER['HTTP_USER_AGENT'], $msie_match) === 1)
|
||||||
|
{
|
||||||
|
$msie_match[1] = trim($msie_match[1]);
|
||||||
|
$msie_match[1] = (($msie_match[1] - (int) $msie_match[1]) == 0) ? (int) $msie_match[1] : $msie_match[1];
|
||||||
|
$this->_browsers['is_ie' . $msie_match[1]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "modern" ie uses trident 4=ie8, 5=ie9, 6=ie10, 7=ie11 even in compatibility view
|
||||||
|
if (preg_match('~Trident/([0-9.])~i', $_SERVER['HTTP_USER_AGENT'], $trident_match) === 1)
|
||||||
|
{
|
||||||
|
$this->_browsers['is_ie' . ((int) $trident_match[1] + 4)] = true;
|
||||||
|
|
||||||
|
// If trident is set, see the (if any) msie tag in the user agent matches ... if not its in some compatibility view
|
||||||
|
if (isset($msie_match[1]) && ($msie_match[1] < $trident_match[1] + 4))
|
||||||
|
$this->_browsers['is_ie_compat_view'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect true IE6 and IE7 and not IE in compat mode.
|
||||||
|
$this->_browsers['is_ie7'] = !empty($this->_browsers['is_ie7']) && ($this->_browsers['is_ie_compat_view'] === false);
|
||||||
|
$this->_browsers['is_ie6'] = !empty($this->_browsers['is_ie6']) && ($this->_browsers['is_ie_compat_view'] === false);
|
||||||
|
|
||||||
|
// IE mobile 7 or 9, ... shucks why not
|
||||||
|
if ((!empty($this->_browsers['is_ie7']) && strpos($_SERVER['HTTP_USER_AGENT'], 'IEMobile/7') !== false) || (!empty($this->_browsers['is_ie9']) && strpos($_SERVER['HTTP_USER_AGENT'], 'IEMobile/9') !== false))
|
||||||
|
{
|
||||||
|
$this->_browsers['is_ie_mobi'] = true;
|
||||||
|
$this->_is_mobile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And some throwbacks to a bygone era, deposited here like cholesterol in your arteries
|
||||||
|
$this->_browsers += array(
|
||||||
|
'is_ie4' => !empty($this->_browsers['is_ie4']) && !$this->_browsers['is_web_tv'],
|
||||||
|
'is_mac_ie' => strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE 5.') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
// Before IE8 we need to fix IE... lots!
|
||||||
|
$this->_browsers['ie_standards_fix'] = (($this->_browsers['is_ie6'] === true) || ($this->_browsers['is_ie7'] === true)) ? true : false;
|
||||||
|
|
||||||
|
// We may even need a size fix...
|
||||||
|
$this->_browsers['needs_size_fix'] = (!empty($this->_browsers['is_ie5']) || !empty($this->_browsers['is_ie5.5']) || !empty($this->_browsers['is_ie4'])) && !$this->_browsers['is_mac_ie'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional firefox checks.
|
||||||
|
* - Gets the version of the FF browser in use
|
||||||
|
* - Considers all FF variants as FF including IceWeasel, IceCat, Shiretoko and Minefiled
|
||||||
|
*/
|
||||||
|
private function setupFirefox()
|
||||||
|
{
|
||||||
|
if (preg_match('~(?:Firefox|Ice[wW]easel|IceCat|Shiretoko|Minefield)[\/ \(]([^ ;\)]+)~', $_SERVER['HTTP_USER_AGENT'], $match) === 1)
|
||||||
|
$this->_browsers['is_firefox' . (int) $match[1]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* More Opera checks if we are opera.
|
||||||
|
* - checks for the version of Opera in use
|
||||||
|
* - uses checks for 10 first and falls through to <9
|
||||||
|
*/
|
||||||
|
private function setupOpera()
|
||||||
|
{
|
||||||
|
// Opera 10+ uses the version tag at the end of the string
|
||||||
|
if (preg_match('~\sVersion/([0-9]+)\.[0-9]+(?:\s*|$)~', $_SERVER['HTTP_USER_AGENT'], $match))
|
||||||
|
$this->_browsers['is_opera' . (int) $match[1]] = true;
|
||||||
|
// Opera pre 10 is supposed to uses the Opera tag alone, as do some spoofers
|
||||||
|
elseif (preg_match('~Opera[ /]([0-9]+)(?!\\.[89])~', $_SERVER['HTTP_USER_AGENT'], $match))
|
||||||
|
$this->_browsers['is_opera' . (int) $match[1]] = true;
|
||||||
|
|
||||||
|
// Needs size fix?
|
||||||
|
$this->_browsers['needs_size_fix'] = !empty($this->_browsers['is_opera6']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the version number for MS edge.
|
||||||
|
*/
|
||||||
|
private function setupEdge()
|
||||||
|
{
|
||||||
|
if (preg_match('~Edge[\/]([0-9][0-9]?[\.][0-9][0-9])~i', $_SERVER['HTTP_USER_AGENT'], $match) === 1)
|
||||||
|
$this->_browsers['is_edge' . (int) $match[1]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the browser name that we will use in the <body id="this_browser">
|
||||||
|
* - The order of each browser in $browser_priority is important
|
||||||
|
* - if you want to have id='ie6' and not id='ie' then it must appear first in the list of ie browsers
|
||||||
|
* - only sets browsers that may need some help via css for compatibility
|
||||||
|
*/
|
||||||
|
private function setupBrowserPriority()
|
||||||
|
{
|
||||||
|
global $context;
|
||||||
|
|
||||||
|
if ($this->_is_mobile)
|
||||||
|
$context['browser_body_id'] = 'mobile';
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add in any specific detection conversions here if you want a special body id e.g. 'is_opera9' => 'opera9'
|
||||||
|
$browser_priority = array(
|
||||||
|
'is_ie6' => 'ie6',
|
||||||
|
'is_ie7' => 'ie7',
|
||||||
|
'is_ie8' => 'ie8',
|
||||||
|
'is_ie9' => 'ie9',
|
||||||
|
'is_ie10' => 'ie10',
|
||||||
|
'is_ie11' => 'ie11',
|
||||||
|
'is_ie' => 'ie',
|
||||||
|
'is_edge' => 'edge',
|
||||||
|
'is_firefox' => 'firefox',
|
||||||
|
'is_chrome' => 'chrome',
|
||||||
|
'is_safari' => 'safari',
|
||||||
|
'is_opera10' => 'opera10',
|
||||||
|
'is_opera11' => 'opera11',
|
||||||
|
'is_opera12' => 'opera12',
|
||||||
|
'is_opera' => 'opera',
|
||||||
|
'is_konqueror' => 'konqueror',
|
||||||
|
);
|
||||||
|
|
||||||
|
$context['browser_body_id'] = 'smf';
|
||||||
|
$active = array_reverse(array_keys($this->_browsers, true));
|
||||||
|
foreach ($active as $browser)
|
||||||
|
{
|
||||||
|
if (array_key_exists($browser, $browser_priority))
|
||||||
|
{
|
||||||
|
$context['browser_body_id'] = $browser_priority[$browser];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill out the historical array
|
||||||
|
* - needed to support old mods that don't use isBrowser
|
||||||
|
*/
|
||||||
|
function fillInformation()
|
||||||
|
{
|
||||||
|
$this->_browsers += array(
|
||||||
|
'is_opera' => false,
|
||||||
|
'is_opera6' => false,
|
||||||
|
'is_opera7' => false,
|
||||||
|
'is_opera8' => false,
|
||||||
|
'is_opera9' => false,
|
||||||
|
'is_opera10' => false,
|
||||||
|
'is_webkit' => false,
|
||||||
|
'is_mac_ie' => false,
|
||||||
|
'is_web_tv' => false,
|
||||||
|
'is_konqueror' => false,
|
||||||
|
'is_firefox' => false,
|
||||||
|
'is_firefox1' => false,
|
||||||
|
'is_firefox2' => false,
|
||||||
|
'is_firefox3' => false,
|
||||||
|
'is_iphone' => false,
|
||||||
|
'is_android' => false,
|
||||||
|
'is_chrome' => false,
|
||||||
|
'is_safari' => false,
|
||||||
|
'is_gecko' => false,
|
||||||
|
'is_edge' => false,
|
||||||
|
'is_ie8' => false,
|
||||||
|
'is_ie7' => false,
|
||||||
|
'is_ie6' => false,
|
||||||
|
'is_ie5.5' => false,
|
||||||
|
'is_ie5' => false,
|
||||||
|
'is_ie' => false,
|
||||||
|
'is_ie4' => false,
|
||||||
|
'ie_standards_fix' => false,
|
||||||
|
'needs_size_fix' => false,
|
||||||
|
'possibly_robot' => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
371
Sources/Class-CurlFetchWeb.php
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class curl_fetch_web_data
|
||||||
|
* Simple cURL class to fetch a web page
|
||||||
|
* Properly redirects even with safe mode and basedir restrictions
|
||||||
|
* Can provide simple post options to a page
|
||||||
|
*
|
||||||
|
* ### Load class
|
||||||
|
* Initiate as
|
||||||
|
* ```
|
||||||
|
* $fetch_data = new cURL_fetch_web_data();
|
||||||
|
* ```
|
||||||
|
* Optionally pass an array of cURL options and redirect count
|
||||||
|
* ```
|
||||||
|
* $fetch_data = new cURL_fetch_web_data(array(CURLOPT_SSL_VERIFYPEER => 1), 5);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ### Make the call
|
||||||
|
* Fetch a page
|
||||||
|
* ```
|
||||||
|
* $fetch_data->get_url_data('https://www.simplemachines.org');
|
||||||
|
* ```
|
||||||
|
* Post to a page providing an array
|
||||||
|
* ```
|
||||||
|
* $fetch_data->get_url_data('https://www.simplemachines.org', array('user' => 'name', 'password' => 'password'));
|
||||||
|
* ```
|
||||||
|
* Post to a page providing a string
|
||||||
|
* ```
|
||||||
|
* $fetch_data->get_url_data('https://www.simplemachines.org', parameter1¶meter2¶meter3);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ### Get the data
|
||||||
|
* Just the page content
|
||||||
|
* ```
|
||||||
|
* $fetch_data->result('body');
|
||||||
|
* ```
|
||||||
|
* An array of results, body, header, http result codes
|
||||||
|
* ```
|
||||||
|
* $fetch_data->result();
|
||||||
|
* ```
|
||||||
|
* Show all results of all calls (in the event of a redirect)
|
||||||
|
* ```
|
||||||
|
* $fetch_data->result_raw();
|
||||||
|
* ```
|
||||||
|
* Show the results of a specific call (in the event of a redirect)
|
||||||
|
* ```
|
||||||
|
* $fetch_data->result_raw(0);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class curl_fetch_web_data
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Set the default items for this class
|
||||||
|
*
|
||||||
|
* @var array $default_options
|
||||||
|
*/
|
||||||
|
private $default_options = array(
|
||||||
|
CURLOPT_RETURNTRANSFER => 1, // Get returned value as a string (don't output it)
|
||||||
|
CURLOPT_HEADER => 1, // We need the headers to do our own redirect
|
||||||
|
CURLOPT_FOLLOWLOCATION => 0, // Don't follow, we will do it ourselves so safe mode and open_basedir will dig it
|
||||||
|
CURLOPT_USERAGENT => SMF_USER_AGENT, // set a normal looking useragent
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 15, // Don't wait forever on a connection
|
||||||
|
CURLOPT_TIMEOUT => 90, // A page should load in this amount of time
|
||||||
|
CURLOPT_MAXREDIRS => 5, // stop after this many redirects
|
||||||
|
CURLOPT_ENCODING => 'gzip,deflate', // accept gzip and decode it
|
||||||
|
CURLOPT_SSL_VERIFYPEER => 0, // stop cURL from verifying the peer's certificate
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 0, // stop cURL from verifying the peer's host
|
||||||
|
CURLOPT_POST => 0, // no post data unless its passed
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int Maximum number of redirects
|
||||||
|
*/
|
||||||
|
public $max_redirect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array An array of cURL options
|
||||||
|
*/
|
||||||
|
public $user_options = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Any post data as form name => value
|
||||||
|
*/
|
||||||
|
public $post_data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array An array of cURL options
|
||||||
|
*/
|
||||||
|
public $options;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int ???
|
||||||
|
*/
|
||||||
|
public $current_redirect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Stores responses (url, code, error, headers, body) in the response array
|
||||||
|
*/
|
||||||
|
public $response = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The header
|
||||||
|
*/
|
||||||
|
public $headers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the curl object
|
||||||
|
* - allow for user override values
|
||||||
|
*
|
||||||
|
* @param array $options An array of cURL options
|
||||||
|
* @param int $max_redirect Maximum number of redirects
|
||||||
|
*/
|
||||||
|
public function __construct($options = array(), $max_redirect = 3)
|
||||||
|
{
|
||||||
|
// Initialize class variables
|
||||||
|
$this->max_redirect = intval($max_redirect);
|
||||||
|
$this->user_options = $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main calling function,
|
||||||
|
* - will request the page data from a given $url
|
||||||
|
* - optionally will post data to the page form if post data is supplied
|
||||||
|
* - passed arrays will be converted to a post string joined with &'s
|
||||||
|
* - calls set_options to set the curl opts array values based on the defaults and user input
|
||||||
|
*
|
||||||
|
* @param string $url the site we are going to fetch
|
||||||
|
* @param array $post_data any post data as form name => value
|
||||||
|
* @return object An instance of the curl_fetch_web_data class
|
||||||
|
*/
|
||||||
|
public function get_url_data($url, $post_data = array())
|
||||||
|
{
|
||||||
|
// POSTing some data perhaps?
|
||||||
|
if (!empty($post_data) && is_array($post_data))
|
||||||
|
$this->post_data = $this->build_post_data($post_data);
|
||||||
|
elseif (!empty($post_data))
|
||||||
|
$this->post_data = trim($post_data);
|
||||||
|
|
||||||
|
// set the options and get it
|
||||||
|
$this->set_options();
|
||||||
|
$this->curl_request(str_replace(' ', '%20', $url));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the actual cURL call
|
||||||
|
* - stores responses (url, code, error, headers, body) in the response array
|
||||||
|
* - detects 301, 302, 307 codes and will redirect to the given response header location
|
||||||
|
*
|
||||||
|
* @param string $url The site to fetch
|
||||||
|
* @param bool $redirect Whether or not this was a redirect request
|
||||||
|
* @return void|bool Sets various properties of the class or returns false if the URL isn't specified
|
||||||
|
*/
|
||||||
|
private function curl_request($url, $redirect = false)
|
||||||
|
{
|
||||||
|
// we do have a url I hope
|
||||||
|
if ($url == '')
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
$this->options[CURLOPT_URL] = $url;
|
||||||
|
|
||||||
|
// if we have not already been redirected, set it up so we can if needed
|
||||||
|
if (!$redirect)
|
||||||
|
{
|
||||||
|
$this->current_redirect = 1;
|
||||||
|
$this->response = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the curl object and make the call
|
||||||
|
$cr = curl_init();
|
||||||
|
curl_setopt_array($cr, $this->options);
|
||||||
|
curl_exec($cr);
|
||||||
|
|
||||||
|
// Get what was returned
|
||||||
|
$curl_info = curl_getinfo($cr);
|
||||||
|
$curl_content = curl_multi_getcontent($cr);
|
||||||
|
$url = $curl_info['url']; // Last effective URL
|
||||||
|
$http_code = $curl_info['http_code']; // Last HTTP code
|
||||||
|
$body = (!curl_error($cr)) ? substr($curl_content, $curl_info['header_size']) : false;
|
||||||
|
$error = (curl_error($cr)) ? curl_error($cr) : false;
|
||||||
|
|
||||||
|
// close this request
|
||||||
|
curl_close($cr);
|
||||||
|
|
||||||
|
// store this 'loops' data, someone may want all of these :O
|
||||||
|
$this->response[] = array(
|
||||||
|
'url' => $url,
|
||||||
|
'code' => $http_code,
|
||||||
|
'error' => $error,
|
||||||
|
'headers' => isset($this->headers) ? $this->headers : false,
|
||||||
|
'body' => $body,
|
||||||
|
'size' => $curl_info['download_content_length'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this a redirect with a location header and we have not given up, then do it again
|
||||||
|
if (preg_match('~30[127]~i', $http_code) === 1 && $this->headers['location'] != '' && $this->current_redirect <= $this->max_redirect)
|
||||||
|
{
|
||||||
|
$this->current_redirect++;
|
||||||
|
$header_location = $this->get_redirect_url($url, $this->headers['location']);
|
||||||
|
$this->redirect($header_location, $url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used if being redirected to ensure we have a fully qualified address
|
||||||
|
*
|
||||||
|
* @param string $last_url The URL we went to
|
||||||
|
* @param string $new_url The URL we were redirected to
|
||||||
|
* @return string The new URL that was in the HTTP header
|
||||||
|
*/
|
||||||
|
private function get_redirect_url($last_url = '', $new_url = '')
|
||||||
|
{
|
||||||
|
// Get the elements for these urls
|
||||||
|
$last_url_parse = parse_url($last_url);
|
||||||
|
$new_url_parse = parse_url($new_url);
|
||||||
|
|
||||||
|
// redirect headers are often incomplete or relative so we need to make sure they are fully qualified
|
||||||
|
$new_url_parse['scheme'] = isset($new_url_parse['scheme']) ? $new_url_parse['scheme'] : $last_url_parse['scheme'];
|
||||||
|
$new_url_parse['host'] = isset($new_url_parse['host']) ? $new_url_parse['host'] : $last_url_parse['host'];
|
||||||
|
$new_url_parse['path'] = isset($new_url_parse['path']) ? $new_url_parse['path'] : $last_url_parse['path'];
|
||||||
|
$new_url_parse['query'] = isset($new_url_parse['query']) ? $new_url_parse['query'] : '';
|
||||||
|
|
||||||
|
// Build the new URL that was in the http header
|
||||||
|
return $new_url_parse['scheme'] . '://' . $new_url_parse['host'] . $new_url_parse['path'] . (!empty($new_url_parse['query']) ? '?' . $new_url_parse['query'] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to return the results to the calling program
|
||||||
|
* - called as ->result() will return the full final array
|
||||||
|
* - called as ->result('body') to just return the page source of the result
|
||||||
|
*
|
||||||
|
* @param string $area Used to return an area such as body, header, error
|
||||||
|
* @return string The response
|
||||||
|
*/
|
||||||
|
public function result($area = '')
|
||||||
|
{
|
||||||
|
$max_result = count($this->response) - 1;
|
||||||
|
|
||||||
|
// just return a specifed area or the entire result?
|
||||||
|
if ($area == '')
|
||||||
|
return $this->response[$max_result];
|
||||||
|
else
|
||||||
|
return isset($this->response[$max_result][$area]) ? $this->response[$max_result][$area] : $this->response[$max_result];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return all results from all loops (redirects)
|
||||||
|
* - Can be called as ->result_raw(x) where x is a specific loop results.
|
||||||
|
* - Call as ->result_raw() for everything.
|
||||||
|
*
|
||||||
|
* @param string $response_number Which response we want to get
|
||||||
|
* @return array|string The entire response array or just the specified response
|
||||||
|
*/
|
||||||
|
public function result_raw($response_number = '')
|
||||||
|
{
|
||||||
|
if (!is_numeric($response_number))
|
||||||
|
return $this->response;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response_number = min($response_number, count($this->response) - 1);
|
||||||
|
return $this->response[$response_number];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes supplied POST data and url encodes it
|
||||||
|
* - forms the date (for post) in to a string var=xyz&var2=abc&var3=123
|
||||||
|
* - drops vars with @ since we don't support sending files (uploading)
|
||||||
|
*
|
||||||
|
* @param array|string $post_data The raw POST data
|
||||||
|
* @return string A string of post data
|
||||||
|
*/
|
||||||
|
private function build_post_data($post_data)
|
||||||
|
{
|
||||||
|
if (is_array($post_data))
|
||||||
|
{
|
||||||
|
$postvars = array();
|
||||||
|
|
||||||
|
// build the post data, drop ones with leading @'s since those can be used to send files, we don't support that.
|
||||||
|
foreach ($post_data as $name => $value)
|
||||||
|
$postvars[] = $name . '=' . urlencode($value[0] == '@' ? '' : $value);
|
||||||
|
|
||||||
|
return implode('&', $postvars);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return $post_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the final cURL options for the current call
|
||||||
|
* - overwrites our default values with user supplied ones or appends new user ones to what we have
|
||||||
|
* - sets the callback function now that $this is existing
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function set_options()
|
||||||
|
{
|
||||||
|
// Callback to parse the returned headers, if any
|
||||||
|
$this->default_options[CURLOPT_HEADERFUNCTION] = array($this, 'header_callback');
|
||||||
|
|
||||||
|
// Any user options to account for
|
||||||
|
if (is_array($this->user_options))
|
||||||
|
{
|
||||||
|
$keys = array_merge(array_keys($this->default_options), array_keys($this->user_options));
|
||||||
|
$vals = array_merge($this->default_options, $this->user_options);
|
||||||
|
$this->options = array_combine($keys, $vals);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->options = $this->default_options;
|
||||||
|
|
||||||
|
// POST data options, here we don't allow any overide
|
||||||
|
if (isset($this->post_data))
|
||||||
|
{
|
||||||
|
$this->options[CURLOPT_POST] = 1;
|
||||||
|
$this->options[CURLOPT_POSTFIELDS] = $this->post_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to initiate a redirect from a 301, 302 or 307 header
|
||||||
|
* - resets the cURL options for the loop, sets the referrer flag
|
||||||
|
*
|
||||||
|
* @param string $target_url The URL we want to redirect to
|
||||||
|
* @param string $referer_url The URL that we're redirecting from
|
||||||
|
*/
|
||||||
|
private function redirect($target_url, $referer_url)
|
||||||
|
{
|
||||||
|
// no no I last saw that over there ... really, 301, 302, 307
|
||||||
|
$this->set_options();
|
||||||
|
$this->options[CURLOPT_REFERER] = $referer_url;
|
||||||
|
$this->curl_request($target_url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function to parse returned headers
|
||||||
|
* - lowercases everything to make it consistent
|
||||||
|
*
|
||||||
|
* @param curl_fetch_web_data $cr The curl request
|
||||||
|
* @param string $header The header
|
||||||
|
* @return int The length of the header
|
||||||
|
*/
|
||||||
|
private function header_callback($cr, $header)
|
||||||
|
{
|
||||||
|
$_header = trim($header);
|
||||||
|
$temp = explode(': ', $_header, 2);
|
||||||
|
|
||||||
|
// set proper headers only
|
||||||
|
if (isset($temp[0]) && isset($temp[1]))
|
||||||
|
$this->headers[strtolower($temp[0])] = strtolower(trim($temp[1]));
|
||||||
|
|
||||||
|
// return the length of what was passed unless you want a Failed writing header error ;)
|
||||||
|
return strlen($header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
705
Sources/Class-Graphics.php
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classes used for reading gif files (in case PHP's GD doesn't provide the
|
||||||
|
* proper gif-functions).
|
||||||
|
*
|
||||||
|
* Gif Util copyright 2003 by Yamasoft (S/C). All rights reserved.
|
||||||
|
* Do not remove this portion of the header, or use these functions except
|
||||||
|
* from the original author. To get it, please navigate to:
|
||||||
|
* http://www.yamasoft.com/php-gif.zip
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class gif_lzw_compression
|
||||||
|
*
|
||||||
|
* An implementation of the LZW compression algorithm
|
||||||
|
*/
|
||||||
|
class gif_lzw_compression
|
||||||
|
{
|
||||||
|
public $MAX_LZW_BITS;
|
||||||
|
public $Fresh, $CodeSize, $SetCodeSize, $MaxCode, $MaxCodeSize, $FirstCode, $OldCode;
|
||||||
|
public $ClearCode, $EndCode, $Next, $Vals, $Stack, $sp, $Buf, $CurBit, $LastBit, $Done, $LastByte;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->MAX_LZW_BITS = 12;
|
||||||
|
unset($this->Next, $this->Vals, $this->Stack, $this->Buf);
|
||||||
|
|
||||||
|
$this->Next = range(0, (1 << $this->MAX_LZW_BITS) - 1);
|
||||||
|
$this->Vals = range(0, (1 << $this->MAX_LZW_BITS) - 1);
|
||||||
|
$this->Stack = range(0, (1 << ($this->MAX_LZW_BITS + 1)) - 1);
|
||||||
|
$this->Buf = range(0, 279);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function decompress($data, &$datLen)
|
||||||
|
{
|
||||||
|
$stLen = strlen($data);
|
||||||
|
$datLen = 0;
|
||||||
|
$ret = '';
|
||||||
|
|
||||||
|
$this->LZWCommand($data, true);
|
||||||
|
|
||||||
|
while (($iIndex = $this->LZWCommand($data, false)) >= 0)
|
||||||
|
$ret .= chr($iIndex);
|
||||||
|
|
||||||
|
$datLen = $stLen - strlen($data);
|
||||||
|
|
||||||
|
if ($iIndex != -2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function LZWCommand(&$data, $bInit)
|
||||||
|
{
|
||||||
|
if ($bInit)
|
||||||
|
{
|
||||||
|
$this->SetCodeSize = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
|
||||||
|
$this->CodeSize = $this->SetCodeSize + 1;
|
||||||
|
$this->ClearCode = 1 << $this->SetCodeSize;
|
||||||
|
$this->EndCode = $this->ClearCode + 1;
|
||||||
|
$this->MaxCode = $this->ClearCode + 2;
|
||||||
|
$this->MaxCodeSize = $this->ClearCode << 1;
|
||||||
|
|
||||||
|
$this->GetCode($data, $bInit);
|
||||||
|
|
||||||
|
$this->Fresh = 1;
|
||||||
|
for ($i = 0; $i < $this->ClearCode; $i++)
|
||||||
|
{
|
||||||
|
$this->Next[$i] = 0;
|
||||||
|
$this->Vals[$i] = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
|
||||||
|
{
|
||||||
|
$this->Next[$i] = 0;
|
||||||
|
$this->Vals[$i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sp = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->Fresh)
|
||||||
|
{
|
||||||
|
$this->Fresh = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
$this->FirstCode = $this->GetCode($data, $bInit);
|
||||||
|
$this->OldCode = $this->FirstCode;
|
||||||
|
}
|
||||||
|
while ($this->FirstCode == $this->ClearCode);
|
||||||
|
|
||||||
|
return $this->FirstCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->sp > 0)
|
||||||
|
{
|
||||||
|
$this->sp--;
|
||||||
|
return $this->Stack[$this->sp];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (($Code = $this->GetCode($data, $bInit)) >= 0)
|
||||||
|
{
|
||||||
|
if ($Code == $this->ClearCode)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < $this->ClearCode; $i++)
|
||||||
|
{
|
||||||
|
$this->Next[$i] = 0;
|
||||||
|
$this->Vals[$i] = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
|
||||||
|
{
|
||||||
|
$this->Next[$i] = 0;
|
||||||
|
$this->Vals[$i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->CodeSize = $this->SetCodeSize + 1;
|
||||||
|
$this->MaxCodeSize = $this->ClearCode << 1;
|
||||||
|
$this->MaxCode = $this->ClearCode + 2;
|
||||||
|
$this->sp = 0;
|
||||||
|
$this->FirstCode = $this->GetCode($data, $bInit);
|
||||||
|
$this->OldCode = $this->FirstCode;
|
||||||
|
|
||||||
|
return $this->FirstCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Code == $this->EndCode)
|
||||||
|
return -2;
|
||||||
|
|
||||||
|
$InCode = $Code;
|
||||||
|
if ($Code >= $this->MaxCode)
|
||||||
|
{
|
||||||
|
$this->Stack[$this->sp] = $this->FirstCode;
|
||||||
|
$this->sp++;
|
||||||
|
$Code = $this->OldCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($Code >= $this->ClearCode)
|
||||||
|
{
|
||||||
|
$this->Stack[$this->sp] = $this->Vals[$Code];
|
||||||
|
$this->sp++;
|
||||||
|
|
||||||
|
if ($Code == $this->Next[$Code]) // Circular table entry, big GIF Error!
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
$Code = $this->Next[$Code];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->FirstCode = $this->Vals[$Code];
|
||||||
|
$this->Stack[$this->sp] = $this->FirstCode;
|
||||||
|
$this->sp++;
|
||||||
|
|
||||||
|
if (($Code = $this->MaxCode) < (1 << $this->MAX_LZW_BITS))
|
||||||
|
{
|
||||||
|
$this->Next[$Code] = $this->OldCode;
|
||||||
|
$this->Vals[$Code] = $this->FirstCode;
|
||||||
|
$this->MaxCode++;
|
||||||
|
|
||||||
|
if (($this->MaxCode >= $this->MaxCodeSize) && ($this->MaxCodeSize < (1 << $this->MAX_LZW_BITS)))
|
||||||
|
{
|
||||||
|
$this->MaxCodeSize *= 2;
|
||||||
|
$this->CodeSize++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->OldCode = $InCode;
|
||||||
|
if ($this->sp > 0)
|
||||||
|
{
|
||||||
|
$this->sp--;
|
||||||
|
return $this->Stack[$this->sp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetCode(&$data, $bInit)
|
||||||
|
{
|
||||||
|
if ($bInit)
|
||||||
|
{
|
||||||
|
$this->CurBit = 0;
|
||||||
|
$this->LastBit = 0;
|
||||||
|
$this->Done = 0;
|
||||||
|
$this->LastByte = 2;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($this->CurBit + $this->CodeSize) >= $this->LastBit)
|
||||||
|
{
|
||||||
|
if ($this->Done)
|
||||||
|
{
|
||||||
|
// Ran off the end of my bits...
|
||||||
|
if ($this->CurBit >= $this->LastBit)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->Buf[0] = $this->Buf[$this->LastByte - 2];
|
||||||
|
$this->Buf[1] = $this->Buf[$this->LastByte - 1];
|
||||||
|
|
||||||
|
$count = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
|
||||||
|
if ($count)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < $count; $i++)
|
||||||
|
$this->Buf[2 + $i] = ord($data[$i]);
|
||||||
|
|
||||||
|
$data = substr($data, $count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$this->Done = 1;
|
||||||
|
|
||||||
|
$this->LastByte = 2 + $count;
|
||||||
|
$this->CurBit = ($this->CurBit - $this->LastBit) + 16;
|
||||||
|
$this->LastBit = (2 + $count) << 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iRet = 0;
|
||||||
|
for ($i = $this->CurBit, $j = 0; $j < $this->CodeSize; $i++, $j++)
|
||||||
|
$iRet |= (($this->Buf[intval($i / 8)] & (1 << ($i % 8))) != 0) << $j;
|
||||||
|
|
||||||
|
$this->CurBit += $this->CodeSize;
|
||||||
|
return $iRet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class gif_color_table
|
||||||
|
{
|
||||||
|
public $m_nColors;
|
||||||
|
public $m_arColors;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
unset($this->m_nColors, $this->m_arColors);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load($lpData, $num)
|
||||||
|
{
|
||||||
|
$this->m_nColors = 0;
|
||||||
|
$this->m_arColors = array();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $num; $i++)
|
||||||
|
{
|
||||||
|
$rgb = substr($lpData, $i * 3, 3);
|
||||||
|
if (strlen($rgb) < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$this->m_arColors[] = (ord($rgb[2]) << 16) + (ord($rgb[1]) << 8) + ord($rgb[0]);
|
||||||
|
$this->m_nColors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString()
|
||||||
|
{
|
||||||
|
$ret = '';
|
||||||
|
|
||||||
|
for ($i = 0; $i < $this->m_nColors; $i++)
|
||||||
|
{
|
||||||
|
$ret .=
|
||||||
|
chr(($this->m_arColors[$i] & 0x000000FF)) . // R
|
||||||
|
chr(($this->m_arColors[$i] & 0x0000FF00) >> 8) . // G
|
||||||
|
chr(($this->m_arColors[$i] & 0x00FF0000) >> 16); // B
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorIndex($rgb)
|
||||||
|
{
|
||||||
|
$dif = 0;
|
||||||
|
$rgb = intval($rgb) & 0xFFFFFF;
|
||||||
|
$r1 = ($rgb & 0x0000FF);
|
||||||
|
$g1 = ($rgb & 0x00FF00) >> 8;
|
||||||
|
$b1 = ($rgb & 0xFF0000) >> 16;
|
||||||
|
$idx = -1;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $this->m_nColors; $i++)
|
||||||
|
{
|
||||||
|
$r2 = ($this->m_arColors[$i] & 0x000000FF);
|
||||||
|
$g2 = ($this->m_arColors[$i] & 0x0000FF00) >> 8;
|
||||||
|
$b2 = ($this->m_arColors[$i] & 0x00FF0000) >> 16;
|
||||||
|
$d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1);
|
||||||
|
|
||||||
|
if (($idx == -1) || ($d < $dif))
|
||||||
|
{
|
||||||
|
$idx = $i;
|
||||||
|
$dif = $d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class gif_file_header
|
||||||
|
{
|
||||||
|
public $m_lpVer, $m_nWidth, $m_nHeight, $m_bGlobalClr, $m_nColorRes;
|
||||||
|
public $m_bSorted, $m_nTableSize, $m_nBgColor, $m_nPixelRatio;
|
||||||
|
public $m_colorTable;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
unset($this->m_lpVer, $this->m_nWidth, $this->m_nHeight, $this->m_bGlobalClr, $this->m_nColorRes);
|
||||||
|
unset($this->m_bSorted, $this->m_nTableSize, $this->m_nBgColor, $this->m_nPixelRatio, $this->m_colorTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load($lpData, &$hdrLen)
|
||||||
|
{
|
||||||
|
$hdrLen = 0;
|
||||||
|
|
||||||
|
$this->m_lpVer = substr($lpData, 0, 6);
|
||||||
|
if (($this->m_lpVer != 'GIF87a') && ($this->m_lpVer != 'GIF89a'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
list ($this->m_nWidth, $this->m_nHeight) = array_values(unpack('v2', substr($lpData, 6, 4)));
|
||||||
|
|
||||||
|
if (!$this->m_nWidth || !$this->m_nHeight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$b = ord(substr($lpData, 10, 1));
|
||||||
|
$this->m_bGlobalClr = ($b & 0x80) ? true : false;
|
||||||
|
$this->m_nColorRes = ($b & 0x70) >> 4;
|
||||||
|
$this->m_bSorted = ($b & 0x08) ? true : false;
|
||||||
|
$this->m_nTableSize = 2 << ($b & 0x07);
|
||||||
|
$this->m_nBgColor = ord(substr($lpData, 11, 1));
|
||||||
|
$this->m_nPixelRatio = ord(substr($lpData, 12, 1));
|
||||||
|
$hdrLen = 13;
|
||||||
|
|
||||||
|
if ($this->m_bGlobalClr)
|
||||||
|
{
|
||||||
|
$this->m_colorTable = new gif_color_table();
|
||||||
|
if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$hdrLen += 3 * $this->m_nTableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class gif_image_header
|
||||||
|
{
|
||||||
|
public $m_nLeft, $m_nTop, $m_nWidth, $m_nHeight, $m_bLocalClr;
|
||||||
|
public $m_bInterlace, $m_bSorted, $m_nTableSize, $m_colorTable;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
unset($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight, $this->m_bLocalClr);
|
||||||
|
unset($this->m_bInterlace, $this->m_bSorted, $this->m_nTableSize, $this->m_colorTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load($lpData, &$hdrLen)
|
||||||
|
{
|
||||||
|
$hdrLen = 0;
|
||||||
|
|
||||||
|
// Get the width/height/etc. from the header.
|
||||||
|
list ($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight) = array_values(unpack('v4', substr($lpData, 0, 8)));
|
||||||
|
|
||||||
|
if (!$this->m_nWidth || !$this->m_nHeight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$b = ord($lpData[8]);
|
||||||
|
$this->m_bLocalClr = ($b & 0x80) ? true : false;
|
||||||
|
$this->m_bInterlace = ($b & 0x40) ? true : false;
|
||||||
|
$this->m_bSorted = ($b & 0x20) ? true : false;
|
||||||
|
$this->m_nTableSize = 2 << ($b & 0x07);
|
||||||
|
$hdrLen = 9;
|
||||||
|
|
||||||
|
if ($this->m_bLocalClr)
|
||||||
|
{
|
||||||
|
$this->m_colorTable = new gif_color_table();
|
||||||
|
if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$hdrLen += 3 * $this->m_nTableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class gif_image
|
||||||
|
{
|
||||||
|
public $m_disp, $m_bUser, $m_bTrans, $m_nDelay, $m_nTrans, $m_lpComm;
|
||||||
|
public $m_gih, $m_data, $m_lzw;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
unset($this->m_disp, $this->m_bUser, $this->m_nDelay, $this->m_nTrans, $this->m_lpComm, $this->m_data);
|
||||||
|
$this->m_gih = new gif_image_header();
|
||||||
|
$this->m_lzw = new gif_lzw_compression();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load($data, &$datLen)
|
||||||
|
{
|
||||||
|
$datLen = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$b = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
$datLen++;
|
||||||
|
|
||||||
|
switch ($b)
|
||||||
|
{
|
||||||
|
// Extension...
|
||||||
|
case 0x21:
|
||||||
|
$len = 0;
|
||||||
|
if (!$this->skipExt($data, $len))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$datLen += $len;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Image...
|
||||||
|
case 0x2C:
|
||||||
|
// Load the header and color table.
|
||||||
|
$len = 0;
|
||||||
|
if (!$this->m_gih->load($data, $len))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$data = substr($data, $len);
|
||||||
|
$datLen += $len;
|
||||||
|
|
||||||
|
// Decompress the data, and ride on home ;).
|
||||||
|
$len = 0;
|
||||||
|
if (!($this->m_data = $this->m_lzw->decompress($data, $len)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$datLen += $len;
|
||||||
|
|
||||||
|
if ($this->m_gih->m_bInterlace)
|
||||||
|
$this->deInterlace();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 0x3B: // EOF
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function skipExt(&$data, &$extLen)
|
||||||
|
{
|
||||||
|
$extLen = 0;
|
||||||
|
|
||||||
|
$b = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
$extLen++;
|
||||||
|
|
||||||
|
switch ($b)
|
||||||
|
{
|
||||||
|
// Graphic Control...
|
||||||
|
case 0xF9:
|
||||||
|
$b = ord($data[1]);
|
||||||
|
$this->m_disp = ($b & 0x1C) >> 2;
|
||||||
|
$this->m_bUser = ($b & 0x02) ? true : false;
|
||||||
|
$this->m_bTrans = ($b & 0x01) ? true : false;
|
||||||
|
list ($this->m_nDelay) = array_values(unpack('v', substr($data, 2, 2)));
|
||||||
|
$this->m_nTrans = ord($data[4]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Comment...
|
||||||
|
case 0xFE:
|
||||||
|
$this->m_lpComm = substr($data, 1, ord($data[0]));
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Plain text...
|
||||||
|
case 0x01:
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Application...
|
||||||
|
case 0xFF:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip default as defs may change.
|
||||||
|
$b = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
$extLen++;
|
||||||
|
while ($b > 0)
|
||||||
|
{
|
||||||
|
$data = substr($data, $b);
|
||||||
|
$extLen += $b;
|
||||||
|
$b = ord($data[0]);
|
||||||
|
$data = substr($data, 1);
|
||||||
|
$extLen++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deInterlace()
|
||||||
|
{
|
||||||
|
$data = $this->m_data;
|
||||||
|
|
||||||
|
for ($i = 0; $i < 4; $i++)
|
||||||
|
{
|
||||||
|
switch ($i)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
$s = 8;
|
||||||
|
$y = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
$s = 8;
|
||||||
|
$y = 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
$s = 4;
|
||||||
|
$y = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
$s = 2;
|
||||||
|
$y = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; $y < $this->m_gih->m_nHeight; $y += $s)
|
||||||
|
{
|
||||||
|
$lne = substr($this->m_data, 0, $this->m_gih->m_nWidth);
|
||||||
|
$this->m_data = substr($this->m_data, $this->m_gih->m_nWidth);
|
||||||
|
|
||||||
|
$data =
|
||||||
|
substr($data, 0, $y * $this->m_gih->m_nWidth) .
|
||||||
|
$lne .
|
||||||
|
substr($data, ($y + 1) * $this->m_gih->m_nWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->m_data = $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class gif_file
|
||||||
|
{
|
||||||
|
public $header, $image, $data, $loaded;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->data = '';
|
||||||
|
$this->loaded = false;
|
||||||
|
$this->header = new gif_file_header();
|
||||||
|
$this->image = new gif_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadFile($filename, $iIndex)
|
||||||
|
{
|
||||||
|
if ($iIndex < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$this->data = @file_get_contents($filename);
|
||||||
|
if ($this->data === false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Tell the header to load up....
|
||||||
|
$len = 0;
|
||||||
|
if (!$this->header->load($this->data, $len))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$this->data = substr($this->data, $len);
|
||||||
|
|
||||||
|
// Keep reading (at least once) so we get to the actual image we're looking for.
|
||||||
|
for ($j = 0; $j <= $iIndex; $j++)
|
||||||
|
{
|
||||||
|
$imgLen = 0;
|
||||||
|
if (!$this->image->load($this->data, $imgLen))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$this->data = substr($this->data, $imgLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_png_data($background_color)
|
||||||
|
{
|
||||||
|
if (!$this->loaded)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Prepare the color table.
|
||||||
|
if ($this->image->m_gih->m_bLocalClr)
|
||||||
|
{
|
||||||
|
$colors = $this->image->m_gih->m_nTableSize;
|
||||||
|
$pal = $this->image->m_gih->m_colorTable->toString();
|
||||||
|
|
||||||
|
if ($background_color != -1)
|
||||||
|
$background_color = $this->image->m_gih->m_colorTable->colorIndex($background_color);
|
||||||
|
}
|
||||||
|
elseif ($this->header->m_bGlobalClr)
|
||||||
|
{
|
||||||
|
$colors = $this->header->m_nTableSize;
|
||||||
|
$pal = $this->header->m_colorTable->toString();
|
||||||
|
|
||||||
|
if ($background_color != -1)
|
||||||
|
$background_color = $this->header->m_colorTable->colorIndex($background_color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$colors = 0;
|
||||||
|
$background_color = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($background_color == -1)
|
||||||
|
$background_color = $this->header->m_nBgColor;
|
||||||
|
|
||||||
|
$data = &$this->image->m_data;
|
||||||
|
$header = &$this->image->m_gih;
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$bmp = '';
|
||||||
|
|
||||||
|
// Prepare the bitmap itself.
|
||||||
|
for ($y = 0; $y < $this->header->m_nHeight; $y++)
|
||||||
|
{
|
||||||
|
$bmp .= "\x00";
|
||||||
|
|
||||||
|
for ($x = 0; $x < $this->header->m_nWidth; $x++, $i++)
|
||||||
|
{
|
||||||
|
// Is this in the proper range? If so, get the specific pixel data...
|
||||||
|
if ($x >= $header->m_nLeft && $y >= $header->m_nTop && $x < ($header->m_nLeft + $header->m_nWidth) && $y < ($header->m_nTop + $header->m_nHeight))
|
||||||
|
$bmp .= $data[$i];
|
||||||
|
// Otherwise, this is background...
|
||||||
|
else
|
||||||
|
$bmp .= chr($background_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$bmp = gzcompress($bmp, 9);
|
||||||
|
|
||||||
|
// Output the basic signature first of all.
|
||||||
|
$out = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
||||||
|
|
||||||
|
// Now, we want the header...
|
||||||
|
$out .= "\x00\x00\x00\x0D";
|
||||||
|
$tmp = 'IHDR' . pack('N', (int) $this->header->m_nWidth) . pack('N', (int) $this->header->m_nHeight) . "\x08\x03\x00\x00\x00";
|
||||||
|
$out .= $tmp . pack('N', smf_crc32($tmp));
|
||||||
|
|
||||||
|
// The palette, assuming we have one to speak of...
|
||||||
|
if ($colors > 0)
|
||||||
|
{
|
||||||
|
$out .= pack('N', (int) $colors * 3);
|
||||||
|
$tmp = 'PLTE' . $pal;
|
||||||
|
$out .= $tmp . pack('N', smf_crc32($tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have any transparency we want to make available?
|
||||||
|
if ($this->image->m_bTrans && $colors > 0)
|
||||||
|
{
|
||||||
|
$out .= pack('N', (int) $colors);
|
||||||
|
$tmp = 'tRNS';
|
||||||
|
|
||||||
|
// Stick each color on - full transparency or none.
|
||||||
|
for ($i = 0; $i < $colors; $i++)
|
||||||
|
$tmp .= $i == $this->image->m_nTrans ? "\x00" : "\xFF";
|
||||||
|
|
||||||
|
$out .= $tmp . pack('N', smf_crc32($tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here's the data itself!
|
||||||
|
$out .= pack('N', strlen($bmp));
|
||||||
|
$tmp = 'IDAT' . $bmp;
|
||||||
|
$out .= $tmp . pack('N', smf_crc32($tmp));
|
||||||
|
|
||||||
|
// EOF marker...
|
||||||
|
$out .= "\x00\x00\x00\x00" . 'IEND' . "\xAE\x42\x60\x82";
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 64-bit only functions?
|
||||||
|
if (!function_exists('smf_crc32'))
|
||||||
|
{
|
||||||
|
require_once $sourcedir . '/Subs-Compat.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1229
Sources/Class-Package.php
Normal file
611
Sources/Class-Punycode.php
Executable file
|
@ -0,0 +1,611 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* A class for encoding/decoding Punycode.
|
||||||
|
*
|
||||||
|
* Derived from this library: https://github.com/true/php-punycode
|
||||||
|
*
|
||||||
|
* @author TrueServer B.V. <support@true.nl>
|
||||||
|
* @package php-punycode
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Punycode implementation as described in RFC 3492
|
||||||
|
*
|
||||||
|
* @link http://tools.ietf.org/html/rfc3492
|
||||||
|
*/
|
||||||
|
class Punycode
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstring parameter values
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const BASE = 36;
|
||||||
|
const TMIN = 1;
|
||||||
|
const TMAX = 26;
|
||||||
|
const SKEW = 38;
|
||||||
|
const DAMP = 700;
|
||||||
|
const INITIAL_BIAS = 72;
|
||||||
|
const INITIAL_N = 128;
|
||||||
|
const PREFIX = 'xn--';
|
||||||
|
const DELIMITER = '-';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDNA Error constants
|
||||||
|
*/
|
||||||
|
const IDNA_ERROR_EMPTY_LABEL = 1;
|
||||||
|
const IDNA_ERROR_LABEL_TOO_LONG = 2;
|
||||||
|
const IDNA_ERROR_DOMAIN_NAME_TOO_LONG = 4;
|
||||||
|
const IDNA_ERROR_LEADING_HYPHEN = 8;
|
||||||
|
const IDNA_ERROR_TRAILING_HYPHEN = 16;
|
||||||
|
const IDNA_ERROR_HYPHEN_3_4 = 32;
|
||||||
|
const IDNA_ERROR_LEADING_COMBINING_MARK = 64;
|
||||||
|
const IDNA_ERROR_DISALLOWED = 128;
|
||||||
|
const IDNA_ERROR_PUNYCODE = 256;
|
||||||
|
const IDNA_ERROR_LABEL_HAS_DOT = 512;
|
||||||
|
const IDNA_ERROR_INVALID_ACE_LABEL = 1024;
|
||||||
|
const IDNA_ERROR_BIDI = 2048;
|
||||||
|
const IDNA_ERROR_CONTEXTJ = 4096;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode table
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
protected static $encodeTable = array(
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
|
||||||
|
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode table
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
*/
|
||||||
|
protected static $decodeTable = array(
|
||||||
|
'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
|
||||||
|
'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
|
||||||
|
'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
|
||||||
|
's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
|
||||||
|
'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
|
||||||
|
'4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Character encoding
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
protected $encoding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use Non-Transitional Processing.
|
||||||
|
* Setting this to true breaks backward compatibility with IDNA2003.
|
||||||
|
*
|
||||||
|
* @param bool
|
||||||
|
*/
|
||||||
|
protected $nonTransitional = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use STD3 ASCII rules.
|
||||||
|
*
|
||||||
|
* @param bool
|
||||||
|
*/
|
||||||
|
protected $std3 = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $encoding Character encoding
|
||||||
|
*/
|
||||||
|
public function __construct($encoding = 'UTF-8')
|
||||||
|
{
|
||||||
|
$this->encoding = $encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable Non-Transitional Processing
|
||||||
|
*
|
||||||
|
* @param bool $nonTransitional Whether to use Non-Transitional Processing
|
||||||
|
*/
|
||||||
|
public function useNonTransitional(bool $nonTransitional)
|
||||||
|
{
|
||||||
|
$this->nonTransitional = $nonTransitional;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable STD3 ASCII rules
|
||||||
|
*
|
||||||
|
* @param bool $std3 Whether to use STD3 ASCII rules
|
||||||
|
*/
|
||||||
|
public function useStd3(bool $std3)
|
||||||
|
{
|
||||||
|
$this->std3 = $std3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a domain to its Punycode version
|
||||||
|
*
|
||||||
|
* @param string $input Domain name in Unicode to be encoded
|
||||||
|
* @return string Punycode representation in ASCII
|
||||||
|
*/
|
||||||
|
public function encode($input)
|
||||||
|
{
|
||||||
|
// For compatibility with idn_to_* functions
|
||||||
|
if ($this->decode($input) === false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$errors = array();
|
||||||
|
$preprocessed = $this->preprocess($input, $errors);
|
||||||
|
|
||||||
|
if (!empty($errors))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode('.', $preprocessed);
|
||||||
|
foreach ($parts as $p => &$part) {
|
||||||
|
$part = $this->encodePart($part);
|
||||||
|
|
||||||
|
$validation_status = $this->validateLabel($part, true);
|
||||||
|
|
||||||
|
switch ($validation_status) {
|
||||||
|
case self::IDNA_ERROR_LABEL_TOO_LONG:
|
||||||
|
case self::IDNA_ERROR_LEADING_HYPHEN:
|
||||||
|
case self::IDNA_ERROR_TRAILING_HYPHEN:
|
||||||
|
case self::IDNA_ERROR_LEADING_COMBINING_MARK:
|
||||||
|
case self::IDNA_ERROR_DISALLOWED:
|
||||||
|
case self::IDNA_ERROR_PUNYCODE:
|
||||||
|
case self::IDNA_ERROR_LABEL_HAS_DOT:
|
||||||
|
case self::IDNA_ERROR_INVALID_ACE_LABEL:
|
||||||
|
case self::IDNA_ERROR_BIDI:
|
||||||
|
case self::IDNA_ERROR_CONTEXTJ:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::IDNA_ERROR_HYPHEN_3_4:
|
||||||
|
$part = $parts[$p];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::IDNA_ERROR_EMPTY_LABEL:
|
||||||
|
$parts_count = count($parts);
|
||||||
|
if ($parts_count === 1 || $p !== $parts_count - 1)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$output = implode('.', $parts);
|
||||||
|
|
||||||
|
// IDNA_ERROR_DOMAIN_NAME_TOO_LONG
|
||||||
|
if (strlen(rtrim($output, '.')) > 253)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a part of a domain name, such as tld, to its Punycode version
|
||||||
|
*
|
||||||
|
* @param string $input Part of a domain name
|
||||||
|
* @return string Punycode representation of a domain part
|
||||||
|
*/
|
||||||
|
protected function encodePart($input)
|
||||||
|
{
|
||||||
|
$codePoints = $this->listCodePoints($input);
|
||||||
|
|
||||||
|
$n = static::INITIAL_N;
|
||||||
|
$bias = static::INITIAL_BIAS;
|
||||||
|
$delta = 0;
|
||||||
|
$h = $b = count($codePoints['basic']);
|
||||||
|
|
||||||
|
$output = '';
|
||||||
|
foreach ($codePoints['basic'] as $code) {
|
||||||
|
$output .= $this->codePointToChar($code);
|
||||||
|
}
|
||||||
|
if ($input === $output) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
if ($b > 0) {
|
||||||
|
$output .= static::DELIMITER;
|
||||||
|
}
|
||||||
|
|
||||||
|
$codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
|
||||||
|
sort($codePoints['nonBasic']);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$length = mb_strlen($input, $this->encoding);
|
||||||
|
while ($h < $length) {
|
||||||
|
$m = $codePoints['nonBasic'][$i++];
|
||||||
|
$delta = $delta + ($m - $n) * ($h + 1);
|
||||||
|
$n = $m;
|
||||||
|
|
||||||
|
foreach ($codePoints['all'] as $c) {
|
||||||
|
if ($c < $n || $c < static::INITIAL_N) {
|
||||||
|
$delta++;
|
||||||
|
}
|
||||||
|
if ($c === $n) {
|
||||||
|
$q = $delta;
|
||||||
|
for ($k = static::BASE;; $k += static::BASE) {
|
||||||
|
$t = $this->calculateThreshold($k, $bias);
|
||||||
|
if ($q < $t) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = $t + (((int) $q - $t) % (static::BASE - $t));
|
||||||
|
$output .= static::$encodeTable[$code];
|
||||||
|
|
||||||
|
$q = ($q - $t) / (static::BASE - $t);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output .= static::$encodeTable[(int) $q];
|
||||||
|
$bias = $this->adapt($delta, $h + 1, ($h === $b));
|
||||||
|
$delta = 0;
|
||||||
|
$h++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$delta++;
|
||||||
|
$n++;
|
||||||
|
}
|
||||||
|
$out = static::PREFIX . $output;
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a Punycode domain name to its Unicode counterpart
|
||||||
|
*
|
||||||
|
* @param string $input Domain name in Punycode
|
||||||
|
* @return string Unicode domain name
|
||||||
|
*/
|
||||||
|
public function decode($input)
|
||||||
|
{
|
||||||
|
$errors = array();
|
||||||
|
$preprocessed = $this->preprocess($input, $errors);
|
||||||
|
|
||||||
|
if (!empty($errors))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode('.', $preprocessed);
|
||||||
|
foreach ($parts as $p => &$part)
|
||||||
|
{
|
||||||
|
if (strpos($part, static::PREFIX) === 0)
|
||||||
|
{
|
||||||
|
$part = substr($part, strlen(static::PREFIX));
|
||||||
|
$part = $this->decodePart($part);
|
||||||
|
|
||||||
|
if ($part === false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->validateLabel($part, false) !== 0)
|
||||||
|
{
|
||||||
|
if ($part === '')
|
||||||
|
{
|
||||||
|
$parts_count = count($parts);
|
||||||
|
|
||||||
|
if ($parts_count === 1 || $p !== $parts_count - 1)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$output = implode('.', $parts);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a part of domain name, such as tld
|
||||||
|
*
|
||||||
|
* @param string $input Part of a domain name
|
||||||
|
* @return string Unicode domain part
|
||||||
|
*/
|
||||||
|
protected function decodePart($input)
|
||||||
|
{
|
||||||
|
$n = static::INITIAL_N;
|
||||||
|
$i = 0;
|
||||||
|
$bias = static::INITIAL_BIAS;
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
$pos = strrpos($input, static::DELIMITER);
|
||||||
|
if ($pos !== false)
|
||||||
|
{
|
||||||
|
$output = substr($input, 0, $pos++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputLength = strlen($output);
|
||||||
|
$inputLength = strlen($input);
|
||||||
|
while ($pos < $inputLength)
|
||||||
|
{
|
||||||
|
$oldi = $i;
|
||||||
|
$w = 1;
|
||||||
|
|
||||||
|
for ($k = static::BASE;; $k += static::BASE)
|
||||||
|
{
|
||||||
|
if (!isset($input[$pos]) || !isset(static::$decodeTable[$input[$pos]]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$digit = static::$decodeTable[$input[$pos++]];
|
||||||
|
$i = $i + ($digit * $w);
|
||||||
|
$t = $this->calculateThreshold($k, $bias);
|
||||||
|
|
||||||
|
if ($digit < $t)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$w = $w * (static::BASE - $t);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0));
|
||||||
|
$n = $n + (int) ($i / $outputLength);
|
||||||
|
$i = $i % ($outputLength);
|
||||||
|
$output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding);
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the bias threshold to fall between TMIN and TMAX
|
||||||
|
*
|
||||||
|
* @param integer $k
|
||||||
|
* @param integer $bias
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function calculateThreshold($k, $bias)
|
||||||
|
{
|
||||||
|
if ($k <= $bias + static::TMIN)
|
||||||
|
{
|
||||||
|
return static::TMIN;
|
||||||
|
}
|
||||||
|
elseif ($k >= $bias + static::TMAX)
|
||||||
|
{
|
||||||
|
return static::TMAX;
|
||||||
|
}
|
||||||
|
return $k - $bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bias adaptation
|
||||||
|
*
|
||||||
|
* @param integer $delta
|
||||||
|
* @param integer $numPoints
|
||||||
|
* @param boolean $firstTime
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function adapt($delta, $numPoints, $firstTime)
|
||||||
|
{
|
||||||
|
$delta = (int) (
|
||||||
|
($firstTime)
|
||||||
|
? $delta / static::DAMP
|
||||||
|
: $delta / 2
|
||||||
|
);
|
||||||
|
$delta += (int) ($delta / $numPoints);
|
||||||
|
|
||||||
|
$k = 0;
|
||||||
|
while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2)
|
||||||
|
{
|
||||||
|
$delta = (int) ($delta / (static::BASE - static::TMIN));
|
||||||
|
$k = $k + static::BASE;
|
||||||
|
}
|
||||||
|
$k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW));
|
||||||
|
|
||||||
|
return $k;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List code points for a given input
|
||||||
|
*
|
||||||
|
* @param string $input
|
||||||
|
* @return array Multi-dimension array with basic, non-basic and aggregated code points
|
||||||
|
*/
|
||||||
|
protected function listCodePoints($input)
|
||||||
|
{
|
||||||
|
$codePoints = array(
|
||||||
|
'all' => array(),
|
||||||
|
'basic' => array(),
|
||||||
|
'nonBasic' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$length = mb_strlen($input, $this->encoding);
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
{
|
||||||
|
$char = mb_substr($input, $i, 1, $this->encoding);
|
||||||
|
$code = $this->charToCodePoint($char);
|
||||||
|
if ($code < 128)
|
||||||
|
{
|
||||||
|
$codePoints['all'][] = $codePoints['basic'][] = $code;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$codePoints['all'][] = $codePoints['nonBasic'][] = $code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $codePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a single or multi-byte character to its code point
|
||||||
|
*
|
||||||
|
* @param string $char
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function charToCodePoint($char)
|
||||||
|
{
|
||||||
|
$code = ord($char[0]);
|
||||||
|
if ($code < 128)
|
||||||
|
{
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
elseif ($code < 224)
|
||||||
|
{
|
||||||
|
return (($code - 192) * 64) + (ord($char[1]) - 128);
|
||||||
|
}
|
||||||
|
elseif ($code < 240)
|
||||||
|
{
|
||||||
|
return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a code point to its single or multi-byte character
|
||||||
|
*
|
||||||
|
* @param integer $code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function codePointToChar($code)
|
||||||
|
{
|
||||||
|
if ($code <= 0x7F)
|
||||||
|
{
|
||||||
|
return chr($code);
|
||||||
|
}
|
||||||
|
elseif ($code <= 0x7FF)
|
||||||
|
{
|
||||||
|
return chr(($code >> 6) + 192) . chr(($code & 63) + 128);
|
||||||
|
}
|
||||||
|
elseif ($code <= 0xFFFF)
|
||||||
|
{
|
||||||
|
return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare domain name string for Punycode processing.
|
||||||
|
* See https://www.unicode.org/reports/tr46/#Processing
|
||||||
|
*
|
||||||
|
* @param string $domain A domain name
|
||||||
|
* @param array $errors Will record any errors encountered during preprocessing
|
||||||
|
*/
|
||||||
|
protected function preprocess(string $domain, array &$errors = array())
|
||||||
|
{
|
||||||
|
global $sourcedir;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Unicode/Idna.php');
|
||||||
|
require_once($sourcedir . '/Subs-Charset.php');
|
||||||
|
|
||||||
|
$regexes = idna_regex();
|
||||||
|
|
||||||
|
if (preg_match('/[' . $regexes['disallowed'] . ($this->std3 ? $regexes['disallowed_std3'] : '') . ']/u', $domain))
|
||||||
|
$errors[] = 'disallowed';
|
||||||
|
|
||||||
|
$domain = preg_replace('/[' . $regexes['ignored'] . ']/u', '', $domain);
|
||||||
|
|
||||||
|
unset($regexes);
|
||||||
|
|
||||||
|
$maps = idna_maps();
|
||||||
|
|
||||||
|
if (!$this->nonTransitional)
|
||||||
|
$maps = array_merge($maps, idna_maps_deviation());
|
||||||
|
|
||||||
|
if (!$this->std3)
|
||||||
|
$maps = array_merge($maps, idna_maps_not_std3());
|
||||||
|
|
||||||
|
return utf8_normalize_c(strtr($domain, $maps));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an individual part of a domain name.
|
||||||
|
*
|
||||||
|
* @param string $label Individual part of a domain name.
|
||||||
|
* @param bool $toPunycode True for encoding to Punycode, false for decoding.
|
||||||
|
*/
|
||||||
|
protected function validateLabel(string $label, bool $toPunycode = true)
|
||||||
|
{
|
||||||
|
global $sourcedir;
|
||||||
|
|
||||||
|
$length = strlen($label);
|
||||||
|
|
||||||
|
if ($length === 0)
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_EMPTY_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($toPunycode)
|
||||||
|
{
|
||||||
|
if ($length > 63)
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_LABEL_TOO_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->std3 && $length !== strspn($label, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-'))
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_PUNYCODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($label, '-') === 0)
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_LEADING_HYPHEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strrpos($label, '-') === $length - 1)
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_TRAILING_HYPHEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($label, 2, 2) === '--')
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_HYPHEN_3_4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^\p{M}/u', $label))
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_LEADING_COMBINING_MARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Unicode/Idna.php');
|
||||||
|
require_once($sourcedir . '/Subs-Charset.php');
|
||||||
|
|
||||||
|
$regexes = idna_regex();
|
||||||
|
|
||||||
|
if (preg_match('/[' . $regexes['disallowed'] . ($this->std3 ? $regexes['disallowed_std3'] : '') . ']/u', $label))
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_INVALID_ACE_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$toPunycode && $label !== utf8_normalize_kc($label))
|
||||||
|
{
|
||||||
|
return self::IDNA_ERROR_INVALID_ACE_LABEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
291
Sources/Class-SearchAPI.php
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface search_api_interface
|
||||||
|
*/
|
||||||
|
interface search_api_interface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check whether the specific search operation can be performed by this API.
|
||||||
|
* The operations are the functions listed in the interface, if not supported
|
||||||
|
* they need not be declared
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $methodName The method
|
||||||
|
* @param array $query_params Any parameters for the query
|
||||||
|
* @return boolean Whether or not the specified method is supported
|
||||||
|
*/
|
||||||
|
public function supportsMethod($methodName, $query_params = array());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this method is valid for implementation or not
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @return bool Whether or not this method is valid
|
||||||
|
*/
|
||||||
|
public function isValid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for usort used to sort the fulltext results.
|
||||||
|
* the order of sorting is: large words, small words, large words that
|
||||||
|
* are excluded from the search, small words that are excluded.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $a Word A
|
||||||
|
* @param string $b Word B
|
||||||
|
* @return int An integer indicating how the words should be sorted
|
||||||
|
*/
|
||||||
|
public function searchSort($a, $b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback while preparing indexes for searching
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $word A word to index
|
||||||
|
* @param array $wordsSearch Search words
|
||||||
|
* @param array $wordsExclude Words to exclude
|
||||||
|
* @param bool $isExcluded Whether the specfied word should be excluded
|
||||||
|
*/
|
||||||
|
public function prepareIndexes($word, array &$wordsSearch, array &$wordsExclude, $isExcluded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for indexed words.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $words An array of words
|
||||||
|
* @param array $search_data An array of search data
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function indexedWordQuery(array $words, array $search_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when a post is created
|
||||||
|
*
|
||||||
|
* @see createPost()
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $msgOptions An array of post data
|
||||||
|
* @param array $topicOptions An array of topic data
|
||||||
|
* @param array $posterOptions An array of info about the person who made this post
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function postCreated(array &$msgOptions, array &$topicOptions, array &$posterOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when a post is modified
|
||||||
|
*
|
||||||
|
* @see modifyPost()
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $msgOptions An array of post data
|
||||||
|
* @param array $topicOptions An array of topic data
|
||||||
|
* @param array $posterOptions An array of info about the person who made this post
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function postModified(array &$msgOptions, array &$topicOptions, array &$posterOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when a post is removed
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param int $id_msg The ID of the post that was removed
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function postRemoved($id_msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when a topic is removed
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $topics The ID(s) of the removed topic(s)
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function topicsRemoved(array $topics);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when a topic is moved
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $topics The ID(s) of the moved topic(s)
|
||||||
|
* @param int $board_to The board that the topics were moved to
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function topicsMoved(array $topics, $board_to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for actually performing the search query
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param array $query_params An array of parameters for the query
|
||||||
|
* @param array $searchWords The words that were searched for
|
||||||
|
* @param array $excludedIndexWords Indexed words that should be excluded
|
||||||
|
* @param array $participants
|
||||||
|
* @param array $searchArray
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function searchQuery(array $query_params, array $searchWords, array $excludedIndexWords, array &$participants, array &$searchArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class search_api
|
||||||
|
*/
|
||||||
|
abstract class search_api implements search_api_interface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The maximum SMF version that this will work with.
|
||||||
|
*/
|
||||||
|
public $version_compatible = '2.1.999';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The minimum SMF version that this will work with.
|
||||||
|
*/
|
||||||
|
public $min_smf_version = '2.1 RC1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool Whether or not it's supported
|
||||||
|
*/
|
||||||
|
public $is_supported = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function supportsMethod($methodName, $query_params = null)
|
||||||
|
{
|
||||||
|
switch ($methodName)
|
||||||
|
{
|
||||||
|
case 'postRemoved':
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// All other methods, too bad dunno you.
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isValid()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function searchSort($a, $b)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function prepareIndexes($word, array &$wordsSearch, array &$wordsExclude, $isExcluded)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function indexedWordQuery(array $words, array $search_data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function postCreated(array &$msgOptions, array &$topicOptions, array &$posterOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function postModified(array &$msgOptions, array &$topicOptions, array &$posterOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function postRemoved($id_msg)
|
||||||
|
{
|
||||||
|
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT DISTINCT id_search
|
||||||
|
FROM {db_prefix}log_search_results
|
||||||
|
WHERE id_msg = {int:id_msg}',
|
||||||
|
array(
|
||||||
|
'id_msg' => $id_msg,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$id_searchs = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
$id_searchs[] = $row['id_search'];
|
||||||
|
|
||||||
|
if (count($id_searchs) < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_search_results
|
||||||
|
WHERE id_search in ({array_int:id_searchs})',
|
||||||
|
array(
|
||||||
|
'id_searchs' => $id_searchs,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_search_topics
|
||||||
|
WHERE id_search in ({array_int:id_searchs})',
|
||||||
|
array(
|
||||||
|
'id_searchs' => $id_searchs,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_search_messages
|
||||||
|
WHERE id_search in ({array_int:id_searchs})',
|
||||||
|
array(
|
||||||
|
'id_searchs' => $id_searchs,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function topicsRemoved(array $topics)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function topicsMoved(array $topics, $board_to)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function searchQuery(array $query_params, array $searchWords, array $excludedIndexWords, array &$participants, array &$searchArray)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
371
Sources/Class-TOTP.php
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for generating the codes compatible with the Google Authenticator and similar TOTP
|
||||||
|
* clients.
|
||||||
|
*
|
||||||
|
* NOTE: A lot of the logic from this class has been borrowed from this class:
|
||||||
|
* https://www.idontplaydarts.com/wp-content/uploads/2011/07/ga.php_.txt
|
||||||
|
*
|
||||||
|
* @author Chris Cornutt <ccornutt@phpdeveloper.org>
|
||||||
|
* @package GAuth
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace TOTP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Auth
|
||||||
|
*
|
||||||
|
* @package TOTP
|
||||||
|
*/
|
||||||
|
class Auth
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array Internal lookup table
|
||||||
|
*/
|
||||||
|
private $lookup = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Initialization key
|
||||||
|
*/
|
||||||
|
private $initKey = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer Seconds between key refreshes
|
||||||
|
*/
|
||||||
|
private $refreshSeconds = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer The length of codes to generate
|
||||||
|
*/
|
||||||
|
private $codeLength = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer Range plus/minus for "window of opportunity" on allowed codes
|
||||||
|
*/
|
||||||
|
private $range = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the object and set up the lookup table
|
||||||
|
* Optionally the Initialization key
|
||||||
|
*
|
||||||
|
* @param string $initKey Initialization key
|
||||||
|
*/
|
||||||
|
public function __construct($initKey = null)
|
||||||
|
{
|
||||||
|
$this->buildLookup();
|
||||||
|
|
||||||
|
if ($initKey !== null)
|
||||||
|
{
|
||||||
|
$this->setInitKey($initKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the base32 lookup table
|
||||||
|
*/
|
||||||
|
public function buildLookup()
|
||||||
|
{
|
||||||
|
$lookup = array_combine(
|
||||||
|
array_merge(range('A', 'Z'), range(2, 7)),
|
||||||
|
range(0, 31)
|
||||||
|
);
|
||||||
|
$this->setLookup($lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current "range" value
|
||||||
|
*
|
||||||
|
* @return integer Range value
|
||||||
|
*/
|
||||||
|
public function getRange()
|
||||||
|
{
|
||||||
|
return $this->range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the "range" value
|
||||||
|
*
|
||||||
|
* @param integer $range Range value
|
||||||
|
* @return \TOTP\Auth instance
|
||||||
|
*/
|
||||||
|
public function setRange($range)
|
||||||
|
{
|
||||||
|
if (!is_numeric($range))
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Invalid window range');
|
||||||
|
}
|
||||||
|
$this->range = $range;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the initialization key for the object
|
||||||
|
*
|
||||||
|
* @param string $key Initialization key
|
||||||
|
* @throws \InvalidArgumentException If hash is not valid base32
|
||||||
|
* @return \TOTP\Auth instance
|
||||||
|
*/
|
||||||
|
public function setInitKey($key)
|
||||||
|
{
|
||||||
|
if (preg_match('/^[' . implode('', array_keys($this->getLookup())) . ']+$/', $key) == false)
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Invalid base32 hash!');
|
||||||
|
}
|
||||||
|
$this->initKey = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Initialization key
|
||||||
|
*
|
||||||
|
* @return string Initialization key
|
||||||
|
*/
|
||||||
|
public function getInitKey()
|
||||||
|
{
|
||||||
|
return $this->initKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the contents of the internal lookup table
|
||||||
|
*
|
||||||
|
* @param array $lookup Lookup data set
|
||||||
|
* @throws \InvalidArgumentException If lookup given is not an array
|
||||||
|
* @return \TOTP\Auth instance
|
||||||
|
*/
|
||||||
|
public function setLookup($lookup)
|
||||||
|
{
|
||||||
|
if (!is_array($lookup))
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Lookup value must be an array');
|
||||||
|
}
|
||||||
|
$this->lookup = $lookup;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current lookup data set
|
||||||
|
*
|
||||||
|
* @return array Lookup data
|
||||||
|
*/
|
||||||
|
public function getLookup()
|
||||||
|
{
|
||||||
|
return $this->lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of seconds for code refresh currently set
|
||||||
|
*
|
||||||
|
* @return integer Refresh in seconds
|
||||||
|
*/
|
||||||
|
public function getRefresh()
|
||||||
|
{
|
||||||
|
return $this->refreshSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the number of seconds to refresh codes
|
||||||
|
*
|
||||||
|
* @param integer $seconds Seconds to refresh
|
||||||
|
* @throws \InvalidArgumentException If seconds value is not numeric
|
||||||
|
* @return \TOTP\Auth instance
|
||||||
|
*/
|
||||||
|
public function setRefresh($seconds)
|
||||||
|
{
|
||||||
|
if (!is_numeric($seconds))
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Seconds must be numeric');
|
||||||
|
}
|
||||||
|
$this->refreshSeconds = $seconds;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current length for generated codes
|
||||||
|
*
|
||||||
|
* @return integer Code length
|
||||||
|
*/
|
||||||
|
public function getCodeLength()
|
||||||
|
{
|
||||||
|
return $this->codeLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the length of the generated codes
|
||||||
|
*
|
||||||
|
* @param integer $length Code length
|
||||||
|
* @return \TOTP\Auth instance
|
||||||
|
*/
|
||||||
|
public function setCodeLength($length)
|
||||||
|
{
|
||||||
|
$this->codeLength = $length;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given code
|
||||||
|
*
|
||||||
|
* @param string $code Code entered by user
|
||||||
|
* @param string $initKey Initialization key
|
||||||
|
* @param string $timestamp Timestamp for calculation
|
||||||
|
* @param integer $range Seconds before/after to validate hash against
|
||||||
|
* @throws \InvalidArgumentException If incorrect code length
|
||||||
|
* @return boolean Pass/fail of validation
|
||||||
|
*/
|
||||||
|
public function validateCode($code, $initKey = null, $timestamp = null, $range = null)
|
||||||
|
{
|
||||||
|
if (strlen($code) !== $this->getCodeLength())
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Incorrect code length');
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = ($range == null) ? $this->getRange() : $range;
|
||||||
|
$timestamp = ($timestamp == null) ? $this->generateTimestamp() : $timestamp;
|
||||||
|
$initKey = ($initKey == null) ? $this->getInitKey() : $initKey;
|
||||||
|
|
||||||
|
$binary = $this->base32_decode($initKey);
|
||||||
|
|
||||||
|
for ($time = ($timestamp - $range); $time <= ($timestamp + $range); $time++)
|
||||||
|
{
|
||||||
|
if ($this->generateOneTime($binary, $time) == $code)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a one-time code
|
||||||
|
*
|
||||||
|
* @param string $initKey Initialization key [optional]
|
||||||
|
* @param string $timestamp Timestamp for calculation [optional]
|
||||||
|
* @return string Generated code/hash
|
||||||
|
*/
|
||||||
|
public function generateOneTime($initKey = null, $timestamp = null)
|
||||||
|
{
|
||||||
|
$initKey = ($initKey == null) ? $this->getInitKey() : $initKey;
|
||||||
|
$timestamp = ($timestamp == null) ? $this->generateTimestamp() : $timestamp;
|
||||||
|
|
||||||
|
$hash = hash_hmac(
|
||||||
|
'sha1',
|
||||||
|
pack('N*', 0) . pack('N*', $timestamp),
|
||||||
|
$initKey,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return str_pad($this->truncateHash($hash), $this->getCodeLength(), '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a code/hash
|
||||||
|
* Useful for making Initialization codes
|
||||||
|
*
|
||||||
|
* @param integer $length Length for the generated code
|
||||||
|
* @return string Generated code
|
||||||
|
*/
|
||||||
|
public function generateCode($length = 16)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$lookup = implode('', array_keys($this->getLookup()));
|
||||||
|
$code = '';
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
{
|
||||||
|
$code .= $lookup[$smcFunc['random_int'](0, strlen($lookup) - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the timestamp for the calculation
|
||||||
|
*
|
||||||
|
* @return integer Timestamp
|
||||||
|
*/
|
||||||
|
public function generateTimestamp()
|
||||||
|
{
|
||||||
|
return floor(microtime(true) / $this->getRefresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate the given hash down to just what we need
|
||||||
|
*
|
||||||
|
* @param string $hash Hash to truncate
|
||||||
|
* @return string Truncated hash value
|
||||||
|
*/
|
||||||
|
public function truncateHash($hash)
|
||||||
|
{
|
||||||
|
$offset = ord($hash[19]) & 0xf;
|
||||||
|
|
||||||
|
return (
|
||||||
|
((ord($hash[$offset + 0]) & 0x7f) << 24) |
|
||||||
|
((ord($hash[$offset + 1]) & 0xff) << 16) |
|
||||||
|
((ord($hash[$offset + 2]) & 0xff) << 8) |
|
||||||
|
(ord($hash[$offset + 3]) & 0xff)
|
||||||
|
) % pow(10, $this->getCodeLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base32 decoding function
|
||||||
|
*
|
||||||
|
* @param string $hash The base32-encoded hash
|
||||||
|
* @throws \InvalidArgumentException When hash is not valid
|
||||||
|
* @return string Binary value of hash
|
||||||
|
*/
|
||||||
|
public function base32_decode($hash)
|
||||||
|
{
|
||||||
|
$lookup = $this->getLookup();
|
||||||
|
|
||||||
|
if (preg_match('/^[' . implode('', array_keys($lookup)) . ']+$/', $hash) == false)
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException('Invalid base32 hash!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash = strtoupper($hash);
|
||||||
|
$buffer = 0;
|
||||||
|
$length = 0;
|
||||||
|
$binary = '';
|
||||||
|
|
||||||
|
for ($i = 0; $i < strlen($hash); $i++)
|
||||||
|
{
|
||||||
|
$buffer = $buffer << 5;
|
||||||
|
$buffer += $lookup[$hash[$i]];
|
||||||
|
$length += 5;
|
||||||
|
|
||||||
|
if ($length >= 8)
|
||||||
|
{
|
||||||
|
$length -= 8;
|
||||||
|
$binary .= chr(($buffer & (0xFF << $length)) >> $length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a URL to QR code for embedding the QR code
|
||||||
|
*
|
||||||
|
* @param string $name The name
|
||||||
|
* @param string $code The generated code
|
||||||
|
* @return string The URL to the QR code
|
||||||
|
*/
|
||||||
|
public function getQrCodeUrl($name, $code)
|
||||||
|
{
|
||||||
|
$url = 'otpauth://totp/' . urlencode($name) . '?secret=' . $code;
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
436
Sources/DbExtra-mysql.php
Normal file
|
@ -0,0 +1,436 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains rarely used extended database functionality.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the functions implemented in this file to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_extra_init()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_backup_table']) || $smcFunc['db_backup_table'] != 'smf_db_backup_table')
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_backup_table' => 'smf_db_backup_table',
|
||||||
|
'db_optimize_table' => 'smf_db_optimize_table',
|
||||||
|
'db_table_sql' => 'smf_db_table_sql',
|
||||||
|
'db_list_tables' => 'smf_db_list_tables',
|
||||||
|
'db_get_version' => 'smf_db_get_version',
|
||||||
|
'db_get_vendor' => 'smf_db_get_vendor',
|
||||||
|
'db_allow_persistent' => 'smf_db_allow_persistent',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup $table to $backup_table.
|
||||||
|
*
|
||||||
|
* @param string $table The name of the table to backup
|
||||||
|
* @param string $backup_table The name of the backup table for this table
|
||||||
|
* @return resource -the request handle to the table creation query
|
||||||
|
*/
|
||||||
|
function smf_db_backup_table($table, $backup_table)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$table = str_replace('{db_prefix}', $db_prefix, $table);
|
||||||
|
|
||||||
|
// First, get rid of the old table.
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DROP TABLE IF EXISTS {raw:backup_table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Can we do this the quick way?
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
CREATE TABLE {raw:backup_table} LIKE {raw:table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'table' => $table
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// If this failed, we go old school.
|
||||||
|
if ($result)
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
INSERT INTO {raw:backup_table}
|
||||||
|
SELECT *
|
||||||
|
FROM {raw:table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'table' => $table
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Old school or no school?
|
||||||
|
if ($request)
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the quick method failed.
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SHOW CREATE TABLE {raw:table}',
|
||||||
|
array(
|
||||||
|
'table' => $table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list (, $create) = $smcFunc['db_fetch_row']($result);
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
$create = preg_split('/[\n\r]/', $create);
|
||||||
|
|
||||||
|
$auto_inc = '';
|
||||||
|
// Default engine type.
|
||||||
|
$engine = 'MyISAM';
|
||||||
|
$charset = '';
|
||||||
|
$collate = '';
|
||||||
|
|
||||||
|
foreach ($create as $k => $l)
|
||||||
|
{
|
||||||
|
// Get the name of the auto_increment column.
|
||||||
|
if (strpos($l, 'auto_increment'))
|
||||||
|
$auto_inc = trim($l);
|
||||||
|
|
||||||
|
// For the engine type, see if we can work out what it is.
|
||||||
|
if (strpos($l, 'ENGINE') !== false || strpos($l, 'TYPE') !== false)
|
||||||
|
{
|
||||||
|
// Extract the engine type.
|
||||||
|
preg_match('~(ENGINE|TYPE)=(\w+)(\sDEFAULT)?(\sCHARSET=(\w+))?(\sCOLLATE=(\w+))?~', $l, $match);
|
||||||
|
|
||||||
|
if (!empty($match[1]))
|
||||||
|
$engine = $match[1];
|
||||||
|
|
||||||
|
if (!empty($match[2]))
|
||||||
|
$engine = $match[2];
|
||||||
|
|
||||||
|
if (!empty($match[5]))
|
||||||
|
$charset = $match[5];
|
||||||
|
|
||||||
|
if (!empty($match[7]))
|
||||||
|
$collate = $match[7];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip everything but keys...
|
||||||
|
if (strpos($l, 'KEY') === false)
|
||||||
|
unset($create[$k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($create))
|
||||||
|
$create = '(
|
||||||
|
' . implode('
|
||||||
|
', $create) . ')';
|
||||||
|
else
|
||||||
|
$create = '';
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
CREATE TABLE {raw:backup_table} {raw:create}
|
||||||
|
ENGINE={raw:engine}' . (empty($charset) ? '' : ' CHARACTER SET {raw:charset}' . (empty($collate) ? '' : ' COLLATE {raw:collate}')) . '
|
||||||
|
SELECT *
|
||||||
|
FROM {raw:table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'table' => $table,
|
||||||
|
'create' => $create,
|
||||||
|
'engine' => $engine,
|
||||||
|
'charset' => empty($charset) ? '' : $charset,
|
||||||
|
'collate' => empty($collate) ? '' : $collate,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($auto_inc != '')
|
||||||
|
{
|
||||||
|
if (preg_match('~\`(.+?)\`\s~', $auto_inc, $match) != 0 && substr($auto_inc, -1, 1) == ',')
|
||||||
|
$auto_inc = substr($auto_inc, 0, -1);
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE {raw:backup_table}
|
||||||
|
CHANGE COLUMN {raw:column_detail} {raw:auto_inc}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'column_detail' => $match[1],
|
||||||
|
'auto_inc' => $auto_inc,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function optimizes a table.
|
||||||
|
*
|
||||||
|
* @param string $table The table to be optimized
|
||||||
|
* @return int How much space was gained
|
||||||
|
*/
|
||||||
|
function smf_db_optimize_table($table)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$table = str_replace('{db_prefix}', $db_prefix, $table);
|
||||||
|
|
||||||
|
// Get how much overhead there is.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SHOW TABLE STATUS LIKE {string:table_name}',
|
||||||
|
array(
|
||||||
|
'table_name' => str_replace('_', '\_', $table),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$data_before = isset($row['Data_free']) ? $row['Data_free'] : 0;
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
OPTIMIZE TABLE `{raw:table}`',
|
||||||
|
array(
|
||||||
|
'table' => $table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!$request)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// How much left?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SHOW TABLE STATUS LIKE {string:table}',
|
||||||
|
array(
|
||||||
|
'table' => str_replace('_', '\_', $table),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$total_change = isset($row['Data_free']) && $data_before > $row['Data_free'] ? $data_before / 1024 : 0;
|
||||||
|
|
||||||
|
return $total_change;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lists all tables in the database.
|
||||||
|
* The listing could be filtered according to $filter.
|
||||||
|
*
|
||||||
|
* @param string|boolean $db string The database name or false to use the current DB
|
||||||
|
* @param string|boolean $filter String to filter by or false to list all tables
|
||||||
|
* @return array An array of table names
|
||||||
|
*/
|
||||||
|
function smf_db_list_tables($db = false, $filter = false)
|
||||||
|
{
|
||||||
|
global $db_name, $smcFunc;
|
||||||
|
|
||||||
|
$db = $db == false ? $db_name : $db;
|
||||||
|
$db = trim($db);
|
||||||
|
$filter = $filter == false ? '' : ' LIKE \'' . $filter . '\'';
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SHOW TABLES
|
||||||
|
FROM `{raw:db}`
|
||||||
|
{raw:filter}',
|
||||||
|
array(
|
||||||
|
'db' => $db[0] == '`' ? strtr($db, array('`' => '')) : $db,
|
||||||
|
'filter' => $filter,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$tables = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_row']($request))
|
||||||
|
$tables[] = $row[0];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the schema (CREATE) for a table.
|
||||||
|
*
|
||||||
|
* @todo why is this needed for?
|
||||||
|
* @param string $tableName The name of the table
|
||||||
|
* @return string The "CREATE TABLE" SQL string for this table
|
||||||
|
*/
|
||||||
|
function smf_db_table_sql($tableName)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
|
||||||
|
|
||||||
|
// This will be needed...
|
||||||
|
$crlf = "\r\n";
|
||||||
|
|
||||||
|
// Drop it if it exists.
|
||||||
|
$schema_create = 'DROP TABLE IF EXISTS `' . $tableName . '`;' . $crlf . $crlf;
|
||||||
|
|
||||||
|
// Start the create table...
|
||||||
|
$schema_create .= 'CREATE TABLE `' . $tableName . '` (' . $crlf;
|
||||||
|
|
||||||
|
// Find all the fields.
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SHOW FIELDS
|
||||||
|
FROM `{raw:table}`',
|
||||||
|
array(
|
||||||
|
'table' => $tableName,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
// Make the CREATE for this column.
|
||||||
|
$schema_create .= ' `' . $row['Field'] . '` ' . $row['Type'] . ($row['Null'] != 'YES' ? ' NOT NULL' : '');
|
||||||
|
|
||||||
|
// Add a default...?
|
||||||
|
if (!empty($row['Default']) || $row['Null'] !== 'YES')
|
||||||
|
{
|
||||||
|
// Make a special case of auto-timestamp.
|
||||||
|
if ($row['Default'] == 'CURRENT_TIMESTAMP')
|
||||||
|
$schema_create .= ' /*!40102 NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP */';
|
||||||
|
// Text shouldn't have a default.
|
||||||
|
elseif ($row['Default'] !== null)
|
||||||
|
{
|
||||||
|
// If this field is numeric the default needs no escaping.
|
||||||
|
$type = strtolower($row['Type']);
|
||||||
|
$isNumericColumn = strpos($type, 'int') !== false || strpos($type, 'bool') !== false || strpos($type, 'bit') !== false || strpos($type, 'float') !== false || strpos($type, 'double') !== false || strpos($type, 'decimal') !== false;
|
||||||
|
|
||||||
|
$schema_create .= ' default ' . ($isNumericColumn ? $row['Default'] : '\'' . $smcFunc['db_escape_string']($row['Default']) . '\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And now any extra information. (such as auto_increment.)
|
||||||
|
$schema_create .= ($row['Extra'] != '' ? ' ' . $row['Extra'] : '') . ',' . $crlf;
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// Take off the last comma.
|
||||||
|
$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
|
||||||
|
|
||||||
|
// Find the keys.
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SHOW KEYS
|
||||||
|
FROM `{raw:table}`',
|
||||||
|
array(
|
||||||
|
'table' => $tableName,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$indexes = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
// IS this a primary key, unique index, or regular index?
|
||||||
|
$row['Key_name'] = $row['Key_name'] == 'PRIMARY' ? 'PRIMARY KEY' : (empty($row['Non_unique']) ? 'UNIQUE ' : ($row['Comment'] == 'FULLTEXT' || (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT') ? 'FULLTEXT ' : 'KEY ')) . '`' . $row['Key_name'] . '`';
|
||||||
|
|
||||||
|
// Is this the first column in the index?
|
||||||
|
if (empty($indexes[$row['Key_name']]))
|
||||||
|
$indexes[$row['Key_name']] = array();
|
||||||
|
|
||||||
|
// A sub part, like only indexing 15 characters of a varchar.
|
||||||
|
if (!empty($row['Sub_part']))
|
||||||
|
$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`(' . $row['Sub_part'] . ')';
|
||||||
|
else
|
||||||
|
$indexes[$row['Key_name']][$row['Seq_in_index']] = '`' . $row['Column_name'] . '`';
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// Build the CREATEs for the keys.
|
||||||
|
foreach ($indexes as $keyname => $columns)
|
||||||
|
{
|
||||||
|
// Ensure the columns are in proper order.
|
||||||
|
ksort($columns);
|
||||||
|
|
||||||
|
$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now just get the comment and engine... (MyISAM, etc.)
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SHOW TABLE STATUS
|
||||||
|
LIKE {string:table}',
|
||||||
|
array(
|
||||||
|
'table' => strtr($tableName, array('_' => '\\_', '%' => '\\%')),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($result);
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// Probably MyISAM.... and it might have a comment.
|
||||||
|
$schema_create .= $crlf . ') ENGINE=' . $row['Engine'] . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
|
||||||
|
|
||||||
|
return $schema_create;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version number.
|
||||||
|
*
|
||||||
|
* @return string The version
|
||||||
|
*/
|
||||||
|
function smf_db_get_version()
|
||||||
|
{
|
||||||
|
static $ver;
|
||||||
|
|
||||||
|
if (!empty($ver))
|
||||||
|
return $ver;
|
||||||
|
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT VERSION()',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($ver) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out if we are using MySQL, Percona or MariaDB
|
||||||
|
*
|
||||||
|
* @return string The database engine we are using
|
||||||
|
*/
|
||||||
|
function smf_db_get_vendor()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
static $db_type;
|
||||||
|
|
||||||
|
if (!empty($db_type))
|
||||||
|
return $db_type;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', 'SELECT @@version_comment');
|
||||||
|
list ($comment) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Skip these if we don't have a comment.
|
||||||
|
if (!empty($comment))
|
||||||
|
{
|
||||||
|
if (stripos($comment, 'percona') !== false)
|
||||||
|
return 'Percona';
|
||||||
|
if (stripos($comment, 'mariadb') !== false)
|
||||||
|
return 'MariaDB';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 'fail';
|
||||||
|
|
||||||
|
return 'MySQL';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out if persistent connection is allowed
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function smf_db_allow_persistent()
|
||||||
|
{
|
||||||
|
$value = ini_get('mysqli.allow_persistent');
|
||||||
|
if (strtolower($value) == 'on' || strtolower($value) == 'true' || $value == '1')
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
341
Sources/DbExtra-postgresql.php
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains rarely used extended database functionality.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the functions implemented in this file to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_extra_init()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_backup_table']) || $smcFunc['db_backup_table'] != 'smf_db_backup_table')
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_backup_table' => 'smf_db_backup_table',
|
||||||
|
'db_optimize_table' => 'smf_db_optimize_table',
|
||||||
|
'db_table_sql' => 'smf_db_table_sql',
|
||||||
|
'db_list_tables' => 'smf_db_list_tables',
|
||||||
|
'db_get_version' => 'smf_db_get_version',
|
||||||
|
'db_get_vendor' => 'smf_db_get_vendor',
|
||||||
|
'db_allow_persistent' => 'smf_db_allow_persistent',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup $table to $backup_table.
|
||||||
|
*
|
||||||
|
* @param string $table The name of the table to backup
|
||||||
|
* @param string $backup_table The name of the backup table for this table
|
||||||
|
* @return resource -the request handle to the table creation query
|
||||||
|
*/
|
||||||
|
function smf_db_backup_table($table, $backup_table)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$table = str_replace('{db_prefix}', $db_prefix, $table);
|
||||||
|
|
||||||
|
// Do we need to drop it first?
|
||||||
|
$tables = smf_db_list_tables(false, $backup_table);
|
||||||
|
if (!empty($tables))
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DROP TABLE {raw:backup_table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Should we create backups of sequences as well?
|
||||||
|
*/
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
CREATE TABLE {raw:backup_table}
|
||||||
|
(
|
||||||
|
LIKE {raw:table}
|
||||||
|
INCLUDING DEFAULTS
|
||||||
|
)',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'table' => $table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
INSERT INTO {raw:backup_table}
|
||||||
|
SELECT * FROM {raw:table}',
|
||||||
|
array(
|
||||||
|
'backup_table' => $backup_table,
|
||||||
|
'table' => $table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function optimizes a table.
|
||||||
|
*
|
||||||
|
* @param string $table The table to be optimized
|
||||||
|
* @return int How much space was gained
|
||||||
|
*/
|
||||||
|
function smf_db_optimize_table($table)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$table = str_replace('{db_prefix}', $db_prefix, $table);
|
||||||
|
|
||||||
|
$pg_tables = array('pg_catalog', 'information_schema');
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT pg_relation_size(C.oid) AS "size"
|
||||||
|
FROM pg_class C
|
||||||
|
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||||
|
WHERE nspname NOT IN ({array_string:pg_tables})
|
||||||
|
AND relname = {string:table}',
|
||||||
|
array(
|
||||||
|
'table' => $table,
|
||||||
|
'pg_tables' => $pg_tables,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$old_size = $row['size'];
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
VACUUM FULL ANALYZE {raw:table}',
|
||||||
|
array(
|
||||||
|
'table' => $table,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$request)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT pg_relation_size(C.oid) AS "size"
|
||||||
|
FROM pg_class C
|
||||||
|
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||||
|
WHERE nspname NOT IN ({array_string:pg_tables})
|
||||||
|
AND relname = {string:table}',
|
||||||
|
array(
|
||||||
|
'table' => $table,
|
||||||
|
'pg_tables' => $pg_tables,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
if (isset($row['size']))
|
||||||
|
return ($old_size - $row['size']) / 1024;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function lists all tables in the database.
|
||||||
|
* The listing could be filtered according to $filter.
|
||||||
|
*
|
||||||
|
* @param string|boolean $db string The database name or false to use the current DB
|
||||||
|
* @param string|boolean $filter String to filter by or false to list all tables
|
||||||
|
* @return array An array of table names
|
||||||
|
*/
|
||||||
|
function smf_db_list_tables($db = false, $filter = false)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT tablename
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE schemaname = {string:schema_public}' . ($filter == false ? '' : '
|
||||||
|
AND tablename LIKE {string:filter}') . '
|
||||||
|
ORDER BY tablename',
|
||||||
|
array(
|
||||||
|
'schema_public' => 'public',
|
||||||
|
'filter' => $filter,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$tables = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_row']($request))
|
||||||
|
$tables[] = $row[0];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the schema (CREATE) for a table.
|
||||||
|
*
|
||||||
|
* @todo why is this needed for?
|
||||||
|
* @param string $tableName The name of the table
|
||||||
|
* @return string The "CREATE TABLE" SQL string for this table
|
||||||
|
*/
|
||||||
|
function smf_db_table_sql($tableName)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);
|
||||||
|
|
||||||
|
// This will be needed...
|
||||||
|
$crlf = "\r\n";
|
||||||
|
|
||||||
|
// Drop it if it exists.
|
||||||
|
$schema_create = 'DROP TABLE IF EXISTS ' . $tableName . ';' . $crlf . $crlf;
|
||||||
|
|
||||||
|
// Start the create table...
|
||||||
|
$schema_create .= 'CREATE TABLE ' . $tableName . ' (' . $crlf;
|
||||||
|
$index_create = '';
|
||||||
|
$seq_create = '';
|
||||||
|
|
||||||
|
// Find all the fields.
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT column_name, column_default, is_nullable, data_type, character_maximum_length
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = {string:table}
|
||||||
|
ORDER BY ordinal_position',
|
||||||
|
array(
|
||||||
|
'table' => $tableName,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
if ($row['data_type'] == 'character varying')
|
||||||
|
$row['data_type'] = 'varchar';
|
||||||
|
elseif ($row['data_type'] == 'character')
|
||||||
|
$row['data_type'] = 'char';
|
||||||
|
if ($row['character_maximum_length'])
|
||||||
|
$row['data_type'] .= '(' . $row['character_maximum_length'] . ')';
|
||||||
|
|
||||||
|
// Make the CREATE for this column.
|
||||||
|
$schema_create .= ' "' . $row['column_name'] . '" ' . $row['data_type'] . ($row['is_nullable'] != 'YES' ? ' NOT NULL' : '');
|
||||||
|
|
||||||
|
// Add a default...?
|
||||||
|
if (trim($row['column_default']) != '')
|
||||||
|
{
|
||||||
|
$schema_create .= ' default ' . $row['column_default'] . '';
|
||||||
|
|
||||||
|
// Auto increment?
|
||||||
|
if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
|
||||||
|
{
|
||||||
|
// Get to find the next variable first!
|
||||||
|
$count_req = $smcFunc['db_query']('', '
|
||||||
|
SELECT MAX("{raw:column}")
|
||||||
|
FROM {raw:table}',
|
||||||
|
array(
|
||||||
|
'column' => $row['column_name'],
|
||||||
|
'table' => $tableName,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($max_ind) = $smcFunc['db_fetch_row']($count_req);
|
||||||
|
$smcFunc['db_free_result']($count_req);
|
||||||
|
// Get the right bloody start!
|
||||||
|
$seq_create .= 'CREATE SEQUENCE ' . $matches[1] . ' START WITH ' . ($max_ind + 1) . ';' . $crlf . $crlf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema_create .= ',' . $crlf;
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// Take off the last comma.
|
||||||
|
$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT pg_get_indexdef(i.indexrelid) AS inddef
|
||||||
|
FROM pg_class AS c
|
||||||
|
INNER JOIN pg_index AS i ON (i.indrelid = c.oid)
|
||||||
|
INNER JOIN pg_class AS c2 ON (c2.oid = i.indexrelid)
|
||||||
|
WHERE c.relname = {string:table} AND i.indisprimary is {raw:pk}',
|
||||||
|
array(
|
||||||
|
'table' => $tableName,
|
||||||
|
'pk' => 'false',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
$index_create .= $crlf . $row['inddef'] . ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT pg_get_constraintdef(c.oid) as pkdef
|
||||||
|
FROM pg_constraint as c
|
||||||
|
WHERE c.conrelid::regclass::text = {string:table} AND
|
||||||
|
c.contype = {string:constraintType}',
|
||||||
|
array(
|
||||||
|
'table' => $tableName,
|
||||||
|
'constraintType' => 'p',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD ' . $row['pkdef'] . ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// Finish it off!
|
||||||
|
$schema_create .= $crlf . ');';
|
||||||
|
|
||||||
|
return $seq_create . $schema_create . $index_create;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version number.
|
||||||
|
*
|
||||||
|
* @return string The version
|
||||||
|
*/
|
||||||
|
function smf_db_get_version()
|
||||||
|
{
|
||||||
|
global $db_connection;
|
||||||
|
static $ver;
|
||||||
|
|
||||||
|
if (!empty($ver))
|
||||||
|
return $ver;
|
||||||
|
|
||||||
|
$ver = pg_version($db_connection)['server'];
|
||||||
|
|
||||||
|
return $ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return PostgreSQL
|
||||||
|
*
|
||||||
|
* @return string The database engine we are using
|
||||||
|
*/
|
||||||
|
function smf_db_get_vendor()
|
||||||
|
{
|
||||||
|
return 'PostgreSQL';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out if persistent connection is allowed
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function smf_db_allow_persistent()
|
||||||
|
{
|
||||||
|
$value = ini_get('pgsql.allow_persistent');
|
||||||
|
if (strtolower($value) == 'on' || strtolower($value) == 'true' || $value == '1')
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
927
Sources/DbPackages-mysql.php
Normal file
|
@ -0,0 +1,927 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains database functionality specifically designed for packages (mods) to utilize.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the file functions to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_packages_init()
|
||||||
|
{
|
||||||
|
global $smcFunc, $reservedTables, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table')
|
||||||
|
{
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_add_column' => 'smf_db_add_column',
|
||||||
|
'db_add_index' => 'smf_db_add_index',
|
||||||
|
'db_calculate_type' => 'smf_db_calculate_type',
|
||||||
|
'db_change_column' => 'smf_db_change_column',
|
||||||
|
'db_create_table' => 'smf_db_create_table',
|
||||||
|
'db_drop_table' => 'smf_db_drop_table',
|
||||||
|
'db_table_structure' => 'smf_db_table_structure',
|
||||||
|
'db_list_columns' => 'smf_db_list_columns',
|
||||||
|
'db_list_indexes' => 'smf_db_list_indexes',
|
||||||
|
'db_remove_column' => 'smf_db_remove_column',
|
||||||
|
'db_remove_index' => 'smf_db_remove_index',
|
||||||
|
);
|
||||||
|
$db_package_log = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up!
|
||||||
|
$reservedTables = array(
|
||||||
|
'admin_info_files', 'approval_queue', 'attachments',
|
||||||
|
'background_tasks', 'ban_groups', 'ban_items', 'board_permissions',
|
||||||
|
'board_permissions_view', 'boards', 'calendar', 'calendar_holidays',
|
||||||
|
'categories', 'custom_fields', 'group_moderators', 'log_actions',
|
||||||
|
'log_activity', 'log_banned', 'log_boards', 'log_comments',
|
||||||
|
'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests',
|
||||||
|
'log_mark_read', 'log_member_notices', 'log_notify', 'log_online',
|
||||||
|
'log_packages', 'log_polls', 'log_reported', 'log_reported_comments',
|
||||||
|
'log_scheduled_tasks', 'log_search_messages', 'log_search_results',
|
||||||
|
'log_search_subjects', 'log_search_topics', 'log_spider_hits',
|
||||||
|
'log_spider_stats', 'log_subscribed', 'log_topics', 'mail_queue',
|
||||||
|
'member_logins', 'membergroups', 'members', 'mentions',
|
||||||
|
'message_icons', 'messages', 'moderator_groups', 'moderators',
|
||||||
|
'package_servers', 'permission_profiles', 'permissions',
|
||||||
|
'personal_messages', 'pm_labeled_messages', 'pm_labels',
|
||||||
|
'pm_recipients', 'pm_rules', 'poll_choices', 'polls', 'qanda',
|
||||||
|
'scheduled_tasks', 'sessions', 'settings', 'smiley_files', 'smileys',
|
||||||
|
'spiders', 'subscriptions', 'themes', 'topics', 'user_alerts',
|
||||||
|
'user_alerts_prefs', 'user_drafts', 'user_likes',
|
||||||
|
);
|
||||||
|
foreach ($reservedTables as $k => $table_name)
|
||||||
|
$reservedTables[$k] = strtolower($db_prefix . $table_name);
|
||||||
|
|
||||||
|
// We in turn may need the extra stuff.
|
||||||
|
db_extend('extra');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function can be used to create a table without worrying about schema
|
||||||
|
* compatibilities across supported database systems.
|
||||||
|
* - If the table exists will, by default, do nothing.
|
||||||
|
* - Builds table with columns as passed to it - at least one column must be sent.
|
||||||
|
* The columns array should have one sub-array for each column - these sub arrays contain:
|
||||||
|
* 'name' = Column name
|
||||||
|
* 'type' = Type of column - values from (smallint, mediumint, int, text, varchar, char, tinytext, mediumtext, largetext)
|
||||||
|
* 'size' => Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc.
|
||||||
|
* If not set SMF will pick a size.
|
||||||
|
* - 'default' = Default value - do not set if no default required.
|
||||||
|
* - 'not_null' => Can it be null (true or false) - if not set default will be false.
|
||||||
|
* - 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set from what
|
||||||
|
* it should begin counting.
|
||||||
|
* - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are:
|
||||||
|
* - 'name' => Index name (If left empty SMF will generate).
|
||||||
|
* - 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'.
|
||||||
|
* - 'columns' => Array containing columns that form part of key - in the order the index is to be created.
|
||||||
|
* - parameters: (None yet)
|
||||||
|
* - if_exists values:
|
||||||
|
* - 'ignore' will do nothing if the table exists. (And will return true)
|
||||||
|
* - 'overwrite' will drop any existing table of the same name.
|
||||||
|
* - 'error' will return false if the table already exists.
|
||||||
|
* - 'update' will update the table if the table already exists (no change of ai field and only colums with the same name keep the data)
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to create
|
||||||
|
* @param array $columns An array of column info in the specified format
|
||||||
|
* @param array $indexes An array of index info in the specified format
|
||||||
|
* @param array $parameters Extra parameters. Currently only 'engine', the desired MySQL storage engine, is used.
|
||||||
|
* @param string $if_exists What to do if the table exists.
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $reservedTables, $smcFunc, $db_package_log, $db_prefix, $db_character_set, $db_name;
|
||||||
|
|
||||||
|
static $engines = array();
|
||||||
|
|
||||||
|
$old_table_exists = false;
|
||||||
|
|
||||||
|
// Strip out the table name, we might not need it in some cases
|
||||||
|
$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
// With or without the database name, the fullname looks like this.
|
||||||
|
$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
|
||||||
|
// Do not overwrite $table_name, this causes issues if we pass it onto a helper function.
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// First - no way do we touch SMF tables.
|
||||||
|
if (in_array(strtolower($short_table_name), $reservedTables))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Log that we'll want to remove this on uninstall.
|
||||||
|
$db_package_log[] = array('remove_table', $short_table_name);
|
||||||
|
|
||||||
|
// Slightly easier on MySQL than the others...
|
||||||
|
$tables = $smcFunc['db_list_tables']($database);
|
||||||
|
|
||||||
|
if (in_array($full_table_name, $tables))
|
||||||
|
{
|
||||||
|
// This is a sad day... drop the table? If not, return false (error) by default.
|
||||||
|
if ($if_exists == 'overwrite')
|
||||||
|
$smcFunc['db_drop_table']($table_name);
|
||||||
|
elseif ($if_exists == 'update')
|
||||||
|
{
|
||||||
|
$smcFunc['db_transaction']('begin');
|
||||||
|
$db_trans = true;
|
||||||
|
$smcFunc['db_drop_table']($short_table_name . '_old');
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
RENAME TABLE ' . $short_table_name . ' TO ' . $short_table_name . '_old',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$old_table_exists = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return $if_exists == 'ignore';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Righty - let's do the damn thing!
|
||||||
|
$table_query = 'CREATE TABLE ' . $short_table_name . "\n" . '(';
|
||||||
|
foreach ($columns as $column)
|
||||||
|
$table_query .= "\n\t" . smf_db_create_query_column($column) . ',';
|
||||||
|
|
||||||
|
// Loop through the indexes next...
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
// MySQL If its a text column, we need to add a size.
|
||||||
|
foreach ($index['columns'] as &$c)
|
||||||
|
{
|
||||||
|
$c = trim($c);
|
||||||
|
|
||||||
|
// If a size was already specified, we won't be able to match it anyways.
|
||||||
|
$key = array_search($c, array_column($columns, 'name'));
|
||||||
|
$columns[$key]['size'] = isset($columns[$key]['size']) && is_numeric($columns[$key]['size']) ? $columns[$key]['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($columns[$key]['type'], $columns[$key]['size']);
|
||||||
|
if (
|
||||||
|
$key === false
|
||||||
|
|| !isset($columns[$key])
|
||||||
|
|| !in_array($columns[$key]['type'], array('text', 'mediumntext', 'largetext', 'varchar', 'char'))
|
||||||
|
|| (
|
||||||
|
isset($size)
|
||||||
|
&& $size <= 191
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$c .= '(191)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$idx_columns = implode(',', $index['columns']);
|
||||||
|
|
||||||
|
// Is it the primary?
|
||||||
|
if (isset($index['type']) && $index['type'] == 'primary')
|
||||||
|
$table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),';
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (empty($index['name']))
|
||||||
|
$index['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index['columns'])));
|
||||||
|
|
||||||
|
$table_query .= "\n\t" . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : 'KEY') . ' ' . $index['name'] . ' (' . $idx_columns . '),';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No trailing commas!
|
||||||
|
if (substr($table_query, -1) == ',')
|
||||||
|
$table_query = substr($table_query, 0, -1);
|
||||||
|
|
||||||
|
// Which engine do we want here?
|
||||||
|
if (empty($engines))
|
||||||
|
{
|
||||||
|
// Figure out which engines we have
|
||||||
|
$get_engines = $smcFunc['db_query']('', 'SHOW ENGINES', array());
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($get_engines))
|
||||||
|
{
|
||||||
|
if ($row['Support'] == 'YES' || $row['Support'] == 'DEFAULT')
|
||||||
|
$engines[] = $row['Engine'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_free_result']($get_engines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have this engine, or didn't specify one, default to InnoDB or MyISAM
|
||||||
|
// depending on which one is available
|
||||||
|
if (!isset($parameters['engine']) || !in_array($parameters['engine'], $engines))
|
||||||
|
{
|
||||||
|
$parameters['engine'] = in_array('InnoDB', $engines) ? 'InnoDB' : 'MyISAM';
|
||||||
|
}
|
||||||
|
|
||||||
|
$table_query .= ') ENGINE=' . $parameters['engine'];
|
||||||
|
if (!empty($db_character_set) && $db_character_set == 'utf8')
|
||||||
|
$table_query .= ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
|
||||||
|
|
||||||
|
// Create the table!
|
||||||
|
$smcFunc['db_query']('', $table_query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fill the old data
|
||||||
|
if ($old_table_exists)
|
||||||
|
{
|
||||||
|
$same_col = array();
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT count(*), column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name in ({string:table1},{string:table2}) AND table_schema = {string:schema}
|
||||||
|
GROUP BY column_name
|
||||||
|
HAVING count(*) > 1',
|
||||||
|
array(
|
||||||
|
'table1' => $short_table_name,
|
||||||
|
'table2' => $short_table_name . '_old',
|
||||||
|
'schema' => $db_name,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$same_col[] = $row['column_name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
INSERT INTO ' . $short_table_name . '('
|
||||||
|
. implode(',', $same_col) .
|
||||||
|
')
|
||||||
|
SELECT ' . implode(',', $same_col) . '
|
||||||
|
FROM ' . $short_table_name . '_old',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_drop_table']($short_table_name . '_old');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a table.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to drop
|
||||||
|
* @param array $parameters Not used at the moment
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $reservedTables, $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
// After stripping away the database name, this is what's left.
|
||||||
|
$real_prefix = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
// Get some aliases.
|
||||||
|
$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
|
||||||
|
// Do not overwrite $table_name, this causes issues if we pass it onto a helper function.
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// God no - dropping one of these = bad.
|
||||||
|
if (in_array(strtolower($short_table_name), $reservedTables))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Does it exist?
|
||||||
|
$tables = $smcFunc['db_list_tables']($database);
|
||||||
|
if (in_array($full_table_name, $tables))
|
||||||
|
{
|
||||||
|
$query = 'DROP TABLE ' . $short_table_name;
|
||||||
|
$smcFunc['db_query']('',
|
||||||
|
$query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise do 'nout.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function adds a column.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to add the column to
|
||||||
|
* @param array $column_info An array of column info ({@see smf_db_create_table})
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $if_exists What to do if the column exists. If 'update', column is updated.
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$column_info = array_change_key_case($column_info);
|
||||||
|
|
||||||
|
// Log that we will want to uninstall this!
|
||||||
|
$db_package_log[] = array('remove_column', $short_table_name, $column_info['name']);
|
||||||
|
|
||||||
|
// Does it exist - if so don't add it again!
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, false);
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if ($column == $column_info['name'])
|
||||||
|
{
|
||||||
|
// If we're going to overwrite then use change column.
|
||||||
|
if ($if_exists == 'update')
|
||||||
|
return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the specifics...
|
||||||
|
$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
|
||||||
|
|
||||||
|
// Now add the thing!
|
||||||
|
$query = '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ADD ' . smf_db_create_query_column($column_info) . (empty($column_info['auto']) ? '' : ' primary key'
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('', $query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a column.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to drop the column from
|
||||||
|
* @param string $column_name The name of the column to drop
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// Does it exist?
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, true);
|
||||||
|
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if ($column['name'] == $column_name)
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
DROP COLUMN ' . $column_name,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If here we didn't have to work - joy!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change a column. You only need to specify the column attributes that are changing.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table this column is in
|
||||||
|
* @param string $old_column The name of the column we want to change
|
||||||
|
* @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()})
|
||||||
|
* Note that $column_info also supports two additional parameters that only make sense when changing columns:
|
||||||
|
* - drop_default - to drop a default that was previously specified
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function smf_db_change_column($table_name, $old_column, $column_info)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$column_info = array_change_key_case($column_info);
|
||||||
|
|
||||||
|
// Check it does exist!
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, true);
|
||||||
|
$old_info = null;
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if ($column['name'] == $old_column)
|
||||||
|
$old_info = $column;
|
||||||
|
|
||||||
|
// Nothing?
|
||||||
|
if ($old_info == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// backward compatibility
|
||||||
|
if (isset($column_info['null']) && !isset($column_info['not_null']))
|
||||||
|
$column_info['not_null'] = !$column_info['null'];
|
||||||
|
|
||||||
|
// Get the right bits.
|
||||||
|
if (isset($column_info['drop_default']) && !empty($column_info['drop_default']))
|
||||||
|
$column_info['drop_default'] = true;
|
||||||
|
else
|
||||||
|
$column_info['drop_default'] = false;
|
||||||
|
if (!isset($column_info['name']))
|
||||||
|
$column_info['name'] = $old_column;
|
||||||
|
if (!array_key_exists('default', $column_info) && array_key_exists('default', $old_info) && empty($column_info['drop_default']))
|
||||||
|
$column_info['default'] = $old_info['default'];
|
||||||
|
if (!isset($column_info['not_null']))
|
||||||
|
$column_info['not_null'] = $old_info['not_null'];
|
||||||
|
if (!isset($column_info['auto']))
|
||||||
|
$column_info['auto'] = $old_info['auto'];
|
||||||
|
if (!isset($column_info['type']))
|
||||||
|
$column_info['type'] = $old_info['type'];
|
||||||
|
if (!isset($column_info['size']) || !is_numeric($column_info['size']))
|
||||||
|
$column_info['size'] = $old_info['size'];
|
||||||
|
if (!isset($column_info['unsigned']) || !in_array($column_info['type'], array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')))
|
||||||
|
$column_info['unsigned'] = '';
|
||||||
|
|
||||||
|
// If truly unspecified, make that clear, otherwise, might be confused with NULL...
|
||||||
|
// (Unspecified = no default whatsoever = column is not nullable with a value of null...)
|
||||||
|
if (($column_info['not_null'] === true) && !$column_info['drop_default'] && array_key_exists('default', $column_info) && is_null($column_info['default']))
|
||||||
|
unset($column_info['default']);
|
||||||
|
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
|
||||||
|
|
||||||
|
// Allow for unsigned integers (mysql only)
|
||||||
|
$unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column_info['unsigned']) ? 'unsigned ' : '';
|
||||||
|
|
||||||
|
// If you need to drop the default, that needs it's own thing...
|
||||||
|
// Must be done first, in case the default type is inconsistent with the other changes.
|
||||||
|
if ($column_info['drop_default'])
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ALTER COLUMN `' . $old_column . '` DROP DEFAULT',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the default clause.
|
||||||
|
$default_clause = '';
|
||||||
|
if (!$column_info['drop_default'] && array_key_exists('default', $column_info))
|
||||||
|
{
|
||||||
|
if (is_null($column_info['default']))
|
||||||
|
$default_clause = 'DEFAULT NULL';
|
||||||
|
elseif (is_numeric($column_info['default']))
|
||||||
|
$default_clause = 'DEFAULT ' . (strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']));
|
||||||
|
elseif (is_string($column_info['default']))
|
||||||
|
$default_clause = 'DEFAULT \'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size !== null)
|
||||||
|
$type = $type . '(' . $size . ')';
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
CHANGE COLUMN `' . $old_column . '` `' . $column_info['name'] . '` ' . $type . ' ' .
|
||||||
|
(!empty($unsigned) ? $unsigned : '') . (!empty($column_info['not_null']) ? 'NOT NULL' : '') . ' ' .
|
||||||
|
$default_clause . ' ' .
|
||||||
|
(empty($column_info['auto']) ? '' : 'auto_increment') . ' ',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an index.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to add the index to
|
||||||
|
* @param array $index_info An array of index info (see {@link smf_db_create_table()})
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $if_exists What to do if the index exists. If 'update', the definition will be updated.
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// No columns = no index.
|
||||||
|
if (empty($index_info['columns']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// MySQL If its a text column, we need to add a size.
|
||||||
|
$cols = $smcFunc['db_list_columns']($table_name, true);
|
||||||
|
foreach ($index_info['columns'] as &$c)
|
||||||
|
{
|
||||||
|
$c = trim($c);
|
||||||
|
$cols[$c]['size'] = isset($cols[$c]['size']) && is_numeric($cols[$c]['size']) ? $cols[$c]['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($cols[$c]['type'], $cols[$c]['size']);
|
||||||
|
|
||||||
|
// If a size was already specified, we won't be able to match it anyways.
|
||||||
|
if (
|
||||||
|
!isset($cols[$c])
|
||||||
|
|| !in_array($cols[$c]['type'], array('text', 'mediumntext', 'largetext', 'varchar', 'char'))
|
||||||
|
|| (
|
||||||
|
isset($size)
|
||||||
|
&& $size <= 191
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$c .= '(191)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = implode(',', $index_info['columns']);
|
||||||
|
|
||||||
|
// No name - make it up!
|
||||||
|
if (empty($index_info['name']))
|
||||||
|
{
|
||||||
|
// No need for primary.
|
||||||
|
if (isset($index_info['type']) && $index_info['type'] == 'primary')
|
||||||
|
$index_info['name'] = '';
|
||||||
|
else
|
||||||
|
$index_info['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index_info['columns'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log that we are going to want to remove this!
|
||||||
|
$db_package_log[] = array('remove_index', $short_table_name, $index_info['name']);
|
||||||
|
|
||||||
|
// Let's get all our indexes.
|
||||||
|
$indexes = $smcFunc['db_list_indexes']($table_name, true);
|
||||||
|
// Do we already have it?
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary'))
|
||||||
|
{
|
||||||
|
// If we want to overwrite simply remove the current one then continue.
|
||||||
|
if ($if_exists != 'update' || $index['type'] == 'primary')
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
$smcFunc['db_remove_index']($table_name, $index_info['name']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're here we know we don't have the index - so just add it.
|
||||||
|
if (!empty($index_info['type']) && $index_info['type'] == 'primary')
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ADD PRIMARY KEY (' . $columns . ')',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ADD ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : 'INDEX') . ' ' . $index_info['name'] . ' (' . $columns . ')',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an index.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to remove the index from
|
||||||
|
* @param string $index_name The name of the index to remove
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// Better exist!
|
||||||
|
$indexes = $smcFunc['db_list_indexes']($table_name, true);
|
||||||
|
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
// If the name is primary we want the primary key!
|
||||||
|
if ($index['type'] == 'primary' && $index_name == 'primary')
|
||||||
|
{
|
||||||
|
// Dropping primary key?
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
DROP PRIMARY KEY',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($index['name'] == $index_name)
|
||||||
|
{
|
||||||
|
// Drop the bugger...
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
DROP INDEX ' . $index_name,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not to be found ;(
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the schema formatted name for a type.
|
||||||
|
*
|
||||||
|
* @param string $type_name The data type (int, varchar, smallint, etc.)
|
||||||
|
* @param int $type_size The size (8, 255, etc.)
|
||||||
|
* @param boolean $reverse
|
||||||
|
* @return array An array containing the appropriate type and size for this DB type
|
||||||
|
*/
|
||||||
|
function smf_db_calculate_type($type_name, $type_size = null, $reverse = false)
|
||||||
|
{
|
||||||
|
// MySQL is actually the generic baseline.
|
||||||
|
|
||||||
|
$type_name = strtolower($type_name);
|
||||||
|
// Generic => Specific.
|
||||||
|
if (!$reverse)
|
||||||
|
{
|
||||||
|
$types = array(
|
||||||
|
'inet' => 'varbinary',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$types = array(
|
||||||
|
'varbinary' => 'inet',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got it? Change it!
|
||||||
|
if (isset($types[$type_name]))
|
||||||
|
{
|
||||||
|
if ($type_name == 'inet' && !$reverse)
|
||||||
|
{
|
||||||
|
$type_size = 16;
|
||||||
|
$type_name = 'varbinary';
|
||||||
|
}
|
||||||
|
elseif ($type_name == 'varbinary' && $reverse && $type_size == 16)
|
||||||
|
{
|
||||||
|
$type_name = 'inet';
|
||||||
|
$type_size = null;
|
||||||
|
}
|
||||||
|
elseif ($type_name == 'varbinary')
|
||||||
|
$type_name = 'varbinary';
|
||||||
|
else
|
||||||
|
$type_name = $types[$type_name];
|
||||||
|
}
|
||||||
|
elseif ($type_name == 'boolean')
|
||||||
|
$type_size = null;
|
||||||
|
|
||||||
|
return array($type_name, $type_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table structure.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table
|
||||||
|
* @return array An array of table structure - the name, the column info from {@link smf_db_list_columns()} and the index info from {@link smf_db_list_indexes()}
|
||||||
|
*/
|
||||||
|
function smf_db_table_structure($table_name)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
// Find the table engine and add that to the info as well
|
||||||
|
$table_status = $smcFunc['db_query']('', '
|
||||||
|
SHOW TABLE STATUS
|
||||||
|
IN {raw:db}
|
||||||
|
LIKE {string:table}',
|
||||||
|
array(
|
||||||
|
'db' => $database,
|
||||||
|
'table' => $real_table_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only one row, so no need for a loop...
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($table_status);
|
||||||
|
|
||||||
|
$smcFunc['db_free_result']($table_status);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'name' => $parsed_table_name,
|
||||||
|
'columns' => $smcFunc['db_list_columns']($table_name, true),
|
||||||
|
'indexes' => $smcFunc['db_list_indexes']($table_name, true),
|
||||||
|
'engine' => $row['Engine'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return column information for a table.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to get column info for
|
||||||
|
* @param bool $detail Whether or not to return detailed info. If true, returns the column info. If false, just returns the column names.
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @return array An array of column names or detailed column info, depending on $detail
|
||||||
|
*/
|
||||||
|
function smf_db_list_columns($table_name, $detail = false, $parameters = array())
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT column_name "Field", COLUMN_TYPE "Type", is_nullable "Null", COLUMN_KEY "Key" , column_default "Default", extra "Extra"
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = {string:table_name}
|
||||||
|
AND table_schema = {string:db_name}
|
||||||
|
ORDER BY ordinal_position',
|
||||||
|
array(
|
||||||
|
'table_name' => $real_table_name,
|
||||||
|
'db_name' => $db_name,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$columns = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
if (!$detail)
|
||||||
|
{
|
||||||
|
$columns[] = $row['Field'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Is there an auto_increment?
|
||||||
|
$auto = strpos($row['Extra'], 'auto_increment') !== false ? true : false;
|
||||||
|
|
||||||
|
// Can we split out the size?
|
||||||
|
if (preg_match('~(.+?)\s*\((\d+)\)(?:(?:\s*)?(unsigned))?~i', $row['Type'], $matches) === 1)
|
||||||
|
{
|
||||||
|
$type = $matches[1];
|
||||||
|
$size = $matches[2];
|
||||||
|
if (!empty($matches[3]) && $matches[3] == 'unsigned')
|
||||||
|
$unsigned = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$type = $row['Type'];
|
||||||
|
$size = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns[$row['Field']] = array(
|
||||||
|
'name' => $row['Field'],
|
||||||
|
'not_null' => $row['Null'] != 'YES',
|
||||||
|
'null' => $row['Null'] == 'YES',
|
||||||
|
'default' => isset($row['Default']) ? $row['Default'] : null,
|
||||||
|
'type' => $type,
|
||||||
|
'size' => $size,
|
||||||
|
'auto' => $auto,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($unsigned))
|
||||||
|
{
|
||||||
|
$columns[$row['Field']]['unsigned'] = $unsigned;
|
||||||
|
unset($unsigned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get index information.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to get indexes for
|
||||||
|
* @param bool $detail Whether or not to return detailed info.
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @return array An array of index names or a detailed array of index info, depending on $detail
|
||||||
|
*/
|
||||||
|
function smf_db_list_indexes($table_name, $detail = false, $parameters = array())
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SHOW KEYS
|
||||||
|
FROM {raw:table_name}
|
||||||
|
IN {raw:db}',
|
||||||
|
array(
|
||||||
|
'db' => $database,
|
||||||
|
'table_name' => $real_table_name,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$indexes = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
if (!$detail)
|
||||||
|
$indexes[] = $row['Key_name'];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// What is the type?
|
||||||
|
if ($row['Key_name'] == 'PRIMARY')
|
||||||
|
$type = 'primary';
|
||||||
|
elseif (empty($row['Non_unique']))
|
||||||
|
$type = 'unique';
|
||||||
|
elseif (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT')
|
||||||
|
$type = 'fulltext';
|
||||||
|
else
|
||||||
|
$type = 'index';
|
||||||
|
|
||||||
|
// This is the first column we've seen?
|
||||||
|
if (empty($indexes[$row['Key_name']]))
|
||||||
|
{
|
||||||
|
$indexes[$row['Key_name']] = array(
|
||||||
|
'name' => $row['Key_name'],
|
||||||
|
'type' => $type,
|
||||||
|
'columns' => array(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it a partial index?
|
||||||
|
if (!empty($row['Sub_part']))
|
||||||
|
$indexes[$row['Key_name']]['columns'][] = $row['Column_name'] . '(' . $row['Sub_part'] . ')';
|
||||||
|
else
|
||||||
|
$indexes[$row['Key_name']]['columns'][] = $row['Column_name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
return $indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a query for a column
|
||||||
|
*
|
||||||
|
* @param array $column An array of column info
|
||||||
|
* @return string The column definition
|
||||||
|
*/
|
||||||
|
function smf_db_create_query_column($column)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$column = array_change_key_case($column);
|
||||||
|
|
||||||
|
// Auto increment is easy here!
|
||||||
|
if (!empty($column['auto']))
|
||||||
|
$default = 'auto_increment';
|
||||||
|
// Make it null.
|
||||||
|
elseif (array_key_exists('default', $column) && is_null($column['default']))
|
||||||
|
$default = 'DEFAULT NULL';
|
||||||
|
// Numbers don't need quotes.
|
||||||
|
elseif (isset($column['default']) && is_numeric($column['default']))
|
||||||
|
$default = 'DEFAULT ' . (strpos($column['default'], '.') ? floatval($column['default']) : intval($column['default']));
|
||||||
|
// Non empty string.
|
||||||
|
elseif (isset($column['default']))
|
||||||
|
$default = 'DEFAULT \'' . $smcFunc['db_escape_string']($column['default']) . '\'';
|
||||||
|
else
|
||||||
|
$default = '';
|
||||||
|
|
||||||
|
// Backwards compatible with the nullable column.
|
||||||
|
if (isset($column['null']) && !isset($column['not_null']))
|
||||||
|
$column['not_null'] = !$column['null'];
|
||||||
|
|
||||||
|
// Sort out the size... and stuff...
|
||||||
|
$column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']);
|
||||||
|
|
||||||
|
// Allow unsigned integers (mysql only)
|
||||||
|
$unsigned = in_array($type, array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')) && !empty($column['unsigned']) ? 'unsigned ' : '';
|
||||||
|
|
||||||
|
if ($size !== null)
|
||||||
|
$type = $type . '(' . $size . ')';
|
||||||
|
|
||||||
|
// Now just put it together!
|
||||||
|
return '`' . $column['name'] . '` ' . $type . ' ' . (!empty($unsigned) ? $unsigned : '') . (!empty($column['not_null']) ? 'NOT NULL' : '') . ' ' . $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
959
Sources/DbPackages-postgresql.php
Normal file
|
@ -0,0 +1,959 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains database functionality specifically designed for packages (mods) to utilize.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the file functions to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_packages_init()
|
||||||
|
{
|
||||||
|
global $smcFunc, $reservedTables, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_create_table']) || $smcFunc['db_create_table'] != 'smf_db_create_table')
|
||||||
|
{
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_add_column' => 'smf_db_add_column',
|
||||||
|
'db_add_index' => 'smf_db_add_index',
|
||||||
|
'db_calculate_type' => 'smf_db_calculate_type',
|
||||||
|
'db_change_column' => 'smf_db_change_column',
|
||||||
|
'db_create_table' => 'smf_db_create_table',
|
||||||
|
'db_drop_table' => 'smf_db_drop_table',
|
||||||
|
'db_table_structure' => 'smf_db_table_structure',
|
||||||
|
'db_list_columns' => 'smf_db_list_columns',
|
||||||
|
'db_list_indexes' => 'smf_db_list_indexes',
|
||||||
|
'db_remove_column' => 'smf_db_remove_column',
|
||||||
|
'db_remove_index' => 'smf_db_remove_index',
|
||||||
|
);
|
||||||
|
$db_package_log = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We setup an array of SMF tables we can't do auto-remove on - in case a mod writer cocks it up!
|
||||||
|
$reservedTables = array(
|
||||||
|
'admin_info_files', 'approval_queue', 'attachments',
|
||||||
|
'background_tasks', 'ban_groups', 'ban_items', 'board_permissions',
|
||||||
|
'board_permissions_view', 'boards', 'calendar', 'calendar_holidays',
|
||||||
|
'categories', 'custom_fields', 'group_moderators', 'log_actions',
|
||||||
|
'log_activity', 'log_banned', 'log_boards', 'log_comments',
|
||||||
|
'log_digest', 'log_errors', 'log_floodcontrol', 'log_group_requests',
|
||||||
|
'log_mark_read', 'log_member_notices', 'log_notify', 'log_online',
|
||||||
|
'log_packages', 'log_polls', 'log_reported', 'log_reported_comments',
|
||||||
|
'log_scheduled_tasks', 'log_search_messages', 'log_search_results',
|
||||||
|
'log_search_subjects', 'log_search_topics', 'log_spider_hits',
|
||||||
|
'log_spider_stats', 'log_subscribed', 'log_topics', 'mail_queue',
|
||||||
|
'member_logins', 'membergroups', 'members', 'mentions',
|
||||||
|
'message_icons', 'messages', 'moderator_groups', 'moderators',
|
||||||
|
'package_servers', 'permission_profiles', 'permissions',
|
||||||
|
'personal_messages', 'pm_labeled_messages', 'pm_labels',
|
||||||
|
'pm_recipients', 'pm_rules', 'poll_choices', 'polls', 'qanda',
|
||||||
|
'scheduled_tasks', 'sessions', 'settings', 'smiley_files', 'smileys',
|
||||||
|
'spiders', 'subscriptions', 'themes', 'topics', 'user_alerts',
|
||||||
|
'user_alerts_prefs', 'user_drafts', 'user_likes',
|
||||||
|
);
|
||||||
|
foreach ($reservedTables as $k => $table_name)
|
||||||
|
$reservedTables[$k] = strtolower($db_prefix . $table_name);
|
||||||
|
|
||||||
|
// We in turn may need the extra stuff.
|
||||||
|
db_extend('extra');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function can be used to create a table without worrying about schema
|
||||||
|
* compatibilities across supported database systems.
|
||||||
|
* - If the table exists will, by default, do nothing.
|
||||||
|
* - Builds table with columns as passed to it - at least one column must be sent.
|
||||||
|
* The columns array should have one sub-array for each column - these sub arrays contain:
|
||||||
|
* 'name' = Column name
|
||||||
|
* 'type' = Type of column - values from (smallint, mediumint, int, text, varchar, char, tinytext, mediumtext, largetext)
|
||||||
|
* 'size' => Size of column (If applicable) - for example 255 for a large varchar, 10 for an int etc.
|
||||||
|
* If not set SMF will pick a size.
|
||||||
|
* - 'default' = Default value - do not set if no default required.
|
||||||
|
* - 'not_null' => Can it be null (true or false) - if not set default will be false.
|
||||||
|
* - 'auto' => Set to true to make it an auto incrementing column. Set to a numerical value to set from what
|
||||||
|
* it should begin counting.
|
||||||
|
* - Adds indexes as specified within indexes parameter. Each index should be a member of $indexes. Values are:
|
||||||
|
* - 'name' => Index name (If left empty SMF will generate).
|
||||||
|
* - 'type' => Type of index. Choose from 'primary', 'unique' or 'index'. If not set will default to 'index'.
|
||||||
|
* - 'columns' => Array containing columns that form part of key - in the order the index is to be created.
|
||||||
|
* - parameters: (None yet)
|
||||||
|
* - if_exists values:
|
||||||
|
* - 'ignore' will do nothing if the table exists. (And will return true)
|
||||||
|
* - 'overwrite' will drop any existing table of the same name.
|
||||||
|
* - 'error' will return false if the table already exists.
|
||||||
|
* - 'update' will update the table if the table already exists (no change of ai field and only colums with the same name keep the data)
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to create
|
||||||
|
* @param array $columns An array of column info in the specified format
|
||||||
|
* @param array $indexes An array of index info in the specified format
|
||||||
|
* @param array $parameters Currently not used
|
||||||
|
* @param string $if_exists What to do if the table exists.
|
||||||
|
* @param string $error
|
||||||
|
*/
|
||||||
|
function smf_db_create_table($table_name, $columns, $indexes = array(), $parameters = array(), $if_exists = 'ignore', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $reservedTables, $smcFunc, $db_package_log, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$db_trans = false;
|
||||||
|
$old_table_exists = false;
|
||||||
|
|
||||||
|
// Strip out the table name, we might not need it in some cases
|
||||||
|
$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
// With or without the database name, the fullname looks like this.
|
||||||
|
$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// First - no way do we touch SMF tables.
|
||||||
|
if (in_array(strtolower($short_table_name), $reservedTables))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Log that we'll want to remove this on uninstall.
|
||||||
|
$db_package_log[] = array('remove_table', $short_table_name);
|
||||||
|
|
||||||
|
// This... my friends... is a function in a half - let's start by checking if the table exists!
|
||||||
|
$tables = $smcFunc['db_list_tables']($database);
|
||||||
|
if (in_array($full_table_name, $tables))
|
||||||
|
{
|
||||||
|
// This is a sad day... drop the table? If not, return false (error) by default.
|
||||||
|
if ($if_exists == 'overwrite')
|
||||||
|
$smcFunc['db_drop_table']($table_name);
|
||||||
|
elseif ($if_exists == 'update')
|
||||||
|
{
|
||||||
|
$smcFunc['db_drop_table']($table_name . '_old');
|
||||||
|
$smcFunc['db_transaction']('begin');
|
||||||
|
$db_trans = true;
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . ' RENAME TO ' . $short_table_name . '_old',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$old_table_exists = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return $if_exists == 'ignore';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've got this far - good news - no table exists. We can build our own!
|
||||||
|
if (!$db_trans)
|
||||||
|
$smcFunc['db_transaction']('begin');
|
||||||
|
$table_query = 'CREATE TABLE ' . $short_table_name . "\n" . '(';
|
||||||
|
foreach ($columns as $column)
|
||||||
|
{
|
||||||
|
$column = array_change_key_case($column);
|
||||||
|
|
||||||
|
// If we have an auto increment do it!
|
||||||
|
if (!empty($column['auto']))
|
||||||
|
{
|
||||||
|
if (!$old_table_exists)
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$old_table_exists)
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
CREATE SEQUENCE ' . $short_table_name . '_seq',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$default = 'default nextval(\'' . $short_table_name . '_seq\')';
|
||||||
|
}
|
||||||
|
elseif (isset($column['default']) && $column['default'] !== null)
|
||||||
|
$default = 'default \'' . $smcFunc['db_escape_string']($column['default']) . '\'';
|
||||||
|
else
|
||||||
|
$default = '';
|
||||||
|
|
||||||
|
// Sort out the size...
|
||||||
|
$column['size'] = isset($column['size']) && is_numeric($column['size']) ? $column['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($column['type'], $column['size']);
|
||||||
|
if ($size !== null)
|
||||||
|
$type = $type . '(' . $size . ')';
|
||||||
|
|
||||||
|
// backward compatibility
|
||||||
|
if (isset($column['null']) && !isset($column['not_null']))
|
||||||
|
$column['not_null'] = !$column['null'];
|
||||||
|
|
||||||
|
// Now just put it together!
|
||||||
|
$table_query .= "\n\t\"" . $column['name'] . '" ' . $type . ' ' . (!empty($column['not_null']) ? 'NOT NULL' : '') . ' ' . $default . ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the indexes a sec...
|
||||||
|
$index_queries = array();
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
// MySQL you can do a "column_name (length)", postgresql does not allow this. Strip it.
|
||||||
|
foreach ($index['columns'] as &$c)
|
||||||
|
$c = preg_replace('~\s+(\(\d+\))~', '', $c);
|
||||||
|
|
||||||
|
$idx_columns = implode(',', $index['columns']);
|
||||||
|
|
||||||
|
// Primary goes in the table...
|
||||||
|
if (isset($index['type']) && $index['type'] == 'primary')
|
||||||
|
$table_query .= "\n\t" . 'PRIMARY KEY (' . implode(',', $index['columns']) . '),';
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (empty($index['name']))
|
||||||
|
$index['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index['columns'])));
|
||||||
|
|
||||||
|
$index_queries[] = 'CREATE ' . (isset($index['type']) && $index['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $short_table_name . '_' . $index['name'] . ' ON ' . $short_table_name . ' (' . $idx_columns . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No trailing commas!
|
||||||
|
if (substr($table_query, -1) == ',')
|
||||||
|
$table_query = substr($table_query, 0, -1);
|
||||||
|
|
||||||
|
$table_query .= ')';
|
||||||
|
|
||||||
|
// Create the table!
|
||||||
|
$smcFunc['db_query']('', $table_query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fill the old data
|
||||||
|
if ($old_table_exists)
|
||||||
|
{
|
||||||
|
$same_col = array();
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT count(*), column_name
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name in ({string:table1},{string:table2}) AND table_schema = {string:schema}
|
||||||
|
GROUP BY column_name
|
||||||
|
HAVING count(*) > 1',
|
||||||
|
array(
|
||||||
|
'table1' => $short_table_name,
|
||||||
|
'table2' => $short_table_name . '_old',
|
||||||
|
'schema' => 'public',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$same_col[] = $row['column_name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
INSERT INTO ' . $short_table_name . '('
|
||||||
|
. implode(',', $same_col) .
|
||||||
|
')
|
||||||
|
SELECT ' . implode(',', $same_col) . '
|
||||||
|
FROM ' . $short_table_name . '_old',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the indexes...
|
||||||
|
foreach ($index_queries as $query)
|
||||||
|
$smcFunc['db_query']('', $query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go, go power rangers!
|
||||||
|
$smcFunc['db_transaction']('commit');
|
||||||
|
|
||||||
|
if ($old_table_exists)
|
||||||
|
$smcFunc['db_drop_table']($table_name . '_old');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a table and its associated sequences.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to drop
|
||||||
|
* @param array $parameters Not used at the moment
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_drop_table($table_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $reservedTables, $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
// After stripping away the database name, this is what's left.
|
||||||
|
$real_prefix = preg_match('~^("?)(.+?)\\1\\.(.*?)$~', $db_prefix, $match) === 1 ? $match[3] : $db_prefix;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
// Get some aliases.
|
||||||
|
$full_table_name = str_replace('{db_prefix}', $real_prefix, $table_name);
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// God no - dropping one of these = bad.
|
||||||
|
if (in_array(strtolower($table_name), $reservedTables))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Does it exist?
|
||||||
|
$tables = $smcFunc['db_list_tables']($database);
|
||||||
|
if (in_array($full_table_name, $tables))
|
||||||
|
{
|
||||||
|
// We can then drop the table.
|
||||||
|
$smcFunc['db_transaction']('begin');
|
||||||
|
|
||||||
|
// the table
|
||||||
|
$table_query = 'DROP TABLE ' . $short_table_name;
|
||||||
|
|
||||||
|
// and the assosciated sequence, if any
|
||||||
|
$sequence_query = 'DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq';
|
||||||
|
|
||||||
|
// drop them
|
||||||
|
$smcFunc['db_query']('',
|
||||||
|
$table_query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('',
|
||||||
|
$sequence_query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_transaction']('commit');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise do 'nout.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function adds a column.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to add the column to
|
||||||
|
* @param array $column_info An array of column info (see {@link smf_db_create_table()})
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $if_exists What to do if the column exists. If 'update', column is updated.
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_add_column($table_name, $column_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$column_info = array_change_key_case($column_info);
|
||||||
|
|
||||||
|
// Log that we will want to uninstall this!
|
||||||
|
$db_package_log[] = array('remove_column', $short_table_name, $column_info['name']);
|
||||||
|
|
||||||
|
// Does it exist - if so don't add it again!
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, false);
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if ($column == $column_info['name'])
|
||||||
|
{
|
||||||
|
// If we're going to overwrite then use change column.
|
||||||
|
if ($if_exists == 'update')
|
||||||
|
return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the specifics...
|
||||||
|
$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
|
||||||
|
if ($size !== null)
|
||||||
|
$type = $type . '(' . $size . ')';
|
||||||
|
|
||||||
|
// Now add the thing!
|
||||||
|
$query = '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ADD COLUMN ' . $column_info['name'] . ' ' . $type;
|
||||||
|
$smcFunc['db_query']('', $query,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there's more attributes they need to be done via a change on PostgreSQL.
|
||||||
|
unset($column_info['type'], $column_info['size']);
|
||||||
|
|
||||||
|
if (count($column_info) != 1)
|
||||||
|
return $smcFunc['db_change_column']($table_name, $column_info['name'], $column_info);
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a column.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to drop the column from
|
||||||
|
* @param string $column_name The name of the column to drop
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_remove_column($table_name, $column_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
|
||||||
|
// Does it exist?
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, true);
|
||||||
|
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if (strtolower($column['name']) == strtolower($column_name))
|
||||||
|
{
|
||||||
|
// If there is an auto we need remove it!
|
||||||
|
if ($column['auto'])
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
DROP COLUMN ' . $column_name,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If here we didn't have to work - joy!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change a column. You only need to specify the column attributes that are changing.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table this column is in
|
||||||
|
* @param string $old_column The name of the column we want to change
|
||||||
|
* @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()})
|
||||||
|
* Note that $column_info also supports two additional parameters that only make sense when changing columns:
|
||||||
|
* - drop_default - to drop a default that was previously specified
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function smf_db_change_column($table_name, $old_column, $column_info)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$short_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$column_info = array_change_key_case($column_info);
|
||||||
|
|
||||||
|
// Check it does exist!
|
||||||
|
$columns = $smcFunc['db_list_columns']($table_name, true);
|
||||||
|
$old_info = null;
|
||||||
|
foreach ($columns as $column)
|
||||||
|
if ($column['name'] == $old_column)
|
||||||
|
$old_info = $column;
|
||||||
|
|
||||||
|
// Nothing?
|
||||||
|
if ($old_info == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// backward compatibility
|
||||||
|
if (isset($column_info['null']) && !isset($column_info['not_null']))
|
||||||
|
$column_info['not_null'] = !$column_info['null'];
|
||||||
|
|
||||||
|
// Get the right bits.
|
||||||
|
if (isset($column_info['drop_default']) && !empty($column_info['drop_default']))
|
||||||
|
$column_info['drop_default'] = true;
|
||||||
|
else
|
||||||
|
$column_info['drop_default'] = false;
|
||||||
|
if (!isset($column_info['name']))
|
||||||
|
$column_info['name'] = $old_column;
|
||||||
|
if (!array_key_exists('default', $column_info) && array_key_exists('default', $old_info) && empty($column_info['drop_default']))
|
||||||
|
$column_info['default'] = $old_info['default'];
|
||||||
|
if (!isset($column_info['not_null']))
|
||||||
|
$column_info['not_null'] = $old_info['not_null'];
|
||||||
|
if (!isset($column_info['auto']))
|
||||||
|
$column_info['auto'] = $old_info['auto'];
|
||||||
|
if (!isset($column_info['type']))
|
||||||
|
$column_info['type'] = $old_info['type'];
|
||||||
|
if (!isset($column_info['size']) || !is_numeric($column_info['size']))
|
||||||
|
$column_info['size'] = $old_info['size'];
|
||||||
|
if (!isset($column_info['unsigned']) || !in_array($column_info['type'], array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')))
|
||||||
|
$column_info['unsigned'] = '';
|
||||||
|
|
||||||
|
// If truly unspecified, make that clear, otherwise, might be confused with NULL...
|
||||||
|
// (Unspecified = no default whatsoever = column is not nullable with a value of null...)
|
||||||
|
if (($column_info['not_null'] === true) && !$column_info['drop_default'] && array_key_exists('default', $column_info) && is_null($column_info['default']))
|
||||||
|
unset($column_info['default']);
|
||||||
|
|
||||||
|
// If you need to drop the default, that needs it's own thing...
|
||||||
|
// Must be done first, in case the default type is inconsistent with the other changes.
|
||||||
|
if ($column_info['drop_default'])
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ALTER COLUMN ' . $old_column . ' DROP DEFAULT',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we check each bit individually and ALTER as required.
|
||||||
|
if (isset($column_info['name']) && $column_info['name'] != $old_column)
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name'],
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// What about a change in type?
|
||||||
|
if (isset($column_info['type']) && ($column_info['type'] != $old_info['type'] || (isset($column_info['size']) && $column_info['size'] != $old_info['size'])))
|
||||||
|
{
|
||||||
|
$column_info['size'] = isset($column_info['size']) && is_numeric($column_info['size']) ? $column_info['size'] : null;
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
|
||||||
|
if ($size !== null)
|
||||||
|
$type = $type . '(' . $size . ')';
|
||||||
|
|
||||||
|
// The alter is a pain.
|
||||||
|
$smcFunc['db_transaction']('begin');
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ADD COLUMN ' . $column_info['name'] . '_tempxx ' . $type,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE ' . $short_table_name . '
|
||||||
|
SET ' . $column_info['name'] . '_tempxx = CAST(' . $column_info['name'] . ' AS ' . $type . ')',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
DROP COLUMN ' . $column_info['name'],
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
RENAME COLUMN ' . $column_info['name'] . '_tempxx TO ' . $column_info['name'],
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$smcFunc['db_transaction']('commit');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different default?
|
||||||
|
// Just go ahead & honor the setting. Type changes above introduce defaults that we might need to override here...
|
||||||
|
if (!$column_info['drop_default'] && array_key_exists('default', $column_info))
|
||||||
|
{
|
||||||
|
// Fix the default.
|
||||||
|
$default = '';
|
||||||
|
if (is_null($column_info['default']))
|
||||||
|
$default = 'NULL';
|
||||||
|
elseif (isset($column_info['default']) && is_numeric($column_info['default']))
|
||||||
|
$default = strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']);
|
||||||
|
else
|
||||||
|
$default = '\'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
|
||||||
|
|
||||||
|
$action = 'SET DEFAULT ' . $default;
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it null - or otherwise?
|
||||||
|
// Just go ahead & honor the setting. Type changes above introduce defaults that we might need to override here...
|
||||||
|
if ($column_info['not_null'])
|
||||||
|
$action = 'SET NOT NULL';
|
||||||
|
else
|
||||||
|
$action = 'DROP NOT NULL';
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $short_table_name . '
|
||||||
|
ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an index.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to add the index to
|
||||||
|
* @param array $index_info An array of index info (see {@link smf_db_create_table()})
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $if_exists What to do if the index exists. If 'update', the definition will be updated.
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_add_index($table_name, $index_info, $parameters = array(), $if_exists = 'update', $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_package_log, $db_prefix;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
|
||||||
|
// No columns = no index.
|
||||||
|
if (empty($index_info['columns']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// MySQL you can do a "column_name (length)", postgresql does not allow this. Strip it.
|
||||||
|
foreach ($index_info['columns'] as &$c)
|
||||||
|
$c = preg_replace('~\s+(\(\d+\))~', '', $c);
|
||||||
|
|
||||||
|
$columns = implode(',', $index_info['columns']);
|
||||||
|
|
||||||
|
// No name - make it up!
|
||||||
|
if (empty($index_info['name']))
|
||||||
|
{
|
||||||
|
// No need for primary.
|
||||||
|
if (isset($index_info['type']) && $index_info['type'] == 'primary')
|
||||||
|
$index_info['name'] = '';
|
||||||
|
else
|
||||||
|
$index_info['name'] = trim(implode('_', preg_replace('~(\(\d+\))~', '', $index_info['columns'])));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$index_info['name'] = $index_info['name'];
|
||||||
|
|
||||||
|
// Log that we are going to want to remove this!
|
||||||
|
$db_package_log[] = array('remove_index', $parsed_table_name, $index_info['name']);
|
||||||
|
|
||||||
|
// Let's get all our indexes.
|
||||||
|
$indexes = $smcFunc['db_list_indexes']($table_name, true);
|
||||||
|
|
||||||
|
// Do we already have it?
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
if ($index['name'] == $index_info['name'] || ($index['type'] == 'primary' && isset($index_info['type']) && $index_info['type'] == 'primary'))
|
||||||
|
{
|
||||||
|
// If we want to overwrite simply remove the current one then continue.
|
||||||
|
if ($if_exists != 'update' || $index['type'] == 'primary')
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
$smcFunc['db_remove_index']($table_name, $index_info['name']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're here we know we don't have the index - so just add it.
|
||||||
|
if (!empty($index_info['type']) && $index_info['type'] == 'primary')
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $real_table_name . '
|
||||||
|
ADD PRIMARY KEY (' . $columns . ')',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
CREATE ' . (isset($index_info['type']) && $index_info['type'] == 'unique' ? 'UNIQUE' : '') . ' INDEX ' . $real_table_name . '_' . $index_info['name'] . ' ON ' . $real_table_name . ' (' . $columns . ')',
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an index.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to remove the index from
|
||||||
|
* @param string $index_name The name of the index to remove
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @param string $error
|
||||||
|
* @return boolean Whether or not the operation was successful
|
||||||
|
*/
|
||||||
|
function smf_db_remove_index($table_name, $index_name, $parameters = array(), $error = 'fatal')
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
|
||||||
|
// Better exist!
|
||||||
|
$indexes = $smcFunc['db_list_indexes']($table_name, true);
|
||||||
|
|
||||||
|
// Do not add the table name to the index if it is arleady there.
|
||||||
|
if ($index_name != 'primary' && strpos($index_name, $real_table_name) !== false)
|
||||||
|
$index_name = str_replace($real_table_name . '_', '', $index_name);
|
||||||
|
|
||||||
|
foreach ($indexes as $index)
|
||||||
|
{
|
||||||
|
// If the name is primary we want the primary key!
|
||||||
|
if ($index['type'] == 'primary' && $index_name == 'primary')
|
||||||
|
{
|
||||||
|
// Dropping primary key is odd...
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
ALTER TABLE ' . $real_table_name . '
|
||||||
|
DROP CONSTRAINT ' . $index['name'],
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($index['name'] == $index_name)
|
||||||
|
{
|
||||||
|
// Drop the bugger...
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DROP INDEX ' . $real_table_name . '_' . $index_name,
|
||||||
|
array(
|
||||||
|
'security_override' => true,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not to be found ;(
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the schema formatted name for a type.
|
||||||
|
*
|
||||||
|
* @param string $type_name The data type (int, varchar, smallint, etc.)
|
||||||
|
* @param int $type_size The size (8, 255, etc.)
|
||||||
|
* @param boolean $reverse If true, returns specific types for a generic type
|
||||||
|
* @return array An array containing the appropriate type and size for this DB type
|
||||||
|
*/
|
||||||
|
function smf_db_calculate_type($type_name, $type_size = null, $reverse = false)
|
||||||
|
{
|
||||||
|
// Let's be sure it's lowercase MySQL likes both, others no.
|
||||||
|
$type_name = strtolower($type_name);
|
||||||
|
// Generic => Specific.
|
||||||
|
if (!$reverse)
|
||||||
|
{
|
||||||
|
$types = array(
|
||||||
|
'varchar' => 'character varying',
|
||||||
|
'char' => 'character',
|
||||||
|
'mediumint' => 'int',
|
||||||
|
'tinyint' => 'smallint',
|
||||||
|
'tinytext' => 'character varying',
|
||||||
|
'mediumtext' => 'text',
|
||||||
|
'largetext' => 'text',
|
||||||
|
'inet' => 'inet',
|
||||||
|
'time' => 'time without time zone',
|
||||||
|
'datetime' => 'timestamp without time zone',
|
||||||
|
'timestamp' => 'timestamp without time zone',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$types = array(
|
||||||
|
'character varying' => 'varchar',
|
||||||
|
'character' => 'char',
|
||||||
|
'integer' => 'int',
|
||||||
|
'inet' => 'inet',
|
||||||
|
'time without time zone' => 'time',
|
||||||
|
'timestamp without time zone' => 'datetime',
|
||||||
|
'numeric' => 'decimal',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got it? Change it!
|
||||||
|
if (isset($types[$type_name]))
|
||||||
|
{
|
||||||
|
if ($type_name == 'tinytext')
|
||||||
|
$type_size = 255;
|
||||||
|
$type_name = $types[$type_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only char fields got size
|
||||||
|
if (strpos($type_name, 'char') === false)
|
||||||
|
$type_size = null;
|
||||||
|
|
||||||
|
return array($type_name, $type_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table structure.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table
|
||||||
|
* @return array An array of table structure - the name, the column info from {@link smf_db_list_columns()} and the index info from {@link smf_db_list_indexes()}
|
||||||
|
*/
|
||||||
|
function smf_db_table_structure($table_name)
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'name' => $real_table_name,
|
||||||
|
'columns' => $smcFunc['db_list_columns']($table_name, true),
|
||||||
|
'indexes' => $smcFunc['db_list_indexes']($table_name, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return column information for a table.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to get column info for
|
||||||
|
* @param bool $detail Whether or not to return detailed info. If true, returns the column info. If false, just returns the column names.
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @return array An array of column names or detailed column info, depending on $detail
|
||||||
|
*/
|
||||||
|
function smf_db_list_columns($table_name, $detail = false, $parameters = array())
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT column_name, column_default, is_nullable, data_type, character_maximum_length
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = {string:schema_public}
|
||||||
|
AND table_name = {string:table_name}
|
||||||
|
ORDER BY ordinal_position',
|
||||||
|
array(
|
||||||
|
'schema_public' => 'public',
|
||||||
|
'table_name' => $real_table_name,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$columns = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
if (!$detail)
|
||||||
|
{
|
||||||
|
$columns[] = $row['column_name'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$auto = false;
|
||||||
|
$default = null;
|
||||||
|
// What is the default?
|
||||||
|
if ($row['column_default'] !== null)
|
||||||
|
{
|
||||||
|
if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
|
||||||
|
$auto = true;
|
||||||
|
elseif (substr($row['column_default'], 0, 4) != 'NULL' && trim($row['column_default']) != '')
|
||||||
|
{
|
||||||
|
$pos = strpos($row['column_default'], '::');
|
||||||
|
$default = trim($pos === false ? $row['column_default'] : substr($row['column_default'], 0, $pos), '\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the type generic.
|
||||||
|
list ($type, $size) = $smcFunc['db_calculate_type']($row['data_type'], $row['character_maximum_length'], true);
|
||||||
|
|
||||||
|
$columns[$row['column_name']] = array(
|
||||||
|
'name' => $row['column_name'],
|
||||||
|
'not_null' => $row['is_nullable'] != 'YES',
|
||||||
|
'null' => $row['is_nullable'] == 'YES',
|
||||||
|
'default' => $default,
|
||||||
|
'type' => $type,
|
||||||
|
'size' => $size,
|
||||||
|
'auto' => $auto,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get index information.
|
||||||
|
*
|
||||||
|
* @param string $table_name The name of the table to get indexes for
|
||||||
|
* @param bool $detail Whether or not to return detailed info.
|
||||||
|
* @param array $parameters Not used?
|
||||||
|
* @return array An array of index names or a detailed array of index info, depending on $detail
|
||||||
|
*/
|
||||||
|
function smf_db_list_indexes($table_name, $detail = false, $parameters = array())
|
||||||
|
{
|
||||||
|
global $smcFunc, $db_prefix, $db_name;
|
||||||
|
|
||||||
|
$parsed_table_name = str_replace('{db_prefix}', $db_prefix, $table_name);
|
||||||
|
$real_table_name = preg_match('~^(`?)(.+?)\\1\\.(.*?)$~', $parsed_table_name, $match) === 1 ? $match[3] : $parsed_table_name;
|
||||||
|
$database = !empty($match[2]) ? $match[2] : $db_name;
|
||||||
|
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary,
|
||||||
|
CASE WHEN i.indisunique THEN 1 ELSE 0 END AS is_unique,
|
||||||
|
c2.relname AS name,
|
||||||
|
pg_get_indexdef(i.indexrelid) AS inddef
|
||||||
|
FROM pg_class AS c, pg_class AS c2, pg_index AS i
|
||||||
|
WHERE c.relname = {string:table_name}
|
||||||
|
AND c.oid = i.indrelid
|
||||||
|
AND i.indexrelid = c2.oid',
|
||||||
|
array(
|
||||||
|
'table_name' => $real_table_name,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$indexes = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($result))
|
||||||
|
{
|
||||||
|
// Try get the columns that make it up.
|
||||||
|
if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$columns = explode(',', $matches[1]);
|
||||||
|
|
||||||
|
if (empty($columns))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach ($columns as $k => $v)
|
||||||
|
$columns[$k] = trim($v);
|
||||||
|
|
||||||
|
// Fix up the name to be consistent cross databases
|
||||||
|
if (substr($row['name'], -5) == '_pkey' && $row['is_primary'] == 1)
|
||||||
|
$row['name'] = 'PRIMARY';
|
||||||
|
else
|
||||||
|
$row['name'] = str_replace($real_table_name . '_', '', $row['name']);
|
||||||
|
|
||||||
|
if (!$detail)
|
||||||
|
$indexes[] = $row['name'];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$indexes[$row['name']] = array(
|
||||||
|
'name' => $row['name'],
|
||||||
|
'type' => $row['is_primary'] ? 'primary' : ($row['is_unique'] ? 'unique' : 'index'),
|
||||||
|
'columns' => $columns,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
return $indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
81
Sources/DbSearch-mysql.php
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains database functions specific to search related activity.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the file functions to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_search_init()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_search_query']) || $smcFunc['db_search_query'] != 'smf_db_query')
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_search_query' => 'smf_db_query',
|
||||||
|
'db_search_support' => 'smf_db_search_support',
|
||||||
|
'db_create_word_search' => 'smf_db_create_word_search',
|
||||||
|
'db_support_ignore' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
db_extend();
|
||||||
|
$version = $smcFunc['db_get_version']();
|
||||||
|
$smcFunc['db_supports_pcre'] = version_compare($version, strpos($version, 'MariaDB') !== false ? '10.0.5' : '8.0.4', '>=');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will tell you whether this database type supports this search type.
|
||||||
|
*
|
||||||
|
* @param string $search_type The search type.
|
||||||
|
* @return boolean Whether or not the specified search type is supported by this db system
|
||||||
|
*/
|
||||||
|
function smf_db_search_support($search_type)
|
||||||
|
{
|
||||||
|
$supported_types = array('fulltext');
|
||||||
|
|
||||||
|
return in_array($search_type, $supported_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highly specific function, to create the custom word index table.
|
||||||
|
*
|
||||||
|
* @param string $size The size of the desired index.
|
||||||
|
*/
|
||||||
|
function smf_db_create_word_search($size)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if ($size == 'small')
|
||||||
|
$size = 'smallint(5)';
|
||||||
|
elseif ($size == 'medium')
|
||||||
|
$size = 'mediumint(8)';
|
||||||
|
else
|
||||||
|
$size = 'int(10)';
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
CREATE TABLE {db_prefix}log_search_words (
|
||||||
|
id_word {raw:size} unsigned NOT NULL default {string:string_zero},
|
||||||
|
id_msg int(10) unsigned NOT NULL default {string:string_zero},
|
||||||
|
PRIMARY KEY (id_word, id_msg)
|
||||||
|
) ENGINE=InnoDB',
|
||||||
|
array(
|
||||||
|
'string_zero' => '0',
|
||||||
|
'size' => $size,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
189
Sources/DbSearch-postgresql.php
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains database functions specific to search related activity.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the file functions to the $smcFunc array.
|
||||||
|
*/
|
||||||
|
function db_search_init()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if (!isset($smcFunc['db_search_query']) || $smcFunc['db_search_query'] != 'smf_db_search_query')
|
||||||
|
$smcFunc += array(
|
||||||
|
'db_search_query' => 'smf_db_search_query',
|
||||||
|
'db_search_support' => 'smf_db_search_support',
|
||||||
|
'db_create_word_search' => 'smf_db_create_word_search',
|
||||||
|
'db_support_ignore' => false,
|
||||||
|
'db_search_language' => 'smf_db_search_language',
|
||||||
|
);
|
||||||
|
|
||||||
|
db_extend();
|
||||||
|
|
||||||
|
$smcFunc['db_support_ignore'] = true;
|
||||||
|
$smcFunc['db_supports_pcre'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will tell you whether this database type supports this search type.
|
||||||
|
*
|
||||||
|
* @param string $search_type The search type
|
||||||
|
* @return boolean Whether or not the specified search type is supported by this DB system.
|
||||||
|
*/
|
||||||
|
function smf_db_search_support($search_type)
|
||||||
|
{
|
||||||
|
$supported_types = array('custom', 'fulltext');
|
||||||
|
|
||||||
|
return in_array($search_type, $supported_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the correct query for this search type.
|
||||||
|
*
|
||||||
|
* @param string $identifier A query identifier
|
||||||
|
* @param string $db_string The query text
|
||||||
|
* @param array $db_values An array of values to pass to $smcFunc['db_query']
|
||||||
|
* @param resource $connection The current DB connection resource
|
||||||
|
* @return resource The query result resource from $smcFunc['db_query']
|
||||||
|
*/
|
||||||
|
function smf_db_search_query($identifier, $db_string, $db_values = array(), $connection = null)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$replacements = array(
|
||||||
|
'create_tmp_log_search_topics' => array(
|
||||||
|
'~ENGINE=MEMORY~i' => '',
|
||||||
|
),
|
||||||
|
'create_tmp_log_search_messages' => array(
|
||||||
|
'~ENGINE=MEMORY~i' => '',
|
||||||
|
),
|
||||||
|
'insert_into_log_messages_fulltext' => array(
|
||||||
|
'/NOT\sLIKE/' => 'NOT ILIKE',
|
||||||
|
'/\bLIKE\b/' => 'ILIKE',
|
||||||
|
'/NOT RLIKE/' => '!~*',
|
||||||
|
'/RLIKE/' => '~*',
|
||||||
|
),
|
||||||
|
'insert_log_search_results_subject' => array(
|
||||||
|
'/NOT\sLIKE/' => 'NOT ILIKE',
|
||||||
|
'/\bLIKE\b/' => 'ILIKE',
|
||||||
|
'/NOT RLIKE/' => '!~*',
|
||||||
|
'/RLIKE/' => '~*',
|
||||||
|
),
|
||||||
|
'insert_log_search_topics' => array(
|
||||||
|
'/NOT\sLIKE/' => 'NOT ILIKE',
|
||||||
|
'/\bLIKE\b/' => 'ILIKE',
|
||||||
|
'/NOT RLIKE/' => '!~*',
|
||||||
|
'/RLIKE/' => '~*',
|
||||||
|
),
|
||||||
|
'insert_log_search_results_no_index' => array(
|
||||||
|
'/NOT\sLIKE/' => 'NOT ILIKE',
|
||||||
|
'/\bLIKE\b/' => 'ILIKE',
|
||||||
|
'/NOT RLIKE/' => '!~*',
|
||||||
|
'/RLIKE/' => '~*',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($replacements[$identifier]))
|
||||||
|
$db_string = preg_replace(array_keys($replacements[$identifier]), array_values($replacements[$identifier]), $db_string);
|
||||||
|
if (preg_match('~^\s*INSERT\sIGNORE~i', $db_string) != 0)
|
||||||
|
{
|
||||||
|
$db_string = preg_replace('~^\s*INSERT\sIGNORE~i', 'INSERT', $db_string);
|
||||||
|
if ($smcFunc['db_support_ignore'])
|
||||||
|
{
|
||||||
|
//pg style "INSERT INTO.... ON CONFLICT DO NOTHING"
|
||||||
|
$db_string = $db_string . ' ON CONFLICT DO NOTHING';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't error on multi-insert.
|
||||||
|
$db_values['db_error_skip'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fix double quotes
|
||||||
|
if ($identifier == 'insert_into_log_messages_fulltext')
|
||||||
|
$db_string = str_replace('"', "'", $db_string);
|
||||||
|
|
||||||
|
$return = $smcFunc['db_query']('', $db_string,
|
||||||
|
$db_values, $connection
|
||||||
|
);
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highly specific function, to create the custom word index table.
|
||||||
|
*
|
||||||
|
* @param string $size The column size type (int, mediumint (8), etc.). Not used here.
|
||||||
|
*/
|
||||||
|
function smf_db_create_word_search($size)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$size = 'int';
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
CREATE TABLE {db_prefix}log_search_words (
|
||||||
|
id_word {raw:size} NOT NULL default {string:string_zero},
|
||||||
|
id_msg int NOT NULL default {string:string_zero},
|
||||||
|
PRIMARY KEY (id_word, id_msg)
|
||||||
|
)',
|
||||||
|
array(
|
||||||
|
'size' => $size,
|
||||||
|
'string_zero' => '0',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the language for the textsearch index
|
||||||
|
*/
|
||||||
|
function smf_db_search_language()
|
||||||
|
{
|
||||||
|
global $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
$language_ftx = 'english';
|
||||||
|
|
||||||
|
if (!empty($modSettings['search_language']))
|
||||||
|
$language_ftx = $modSettings['search_language'];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT cfgname FROM pg_ts_config WHERE oid = current_setting({string:default_language})::regconfig',
|
||||||
|
array(
|
||||||
|
'default_language' => 'default_text_search_config'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($request !== false && $smcFunc['db_num_rows']($request) == 1)
|
||||||
|
{
|
||||||
|
$row = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$language_ftx = $row['cfgname'];
|
||||||
|
|
||||||
|
$smcFunc['db_insert']('replace',
|
||||||
|
'{db_prefix}settings',
|
||||||
|
array('variable' => 'string', 'value' => 'string'),
|
||||||
|
array('search_language', $language_ftx),
|
||||||
|
array('variable')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $language_ftx;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1799
Sources/Display.php
Normal file
868
Sources/Drafts.php
Normal file
|
@ -0,0 +1,868 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains all the functions that allow for the saving,
|
||||||
|
* retrieving, deleting and settings for the drafts function.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
loadLanguage('Drafts');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a post draft in the user_drafts table
|
||||||
|
* The core draft feature must be enabled, as well as the post draft option
|
||||||
|
* Determines if this is a new or an existing draft
|
||||||
|
* Returns errors in $post_errors for display in the template
|
||||||
|
*
|
||||||
|
* @param string[] $post_errors Any errors encountered trying to save this draft
|
||||||
|
* @return boolean Always returns true
|
||||||
|
*/
|
||||||
|
function SaveDraft(&$post_errors)
|
||||||
|
{
|
||||||
|
global $context, $user_info, $smcFunc, $modSettings, $board;
|
||||||
|
|
||||||
|
// can you be, should you be ... here?
|
||||||
|
if (empty($modSettings['drafts_post_enabled']) || !allowedTo('post_draft') || !isset($_POST['save_draft']) || !isset($_POST['id_draft']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// read in what they sent us, if anything
|
||||||
|
$id_draft = (int) $_POST['id_draft'];
|
||||||
|
$draft_info = ReadDraft($id_draft);
|
||||||
|
|
||||||
|
// A draft has been saved less than 5 seconds ago, let's not do the autosave again
|
||||||
|
if (isset($_REQUEST['xml']) && !empty($draft_info['poster_time']) && time() < $draft_info['poster_time'] + 5)
|
||||||
|
{
|
||||||
|
$context['draft_saved_on'] = $draft_info['poster_time'];
|
||||||
|
|
||||||
|
// since we were called from the autosave function, send something back
|
||||||
|
if (!empty($id_draft))
|
||||||
|
XmlDraft($id_draft);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_POST['message']))
|
||||||
|
$_POST['message'] = isset($_POST['quickReply']) ? $_POST['quickReply'] : '';
|
||||||
|
|
||||||
|
// prepare any data from the form
|
||||||
|
$topic_id = empty($_REQUEST['topic']) ? 0 : (int) $_REQUEST['topic'];
|
||||||
|
$draft['icon'] = empty($_POST['icon']) ? 'xx' : preg_replace('~[\./\\\\*:"\'<>]~', '', $_POST['icon']);
|
||||||
|
$draft['smileys_enabled'] = isset($_POST['ns']) ? (int) $_POST['ns'] : 1;
|
||||||
|
$draft['locked'] = isset($_POST['lock']) ? (int) $_POST['lock'] : 0;
|
||||||
|
$draft['sticky'] = isset($_POST['sticky']) ? (int) $_POST['sticky'] : 0;
|
||||||
|
$draft['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
|
||||||
|
$draft['body'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
|
||||||
|
|
||||||
|
// message and subject still need a bit more work
|
||||||
|
preparsecode($draft['body']);
|
||||||
|
if ($smcFunc['strlen']($draft['subject']) > 100)
|
||||||
|
$draft['subject'] = $smcFunc['substr']($draft['subject'], 0, 100);
|
||||||
|
|
||||||
|
// Modifying an existing draft, like hitting the save draft button or autosave enabled?
|
||||||
|
if (!empty($id_draft) && !empty($draft_info))
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}user_drafts
|
||||||
|
SET
|
||||||
|
id_topic = {int:id_topic},
|
||||||
|
id_board = {int:id_board},
|
||||||
|
poster_time = {int:poster_time},
|
||||||
|
subject = {string:subject},
|
||||||
|
smileys_enabled = {int:smileys_enabled},
|
||||||
|
body = {string:body},
|
||||||
|
icon = {string:icon},
|
||||||
|
locked = {int:locked},
|
||||||
|
is_sticky = {int:is_sticky}
|
||||||
|
WHERE id_draft = {int:id_draft}',
|
||||||
|
array(
|
||||||
|
'id_topic' => $topic_id,
|
||||||
|
'id_board' => $board,
|
||||||
|
'poster_time' => time(),
|
||||||
|
'subject' => $draft['subject'],
|
||||||
|
'smileys_enabled' => (int) $draft['smileys_enabled'],
|
||||||
|
'body' => $draft['body'],
|
||||||
|
'icon' => $draft['icon'],
|
||||||
|
'locked' => $draft['locked'],
|
||||||
|
'is_sticky' => $draft['sticky'],
|
||||||
|
'id_draft' => $id_draft,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// some items to return to the form
|
||||||
|
$context['draft_saved'] = true;
|
||||||
|
$context['id_draft'] = $id_draft;
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
unset($_POST['save_draft']);
|
||||||
|
}
|
||||||
|
// otherwise creating a new draft
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$id_draft = $smcFunc['db_insert']('',
|
||||||
|
'{db_prefix}user_drafts',
|
||||||
|
array(
|
||||||
|
'id_topic' => 'int',
|
||||||
|
'id_board' => 'int',
|
||||||
|
'type' => 'int',
|
||||||
|
'poster_time' => 'int',
|
||||||
|
'id_member' => 'int',
|
||||||
|
'subject' => 'string-255',
|
||||||
|
'smileys_enabled' => 'int',
|
||||||
|
'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'),
|
||||||
|
'icon' => 'string-16',
|
||||||
|
'locked' => 'int',
|
||||||
|
'is_sticky' => 'int'
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$topic_id,
|
||||||
|
$board,
|
||||||
|
0,
|
||||||
|
time(),
|
||||||
|
$user_info['id'],
|
||||||
|
$draft['subject'],
|
||||||
|
$draft['smileys_enabled'],
|
||||||
|
$draft['body'],
|
||||||
|
$draft['icon'],
|
||||||
|
$draft['locked'],
|
||||||
|
$draft['sticky']
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id_draft'
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// everything go as expected?
|
||||||
|
if (!empty($id_draft))
|
||||||
|
{
|
||||||
|
$context['draft_saved'] = true;
|
||||||
|
$context['id_draft'] = $id_draft;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$post_errors[] = 'draft_not_saved';
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
unset($_POST['save_draft']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we were called from the autosave function, send something back
|
||||||
|
if (!empty($id_draft) && isset($_REQUEST['xml']) && (!in_array('session_timeout', $post_errors)))
|
||||||
|
{
|
||||||
|
$context['draft_saved_on'] = time();
|
||||||
|
XmlDraft($id_draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a PM draft in the user_drafts table
|
||||||
|
* The core draft feature must be enabled, as well as the pm draft option
|
||||||
|
* Determines if this is a new or and update to an existing pm draft
|
||||||
|
*
|
||||||
|
* @param string $post_errors A string of info about errors encountered trying to save this draft
|
||||||
|
* @param array $recipientList An array of data about who this PM is being sent to
|
||||||
|
* @return boolean false if you can't save the draft, true if we're doing this via XML more than 5 seconds after the last save, nothing otherwise
|
||||||
|
*/
|
||||||
|
function SavePMDraft(&$post_errors, $recipientList)
|
||||||
|
{
|
||||||
|
global $context, $user_info, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// PM survey says ... can you stay or must you go
|
||||||
|
if (empty($modSettings['drafts_pm_enabled']) || !allowedTo('pm_draft') || !isset($_POST['save_draft']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// read in what you sent us
|
||||||
|
$id_pm_draft = (int) $_POST['id_pm_draft'];
|
||||||
|
$draft_info = ReadDraft($id_pm_draft, 1);
|
||||||
|
|
||||||
|
// 5 seconds is the same limit we have for posting
|
||||||
|
if (isset($_REQUEST['xml']) && !empty($draft_info['poster_time']) && time() < $draft_info['poster_time'] + 5)
|
||||||
|
{
|
||||||
|
$context['draft_saved_on'] = $draft_info['poster_time'];
|
||||||
|
|
||||||
|
// Send something back to the javascript caller
|
||||||
|
if (!empty($id_draft))
|
||||||
|
XmlDraft($id_draft);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine who this is being sent to
|
||||||
|
if (isset($_REQUEST['xml']))
|
||||||
|
{
|
||||||
|
$recipientList['to'] = isset($_POST['recipient_to']) ? explode(',', $_POST['recipient_to']) : array();
|
||||||
|
$recipientList['bcc'] = isset($_POST['recipient_bcc']) ? explode(',', $_POST['recipient_bcc']) : array();
|
||||||
|
}
|
||||||
|
elseif (!empty($draft_info['to_list']) && empty($recipientList))
|
||||||
|
$recipientList = $smcFunc['json_decode']($draft_info['to_list'], true);
|
||||||
|
|
||||||
|
// prepare the data we got from the form
|
||||||
|
$reply_id = empty($_POST['replied_to']) ? 0 : (int) $_POST['replied_to'];
|
||||||
|
$draft['body'] = $smcFunc['htmlspecialchars']($_POST['message'], ENT_QUOTES);
|
||||||
|
$draft['subject'] = strtr($smcFunc['htmlspecialchars']($_POST['subject']), array("\r" => '', "\n" => '', "\t" => ''));
|
||||||
|
|
||||||
|
// message and subject always need a bit more work
|
||||||
|
preparsecode($draft['body']);
|
||||||
|
if ($smcFunc['strlen']($draft['subject']) > 100)
|
||||||
|
$draft['subject'] = $smcFunc['substr']($draft['subject'], 0, 100);
|
||||||
|
|
||||||
|
// Modifying an existing PM draft?
|
||||||
|
if (!empty($id_pm_draft) && !empty($draft_info))
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}user_drafts
|
||||||
|
SET id_reply = {int:id_reply},
|
||||||
|
type = {int:type},
|
||||||
|
poster_time = {int:poster_time},
|
||||||
|
subject = {string:subject},
|
||||||
|
body = {string:body},
|
||||||
|
to_list = {string:to_list}
|
||||||
|
WHERE id_draft = {int:id_pm_draft}',
|
||||||
|
array(
|
||||||
|
'id_reply' => $reply_id,
|
||||||
|
'type' => 1,
|
||||||
|
'poster_time' => time(),
|
||||||
|
'subject' => $draft['subject'],
|
||||||
|
'body' => $draft['body'],
|
||||||
|
'id_pm_draft' => $id_pm_draft,
|
||||||
|
'to_list' => $smcFunc['json_encode']($recipientList),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// some items to return to the form
|
||||||
|
$context['draft_saved'] = true;
|
||||||
|
$context['id_pm_draft'] = $id_pm_draft;
|
||||||
|
}
|
||||||
|
// otherwise creating a new PM draft.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$id_pm_draft = $smcFunc['db_insert']('',
|
||||||
|
'{db_prefix}user_drafts',
|
||||||
|
array(
|
||||||
|
'id_reply' => 'int',
|
||||||
|
'type' => 'int',
|
||||||
|
'poster_time' => 'int',
|
||||||
|
'id_member' => 'int',
|
||||||
|
'subject' => 'string-255',
|
||||||
|
'body' => 'string-65534',
|
||||||
|
'to_list' => 'string-255',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$reply_id,
|
||||||
|
1,
|
||||||
|
time(),
|
||||||
|
$user_info['id'],
|
||||||
|
$draft['subject'],
|
||||||
|
$draft['body'],
|
||||||
|
$smcFunc['json_encode']($recipientList),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id_draft'
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// everything go as expected, if not toss back an error
|
||||||
|
if (!empty($id_pm_draft))
|
||||||
|
{
|
||||||
|
$context['draft_saved'] = true;
|
||||||
|
$context['id_pm_draft'] = $id_pm_draft;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$post_errors[] = 'draft_not_saved';
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we were called from the autosave function, send something back
|
||||||
|
if (!empty($id_pm_draft) && isset($_REQUEST['xml']) && !in_array('session_timeout', $post_errors))
|
||||||
|
{
|
||||||
|
$context['draft_saved_on'] = time();
|
||||||
|
XmlDraft($id_pm_draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a draft in from the user_drafts table
|
||||||
|
* Validates that the draft is the user''s draft
|
||||||
|
* Optionally loads the draft in to context or superglobal for loading in to the form
|
||||||
|
*
|
||||||
|
* @param int $id_draft ID of the draft to load
|
||||||
|
* @param int $type Type of draft - 0 for post or 1 for PM
|
||||||
|
* @param boolean $check Validate that this draft belongs to the current user
|
||||||
|
* @param boolean $load Whether or not to load the data into variables for use on a form
|
||||||
|
* @return boolean|array False if the data couldn't be loaded, true if it's a PM draft or an array of info about the draft if it's a post draft
|
||||||
|
*/
|
||||||
|
function ReadDraft($id_draft, $type = 0, $check = true, $load = false)
|
||||||
|
{
|
||||||
|
global $context, $user_info, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// like purell always clean to be sure
|
||||||
|
$id_draft = (int) $id_draft;
|
||||||
|
$type = (int) $type;
|
||||||
|
|
||||||
|
// nothing to read, nothing to do
|
||||||
|
if (empty($id_draft))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// load in this draft from the DB
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT is_sticky, locked, smileys_enabled, icon, body , subject,
|
||||||
|
id_board, id_draft, id_reply, to_list
|
||||||
|
FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_draft = {int:id_draft}' . ($check ? '
|
||||||
|
AND id_member = {int:id_member}' : '') . '
|
||||||
|
AND type = {int:type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : '') . '
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'id_member' => $user_info['id'],
|
||||||
|
'id_draft' => $id_draft,
|
||||||
|
'type' => $type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// no results?
|
||||||
|
if (!$smcFunc['db_num_rows']($request))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// load up the data
|
||||||
|
$draft_info = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Load it up for the templates as well
|
||||||
|
if (!empty($load))
|
||||||
|
{
|
||||||
|
if ($type === 0)
|
||||||
|
{
|
||||||
|
// a standard post draft?
|
||||||
|
$context['sticky'] = !empty($draft_info['is_sticky']) ? $draft_info['is_sticky'] : '';
|
||||||
|
$context['locked'] = !empty($draft_info['locked']) ? $draft_info['locked'] : '';
|
||||||
|
$context['use_smileys'] = !empty($draft_info['smileys_enabled']) ? true : false;
|
||||||
|
$context['icon'] = !empty($draft_info['icon']) ? $draft_info['icon'] : 'xx';
|
||||||
|
$context['message'] = !empty($draft_info['body']) ? str_replace('<br>', "\n", un_htmlspecialchars(stripslashes($draft_info['body']))) : '';
|
||||||
|
$context['subject'] = !empty($draft_info['subject']) ? stripslashes($draft_info['subject']) : '';
|
||||||
|
$context['board'] = !empty($draft_info['id_board']) ? $draft_info['id_board'] : '';
|
||||||
|
$context['id_draft'] = !empty($draft_info['id_draft']) ? $draft_info['id_draft'] : 0;
|
||||||
|
}
|
||||||
|
elseif ($type === 1)
|
||||||
|
{
|
||||||
|
// one of those pm drafts? then set it up like we have an error
|
||||||
|
$_REQUEST['subject'] = !empty($draft_info['subject']) ? stripslashes($draft_info['subject']) : '';
|
||||||
|
$_REQUEST['message'] = !empty($draft_info['body']) ? str_replace('<br>', "\n", un_htmlspecialchars(stripslashes($draft_info['body']))) : '';
|
||||||
|
$_REQUEST['replied_to'] = !empty($draft_info['id_reply']) ? $draft_info['id_reply'] : 0;
|
||||||
|
$context['id_pm_draft'] = !empty($draft_info['id_draft']) ? $draft_info['id_draft'] : 0;
|
||||||
|
$recipients = $smcFunc['json_decode']($draft_info['to_list'], true);
|
||||||
|
|
||||||
|
// make sure we only have integers in this array
|
||||||
|
$recipients['to'] = array_map('intval', $recipients['to']);
|
||||||
|
$recipients['bcc'] = array_map('intval', $recipients['bcc']);
|
||||||
|
|
||||||
|
// pretend we messed up to populate the pm message form
|
||||||
|
messagePostError(array(), array(), $recipients);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $draft_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes one or many drafts from the DB
|
||||||
|
* Validates the drafts are from the user
|
||||||
|
* is supplied an array of drafts will attempt to remove all of them
|
||||||
|
*
|
||||||
|
* @param int $id_draft The ID of the draft to delete
|
||||||
|
* @param boolean $check Whether or not to check that the draft belongs to the current user
|
||||||
|
* @return boolean False if it couldn't be deleted (doesn't return anything otherwise)
|
||||||
|
*/
|
||||||
|
function DeleteDraft($id_draft, $check = true)
|
||||||
|
{
|
||||||
|
global $user_info, $smcFunc;
|
||||||
|
|
||||||
|
// Only a single draft.
|
||||||
|
if (is_numeric($id_draft))
|
||||||
|
$id_draft = array($id_draft);
|
||||||
|
|
||||||
|
// can't delete nothing
|
||||||
|
if (empty($id_draft) || ($check && empty($user_info['id'])))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_draft IN ({array_int:id_draft})' . ($check ? '
|
||||||
|
AND id_member = {int:id_member}' : ''),
|
||||||
|
array(
|
||||||
|
'id_draft' => $id_draft,
|
||||||
|
'id_member' => empty($user_info['id']) ? -1 : $user_info['id'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads in a group of drafts for the user of a given type (0/posts, 1/pm's)
|
||||||
|
* loads a specific draft for forum use if selected.
|
||||||
|
* Used in the posting screens to allow draft selection
|
||||||
|
* Will load a draft if selected is supplied via post
|
||||||
|
*
|
||||||
|
* @param int $member_id ID of the member to show drafts for
|
||||||
|
* @param boolean|integer $topic If $type is 1, this can be set to only load drafts for posts in the specific topic
|
||||||
|
* @param int $draft_type The type of drafts to show - 0 for post drafts, 1 for PM drafts
|
||||||
|
* @return boolean False if the drafts couldn't be loaded, nothing otherwise
|
||||||
|
*/
|
||||||
|
function ShowDrafts($member_id, $topic = false, $draft_type = 0)
|
||||||
|
{
|
||||||
|
global $smcFunc, $scripturl, $context, $txt, $modSettings;
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
if (($draft_type === 0 && empty($context['drafts_save'])) || ($draft_type === 1 && empty($context['drafts_pm_save'])) || empty($member_id))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$context['drafts'] = array();
|
||||||
|
|
||||||
|
// has a specific draft has been selected? Load it up if there is not a message already in the editor
|
||||||
|
if (isset($_REQUEST['id_draft']) && empty($_POST['subject']) && empty($_POST['message']))
|
||||||
|
ReadDraft((int) $_REQUEST['id_draft'], $draft_type, true, true);
|
||||||
|
|
||||||
|
// load the drafts this user has available
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT subject, poster_time, id_board, id_topic, id_draft
|
||||||
|
FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_member = {int:id_member}' . ((!empty($topic) && empty($draft_type)) ? '
|
||||||
|
AND id_topic = {int:id_topic}' : (!empty($topic) ? '
|
||||||
|
AND id_reply = {int:id_topic}' : '')) . '
|
||||||
|
AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : '') . '
|
||||||
|
ORDER BY poster_time DESC',
|
||||||
|
array(
|
||||||
|
'id_member' => $member_id,
|
||||||
|
'id_topic' => (int) $topic,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// add them to the draft array for display
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
if (empty($row['subject']))
|
||||||
|
$row['subject'] = $txt['no_subject'];
|
||||||
|
|
||||||
|
// Post drafts
|
||||||
|
if ($draft_type === 0)
|
||||||
|
{
|
||||||
|
$tmp_subject = shorten_subject(stripslashes($row['subject']), 24);
|
||||||
|
$context['drafts'][] = array(
|
||||||
|
'subject' => censorText($tmp_subject),
|
||||||
|
'poster_time' => timeformat($row['poster_time']),
|
||||||
|
'link' => '<a href="' . $scripturl . '?action=post;board=' . $row['id_board'] . ';' . (!empty($row['id_topic']) ? 'topic=' . $row['id_topic'] . '.0;' : '') . 'id_draft=' . $row['id_draft'] . '">' . $row['subject'] . '</a>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// PM drafts
|
||||||
|
elseif ($draft_type === 1)
|
||||||
|
{
|
||||||
|
$tmp_subject = shorten_subject(stripslashes($row['subject']), 24);
|
||||||
|
$context['drafts'][] = array(
|
||||||
|
'subject' => censorText($tmp_subject),
|
||||||
|
'poster_time' => timeformat($row['poster_time']),
|
||||||
|
'link' => '<a href="' . $scripturl . '?action=pm;sa=send;id_draft=' . $row['id_draft'] . '">' . (!empty($row['subject']) ? $row['subject'] : $txt['drafts_none']) . '</a>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an xml response to an autosave ajax request
|
||||||
|
* provides the id of the draft saved and the time it was saved
|
||||||
|
*
|
||||||
|
* @param int $id_draft
|
||||||
|
*/
|
||||||
|
function XmlDraft($id_draft)
|
||||||
|
{
|
||||||
|
global $txt, $context;
|
||||||
|
|
||||||
|
header('content-type: text/xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
|
||||||
|
|
||||||
|
echo '<?xml version="1.0" encoding="', $context['character_set'], '"?>
|
||||||
|
<drafts>
|
||||||
|
<draft id="', $id_draft, '"><![CDATA[', $txt['draft_saved_on'], ': ', timeformat($context['draft_saved_on']), ']]></draft>
|
||||||
|
</drafts>';
|
||||||
|
|
||||||
|
obExit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show all drafts of a given type by the current user
|
||||||
|
* Uses the showdraft template
|
||||||
|
* Allows for the deleting and loading/editing of drafts
|
||||||
|
*
|
||||||
|
* @param int $memID
|
||||||
|
* @param int $draft_type
|
||||||
|
*/
|
||||||
|
function showProfileDrafts($memID, $draft_type = 0)
|
||||||
|
{
|
||||||
|
global $txt, $scripturl, $modSettings, $context, $smcFunc, $options;
|
||||||
|
|
||||||
|
// Some initial context.
|
||||||
|
$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
|
||||||
|
$context['current_member'] = $memID;
|
||||||
|
|
||||||
|
// If just deleting a draft, do it and then redirect back.
|
||||||
|
if (!empty($_REQUEST['delete']))
|
||||||
|
{
|
||||||
|
checkSession('get');
|
||||||
|
$id_delete = (int) $_REQUEST['delete'];
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_draft = {int:id_draft}
|
||||||
|
AND id_member = {int:id_member}
|
||||||
|
AND type = {int:draft_type}',
|
||||||
|
array(
|
||||||
|
'id_draft' => $id_delete,
|
||||||
|
'id_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
redirectexit('action=profile;u=' . $memID . ';area=showdrafts;start=' . $context['start']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to 10.
|
||||||
|
if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
|
||||||
|
$_REQUEST['viewscount'] = 10;
|
||||||
|
|
||||||
|
// Get the count of applicable drafts on the boards they can (still) see ...
|
||||||
|
// @todo .. should we just let them see their drafts even if they have lost board access ?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}user_drafts AS ud
|
||||||
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = ud.id_board AND {query_see_board})
|
||||||
|
WHERE id_member = {int:id_member}
|
||||||
|
AND type={int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : ''),
|
||||||
|
array(
|
||||||
|
'id_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($msgCount) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
|
||||||
|
$maxIndex = $maxPerPage;
|
||||||
|
|
||||||
|
// Make sure the starting place makes sense and construct our friend the page index.
|
||||||
|
$context['page_index'] = constructPageIndex($scripturl . '?action=profile;u=' . $memID . ';area=showdrafts', $context['start'], $msgCount, $maxIndex);
|
||||||
|
$context['current_page'] = $context['start'] / $maxIndex;
|
||||||
|
|
||||||
|
// Reverse the query if we're past 50% of the pages for better performance.
|
||||||
|
$start = $context['start'];
|
||||||
|
$reverse = $_REQUEST['start'] > $msgCount / 2;
|
||||||
|
if ($reverse)
|
||||||
|
{
|
||||||
|
$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
|
||||||
|
$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find this user's drafts for the boards they can access
|
||||||
|
// @todo ... do we want to do this? If they were able to create a draft, do we remove thier access to said draft if they loose
|
||||||
|
// access to the board or if the topic moves to a board they can not see?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT
|
||||||
|
b.id_board, b.name AS bname,
|
||||||
|
ud.id_member, ud.id_draft, ud.body, ud.smileys_enabled, ud.subject, ud.poster_time, ud.icon, ud.id_topic, ud.locked, ud.is_sticky
|
||||||
|
FROM {db_prefix}user_drafts AS ud
|
||||||
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = ud.id_board AND {query_see_board})
|
||||||
|
WHERE ud.id_member = {int:current_member}
|
||||||
|
AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : '') . '
|
||||||
|
ORDER BY ud.id_draft ' . ($reverse ? 'ASC' : 'DESC') . '
|
||||||
|
LIMIT {int:start}, {int:max}',
|
||||||
|
array(
|
||||||
|
'current_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
'start' => $start,
|
||||||
|
'max' => $maxIndex,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start counting at the number of the first message displayed.
|
||||||
|
$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
|
||||||
|
$context['posts'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
// Censor....
|
||||||
|
if (empty($row['body']))
|
||||||
|
$row['body'] = '';
|
||||||
|
|
||||||
|
$row['subject'] = $smcFunc['htmltrim']($row['subject']);
|
||||||
|
if (empty($row['subject']))
|
||||||
|
$row['subject'] = $txt['no_subject'];
|
||||||
|
|
||||||
|
censorText($row['body']);
|
||||||
|
censorText($row['subject']);
|
||||||
|
|
||||||
|
// BBC-ilize the message.
|
||||||
|
$row['body'] = parse_bbc($row['body'], $row['smileys_enabled'], 'draft' . $row['id_draft']);
|
||||||
|
|
||||||
|
// And the array...
|
||||||
|
$context['drafts'][$counter += $reverse ? -1 : 1] = array(
|
||||||
|
'body' => $row['body'],
|
||||||
|
'counter' => $counter,
|
||||||
|
'board' => array(
|
||||||
|
'name' => $row['bname'],
|
||||||
|
'id' => $row['id_board']
|
||||||
|
),
|
||||||
|
'topic' => array(
|
||||||
|
'id' => $row['id_topic'],
|
||||||
|
'link' => empty($row['id']) ? $row['subject'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>',
|
||||||
|
),
|
||||||
|
'subject' => $row['subject'],
|
||||||
|
'time' => timeformat($row['poster_time']),
|
||||||
|
'timestamp' => $row['poster_time'],
|
||||||
|
'icon' => $row['icon'],
|
||||||
|
'id_draft' => $row['id_draft'],
|
||||||
|
'locked' => $row['locked'],
|
||||||
|
'sticky' => $row['is_sticky'],
|
||||||
|
'quickbuttons' => array(
|
||||||
|
'edit' => array(
|
||||||
|
'label' => $txt['draft_edit'],
|
||||||
|
'href' => $scripturl.'?action=post;'.(empty($row['id_topic']) ? 'board='.$row['id_board'] : 'topic='.$row['id_topic']).'.0;id_draft='.$row['id_draft'],
|
||||||
|
'icon' => 'modify_button'
|
||||||
|
),
|
||||||
|
'delete' => array(
|
||||||
|
'label' => $txt['draft_delete'],
|
||||||
|
'href' => $scripturl.'?action=profile;u='.$context['member']['id'].';area=showdrafts;delete='.$row['id_draft'].';'.$context['session_var'].'='.$context['session_id'],
|
||||||
|
'javascript' => 'data-confirm="'.$txt['draft_remove'].'"',
|
||||||
|
'class' => 'you_sure',
|
||||||
|
'icon' => 'remove_button'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// If the drafts were retrieved in reverse order, get them right again.
|
||||||
|
if ($reverse)
|
||||||
|
$context['drafts'] = array_reverse($context['drafts'], true);
|
||||||
|
|
||||||
|
// Menu tab
|
||||||
|
$context[$context['profile_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['drafts_show'],
|
||||||
|
'description' => $txt['drafts_show_desc'],
|
||||||
|
'icon_class' => 'main_icons drafts'
|
||||||
|
);
|
||||||
|
$context['sub_template'] = 'showDrafts';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show all PM drafts of the current user
|
||||||
|
* Uses the showpmdraft template
|
||||||
|
* Allows for the deleting and loading/editing of drafts
|
||||||
|
*
|
||||||
|
* @param int $memID
|
||||||
|
*/
|
||||||
|
function showPMDrafts($memID = -1)
|
||||||
|
{
|
||||||
|
global $txt, $user_info, $scripturl, $modSettings, $context, $smcFunc, $options;
|
||||||
|
|
||||||
|
// init
|
||||||
|
$draft_type = 1;
|
||||||
|
$context['start'] = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
|
||||||
|
|
||||||
|
// If just deleting a draft, do it and then redirect back.
|
||||||
|
if (!empty($_REQUEST['delete']))
|
||||||
|
{
|
||||||
|
checkSession('get');
|
||||||
|
$id_delete = (int) $_REQUEST['delete'];
|
||||||
|
$start = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] : 0;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_draft = {int:id_draft}
|
||||||
|
AND id_member = {int:id_member}
|
||||||
|
AND type = {int:draft_type}',
|
||||||
|
array(
|
||||||
|
'id_draft' => $id_delete,
|
||||||
|
'id_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// now redirect back to the list
|
||||||
|
redirectexit('action=pm;sa=showpmdrafts;start=' . $start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// perhaps a draft was selected for editing? if so pass this off
|
||||||
|
if (!empty($_REQUEST['id_draft']) && !empty($context['drafts_pm_save']) && $memID == $user_info['id'])
|
||||||
|
{
|
||||||
|
checkSession('get');
|
||||||
|
$id_draft = (int) $_REQUEST['id_draft'];
|
||||||
|
redirectexit('action=pm;sa=send;id_draft=' . $id_draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to 10.
|
||||||
|
if (empty($_REQUEST['viewscount']) || !is_numeric($_REQUEST['viewscount']))
|
||||||
|
$_REQUEST['viewscount'] = 10;
|
||||||
|
|
||||||
|
// Get the count of applicable drafts
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}user_drafts
|
||||||
|
WHERE id_member = {int:id_member}
|
||||||
|
AND type={int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : ''),
|
||||||
|
array(
|
||||||
|
'id_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($msgCount) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$maxPerPage = empty($modSettings['disableCustomPerPage']) && !empty($options['messages_per_page']) ? $options['messages_per_page'] : $modSettings['defaultMaxMessages'];
|
||||||
|
$maxIndex = $maxPerPage;
|
||||||
|
|
||||||
|
// Make sure the starting place makes sense and construct our friend the page index.
|
||||||
|
$context['page_index'] = constructPageIndex($scripturl . '?action=pm;sa=showpmdrafts', $context['start'], $msgCount, $maxIndex);
|
||||||
|
$context['current_page'] = $context['start'] / $maxIndex;
|
||||||
|
|
||||||
|
// Reverse the query if we're past 50% of the total for better performance.
|
||||||
|
$start = $context['start'];
|
||||||
|
$reverse = $_REQUEST['start'] > $msgCount / 2;
|
||||||
|
if ($reverse)
|
||||||
|
{
|
||||||
|
$maxIndex = $msgCount < $context['start'] + $maxPerPage + 1 && $msgCount > $context['start'] ? $msgCount - $context['start'] : $maxPerPage;
|
||||||
|
$start = $msgCount < $context['start'] + $maxPerPage + 1 || $msgCount < $context['start'] + $maxPerPage ? 0 : $msgCount - $context['start'] - $maxPerPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load in this user's PM drafts
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT
|
||||||
|
ud.id_member, ud.id_draft, ud.body, ud.subject, ud.poster_time, ud.id_reply, ud.to_list
|
||||||
|
FROM {db_prefix}user_drafts AS ud
|
||||||
|
WHERE ud.id_member = {int:current_member}
|
||||||
|
AND type = {int:draft_type}' . (!empty($modSettings['drafts_keep_days']) ? '
|
||||||
|
AND poster_time > {int:time}' : '') . '
|
||||||
|
ORDER BY ud.id_draft ' . ($reverse ? 'ASC' : 'DESC') . '
|
||||||
|
LIMIT {int:start}, {int:max}',
|
||||||
|
array(
|
||||||
|
'current_member' => $memID,
|
||||||
|
'draft_type' => $draft_type,
|
||||||
|
'time' => (!empty($modSettings['drafts_keep_days']) ? (time() - ($modSettings['drafts_keep_days'] * 86400)) : 0),
|
||||||
|
'start' => $start,
|
||||||
|
'max' => $maxIndex,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Start counting at the number of the first message displayed.
|
||||||
|
$counter = $reverse ? $context['start'] + $maxIndex + 1 : $context['start'];
|
||||||
|
$context['posts'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
// Censor....
|
||||||
|
if (empty($row['body']))
|
||||||
|
$row['body'] = '';
|
||||||
|
|
||||||
|
$row['subject'] = $smcFunc['htmltrim']($row['subject']);
|
||||||
|
if (empty($row['subject']))
|
||||||
|
$row['subject'] = $txt['no_subject'];
|
||||||
|
|
||||||
|
censorText($row['body']);
|
||||||
|
censorText($row['subject']);
|
||||||
|
|
||||||
|
// BBC-ilize the message.
|
||||||
|
$row['body'] = parse_bbc($row['body'], true, 'draft' . $row['id_draft']);
|
||||||
|
|
||||||
|
// Have they provide who this will go to?
|
||||||
|
$recipients = array(
|
||||||
|
'to' => array(),
|
||||||
|
'bcc' => array(),
|
||||||
|
);
|
||||||
|
$recipient_ids = (!empty($row['to_list'])) ? $smcFunc['json_decode']($row['to_list'], true) : array();
|
||||||
|
|
||||||
|
// @todo ... this is a bit ugly since it runs an extra query for every message, do we want this?
|
||||||
|
// at least its only for draft PM's and only the user can see them ... so not heavily used .. still
|
||||||
|
if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc']))
|
||||||
|
{
|
||||||
|
$recipient_ids['to'] = array_map('intval', $recipient_ids['to']);
|
||||||
|
$recipient_ids['bcc'] = array_map('intval', $recipient_ids['bcc']);
|
||||||
|
$allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']);
|
||||||
|
|
||||||
|
$request_2 = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_member, real_name
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE id_member IN ({array_int:member_list})',
|
||||||
|
array(
|
||||||
|
'member_list' => $allRecipients,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($result = $smcFunc['db_fetch_assoc']($request_2))
|
||||||
|
{
|
||||||
|
$recipientType = in_array($result['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to';
|
||||||
|
$recipients[$recipientType][] = $result['real_name'];
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the items to the array for template use
|
||||||
|
$context['drafts'][$counter += $reverse ? -1 : 1] = array(
|
||||||
|
'body' => $row['body'],
|
||||||
|
'counter' => $counter,
|
||||||
|
'subject' => $row['subject'],
|
||||||
|
'time' => timeformat($row['poster_time']),
|
||||||
|
'timestamp' => $row['poster_time'],
|
||||||
|
'id_draft' => $row['id_draft'],
|
||||||
|
'recipients' => $recipients,
|
||||||
|
'age' => floor((time() - $row['poster_time']) / 86400),
|
||||||
|
'remaining' => (!empty($modSettings['drafts_keep_days']) ? floor($modSettings['drafts_keep_days'] - ((time() - $row['poster_time']) / 86400)) : 0),
|
||||||
|
'quickbuttons' => array(
|
||||||
|
'edit' => array(
|
||||||
|
'label' => $txt['draft_edit'],
|
||||||
|
'href' => $scripturl.'?action=pm;sa=showpmdrafts;id_draft='.$row['id_draft'].';'.$context['session_var'].'='.$context['session_id'],
|
||||||
|
'icon' => 'modify_button'
|
||||||
|
),
|
||||||
|
'delete' => array(
|
||||||
|
'label' => $txt['draft_delete'],
|
||||||
|
'href' => $scripturl.'?action=pm;sa=showpmdrafts;delete='.$row['id_draft'].';'.$context['session_var'].'='.$context['session_id'],
|
||||||
|
'javascript' => 'data-confirm="'.$txt['draft_remove'].'?"',
|
||||||
|
'class' => 'you_sure',
|
||||||
|
'icon' => 'remove_button'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// if the drafts were retrieved in reverse order, then put them in the right order again.
|
||||||
|
if ($reverse)
|
||||||
|
$context['drafts'] = array_reverse($context['drafts'], true);
|
||||||
|
|
||||||
|
// off to the template we go
|
||||||
|
$context['page_title'] = $txt['drafts'];
|
||||||
|
$context['sub_template'] = 'showPMDrafts';
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=pm;sa=showpmdrafts',
|
||||||
|
'name' => $txt['drafts'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
586
Sources/Errors.php
Normal file
|
@ -0,0 +1,586 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of this file is... errors. (hard to guess, I guess?) It takes
|
||||||
|
* care of logging, error messages, error handling, database errors, and
|
||||||
|
* error log administration.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error, if the error logging is enabled.
|
||||||
|
* filename and line should be __FILE__ and __LINE__, respectively.
|
||||||
|
* Example use:
|
||||||
|
* die(log_error($msg));
|
||||||
|
*
|
||||||
|
* @param string $error_message The message to log
|
||||||
|
* @param string|bool $error_type The type of error
|
||||||
|
* @param string $file The name of the file where this error occurred
|
||||||
|
* @param int $line The line where the error occurred
|
||||||
|
* @return string The message that was logged
|
||||||
|
*/
|
||||||
|
function log_error($error_message, $error_type = 'general', $file = null, $line = null)
|
||||||
|
{
|
||||||
|
global $modSettings, $sc, $user_info, $smcFunc, $scripturl, $last_error, $context, $db_show_debug;
|
||||||
|
static $tried_hook = false;
|
||||||
|
static $error_call = 0;
|
||||||
|
|
||||||
|
$error_call++;
|
||||||
|
|
||||||
|
// Collect a backtrace
|
||||||
|
if (!isset($db_show_debug) || $db_show_debug === false)
|
||||||
|
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
else
|
||||||
|
$backtrace = debug_backtrace();
|
||||||
|
|
||||||
|
// are we in a loop?
|
||||||
|
if ($error_call > 2)
|
||||||
|
{
|
||||||
|
var_dump($backtrace);
|
||||||
|
die('Error loop.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if error logging is actually on.
|
||||||
|
if (empty($modSettings['enableErrorLogging']))
|
||||||
|
return $error_message;
|
||||||
|
|
||||||
|
// Basically, htmlspecialchars it minus &. (for entities!)
|
||||||
|
$error_message = strtr($error_message, array('<' => '<', '>' => '>', '"' => '"'));
|
||||||
|
$error_message = strtr($error_message, array('<br />' => '<br>', '<br>' => '<br>', '<b>' => '<strong>', '</b>' => '</strong>', "\n" => '<br>'));
|
||||||
|
|
||||||
|
// Add a file and line to the error message?
|
||||||
|
// Don't use the actual txt entries for file and line but instead use %1$s for file and %2$s for line
|
||||||
|
if ($file == null)
|
||||||
|
$file = '';
|
||||||
|
else
|
||||||
|
// Windows style slashes don't play well, lets convert them to the unix style.
|
||||||
|
$file = str_replace('\\', '/', $file);
|
||||||
|
|
||||||
|
if ($line == null)
|
||||||
|
$line = 0;
|
||||||
|
else
|
||||||
|
$line = (int) $line;
|
||||||
|
|
||||||
|
// Just in case there's no id_member or IP set yet.
|
||||||
|
if (empty($user_info['id']))
|
||||||
|
$user_info['id'] = 0;
|
||||||
|
if (empty($user_info['ip']))
|
||||||
|
$user_info['ip'] = '';
|
||||||
|
|
||||||
|
// Find the best query string we can...
|
||||||
|
$query_string = empty($_SERVER['QUERY_STRING']) ? (empty($_SERVER['REQUEST_URL']) ? '' : str_replace($scripturl, '', $_SERVER['REQUEST_URL'])) : $_SERVER['QUERY_STRING'];
|
||||||
|
|
||||||
|
// Don't log the session hash in the url twice, it's a waste.
|
||||||
|
if (!empty($smcFunc['htmlspecialchars']))
|
||||||
|
$query_string = $smcFunc['htmlspecialchars']((SMF == 'SSI' || SMF == 'BACKGROUND' ? '' : '?') . preg_replace(array('~;sesc=[^&;]+~', '~' . session_name() . '=' . session_id() . '[&;]~'), array(';sesc', ''), $query_string));
|
||||||
|
|
||||||
|
// Just so we know what board error messages are from.
|
||||||
|
if (isset($_POST['board']) && !isset($_GET['board']))
|
||||||
|
$query_string .= ($query_string == '' ? 'board=' : ';board=') . $_POST['board'];
|
||||||
|
|
||||||
|
// What types of categories do we have?
|
||||||
|
$known_error_types = array(
|
||||||
|
'general',
|
||||||
|
'critical',
|
||||||
|
'database',
|
||||||
|
'undefined_vars',
|
||||||
|
'user',
|
||||||
|
'ban',
|
||||||
|
'template',
|
||||||
|
'debug',
|
||||||
|
'cron',
|
||||||
|
'paidsubs',
|
||||||
|
'backup',
|
||||||
|
'login',
|
||||||
|
);
|
||||||
|
|
||||||
|
// This prevents us from infinite looping if the hook or call produces an error.
|
||||||
|
$other_error_types = array();
|
||||||
|
if (empty($tried_hook))
|
||||||
|
{
|
||||||
|
$tried_hook = true;
|
||||||
|
// Allow the hook to change the error_type and know about the error.
|
||||||
|
call_integration_hook('integrate_error_types', array(&$other_error_types, &$error_type, $error_message, $file, $line));
|
||||||
|
$known_error_types += $other_error_types;
|
||||||
|
}
|
||||||
|
// Make sure the category that was specified is a valid one
|
||||||
|
$error_type = in_array($error_type, $known_error_types) && $error_type !== true ? $error_type : 'general';
|
||||||
|
|
||||||
|
// leave out the call to log_error
|
||||||
|
array_splice($backtrace, 0, 1);
|
||||||
|
$backtrace = !empty($smcFunc['json_encode']) ? $smcFunc['json_encode']($backtrace) : json_encode($backtrace);
|
||||||
|
|
||||||
|
// Don't log the same error countless times, as we can get in a cycle of depression...
|
||||||
|
$error_info = array($user_info['id'], time(), $user_info['ip'], $query_string, $error_message, (string) $sc, $error_type, $file, $line, $backtrace);
|
||||||
|
if (empty($last_error) || $last_error != $error_info)
|
||||||
|
{
|
||||||
|
// Insert the error into the database.
|
||||||
|
$smcFunc['db_error_insert']($error_info);
|
||||||
|
$last_error = $error_info;
|
||||||
|
|
||||||
|
// Get an error count, if necessary
|
||||||
|
if (!isset($context['num_errors']))
|
||||||
|
{
|
||||||
|
$query = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}log_errors',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
|
||||||
|
$smcFunc['db_free_result']($query);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$context['num_errors']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset error call
|
||||||
|
$error_call = 0;
|
||||||
|
|
||||||
|
// Return the message to make things simpler.
|
||||||
|
return $error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An irrecoverable error. This function stops execution and displays an error message.
|
||||||
|
* It logs the error message if $log is specified.
|
||||||
|
*
|
||||||
|
* @param string $error The error message
|
||||||
|
* @param string|bool $log = 'general' What type of error to log this as (false to not log it))
|
||||||
|
* @param int $status The HTTP status code associated with this error
|
||||||
|
*/
|
||||||
|
function fatal_error($error, $log = 'general', $status = 500)
|
||||||
|
{
|
||||||
|
global $txt;
|
||||||
|
|
||||||
|
// Send the appropriate HTTP status header - set this to 0 or false if you don't want to send one at all
|
||||||
|
if (!empty($status))
|
||||||
|
send_http_status($status);
|
||||||
|
|
||||||
|
// We don't have $txt yet, but that's okay...
|
||||||
|
if (empty($txt))
|
||||||
|
die($error);
|
||||||
|
|
||||||
|
log_error_online($error);
|
||||||
|
setup_fatal_error_context($log ? log_error($error, $log) : $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a fatal error with a message stored in the language file.
|
||||||
|
*
|
||||||
|
* This function stops execution and displays an error message by key.
|
||||||
|
* - uses the string with the error_message_key key.
|
||||||
|
* - logs the error in the forum's default language while displaying the error
|
||||||
|
* message in the user's language.
|
||||||
|
* - uses Errors language file and applies the $sprintf information if specified.
|
||||||
|
* - the information is logged if log is specified.
|
||||||
|
*
|
||||||
|
* @param string $error The error message
|
||||||
|
* @param string|false $log The type of error, or false to not log it
|
||||||
|
* @param array $sprintf An array of data to be sprintf()'d into the specified message
|
||||||
|
* @param int $status = false The HTTP status code associated with this error
|
||||||
|
*/
|
||||||
|
function fatal_lang_error($error, $log = 'general', $sprintf = array(), $status = 403)
|
||||||
|
{
|
||||||
|
global $txt, $language, $user_info, $context;
|
||||||
|
static $fatal_error_called = false;
|
||||||
|
|
||||||
|
// Ensure this is an array.
|
||||||
|
$sprintf = (array) $sprintf;
|
||||||
|
|
||||||
|
// Send the status header - set this to 0 or false if you don't want to send one at all
|
||||||
|
if (!empty($status))
|
||||||
|
send_http_status($status);
|
||||||
|
|
||||||
|
// Try to load a theme if we don't have one.
|
||||||
|
if (empty($context['theme_loaded']) && empty($fatal_error_called))
|
||||||
|
{
|
||||||
|
$fatal_error_called = true;
|
||||||
|
loadTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no theme stuff we can't have the language file...
|
||||||
|
if (empty($context['theme_loaded']))
|
||||||
|
die($error);
|
||||||
|
|
||||||
|
$reload_lang_file = true;
|
||||||
|
// Log the error in the forum's language, but don't waste the time if we aren't logging
|
||||||
|
if ($log)
|
||||||
|
{
|
||||||
|
loadLanguage('Errors', $language);
|
||||||
|
$reload_lang_file = $language != $user_info['language'];
|
||||||
|
if (empty($txt[$error]))
|
||||||
|
$error_message = $error;
|
||||||
|
else
|
||||||
|
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);
|
||||||
|
log_error($error_message, $log);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the language file, only if it needs to be reloaded
|
||||||
|
if ($reload_lang_file)
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
$error_message = empty($sprintf) ? $txt[$error] : vsprintf($txt[$error], $sprintf);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error_online($error, $sprintf);
|
||||||
|
setup_fatal_error_context($error_message, $error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for standard error messages, standard PHP error handler replacement.
|
||||||
|
* It dies with fatal_error() if the error_level matches with error_reporting.
|
||||||
|
*
|
||||||
|
* @param int $error_level A pre-defined error-handling constant (see {@link https://php.net/errorfunc.constants})
|
||||||
|
* @param string $error_string The error message
|
||||||
|
* @param string $file The file where the error occurred
|
||||||
|
* @param int $line The line where the error occurred
|
||||||
|
*/
|
||||||
|
function smf_error_handler($error_level, $error_string, $file, $line)
|
||||||
|
{
|
||||||
|
global $settings, $modSettings, $db_show_debug;
|
||||||
|
|
||||||
|
// Error was suppressed with the @-operator.
|
||||||
|
if (error_reporting() == 0 || error_reporting() == (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Ignore errors that should should not be logged.
|
||||||
|
$error_match = error_reporting() & $error_level;
|
||||||
|
if (empty($error_match) || empty($modSettings['enableErrorLogging']))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (strpos($file, 'eval()') !== false && !empty($settings['current_include_filename']))
|
||||||
|
{
|
||||||
|
$array = debug_backtrace();
|
||||||
|
$count = count($array);
|
||||||
|
for ($i = 0; $i < $count; $i++)
|
||||||
|
{
|
||||||
|
if ($array[$i]['function'] != 'loadSubTemplate')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// This is a bug in PHP, with eval, it seems!
|
||||||
|
if (empty($array[$i]['args']))
|
||||||
|
$i++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($array[$i]) && !empty($array[$i]['args']))
|
||||||
|
$file = realpath($settings['current_include_filename']) . ' (' . $array[$i]['args'][0] . ' sub template - eval?)';
|
||||||
|
else
|
||||||
|
$file = realpath($settings['current_include_filename']) . ' (eval?)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($db_show_debug) && $db_show_debug === true)
|
||||||
|
{
|
||||||
|
// Commonly, undefined indexes will occur inside attributes; try to show them anyway!
|
||||||
|
if ($error_level % 255 != E_ERROR)
|
||||||
|
{
|
||||||
|
$temporary = ob_get_contents();
|
||||||
|
if (substr($temporary, -2) == '="')
|
||||||
|
echo '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugging! This should look like a PHP error message.
|
||||||
|
echo '<br>
|
||||||
|
<strong>', $error_level % 255 == E_ERROR ? 'Error' : ($error_level % 255 == E_WARNING ? 'Warning' : 'Notice'), '</strong>: ', $error_string, ' in <strong>', $file, '</strong> on line <strong>', $line, '</strong><br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$error_type = stripos($error_string, 'undefined') !== false ? 'undefined_vars' : 'general';
|
||||||
|
|
||||||
|
$message = log_error($error_level . ': ' . $error_string, $error_type, $file, $line);
|
||||||
|
|
||||||
|
// Let's give integrations a chance to ouput a bit differently
|
||||||
|
call_integration_hook('integrate_output_error', array($message, $error_type, $error_level, $file, $line));
|
||||||
|
|
||||||
|
// Dying on these errors only causes MORE problems (blank pages!)
|
||||||
|
if ($file == 'Unknown')
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this is an E_ERROR or E_USER_ERROR.... die. Violently so.
|
||||||
|
if ($error_level % 255 == E_ERROR)
|
||||||
|
obExit(false);
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this is an E_ERROR, E_USER_ERROR, E_WARNING, or E_USER_WARNING.... die. Violently so.
|
||||||
|
if ($error_level % 255 == E_ERROR || $error_level % 255 == E_WARNING)
|
||||||
|
fatal_error(allowedTo('admin_forum') ? $message : $error_string, false);
|
||||||
|
|
||||||
|
// We should NEVER get to this point. Any fatal error MUST quit, or very bad things can happen.
|
||||||
|
if ($error_level % 255 == E_ERROR)
|
||||||
|
die('No direct access...');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is called by {@link fatal_error()} and {@link fatal_lang_error()}.
|
||||||
|
*
|
||||||
|
* @uses template_fatal_error()
|
||||||
|
*
|
||||||
|
* @param string $error_message The error message
|
||||||
|
* @param string $error_code An error code
|
||||||
|
* @return void|false Normally doesn't return anything, but returns false if a recursive loop is detected
|
||||||
|
*/
|
||||||
|
function setup_fatal_error_context($error_message, $error_code = null)
|
||||||
|
{
|
||||||
|
global $context, $txt, $ssi_on_error_method;
|
||||||
|
static $level = 0;
|
||||||
|
|
||||||
|
// Attempt to prevent a recursive loop.
|
||||||
|
++$level;
|
||||||
|
if ($level > 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Maybe they came from dlattach or similar?
|
||||||
|
if (SMF != 'SSI' && SMF != 'BACKGROUND' && empty($context['theme_loaded']))
|
||||||
|
loadTheme();
|
||||||
|
|
||||||
|
// Don't bother indexing errors mate...
|
||||||
|
$context['robot_no_index'] = true;
|
||||||
|
|
||||||
|
if (!isset($context['error_title']))
|
||||||
|
$context['error_title'] = $txt['error_occured'];
|
||||||
|
$context['error_message'] = isset($context['error_message']) ? $context['error_message'] : $error_message;
|
||||||
|
|
||||||
|
$context['error_code'] = isset($error_code) ? 'id="' . $error_code . '" ' : '';
|
||||||
|
|
||||||
|
$context['error_link'] = isset($context['error_link']) ? $context['error_link'] : 'javascript:document.location=document.referrer';
|
||||||
|
|
||||||
|
if (empty($context['page_title']))
|
||||||
|
$context['page_title'] = $context['error_title'];
|
||||||
|
|
||||||
|
loadTemplate('Errors');
|
||||||
|
$context['sub_template'] = 'fatal_error';
|
||||||
|
|
||||||
|
// If this is SSI, what do they want us to do?
|
||||||
|
if (SMF == 'SSI')
|
||||||
|
{
|
||||||
|
if (!empty($ssi_on_error_method) && $ssi_on_error_method !== true && is_callable($ssi_on_error_method))
|
||||||
|
$ssi_on_error_method();
|
||||||
|
elseif (empty($ssi_on_error_method) || $ssi_on_error_method !== true)
|
||||||
|
loadSubTemplate('fatal_error');
|
||||||
|
|
||||||
|
// No layers?
|
||||||
|
if (empty($ssi_on_error_method) || $ssi_on_error_method !== true)
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
// Alternatively from the cron call?
|
||||||
|
elseif (SMF == 'BACKGROUND')
|
||||||
|
{
|
||||||
|
// We can't rely on even having language files available.
|
||||||
|
if (defined('FROM_CLI') && FROM_CLI)
|
||||||
|
echo 'cron error: ', $context['error_message'];
|
||||||
|
else
|
||||||
|
echo 'An error occurred. More information may be available in your logs.';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want whatever for the header, and a footer. (footer includes sub template!)
|
||||||
|
obExit(null, true, false, true);
|
||||||
|
|
||||||
|
/* DO NOT IGNORE:
|
||||||
|
If you are creating a bridge to SMF or modifying this function, you MUST
|
||||||
|
make ABSOLUTELY SURE that this function quits and DOES NOT RETURN TO NORMAL
|
||||||
|
PROGRAM FLOW. Otherwise, security error messages will not be shown, and
|
||||||
|
your forum will be in a very easily hackable state.
|
||||||
|
*/
|
||||||
|
trigger_error('No direct access...', E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message for the (full block) maintenance mode.
|
||||||
|
* It shows a complete page independent of language files or themes.
|
||||||
|
* It is used only if $maintenance = 2 in Settings.php.
|
||||||
|
* It stops further execution of the script.
|
||||||
|
*/
|
||||||
|
function display_maintenance_message()
|
||||||
|
{
|
||||||
|
global $maintenance, $mtitle, $mmessage;
|
||||||
|
|
||||||
|
set_fatal_error_headers();
|
||||||
|
|
||||||
|
if (!empty($maintenance))
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<title>', $mtitle, '</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>', $mtitle, '</h3>
|
||||||
|
', $mmessage, '
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error message for the connection problems.
|
||||||
|
* It shows a complete page independent of language files or themes.
|
||||||
|
* It is used only if there's no way to connect to the database.
|
||||||
|
* It stops further execution of the script.
|
||||||
|
*/
|
||||||
|
function display_db_error()
|
||||||
|
{
|
||||||
|
global $mbname, $modSettings, $maintenance;
|
||||||
|
global $db_connection, $webmaster_email, $db_last_error, $db_error_send, $smcFunc, $sourcedir, $cache_enable;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Logging.php');
|
||||||
|
set_fatal_error_headers();
|
||||||
|
|
||||||
|
// For our purposes, we're gonna want this on if at all possible.
|
||||||
|
$cache_enable = '1';
|
||||||
|
|
||||||
|
if (($temp = cache_get_data('db_last_error', 600)) !== null)
|
||||||
|
$db_last_error = max($db_last_error, $temp);
|
||||||
|
|
||||||
|
if ($db_last_error < time() - 3600 * 24 * 3 && empty($maintenance) && !empty($db_error_send))
|
||||||
|
{
|
||||||
|
// Avoid writing to the Settings.php file if at all possible; use shared memory instead.
|
||||||
|
cache_put_data('db_last_error', time(), 600);
|
||||||
|
if (($temp = cache_get_data('db_last_error', 600)) === null)
|
||||||
|
logLastDatabaseError();
|
||||||
|
|
||||||
|
// Language files aren't loaded yet :(.
|
||||||
|
$db_error = @$smcFunc['db_error']($db_connection);
|
||||||
|
@mail($webmaster_email, $mbname . ': SMF Database Error!', 'There has been a problem with the database!' . ($db_error == '' ? '' : "\n" . $smcFunc['db_title'] . ' reported:' . "\n" . $db_error) . "\n\n" . 'This is a notice email to let you know that SMF could not connect to the database, contact your host if this continues.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// What to do? Language files haven't and can't be loaded yet...
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<title>Connection Problems</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Connection Problems</h3>
|
||||||
|
Sorry, SMF was unable to connect to the database. This may be caused by the server being busy. Please try again later.
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error message for load average blocking problems.
|
||||||
|
* It shows a complete page independent of language files or themes.
|
||||||
|
* It is used only if the load averages are too high to continue execution.
|
||||||
|
* It stops further execution of the script.
|
||||||
|
*/
|
||||||
|
function display_loadavg_error()
|
||||||
|
{
|
||||||
|
// If this is a load average problem, display an appropriate message (but we still don't have language files!)
|
||||||
|
|
||||||
|
set_fatal_error_headers();
|
||||||
|
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<title>Temporarily Unavailable</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Temporarily Unavailable</h3>
|
||||||
|
Due to high stress on the server the forum is temporarily unavailable. Please try again later.
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small utility function for fatal error pages.
|
||||||
|
* Used by {@link display_db_error()}, {@link display_loadavg_error()},
|
||||||
|
* {@link display_maintenance_message()}
|
||||||
|
*/
|
||||||
|
function set_fatal_error_headers()
|
||||||
|
{
|
||||||
|
if (headers_sent())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Don't cache this page!
|
||||||
|
header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||||
|
header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||||
|
header('cache-control: no-cache');
|
||||||
|
|
||||||
|
// Send the right error codes.
|
||||||
|
send_http_status(503, 'Service Temporarily Unavailable');
|
||||||
|
header('status: 503 Service Temporarily Unavailable');
|
||||||
|
header('retry-after: 3600');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small utility function for fatal error pages.
|
||||||
|
* Used by fatal_error(), fatal_lang_error()
|
||||||
|
*
|
||||||
|
* @param string $error The error
|
||||||
|
* @param array $sprintf An array of data to be sprintf()'d into the specified message
|
||||||
|
*/
|
||||||
|
function log_error_online($error, $sprintf = array())
|
||||||
|
{
|
||||||
|
global $smcFunc, $user_info, $modSettings;
|
||||||
|
|
||||||
|
// Don't bother if Who's Online is disabled.
|
||||||
|
if (empty($modSettings['who_enabled']))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Maybe they came from SSI or similar where sessions are not recorded?
|
||||||
|
if (SMF == 'SSI' || SMF == 'BACKGROUND')
|
||||||
|
return;
|
||||||
|
|
||||||
|
$session_id = !empty($user_info['is_guest']) ? 'ip' . $user_info['ip'] : session_id();
|
||||||
|
|
||||||
|
// First, we have to get the online log, because we need to break apart the serialized string.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT url
|
||||||
|
FROM {db_prefix}log_online
|
||||||
|
WHERE session = {string:session}',
|
||||||
|
array(
|
||||||
|
'session' => $session_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($smcFunc['db_num_rows']($request) != 0)
|
||||||
|
{
|
||||||
|
// If this happened very early on in SMF startup, $smcFunc may not fully be defined.
|
||||||
|
if (!isset($smcFunc['json_decode']))
|
||||||
|
{
|
||||||
|
$smcFunc['json_decode'] = 'smf_json_decode';
|
||||||
|
$smcFunc['json_encode'] = 'json_encode';
|
||||||
|
}
|
||||||
|
|
||||||
|
list ($url) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$url = $smcFunc['json_decode']($url, true);
|
||||||
|
$url['error'] = $error;
|
||||||
|
// Url field got a max length of 1024 in db
|
||||||
|
if (strlen($url['error']) > 500)
|
||||||
|
$url['error'] = substr($url['error'], 0, 500);
|
||||||
|
|
||||||
|
if (!empty($sprintf))
|
||||||
|
$url['error_params'] = $sprintf;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}log_online
|
||||||
|
SET url = {string:url}
|
||||||
|
WHERE session = {string:session}',
|
||||||
|
array(
|
||||||
|
'url' => $smcFunc['json_encode']($url),
|
||||||
|
'session' => $session_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
785
Sources/Groups.php
Normal file
|
@ -0,0 +1,785 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file currently just shows group info, and allows certain priviledged members to add/remove members.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point function, permission checks, admin bars, etc.
|
||||||
|
* It allows moderators and users to access the group showing functions.
|
||||||
|
* It handles permission checks, and puts the moderation bar on as required.
|
||||||
|
*/
|
||||||
|
function Groups()
|
||||||
|
{
|
||||||
|
global $context, $txt, $scripturl, $sourcedir, $user_info;
|
||||||
|
|
||||||
|
// The sub-actions that we can do. Format "Function Name, Mod Bar Index if appropriate".
|
||||||
|
$subActions = array(
|
||||||
|
'index' => array('GroupList', 'view_groups'),
|
||||||
|
'members' => array('MembergroupMembers', 'view_groups'),
|
||||||
|
'requests' => array('GroupRequests', 'group_requests'),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_manage_groups', array(&$subActions));
|
||||||
|
|
||||||
|
// Default to sub action 'index'.
|
||||||
|
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'index';
|
||||||
|
|
||||||
|
// Get the template stuff up and running.
|
||||||
|
loadLanguage('ManageMembers');
|
||||||
|
loadLanguage('ModerationCenter');
|
||||||
|
loadTemplate('ManageMembergroups');
|
||||||
|
|
||||||
|
// If we can see the moderation center, and this has a mod bar entry, add the mod center bar.
|
||||||
|
if (allowedTo('access_mod_center') || $user_info['mod_cache']['bq'] != '0=1' || $user_info['mod_cache']['gq'] != '0=1' || allowedTo('manage_membergroups'))
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/ModerationCenter.php');
|
||||||
|
$_GET['area'] = $_REQUEST['sa'] == 'requests' ? 'groups' : 'viewgroups';
|
||||||
|
ModerationMain(true);
|
||||||
|
}
|
||||||
|
// Otherwise add something to the link tree, for normal people.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isAllowedTo('view_mlist');
|
||||||
|
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=groups',
|
||||||
|
'name' => $txt['groups'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the actual function.
|
||||||
|
call_helper($subActions[$_REQUEST['sa']][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This very simply lists the groups, nothing snazy.
|
||||||
|
*/
|
||||||
|
function GroupList()
|
||||||
|
{
|
||||||
|
global $txt, $context, $sourcedir, $scripturl;
|
||||||
|
|
||||||
|
$context['page_title'] = $txt['viewing_groups'];
|
||||||
|
|
||||||
|
// Making a list is not hard with this beauty.
|
||||||
|
require_once($sourcedir . '/Subs-List.php');
|
||||||
|
|
||||||
|
// Use the standard templates for showing this.
|
||||||
|
$listOptions = array(
|
||||||
|
'id' => 'group_lists',
|
||||||
|
'title' => $context['page_title'],
|
||||||
|
'base_href' => $scripturl . '?action=moderate;area=viewgroups;sa=view',
|
||||||
|
'default_sort_col' => 'group',
|
||||||
|
'get_items' => array(
|
||||||
|
'file' => $sourcedir . '/Subs-Membergroups.php',
|
||||||
|
'function' => 'list_getMembergroups',
|
||||||
|
'params' => array(
|
||||||
|
'regular',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'columns' => array(
|
||||||
|
'group' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['name'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData) use ($scripturl)
|
||||||
|
{
|
||||||
|
// Since the moderator group has no explicit members, no link is needed.
|
||||||
|
if ($rowData['id_group'] == 3)
|
||||||
|
$group_name = $rowData['group_name'];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$color_style = empty($rowData['online_color']) ? '' : sprintf(' style="color: %1$s;"', $rowData['online_color']);
|
||||||
|
|
||||||
|
if (allowedTo('manage_membergroups'))
|
||||||
|
{
|
||||||
|
$group_name = sprintf('<a href="%1$s?action=admin;area=membergroups;sa=members;group=%2$d"%3$s>%4$s</a>', $scripturl, $rowData['id_group'], $color_style, $rowData['group_name']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$group_name = sprintf('<a href="%1$s?action=groups;sa=members;group=%2$d"%3$s>%4$s</a>', $scripturl, $rowData['id_group'], $color_style, $rowData['group_name']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a help option for moderator and administrator.
|
||||||
|
if ($rowData['id_group'] == 1)
|
||||||
|
$group_name .= sprintf(' (<a href="%1$s?action=helpadmin;help=membergroup_administrator" onclick="return reqOverlayDiv(this.href);">?</a>)', $scripturl);
|
||||||
|
elseif ($rowData['id_group'] == 3)
|
||||||
|
$group_name .= sprintf(' (<a href="%1$s?action=helpadmin;help=membergroup_moderator" onclick="return reqOverlayDiv(this.href);">?</a>)', $scripturl);
|
||||||
|
|
||||||
|
return $group_name;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'CASE WHEN mg.id_group < 4 THEN mg.id_group ELSE 4 END, mg.group_name',
|
||||||
|
'reverse' => 'CASE WHEN mg.id_group < 4 THEN mg.id_group ELSE 4 END, mg.group_name DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'icons' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['membergroups_icons'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'db' => 'icons',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'mg.icons',
|
||||||
|
'reverse' => 'mg.icons DESC',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'moderators' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['moderators'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($group) use ($txt)
|
||||||
|
{
|
||||||
|
return empty($group['moderators']) ? '<em>' . $txt['membergroups_new_copy_none'] . '</em>' : implode(', ', $group['moderators']);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'members' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['membergroups_members_top'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData) use ($txt)
|
||||||
|
{
|
||||||
|
// No explicit members for the moderator group.
|
||||||
|
return $rowData['id_group'] == 3 ? $txt['membergroups_guests_na'] : comma_format($rowData['num_members']);
|
||||||
|
},
|
||||||
|
'class' => 'centercol',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'CASE WHEN mg.id_group < 4 THEN mg.id_group ELSE 4 END, 1',
|
||||||
|
'reverse' => 'CASE WHEN mg.id_group < 4 THEN mg.id_group ELSE 4 END, 1 DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the request list.
|
||||||
|
createList($listOptions);
|
||||||
|
|
||||||
|
$context['sub_template'] = 'show_list';
|
||||||
|
$context['default_list'] = 'group_lists';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display members of a group, and allow adding of members to a group. Silly function name though ;)
|
||||||
|
* It can be called from ManageMembergroups if it needs templating within the admin environment.
|
||||||
|
* It shows a list of members that are part of a given membergroup.
|
||||||
|
* It is called by ?action=moderate;area=viewgroups;sa=members;group=x
|
||||||
|
* It requires the manage_membergroups permission.
|
||||||
|
* It allows to add and remove members from the selected membergroup.
|
||||||
|
* It allows sorting on several columns.
|
||||||
|
* It redirects to itself.
|
||||||
|
*
|
||||||
|
* @uses template_group_members()
|
||||||
|
* @todo: use createList
|
||||||
|
*/
|
||||||
|
function MembergroupMembers()
|
||||||
|
{
|
||||||
|
global $txt, $scripturl, $context, $modSettings, $sourcedir, $user_info, $settings, $smcFunc;
|
||||||
|
|
||||||
|
$_REQUEST['group'] = isset($_REQUEST['group']) ? (int) $_REQUEST['group'] : 0;
|
||||||
|
|
||||||
|
// No browsing of guests, membergroup 0 or moderators.
|
||||||
|
if (in_array($_REQUEST['group'], array(-1, 0, 3)))
|
||||||
|
fatal_lang_error('membergroup_does_not_exist', false);
|
||||||
|
|
||||||
|
// Load up the group details.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_group AS id, group_name AS name, CASE WHEN min_posts = {int:min_posts} THEN 1 ELSE 0 END AS assignable, hidden, online_color,
|
||||||
|
icons, description, CASE WHEN min_posts != {int:min_posts} THEN 1 ELSE 0 END AS is_post_group, group_type
|
||||||
|
FROM {db_prefix}membergroups
|
||||||
|
WHERE id_group = {int:id_group}
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'min_posts' => -1,
|
||||||
|
'id_group' => $_REQUEST['group'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Doesn't exist?
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 0)
|
||||||
|
fatal_lang_error('membergroup_does_not_exist', false);
|
||||||
|
$context['group'] = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Fix the membergroup icons.
|
||||||
|
$context['group']['icons'] = explode('#', $context['group']['icons']);
|
||||||
|
$context['group']['icons'] = !empty($context['group']['icons'][0]) && !empty($context['group']['icons'][1]) ? str_repeat('<img src="' . $settings['images_url'] . '/membericons/' . $context['group']['icons'][1] . '" alt="*">', $context['group']['icons'][0]) : '';
|
||||||
|
$context['group']['can_moderate'] = allowedTo('manage_membergroups') && (allowedTo('admin_forum') || $context['group']['group_type'] != 1);
|
||||||
|
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=groups;sa=members;group=' . $context['group']['id'],
|
||||||
|
'name' => $context['group']['name'],
|
||||||
|
);
|
||||||
|
$context['can_send_email'] = allowedTo('moderate_forum');
|
||||||
|
|
||||||
|
// Load all the group moderators, for fun.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT mem.id_member, mem.real_name
|
||||||
|
FROM {db_prefix}group_moderators AS mods
|
||||||
|
INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
|
||||||
|
WHERE mods.id_group = {int:id_group}',
|
||||||
|
array(
|
||||||
|
'id_group' => $_REQUEST['group'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['group']['moderators'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$context['group']['moderators'][] = array(
|
||||||
|
'id' => $row['id_member'],
|
||||||
|
'name' => $row['real_name']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($user_info['id'] == $row['id_member'] && $context['group']['group_type'] != 1)
|
||||||
|
$context['group']['can_moderate'] = true;
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// If this group is hidden then it can only "exists" if the user can moderate it!
|
||||||
|
if ($context['group']['hidden'] && !$context['group']['can_moderate'])
|
||||||
|
fatal_lang_error('membergroup_does_not_exist', false);
|
||||||
|
|
||||||
|
// You can only assign membership if you are the moderator and/or can manage groups!
|
||||||
|
if (!$context['group']['can_moderate'])
|
||||||
|
$context['group']['assignable'] = 0;
|
||||||
|
// Non-admins cannot assign admins.
|
||||||
|
elseif ($context['group']['id'] == 1 && !allowedTo('admin_forum'))
|
||||||
|
$context['group']['assignable'] = 0;
|
||||||
|
|
||||||
|
// Removing member from group?
|
||||||
|
if (isset($_POST['remove']) && !empty($_REQUEST['rem']) && is_array($_REQUEST['rem']) && $context['group']['assignable'])
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
validateToken('mod-mgm');
|
||||||
|
|
||||||
|
// Only proven admins can remove admins.
|
||||||
|
if ($context['group']['id'] == 1)
|
||||||
|
validateSession();
|
||||||
|
|
||||||
|
// Make sure we're dealing with integers only.
|
||||||
|
foreach ($_REQUEST['rem'] as $key => $group)
|
||||||
|
$_REQUEST['rem'][$key] = (int) $group;
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Membergroups.php');
|
||||||
|
removeMembersFromGroups($_REQUEST['rem'], $_REQUEST['group'], true);
|
||||||
|
}
|
||||||
|
// Must be adding new members to the group...
|
||||||
|
elseif (isset($_REQUEST['add']) && (!empty($_REQUEST['toAdd']) || !empty($_REQUEST['member_add'])) && $context['group']['assignable'])
|
||||||
|
{
|
||||||
|
// Demand an admin password before adding new admins -- every time, no matter what.
|
||||||
|
if ($context['group']['id'] == 1)
|
||||||
|
validateSession('admin', true);
|
||||||
|
|
||||||
|
checkSession();
|
||||||
|
validateToken('mod-mgm');
|
||||||
|
|
||||||
|
$member_query = array();
|
||||||
|
$member_parameters = array();
|
||||||
|
|
||||||
|
// Get all the members to be added... taking into account names can be quoted ;)
|
||||||
|
$_REQUEST['toAdd'] = strtr($smcFunc['htmlspecialchars']($_REQUEST['toAdd'], ENT_QUOTES), array('"' => '"'));
|
||||||
|
preg_match_all('~"([^"]+)"~', $_REQUEST['toAdd'], $matches);
|
||||||
|
$member_names = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_REQUEST['toAdd']))));
|
||||||
|
|
||||||
|
foreach ($member_names as $index => $member_name)
|
||||||
|
{
|
||||||
|
$member_names[$index] = trim($smcFunc['strtolower']($member_names[$index]));
|
||||||
|
|
||||||
|
if (strlen($member_names[$index]) == 0)
|
||||||
|
unset($member_names[$index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any passed by ID?
|
||||||
|
$member_ids = array();
|
||||||
|
if (!empty($_REQUEST['member_add']))
|
||||||
|
foreach ($_REQUEST['member_add'] as $id)
|
||||||
|
if ($id > 0)
|
||||||
|
$member_ids[] = (int) $id;
|
||||||
|
|
||||||
|
// Construct the query pelements.
|
||||||
|
if (!empty($member_ids))
|
||||||
|
{
|
||||||
|
$member_query[] = 'id_member IN ({array_int:member_ids})';
|
||||||
|
$member_parameters['member_ids'] = $member_ids;
|
||||||
|
}
|
||||||
|
if (!empty($member_names))
|
||||||
|
{
|
||||||
|
$member_query[] = 'LOWER(member_name) IN ({array_string:member_names})';
|
||||||
|
$member_query[] = 'LOWER(real_name) IN ({array_string:member_names})';
|
||||||
|
$member_parameters['member_names'] = $member_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
$members = array();
|
||||||
|
if (!empty($member_query))
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_member
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE (' . implode(' OR ', $member_query) . ')
|
||||||
|
AND id_group != {int:id_group}
|
||||||
|
AND FIND_IN_SET({int:id_group}, additional_groups) = 0',
|
||||||
|
array_merge($member_parameters, array(
|
||||||
|
'id_group' => $_REQUEST['group'],
|
||||||
|
))
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$members[] = $row['id_member'];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo Add $_POST['additional'] to templates!
|
||||||
|
|
||||||
|
// Do the updates...
|
||||||
|
if (!empty($members))
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-Membergroups.php');
|
||||||
|
addMembersToGroup($members, $_REQUEST['group'], isset($_POST['additional']) || $context['group']['hidden'] ? 'only_additional' : 'auto', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort out the sorting!
|
||||||
|
$sort_methods = array(
|
||||||
|
'name' => 'real_name',
|
||||||
|
'email' => 'email_address',
|
||||||
|
'active' => 'last_login',
|
||||||
|
'registered' => 'date_registered',
|
||||||
|
'posts' => 'posts',
|
||||||
|
);
|
||||||
|
|
||||||
|
// They didn't pick one, default to by name..
|
||||||
|
if (!isset($_REQUEST['sort']) || !isset($sort_methods[$_REQUEST['sort']]))
|
||||||
|
{
|
||||||
|
$context['sort_by'] = 'name';
|
||||||
|
$querySort = 'real_name';
|
||||||
|
}
|
||||||
|
// Otherwise default to ascending.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['sort_by'] = $_REQUEST['sort'];
|
||||||
|
$querySort = $sort_methods[$_REQUEST['sort']];
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['sort_direction'] = isset($_REQUEST['desc']) ? 'down' : 'up';
|
||||||
|
|
||||||
|
// The where on the query is interesting. Non-moderators should only see people who are in this group as primary.
|
||||||
|
if ($context['group']['can_moderate'])
|
||||||
|
$where = $context['group']['is_post_group'] ? 'id_post_group = {int:group}' : 'id_group = {int:group} OR FIND_IN_SET({int:group}, additional_groups) != 0';
|
||||||
|
else
|
||||||
|
$where = $context['group']['is_post_group'] ? 'id_post_group = {int:group}' : 'id_group = {int:group}';
|
||||||
|
|
||||||
|
// Count members of the group.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE ' . $where,
|
||||||
|
array(
|
||||||
|
'group' => $_REQUEST['group'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($context['total_members']) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Create the page index.
|
||||||
|
$context['page_index'] = constructPageIndex($scripturl . '?action=' . ($context['group']['can_moderate'] ? 'moderate;area=viewgroups' : 'groups') . ';sa=members;group=' . $_REQUEST['group'] . ';sort=' . $context['sort_by'] . (isset($_REQUEST['desc']) ? ';desc' : ''), $_REQUEST['start'], $context['total_members'], $modSettings['defaultMaxMembers']);
|
||||||
|
$context['total_members'] = comma_format($context['total_members']);
|
||||||
|
$context['start'] = $_REQUEST['start'];
|
||||||
|
$context['can_moderate_forum'] = allowedTo('moderate_forum');
|
||||||
|
|
||||||
|
// Load up all members of this group.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_member, member_name, real_name, email_address, member_ip, date_registered, last_login,
|
||||||
|
posts, is_activated, real_name
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE ' . $where . '
|
||||||
|
ORDER BY ' . $querySort . ' ' . ($context['sort_direction'] == 'down' ? 'DESC' : 'ASC') . '
|
||||||
|
LIMIT {int:start}, {int:max}',
|
||||||
|
array(
|
||||||
|
'group' => $_REQUEST['group'],
|
||||||
|
'start' => $context['start'],
|
||||||
|
'max' => $modSettings['defaultMaxMembers'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['members'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$row['member_ip'] = inet_dtop($row['member_ip']);
|
||||||
|
$last_online = empty($row['last_login']) ? $txt['never'] : timeformat($row['last_login']);
|
||||||
|
|
||||||
|
// Italicize the online note if they aren't activated.
|
||||||
|
if ($row['is_activated'] % 10 != 1)
|
||||||
|
$last_online = '<em title="' . $txt['not_activated'] . '">' . $last_online . '</em>';
|
||||||
|
|
||||||
|
$context['members'][] = array(
|
||||||
|
'id' => $row['id_member'],
|
||||||
|
'name' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
|
||||||
|
'email' => $row['email_address'],
|
||||||
|
'ip' => '<a href="' . $scripturl . '?action=trackip;searchip=' . $row['member_ip'] . '">' . $row['member_ip'] . '</a>',
|
||||||
|
'registered' => timeformat($row['date_registered']),
|
||||||
|
'last_online' => $last_online,
|
||||||
|
'posts' => comma_format($row['posts']),
|
||||||
|
'is_activated' => $row['is_activated'] % 10 == 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Select the template.
|
||||||
|
$context['sub_template'] = 'group_members';
|
||||||
|
$context['page_title'] = $txt['membergroups_members_title'] . ': ' . $context['group']['name'];
|
||||||
|
createToken('mod-mgm');
|
||||||
|
|
||||||
|
if ($context['group']['assignable'])
|
||||||
|
loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show and manage all group requests.
|
||||||
|
*/
|
||||||
|
function GroupRequests()
|
||||||
|
{
|
||||||
|
global $txt, $context, $scripturl, $user_info, $sourcedir, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// Set up the template stuff...
|
||||||
|
$context['page_title'] = $txt['mc_group_requests'];
|
||||||
|
$context['sub_template'] = 'show_list';
|
||||||
|
|
||||||
|
// Verify we can be here.
|
||||||
|
if ($user_info['mod_cache']['gq'] == '0=1')
|
||||||
|
isAllowedTo('manage_membergroups');
|
||||||
|
|
||||||
|
// Normally, we act normally...
|
||||||
|
$where = ($user_info['mod_cache']['gq'] == '1=1' || $user_info['mod_cache']['gq'] == '0=1' ? $user_info['mod_cache']['gq'] : 'lgr.' . $user_info['mod_cache']['gq']);
|
||||||
|
|
||||||
|
if (isset($_GET['closed']))
|
||||||
|
$where .= ' AND lgr.status != {int:status_open}';
|
||||||
|
else
|
||||||
|
$where .= ' AND lgr.status = {int:status_open}';
|
||||||
|
|
||||||
|
$where_parameters = array(
|
||||||
|
'status_open' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// We've submitted?
|
||||||
|
if (isset($_POST[$context['session_var']]) && !empty($_POST['groupr']) && !empty($_POST['req_action']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
validateToken('mod-gr');
|
||||||
|
|
||||||
|
// Clean the values.
|
||||||
|
foreach ($_POST['groupr'] as $k => $request)
|
||||||
|
$_POST['groupr'][$k] = (int) $request;
|
||||||
|
|
||||||
|
$log_changes = array();
|
||||||
|
|
||||||
|
// If we are giving a reason (And why shouldn't we?), then we don't actually do much.
|
||||||
|
if ($_POST['req_action'] == 'reason')
|
||||||
|
{
|
||||||
|
// Different sub template...
|
||||||
|
$context['sub_template'] = 'group_request_reason';
|
||||||
|
// And a limitation. We don't care that the page number bit makes no sense, as we don't need it!
|
||||||
|
$where .= ' AND lgr.id_request IN ({array_int:request_ids})';
|
||||||
|
$where_parameters['request_ids'] = $_POST['groupr'];
|
||||||
|
|
||||||
|
$context['group_requests'] = list_getGroupRequests(0, $modSettings['defaultMaxListItems'], 'lgr.id_request', $where, $where_parameters);
|
||||||
|
|
||||||
|
// Need to make another token for this.
|
||||||
|
createToken('mod-gr');
|
||||||
|
|
||||||
|
// Let obExit etc sort things out.
|
||||||
|
obExit();
|
||||||
|
}
|
||||||
|
// Otherwise we do something!
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT lgr.id_request
|
||||||
|
FROM {db_prefix}log_group_requests AS lgr
|
||||||
|
WHERE ' . $where . '
|
||||||
|
AND lgr.id_request IN ({array_int:request_list})',
|
||||||
|
array(
|
||||||
|
'request_list' => $_POST['groupr'],
|
||||||
|
'status_open' => 0,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$request_list = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
if (!isset($log_changes[$row['id_request']]))
|
||||||
|
$log_changes[$row['id_request']] = array(
|
||||||
|
'id_request' => $row['id_request'],
|
||||||
|
'status' => $_POST['req_action'] == 'approve' ? 1 : 2, // 1 = approved, 2 = rejected
|
||||||
|
'id_member_acted' => $user_info['id'],
|
||||||
|
'member_name_acted' => $user_info['name'],
|
||||||
|
'time_acted' => time(),
|
||||||
|
'act_reason' => $_POST['req_action'] != 'approve' && !empty($_POST['groupreason']) && !empty($_POST['groupreason'][$row['id_request']]) ? $smcFunc['htmlspecialchars']($_POST['groupreason'][$row['id_request']], ENT_QUOTES) : '',
|
||||||
|
);
|
||||||
|
$request_list[] = $row['id_request'];
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Add a background task to handle notifying people of this request
|
||||||
|
$data = $smcFunc['json_encode'](array('member_id' => $user_info['id'], 'member_ip' => $user_info['ip'], 'request_list' => $request_list, 'status' => $_POST['req_action'], 'reason' => isset($_POST['groupreason']) ? $_POST['groupreason'] : '', 'time' => time()));
|
||||||
|
$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
|
||||||
|
array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
|
||||||
|
array('$sourcedir/tasks/GroupAct-Notify.php', 'GroupAct_Notify_Background', $data, 0), array()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Some changes to log?
|
||||||
|
if (!empty($log_changes))
|
||||||
|
{
|
||||||
|
foreach ($log_changes as $id_request => $details)
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}log_group_requests
|
||||||
|
SET status = {int:status},
|
||||||
|
id_member_acted = {int:id_member_acted},
|
||||||
|
member_name_acted = {string:member_name_acted},
|
||||||
|
time_acted = {int:time_acted},
|
||||||
|
act_reason = {string:act_reason}
|
||||||
|
WHERE id_request = {int:id_request}',
|
||||||
|
$details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're going to want this for making our list.
|
||||||
|
require_once($sourcedir . '/Subs-List.php');
|
||||||
|
|
||||||
|
// This is all the information required for a group listing.
|
||||||
|
$listOptions = array(
|
||||||
|
'id' => 'group_request_list',
|
||||||
|
'width' => '100%',
|
||||||
|
'items_per_page' => $modSettings['defaultMaxListItems'],
|
||||||
|
'no_items_label' => $txt['mc_groupr_none_found'],
|
||||||
|
'base_href' => $scripturl . '?action=groups;sa=requests',
|
||||||
|
'default_sort_col' => 'member',
|
||||||
|
'get_items' => array(
|
||||||
|
'function' => 'list_getGroupRequests',
|
||||||
|
'params' => array(
|
||||||
|
$where,
|
||||||
|
$where_parameters,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'get_count' => array(
|
||||||
|
'function' => 'list_getGroupRequestCount',
|
||||||
|
'params' => array(
|
||||||
|
$where,
|
||||||
|
$where_parameters,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'columns' => array(
|
||||||
|
'member' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mc_groupr_member'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'db' => 'member_link',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'mem.member_name',
|
||||||
|
'reverse' => 'mem.member_name DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'group' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mc_groupr_group'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'db' => 'group_link',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'mg.group_name',
|
||||||
|
'reverse' => 'mg.group_name DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'reason' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mc_groupr_reason'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'db' => 'reason',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'date' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['date'],
|
||||||
|
'style' => 'width: 18%; white-space:nowrap;',
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'db' => 'time_submitted',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'action' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
|
||||||
|
'style' => 'width: 4%;',
|
||||||
|
'class' => 'centercol',
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'sprintf' => array(
|
||||||
|
'format' => '<input type="checkbox" name="groupr[]" value="%1$d">',
|
||||||
|
'params' => array(
|
||||||
|
'id' => false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'class' => 'centercol',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'form' => array(
|
||||||
|
'href' => $scripturl . '?action=groups;sa=requests',
|
||||||
|
'include_sort' => true,
|
||||||
|
'include_start' => true,
|
||||||
|
'hidden_fields' => array(
|
||||||
|
$context['session_var'] => $context['session_id'],
|
||||||
|
),
|
||||||
|
'token' => 'mod-gr',
|
||||||
|
),
|
||||||
|
'additional_rows' => array(
|
||||||
|
array(
|
||||||
|
'position' => 'bottom_of_list',
|
||||||
|
'value' => '
|
||||||
|
<select id="req_action" name="req_action" onchange="if (this.value != 0 && (this.value == \'reason\' || confirm(\'' . $txt['mc_groupr_warning'] . '\'))) this.form.submit();">
|
||||||
|
<option value="0">' . $txt['with_selected'] . ':</option>
|
||||||
|
<option value="0" disabled>---------------------</option>
|
||||||
|
<option value="approve">' . $txt['mc_groupr_approve'] . '</option>
|
||||||
|
<option value="reject">' . $txt['mc_groupr_reject'] . '</option>
|
||||||
|
<option value="reason">' . $txt['mc_groupr_reject_w_reason'] . '</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" name="go" value="' . $txt['go'] . '" onclick="var sel = document.getElementById(\'req_action\'); if (sel.value != 0 && sel.value != \'reason\' && !confirm(\'' . $txt['mc_groupr_warning'] . '\')) return false;" class="button">',
|
||||||
|
'class' => 'floatright',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($_GET['closed']))
|
||||||
|
{
|
||||||
|
// Closed requests don't require interaction.
|
||||||
|
unset($listOptions['columns']['action'], $listOptions['form'], $listOptions['additional_rows'][0]);
|
||||||
|
$listOptions['base_href'] .= 'closed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the request list.
|
||||||
|
createToken('mod-gr');
|
||||||
|
createList($listOptions);
|
||||||
|
|
||||||
|
$context['default_list'] = 'group_request_list';
|
||||||
|
$context[$context['moderation_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['mc_group_requests'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for createList().
|
||||||
|
*
|
||||||
|
* @param string $where The WHERE clause for the query
|
||||||
|
* @param array $where_parameters The parameters for the WHERE clause
|
||||||
|
* @return int The number of group requests
|
||||||
|
*/
|
||||||
|
function list_getGroupRequestCount($where, $where_parameters)
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}log_group_requests AS lgr
|
||||||
|
WHERE ' . $where,
|
||||||
|
array_merge($where_parameters, array(
|
||||||
|
))
|
||||||
|
);
|
||||||
|
list ($totalRequests) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $totalRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function for createList()
|
||||||
|
*
|
||||||
|
* @param int $start The result to start with
|
||||||
|
* @param int $items_per_page The number of items per page
|
||||||
|
* @param string $sort An SQL sort expression (column/direction)
|
||||||
|
* @param string $where Data for the WHERE clause
|
||||||
|
* @param string $where_parameters Parameter values to be inserted into the WHERE clause
|
||||||
|
* @return array An array of group requests
|
||||||
|
* Each group request has:
|
||||||
|
* 'id'
|
||||||
|
* 'member_link'
|
||||||
|
* 'group_link'
|
||||||
|
* 'reason'
|
||||||
|
* 'time_submitted'
|
||||||
|
*/
|
||||||
|
function list_getGroupRequests($start, $items_per_page, $sort, $where, $where_parameters)
|
||||||
|
{
|
||||||
|
global $smcFunc, $scripturl, $txt;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT
|
||||||
|
lgr.id_request, lgr.id_member, lgr.id_group, lgr.time_applied, lgr.reason,
|
||||||
|
lgr.status, lgr.id_member_acted, lgr.member_name_acted, lgr.time_acted, lgr.act_reason,
|
||||||
|
mem.member_name, mg.group_name, mg.online_color, mem.real_name
|
||||||
|
FROM {db_prefix}log_group_requests AS lgr
|
||||||
|
INNER JOIN {db_prefix}members AS mem ON (mem.id_member = lgr.id_member)
|
||||||
|
INNER JOIN {db_prefix}membergroups AS mg ON (mg.id_group = lgr.id_group)
|
||||||
|
WHERE ' . $where . '
|
||||||
|
ORDER BY {raw:sort}
|
||||||
|
LIMIT {int:start}, {int:max}',
|
||||||
|
array_merge($where_parameters, array(
|
||||||
|
'sort' => $sort,
|
||||||
|
'start' => $start,
|
||||||
|
'max' => $items_per_page,
|
||||||
|
))
|
||||||
|
);
|
||||||
|
$group_requests = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
if (empty($row['reason']))
|
||||||
|
$reason = '<em>(' . $txt['mc_groupr_no_reason'] . ')</em>';
|
||||||
|
else
|
||||||
|
$reason = censorText($row['reason']);
|
||||||
|
|
||||||
|
if (isset($_GET['closed']))
|
||||||
|
{
|
||||||
|
if ($row['status'] == 1)
|
||||||
|
$reason .= '<br><br><strong>' . $txt['mc_groupr_approved'] . '</strong>';
|
||||||
|
elseif ($row['status'] == 2)
|
||||||
|
$reason .= '<br><br><strong>' . $txt['mc_groupr_rejected'] . '</strong>';
|
||||||
|
|
||||||
|
$reason .= ' (' . timeformat($row['time_acted']) . ')';
|
||||||
|
if (!empty($row['act_reason']))
|
||||||
|
$reason .= '<br><br>' . censorText($row['act_reason']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$group_requests[] = array(
|
||||||
|
'id' => $row['id_request'],
|
||||||
|
'member_link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name'] . '</a>',
|
||||||
|
'group_link' => '<span style="color: ' . $row['online_color'] . '">' . $row['group_name'] . '</span>',
|
||||||
|
'reason' => $reason,
|
||||||
|
'time_submitted' => timeformat($row['time_applied']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $group_requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
147
Sources/Help.php
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file has the important job of taking care of help messages and the help center.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the user help ;).
|
||||||
|
* It loads information needed for the help section.
|
||||||
|
* It is accessed by ?action=help.
|
||||||
|
*
|
||||||
|
* Uses Help template and Manual language file.
|
||||||
|
*/
|
||||||
|
function ShowHelp()
|
||||||
|
{
|
||||||
|
loadTemplate('Help');
|
||||||
|
loadLanguage('Manual');
|
||||||
|
|
||||||
|
$subActions = array(
|
||||||
|
'index' => 'HelpIndex',
|
||||||
|
);
|
||||||
|
|
||||||
|
// CRUD $subActions as needed.
|
||||||
|
call_integration_hook('integrate_manage_help', array(&$subActions));
|
||||||
|
|
||||||
|
$sa = isset($_GET['sa'], $subActions[$_GET['sa']]) ? $_GET['sa'] : 'index';
|
||||||
|
call_helper($subActions[$sa]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main page for the Help section
|
||||||
|
*/
|
||||||
|
function HelpIndex()
|
||||||
|
{
|
||||||
|
global $scripturl, $context, $txt;
|
||||||
|
|
||||||
|
// We need to know where our wiki is.
|
||||||
|
$context['wiki_url'] = 'https://wiki.simplemachines.org/smf';
|
||||||
|
$context['wiki_prefix'] = 'SMF2.1:';
|
||||||
|
|
||||||
|
$context['canonical_url'] = $scripturl . '?action=help';
|
||||||
|
|
||||||
|
// Sections were are going to link...
|
||||||
|
$context['manual_sections'] = array(
|
||||||
|
'registering' => 'Registering',
|
||||||
|
'logging_in' => 'Logging_In',
|
||||||
|
'profile' => 'Profile',
|
||||||
|
'search' => 'Search',
|
||||||
|
'posting' => 'Posting',
|
||||||
|
'bbc' => 'Bulletin_board_code',
|
||||||
|
'personal_messages' => 'Personal_messages',
|
||||||
|
'memberlist' => 'Memberlist',
|
||||||
|
'calendar' => 'Calendar',
|
||||||
|
'features' => 'Features',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the link tree.
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=help',
|
||||||
|
'name' => $txt['help'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Lastly, some minor template stuff.
|
||||||
|
$context['page_title'] = $txt['manual_smf_user_help'];
|
||||||
|
$context['sub_template'] = 'manual';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show some of the more detailed help to give the admin an idea...
|
||||||
|
* It shows a popup for administrative or user help.
|
||||||
|
* It uses the help parameter to decide what string to display and where to get
|
||||||
|
* the string from. ($helptxt or $txt?)
|
||||||
|
* It is accessed via ?action=helpadmin;help=?.
|
||||||
|
*
|
||||||
|
* Uses ManagePermissions language file, if the help starts with permissionhelp.
|
||||||
|
* @uses template_popup() with no layers.
|
||||||
|
*/
|
||||||
|
function ShowAdminHelp()
|
||||||
|
{
|
||||||
|
global $txt, $helptxt, $context, $scripturl, $boarddir, $boardurl;
|
||||||
|
|
||||||
|
if (!isset($_GET['help']) || !is_string($_GET['help']))
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
if (!isset($helptxt))
|
||||||
|
$helptxt = array();
|
||||||
|
|
||||||
|
// Load the admin help language file and template.
|
||||||
|
loadLanguage('Help');
|
||||||
|
|
||||||
|
// Permission specific help?
|
||||||
|
if (isset($_GET['help']) && substr($_GET['help'], 0, 14) == 'permissionhelp')
|
||||||
|
loadLanguage('ManagePermissions');
|
||||||
|
|
||||||
|
loadTemplate('Help');
|
||||||
|
|
||||||
|
// Allow mods to load their own language file here
|
||||||
|
call_integration_hook('integrate_helpadmin');
|
||||||
|
|
||||||
|
// What help string should be used?
|
||||||
|
if (isset($helptxt[$_GET['help']]))
|
||||||
|
$context['help_text'] = $helptxt[$_GET['help']];
|
||||||
|
elseif (isset($txt[$_GET['help']]))
|
||||||
|
$context['help_text'] = $txt[$_GET['help']];
|
||||||
|
else
|
||||||
|
fatal_lang_error('not_found', false, array(), 404);
|
||||||
|
|
||||||
|
switch ($_GET['help']) {
|
||||||
|
case 'cal_short_months':
|
||||||
|
$context['help_text'] = sprintf($context['help_text'], $txt['months_short'][1], $txt['months_titles'][1]);
|
||||||
|
break;
|
||||||
|
case 'cal_short_days':
|
||||||
|
$context['help_text'] = sprintf($context['help_text'], $txt['days_short'][1], $txt['days'][1]);
|
||||||
|
break;
|
||||||
|
case 'cron_is_real_cron':
|
||||||
|
$context['help_text'] = sprintf($context['help_text'], allowedTo('admin_forum') ? $boarddir : '[' . $txt['hidden'] . ']', $boardurl);
|
||||||
|
break;
|
||||||
|
case 'queryless_urls':
|
||||||
|
$context['help_text'] = sprintf($context['help_text'], (isset($_SERVER['SERVER_SOFTWARE']) && (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false) ? $helptxt['queryless_urls_supported'] : $helptxt['queryless_urls_unsupported']));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does this text contain a link that we should fill in?
|
||||||
|
if (preg_match('~%([0-9]+\$)?s\?~', $context['help_text'], $match))
|
||||||
|
$context['help_text'] = sprintf($context['help_text'], $scripturl, $context['session_id'], $context['session_var']);
|
||||||
|
|
||||||
|
// Set the page title to something relevant.
|
||||||
|
$context['page_title'] = $context['forum_name'] . ' - ' . $txt['help'];
|
||||||
|
|
||||||
|
// Don't show any template layers, just the popup sub template.
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
$context['sub_template'] = 'popup';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
715
Sources/Likes.php
Normal file
|
@ -0,0 +1,715 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains liking posts and displaying the list of who liked a post.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Likes
|
||||||
|
*/
|
||||||
|
class Likes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var boolean Know if a request comes from an ajax call or not, depends on $_GET['js'] been set.
|
||||||
|
*/
|
||||||
|
protected $_js = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string If filled, its value will contain a string matching a key on a language var $txt[$this->_error]
|
||||||
|
*/
|
||||||
|
protected $_error = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The unique type to like, needs to be unique and it needs to be no longer than 6 characters, only numbers and letters are allowed.
|
||||||
|
*/
|
||||||
|
protected $_type = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string A generic string used if you need to pass any extra info. It gets set via $_GET['extra'].
|
||||||
|
*/
|
||||||
|
protected $_extra = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer a valid ID to identify your like content.
|
||||||
|
*/
|
||||||
|
protected $_content = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer The number of times your content has been liked.
|
||||||
|
*/
|
||||||
|
protected $_numLikes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var boolean If the current user has already liked this content.
|
||||||
|
*/
|
||||||
|
protected $_alreadyLiked = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $_validLikes mostly used for external integration, needs to be filled as an array with the following keys:
|
||||||
|
* => 'can_like' boolean|string whether or not the current user can actually like your content.
|
||||||
|
* for can_like: Return a boolean true if the user can, otherwise return a string, the string will be used as key in a regular $txt language error var. The code assumes you already loaded your language file. If no value is returned or the $txt var isn't set, the code will use a generic error message.
|
||||||
|
* => 'redirect' string To add support for non JS users, It is highly encouraged to set a valid URL to redirect the user to, if you don't provide any, the code will redirect the user to the main page. The code only performs a light check to see if the redirect is valid so be extra careful while building it.
|
||||||
|
* => 'type' string 6 letters or numbers. The unique identifier for your content, the code doesn't check for duplicate entries, if there are 2 or more exact hook calls, the code will take the first registered one so make sure you provide a unique identifier. Must match with what you sent in $_GET['ltype'].
|
||||||
|
* => 'flush_cache' boolean this is optional, it tells the code to reset your like content's cache entry after a new entry has been inserted.
|
||||||
|
* => 'callback' callable optional, useful if you don't want to issue a separate hook for updating your data, it is called immediately after the data was inserted or deleted and before the actual hook. Uses call_helper(); so the same format for your function/method can be applied here.
|
||||||
|
* => 'json' boolean optional defaults to false, if true the Like class will return a json object as response instead of HTML.
|
||||||
|
*/
|
||||||
|
protected $_validLikes = array(
|
||||||
|
'can_like' => false,
|
||||||
|
'redirect' => '',
|
||||||
|
'type' => '',
|
||||||
|
'flush_cache' => '',
|
||||||
|
'callback' => false,
|
||||||
|
'json' => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array The current user info ($user_info).
|
||||||
|
*/
|
||||||
|
protected $_user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer The topic ID, used for liking messages.
|
||||||
|
*/
|
||||||
|
protected $_idTopic = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var boolean to know if response(); will be executed as normal. If this is set to false it indicates the method already solved its own way to send back a response.
|
||||||
|
*/
|
||||||
|
protected $_setResponse = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::__construct()
|
||||||
|
*
|
||||||
|
* Sets the basic data needed for the rest of the process.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
global $db_show_debug;
|
||||||
|
|
||||||
|
$this->_type = isset($_GET['ltype']) ? $_GET['ltype'] : '';
|
||||||
|
$this->_content = isset($_GET['like']) ? (int) $_GET['like'] : 0;
|
||||||
|
$this->_js = isset($_GET['js']) ? true : false;
|
||||||
|
$this->_sa = isset($_GET['sa']) ? $_GET['sa'] : 'like';
|
||||||
|
$this->_extra = isset($_GET['extra']) ? $_GET['extra'] : false;
|
||||||
|
|
||||||
|
// We do not want to output debug information here.
|
||||||
|
if ($this->_js)
|
||||||
|
$db_show_debug = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::call()
|
||||||
|
*
|
||||||
|
* The main handler. Verifies permissions (whether the user can see the content in question), dispatch different method for different sub-actions.
|
||||||
|
* Accessed from index.php?action=likes
|
||||||
|
*/
|
||||||
|
public function call()
|
||||||
|
{
|
||||||
|
global $context;
|
||||||
|
|
||||||
|
$this->_user = $context['user'];
|
||||||
|
|
||||||
|
// Make sure the user can see and like your content.
|
||||||
|
$this->check();
|
||||||
|
|
||||||
|
$subActions = array(
|
||||||
|
'like',
|
||||||
|
'view',
|
||||||
|
'delete',
|
||||||
|
'insert',
|
||||||
|
'_count',
|
||||||
|
);
|
||||||
|
|
||||||
|
// So at this point, whatever type of like the user supplied and the item of content in question,
|
||||||
|
// we know it exists, now we need to figure out what we're doing with that.
|
||||||
|
if (in_array($this->_sa, $subActions) && !is_string($this->_error))
|
||||||
|
{
|
||||||
|
// To avoid ambiguity, turn the property to a normal var.
|
||||||
|
$call = $this->_sa;
|
||||||
|
|
||||||
|
// Guest can only view likes.
|
||||||
|
if ($call != 'view')
|
||||||
|
is_not_guest();
|
||||||
|
|
||||||
|
checkSession('get');
|
||||||
|
|
||||||
|
// Call the appropriate method.
|
||||||
|
$this->$call();
|
||||||
|
}
|
||||||
|
|
||||||
|
// else An error message.
|
||||||
|
$this->response();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::get()
|
||||||
|
*
|
||||||
|
* A simple getter for all protected properties.
|
||||||
|
* Accessed from index.php?action=likes
|
||||||
|
*
|
||||||
|
* @param string $property The name of the property to get.
|
||||||
|
* @return mixed Either return the property or false if there isn't a property with that name.
|
||||||
|
*/
|
||||||
|
public function get($property = '')
|
||||||
|
{
|
||||||
|
// All properties inside Likes are protected, thus, an underscore is used.
|
||||||
|
$property = '_' . $property;
|
||||||
|
return property_exists($this, $property) ? $this->$property : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::check()
|
||||||
|
*
|
||||||
|
* Performs basic checks on the data provided, checks for a valid msg like.
|
||||||
|
* Calls integrate_valid_likes hook for retrieving all the data needed and apply checks based on the data provided.
|
||||||
|
*/
|
||||||
|
protected function check()
|
||||||
|
{
|
||||||
|
global $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// This feature is currently disable.
|
||||||
|
if (empty($modSettings['enable_likes']))
|
||||||
|
return $this->_error = 'like_disable';
|
||||||
|
|
||||||
|
// Zerothly, they did indicate some kind of content to like, right?
|
||||||
|
preg_match('~^([a-z0-9\-\_]{1,6})~i', $this->_type, $matches);
|
||||||
|
$this->_type = isset($matches[1]) ? $matches[1] : '';
|
||||||
|
|
||||||
|
if ($this->_type == '' || $this->_content <= 0)
|
||||||
|
return $this->_error = 'cannot_';
|
||||||
|
|
||||||
|
// First we need to verify if the user can see the type of content or not. This is set up to be extensible,
|
||||||
|
// so we'll check for the one type we do know about, and if it's not that, we'll defer to any hooks.
|
||||||
|
if ($this->_type == 'msg')
|
||||||
|
{
|
||||||
|
// So we're doing something off a like. We need to verify that it exists, and that the current user can see it.
|
||||||
|
// Fortunately for messages, this is quite easy to do - and we'll get the topic id while we're at it, because
|
||||||
|
// we need this later for other things.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT m.id_topic, m.id_member
|
||||||
|
FROM {db_prefix}messages AS m
|
||||||
|
WHERE {query_see_message_board}
|
||||||
|
AND m.id_msg = {int:msg}',
|
||||||
|
array(
|
||||||
|
'msg' => $this->_content,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 1)
|
||||||
|
list ($this->_idTopic, $topicOwner) = $smcFunc['db_fetch_row']($request);
|
||||||
|
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
if (empty($this->_idTopic))
|
||||||
|
return $this->_error = 'cannot_';
|
||||||
|
|
||||||
|
// So we know what topic it's in and more importantly we know the user can see it.
|
||||||
|
// If we're not viewing, we need some info set up.
|
||||||
|
$this->_validLikes['type'] = 'msg';
|
||||||
|
$this->_validLikes['flush_cache'] = 'likes_topic_' . $this->_idTopic . '_' . $this->_user['id'];
|
||||||
|
$this->_validLikes['redirect'] = 'topic=' . $this->_idTopic . '.msg' . $this->_content . '#msg' . $this->_content;
|
||||||
|
|
||||||
|
$this->_validLikes['can_like'] = ($this->_user['id'] == $topicOwner ? 'cannot_like_content' : (allowedTo('likes_like') ? true : 'cannot_like_content'));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Modders: This will give you whatever the user offers up in terms of liking, e.g. $this->_type=msg, $this->_content=1
|
||||||
|
// When you hook this, check $this->_type first. If it is not something your mod worries about, return false.
|
||||||
|
// Otherwise, fill an array according to the docs for $this->_validLikes. Determine (however you need to) that the user can see and can_like the relevant liked content (and it exists) Remember that users can't like their own content.
|
||||||
|
// If the user can like it, you MUST return your type in the 'type' key back.
|
||||||
|
// See also issueLike() for further notes.
|
||||||
|
$can_like = call_integration_hook('integrate_valid_likes', array($this->_type, $this->_content, $this->_sa, $this->_js, $this->_extra));
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
if (!empty($can_like))
|
||||||
|
{
|
||||||
|
$can_like = (array) $can_like;
|
||||||
|
foreach ($can_like as $result)
|
||||||
|
{
|
||||||
|
if ($result !== false)
|
||||||
|
{
|
||||||
|
// Match the type with what we already have.
|
||||||
|
if (!isset($result['type']) || $result['type'] != $this->_type)
|
||||||
|
return $this->_error = 'not_valid_like_type';
|
||||||
|
|
||||||
|
// Fill out the rest.
|
||||||
|
$this->_type = $result['type'];
|
||||||
|
$this->_validLikes = array_merge($this->_validLikes, $result);
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$found)
|
||||||
|
return $this->_error = 'cannot_';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the user can like this? Viewing a list of likes doesn't require this permission.
|
||||||
|
if ($this->_sa != 'view' && isset($this->_validLikes['can_like']) && is_string($this->_validLikes['can_like']))
|
||||||
|
return $this->_error = $this->_validLikes['can_like'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::delete()
|
||||||
|
*
|
||||||
|
* Deletes an entry from user_likes table, needs 3 properties: $_content, $_type and $_user['id'].
|
||||||
|
*/
|
||||||
|
protected function delete()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}user_likes
|
||||||
|
WHERE content_id = {int:like_content}
|
||||||
|
AND content_type = {string:like_type}
|
||||||
|
AND id_member = {int:id_member}',
|
||||||
|
array(
|
||||||
|
'like_content' => $this->_content,
|
||||||
|
'like_type' => $this->_type,
|
||||||
|
'id_member' => $this->_user['id'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Are we calling this directly? if so, set a proper data for the response. Do note that __METHOD__ returns both the class name and the function name.
|
||||||
|
if ($this->_sa == __FUNCTION__)
|
||||||
|
$this->_data = __FUNCTION__;
|
||||||
|
|
||||||
|
// Check to see if there is an unread alert to delete as well...
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_alert, id_member FROM {db_prefix}user_alerts
|
||||||
|
WHERE content_id = {int:like_content}
|
||||||
|
AND content_type = {string:like_type}
|
||||||
|
AND id_member_started = {int:id_member_started}
|
||||||
|
AND content_action = {string:content_action}
|
||||||
|
AND is_read = {int:unread}',
|
||||||
|
array(
|
||||||
|
'like_content' => $this->_content,
|
||||||
|
'like_type' => $this->_type,
|
||||||
|
'id_member_started' => $this->_user['id'],
|
||||||
|
'content_action' => 'like',
|
||||||
|
'unread' => 0,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Found one?
|
||||||
|
if ($smcFunc['db_num_rows']($result) == 1)
|
||||||
|
{
|
||||||
|
list($alert, $member) = $smcFunc['db_fetch_row']($result);
|
||||||
|
|
||||||
|
// Delete it
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}user_alerts
|
||||||
|
WHERE id_alert = {int:alert}',
|
||||||
|
array(
|
||||||
|
'alert' => $alert,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Decrement counter for member who received the like
|
||||||
|
updateMemberData($member, array('alerts' => '-'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::insert()
|
||||||
|
*
|
||||||
|
* Inserts a new entry on user_likes table. Creates a background task for the inserted entry.
|
||||||
|
*/
|
||||||
|
protected function insert()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
// Any last minute changes? Temporarily turn the passed properties to normal vars to prevent unexpected behaviour with other methods using these properties.
|
||||||
|
$type = $this->_type;
|
||||||
|
$content = $this->_content;
|
||||||
|
$user = $this->_user;
|
||||||
|
$time = time();
|
||||||
|
|
||||||
|
call_integration_hook('integrate_issue_like_before', array(&$type, &$content, &$user, &$time));
|
||||||
|
|
||||||
|
// Insert the like.
|
||||||
|
$smcFunc['db_insert']('insert',
|
||||||
|
'{db_prefix}user_likes',
|
||||||
|
array('content_id' => 'int', 'content_type' => 'string-6', 'id_member' => 'int', 'like_time' => 'int'),
|
||||||
|
array($content, $type, $user['id'], $time),
|
||||||
|
array('content_id', 'content_type', 'id_member')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a background task to process sending alerts.
|
||||||
|
// Mod author, you can add your own background task for your own custom like event using the "integrate_issue_like" hook or your callback, both are immediately called after this.
|
||||||
|
if ($this->_type == 'msg')
|
||||||
|
$smcFunc['db_insert']('insert',
|
||||||
|
'{db_prefix}background_tasks',
|
||||||
|
array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
|
||||||
|
array('$sourcedir/tasks/Likes-Notify.php', 'Likes_Notify_Background', $smcFunc['json_encode'](array(
|
||||||
|
'content_id' => $content,
|
||||||
|
'content_type' => $type,
|
||||||
|
'sender_id' => $user['id'],
|
||||||
|
'sender_name' => $user['name'],
|
||||||
|
'time' => $time,
|
||||||
|
)), 0),
|
||||||
|
array('id_task')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Are we calling this directly? if so, set a proper data for the response. Do note that __METHOD__ returns both the class name and the function name.
|
||||||
|
if ($this->_sa == __FUNCTION__)
|
||||||
|
$this->_data = __FUNCTION__;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::_count()
|
||||||
|
*
|
||||||
|
* Sets $_numLikes with the actual number of likes your content has, needs two properties: $_content and $_view. When called directly it will return the number of likes as response.
|
||||||
|
*/
|
||||||
|
protected function _count()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}user_likes
|
||||||
|
WHERE content_id = {int:like_content}
|
||||||
|
AND content_type = {string:like_type}',
|
||||||
|
array(
|
||||||
|
'like_content' => $this->_content,
|
||||||
|
'like_type' => $this->_type,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($this->_numLikes) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// If you want to call this directly, fill out _data property too.
|
||||||
|
if ($this->_sa == __FUNCTION__)
|
||||||
|
$this->_data = $this->_numLikes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::like()
|
||||||
|
*
|
||||||
|
* Performs a like action, either like or unlike. Counts the total of likes and calls a hook after the event.
|
||||||
|
*/
|
||||||
|
protected function like()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
// Safety first!
|
||||||
|
if (empty($this->_type) || empty($this->_content))
|
||||||
|
return $this->_error = 'cannot_';
|
||||||
|
|
||||||
|
// Do we already like this?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT content_id, content_type, id_member
|
||||||
|
FROM {db_prefix}user_likes
|
||||||
|
WHERE content_id = {int:like_content}
|
||||||
|
AND content_type = {string:like_type}
|
||||||
|
AND id_member = {int:id_member}',
|
||||||
|
array(
|
||||||
|
'like_content' => $this->_content,
|
||||||
|
'like_type' => $this->_type,
|
||||||
|
'id_member' => $this->_user['id'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$this->_alreadyLiked = (bool) $smcFunc['db_num_rows']($request) != 0;
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
if ($this->_alreadyLiked)
|
||||||
|
$this->delete();
|
||||||
|
|
||||||
|
else
|
||||||
|
$this->insert();
|
||||||
|
|
||||||
|
// Now, how many people like this content now? We *could* just +1 / -1 the relevant container but that has proven to become unstable.
|
||||||
|
$this->_count();
|
||||||
|
|
||||||
|
// Update the likes count for messages.
|
||||||
|
if ($this->_type == 'msg')
|
||||||
|
$this->msgIssueLike();
|
||||||
|
|
||||||
|
// Any callbacks?
|
||||||
|
elseif (!empty($this->_validLikes['callback']))
|
||||||
|
{
|
||||||
|
$call = call_helper($this->_validLikes['callback'], true);
|
||||||
|
|
||||||
|
if (!empty($call))
|
||||||
|
call_user_func_array($call, array($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sometimes there might be other things that need updating after we do this like.
|
||||||
|
call_integration_hook('integrate_issue_like', array($this));
|
||||||
|
|
||||||
|
// Now some clean up. This is provided here for any like handlers that want to do any cache flushing.
|
||||||
|
// This way a like handler doesn't need to explicitly declare anything in integrate_issue_like, but do so
|
||||||
|
// in integrate_valid_likes where it absolutely has to exist.
|
||||||
|
if (!empty($this->_validLikes['flush_cache']))
|
||||||
|
cache_put_data($this->_validLikes['flush_cache'], null);
|
||||||
|
|
||||||
|
// All done, start building the data to pass as response.
|
||||||
|
$this->_data = array(
|
||||||
|
'id_topic' => !empty($this->_idTopic) ? $this->_idTopic : 0,
|
||||||
|
'id_content' => $this->_content,
|
||||||
|
'count' => $this->_numLikes,
|
||||||
|
'can_like' => $this->_validLikes['can_like'],
|
||||||
|
'already_liked' => empty($this->_alreadyLiked),
|
||||||
|
'type' => $this->_type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::msgIssueLike()
|
||||||
|
*
|
||||||
|
* Partly it indicates how it's supposed to work and partly it deals with updating the count of likes
|
||||||
|
* attached to this message now.
|
||||||
|
*/
|
||||||
|
function msgIssueLike()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
if ($this->_type !== 'msg')
|
||||||
|
return;
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}messages
|
||||||
|
SET likes = {int:num_likes}
|
||||||
|
WHERE id_msg = {int:id_msg}',
|
||||||
|
array(
|
||||||
|
'id_msg' => $this->_content,
|
||||||
|
'num_likes' => $this->_numLikes,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Note that we could just as easily have cleared the cache here, or set up the redirection address
|
||||||
|
// but if your liked content doesn't need to do anything other than have the record in smf_user_likes,
|
||||||
|
// there's no point in creating another function unnecessarily.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::view()
|
||||||
|
*
|
||||||
|
* This is for viewing the people who liked a thing.
|
||||||
|
* Accessed from index.php?action=likes;view and should generally load in a popup.
|
||||||
|
* We use a template for this in case themers want to style it.
|
||||||
|
*/
|
||||||
|
function view()
|
||||||
|
{
|
||||||
|
global $smcFunc, $txt, $context, $memberContext;
|
||||||
|
|
||||||
|
// Firstly, load what we need. We already know we can see this, so that's something.
|
||||||
|
$context['likers'] = array();
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_member, like_time
|
||||||
|
FROM {db_prefix}user_likes
|
||||||
|
WHERE content_id = {int:like_content}
|
||||||
|
AND content_type = {string:like_type}
|
||||||
|
ORDER BY like_time DESC',
|
||||||
|
array(
|
||||||
|
'like_content' => $this->_content,
|
||||||
|
'like_type' => $this->_type,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$context['likers'][$row['id_member']] = array('timestamp' => $row['like_time']);
|
||||||
|
|
||||||
|
// Now to get member data, including avatars and so on.
|
||||||
|
$members = array_keys($context['likers']);
|
||||||
|
$loaded = loadMemberData($members);
|
||||||
|
if (count($loaded) != count($members))
|
||||||
|
{
|
||||||
|
$members = array_diff($members, $loaded);
|
||||||
|
foreach ($members as $not_loaded)
|
||||||
|
unset ($context['likers'][$not_loaded]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($context['likers'] as $liker => $dummy)
|
||||||
|
{
|
||||||
|
$loaded = loadMemberContext($liker);
|
||||||
|
if (!$loaded)
|
||||||
|
{
|
||||||
|
unset ($context['likers'][$liker]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['likers'][$liker]['profile'] = &$memberContext[$liker];
|
||||||
|
$context['likers'][$liker]['time'] = !empty($dummy['timestamp']) ? timeformat($dummy['timestamp']) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = count($context['likers']);
|
||||||
|
$title_base = isset($txt['likes_' . $count]) ? 'likes_' . $count : 'likes_n';
|
||||||
|
$context['page_title'] = strip_tags(sprintf($txt[$title_base], '', comma_format($count)));
|
||||||
|
|
||||||
|
// Lastly, setting up for display.
|
||||||
|
loadTemplate('Likes');
|
||||||
|
loadLanguage('Help'); // For the close window button.
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
$context['sub_template'] = 'popup';
|
||||||
|
|
||||||
|
// We already took care of our response so there is no need to bother with respond();
|
||||||
|
$this->_setResponse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likes::response()
|
||||||
|
*
|
||||||
|
* Checks if the user can use JavaScript and acts accordingly.
|
||||||
|
* Calls the appropriate sub-template for each method
|
||||||
|
* Handles error messages.
|
||||||
|
*/
|
||||||
|
protected function response()
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
// Don't do anything if someone else has already take care of the response.
|
||||||
|
if (!$this->_setResponse)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Want a json response huh?
|
||||||
|
if ($this->_validLikes['json'])
|
||||||
|
return $this->jsonResponse();
|
||||||
|
|
||||||
|
// Set everything up for display.
|
||||||
|
loadTemplate('Likes');
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
|
||||||
|
// If there are any errors, process them first.
|
||||||
|
if ($this->_error)
|
||||||
|
{
|
||||||
|
// If this is a generic error, set it up good.
|
||||||
|
if ($this->_error == 'cannot_')
|
||||||
|
$this->_error = $this->_sa == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
|
||||||
|
|
||||||
|
// Is this request coming from an ajax call?
|
||||||
|
if ($this->_js)
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'generic';
|
||||||
|
$context['data'] = isset($txt[$this->_error]) ? $txt[$this->_error] : $txt['like_error'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nope? then just do a redirect to whatever URL was provided.
|
||||||
|
else
|
||||||
|
redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] . ';error=' . $this->_error : '');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A like operation.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not an ajax request so send the user back to the previous location or the main page.
|
||||||
|
if (!$this->_js)
|
||||||
|
redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] : '');
|
||||||
|
|
||||||
|
// These fine gentlemen all share the same template.
|
||||||
|
$generic = array('delete', 'insert', '_count');
|
||||||
|
if (in_array($this->_sa, $generic))
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'generic';
|
||||||
|
$context['data'] = isset($txt['like_' . $this->_data]) ? $txt['like_' . $this->_data] : $this->_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directly pass the current called sub-action and the data generated by its associated Method.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['sub_template'] = $this->_sa;
|
||||||
|
$context['data'] = $this->_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs a JSON-encoded response
|
||||||
|
*/
|
||||||
|
protected function jsonResponse()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
$print = array(
|
||||||
|
'data' => $this->_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there is an error, send it.
|
||||||
|
if ($this->_error)
|
||||||
|
{
|
||||||
|
if ($this->_error == 'cannot_')
|
||||||
|
$this->_error = $this->_sa == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
|
||||||
|
|
||||||
|
$print['error'] = $this->_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do you want to add something at the very last minute?
|
||||||
|
call_integration_hook('integrate_likes_json_response', array(&$print));
|
||||||
|
|
||||||
|
// Print the data.
|
||||||
|
smf_serverResponse($smcFunc['json_encode']($print));
|
||||||
|
die;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What's this? I dunno, what are you talking about? Never seen this before, nope. No sir.
|
||||||
|
*/
|
||||||
|
function BookOfUnknown()
|
||||||
|
{
|
||||||
|
global $context, $scripturl;
|
||||||
|
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html', $context['right_to_left'] ? ' dir="rtl"' : '', '>
|
||||||
|
<head>
|
||||||
|
<title>The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', '</title>
|
||||||
|
<style>
|
||||||
|
em
|
||||||
|
{
|
||||||
|
font-size: 1.3em;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="background-color: #444455; color: white; font-style: italic; font-family: serif;">
|
||||||
|
<div style="margin-top: 12%; font-size: 1.1em; line-height: 1.4; text-align: center;">';
|
||||||
|
|
||||||
|
if (!isset($_GET['verse']) || ($_GET['verse'] != '2:18' && $_GET['verse'] != '22:1-2'))
|
||||||
|
$_GET['verse'] = '4:16';
|
||||||
|
|
||||||
|
if ($_GET['verse'] == '2:18')
|
||||||
|
echo '
|
||||||
|
Woe, it was that his name wasn\'t <em>known</em>, that he came in mystery, and was recognized by none. And it became to be in those days <em>something</em>. Something not yet <em id="unknown" name="[Unknown]">unknown</em> to mankind. And thus what was to be known the <em>secret project</em> began into its existence. Henceforth the opposition was only <em>weary</em> and <em>fearful</em>, for now their match was at arms against them.';
|
||||||
|
elseif ($_GET['verse'] == '4:16')
|
||||||
|
echo '
|
||||||
|
And it came to pass that the <em>unbelievers</em> dwindled in number and saw rise of many <em>proselytizers</em>, and the opposition found fear in the face of the <em>x</em> and the <em>j</em> while those who stood with the <em>something</em> grew stronger and came together. Still, this was only the <em>beginning</em>, and what lay in the future was <em id="unknown" name="[Unknown]">unknown</em> to all, even those on the right side.';
|
||||||
|
elseif ($_GET['verse'] == '22:1-2')
|
||||||
|
echo '
|
||||||
|
<p>Now <em>behold</em>, that which was once the secret project was <em id="unknown" name="[Unknown]">unknown</em> no longer. Alas, it needed more than <em>only one</em>, but yet even thought otherwise. It became that the opposition <em>rumored</em> and lied, but still to no avail. Their match, though not <em>perfect</em>, had them outdone.</p>
|
||||||
|
<p style="margin: 2ex 1ex 0 1ex; font-size: 1.05em; line-height: 1.5; text-align: center;">Let it continue. <em>The end</em>.</p>';
|
||||||
|
|
||||||
|
echo '
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 2ex; font-size: 2em; text-align: right;">';
|
||||||
|
|
||||||
|
if ($_GET['verse'] == '2:18')
|
||||||
|
echo '
|
||||||
|
from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=4:16" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 2:18</span>';
|
||||||
|
elseif ($_GET['verse'] == '4:16')
|
||||||
|
echo '
|
||||||
|
from <span style="font-family: Georgia, serif;"><strong><a href="', $scripturl, '?action=about:unknown;verse=22:1-2" style="color: white; text-decoration: none; cursor: text;">The Book of Unknown</a></strong>, 4:16</span>';
|
||||||
|
elseif ($_GET['verse'] == '22:1-2')
|
||||||
|
echo '
|
||||||
|
from <span style="font-family: Georgia, serif;"><strong>The Book of Unknown</strong>, 22:1-2</span>';
|
||||||
|
|
||||||
|
echo '
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
obExit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
4055
Sources/Load.php
Normal file
964
Sources/LogInOut.php
Normal file
|
@ -0,0 +1,964 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is concerned pretty entirely, as you see from its name, with
|
||||||
|
* logging in and out members, and the validation of that.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask them for their login information. (shows a page for the user to type
|
||||||
|
* in their username and password.)
|
||||||
|
* It caches the referring URL in $_SESSION['login_url'].
|
||||||
|
* It is accessed from ?action=login.
|
||||||
|
*
|
||||||
|
* Uses Login template and language file with the login sub-template.
|
||||||
|
*/
|
||||||
|
function Login()
|
||||||
|
{
|
||||||
|
global $txt, $context, $scripturl, $user_info;
|
||||||
|
|
||||||
|
// You are already logged in, go take a tour of the boards
|
||||||
|
if (!empty($user_info['id']))
|
||||||
|
{
|
||||||
|
// This came from a valid hashed return url. Or something that knows our secrets...
|
||||||
|
if (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
|
||||||
|
redirectexit(un_htmlspecialchars($_REQUEST['return_to']));
|
||||||
|
else
|
||||||
|
redirectexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to load the Login template/language file.
|
||||||
|
loadLanguage('Login');
|
||||||
|
loadTemplate('Login');
|
||||||
|
|
||||||
|
$context['sub_template'] = 'login';
|
||||||
|
|
||||||
|
/* This is true when:
|
||||||
|
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
|
||||||
|
* OR we have found:
|
||||||
|
* 1. valid cors host
|
||||||
|
* 2. A header indicating a SMF request
|
||||||
|
* 3. The url has a ajax in either the GET or POST
|
||||||
|
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
||||||
|
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
!empty($context['valid_cors_found'])
|
||||||
|
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
|
||||||
|
&& isset($_REQUEST['ajax'])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$context['from_ajax'] = true;
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the template ready.... not really much else to do.
|
||||||
|
$context['page_title'] = $txt['login'];
|
||||||
|
$context['default_username'] = &$_REQUEST['u'];
|
||||||
|
$context['default_password'] = '';
|
||||||
|
$context['never_expire'] = false;
|
||||||
|
|
||||||
|
// Add the login chain to the link tree.
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=login',
|
||||||
|
'name' => $txt['login'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the login URL - will be used when the login process is done (but careful not to send us to an attachment).
|
||||||
|
if (isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0)
|
||||||
|
$_SESSION['login_url'] = $_SESSION['old_url'];
|
||||||
|
// This came from a valid hashed return url. Or something that knows our secrets...
|
||||||
|
elseif (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
|
||||||
|
$_SESSION['login_url'] = un_htmlspecialchars($_REQUEST['return_to']);
|
||||||
|
elseif (isset($_SESSION['login_url']) && strpos($_SESSION['login_url'], 'dlattach') !== false)
|
||||||
|
unset($_SESSION['login_url']);
|
||||||
|
|
||||||
|
// Create a one time token.
|
||||||
|
createToken('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actually logs you in.
|
||||||
|
* What it does:
|
||||||
|
* - checks credentials and checks that login was successful.
|
||||||
|
* - it employs protection against a specific IP or user trying to brute force
|
||||||
|
* a login to an account.
|
||||||
|
* - upgrades password encryption on login, if necessary.
|
||||||
|
* - after successful login, redirects you to $_SESSION['login_url'].
|
||||||
|
* - accessed from ?action=login2, by forms.
|
||||||
|
* On error, uses the same templates Login() uses.
|
||||||
|
*/
|
||||||
|
function Login2()
|
||||||
|
{
|
||||||
|
global $txt, $scripturl, $user_info, $user_settings, $smcFunc;
|
||||||
|
global $cookiename, $modSettings, $context, $sourcedir, $maintenance;
|
||||||
|
|
||||||
|
// Check to ensure we're forcing SSL for authentication
|
||||||
|
if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
|
||||||
|
fatal_lang_error('login_ssl_required', false);
|
||||||
|
|
||||||
|
// Load cookie authentication stuff.
|
||||||
|
require_once($sourcedir . '/Subs-Auth.php');
|
||||||
|
|
||||||
|
/* This is true when:
|
||||||
|
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
|
||||||
|
* OR we have found:
|
||||||
|
* 1. valid cors host
|
||||||
|
* 2. A header indicating a SMF request
|
||||||
|
* 3. The url has a ajax in either the GET or POST
|
||||||
|
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
||||||
|
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
!empty($context['valid_cors_found'])
|
||||||
|
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
|
||||||
|
&& isset($_REQUEST['ajax'])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$context['from_ajax'] = true;
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['sa']) && $_GET['sa'] == 'salt' && !$user_info['is_guest'])
|
||||||
|
{
|
||||||
|
// First check for 2.1 json-format cookie in $_COOKIE
|
||||||
|
if (isset($_COOKIE[$cookiename]) && preg_match('~^{"0":\d+,"1":"[0-9a-f]*","2":\d+~', $_COOKIE[$cookiename]) === 1)
|
||||||
|
list (,, $timeout) = $smcFunc['json_decode']($_COOKIE[$cookiename], true);
|
||||||
|
|
||||||
|
// Try checking for 2.1 json-format cookie in $_SESSION
|
||||||
|
elseif (isset($_SESSION['login_' . $cookiename]) && preg_match('~^{"0":\d+,"1":"[0-9a-f]*","2":\d+~', $_SESSION['login_' . $cookiename]) === 1)
|
||||||
|
list (,, $timeout) = $smcFunc['json_decode']($_SESSION['login_' . $cookiename]);
|
||||||
|
|
||||||
|
// Next, try checking for 2.0 serialized string cookie in $_COOKIE
|
||||||
|
elseif (isset($_COOKIE[$cookiename]) && preg_match('~^a:[34]:\{i:0;i:\d+;i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d+;~', $_COOKIE[$cookiename]) === 1)
|
||||||
|
list (,, $timeout) = safe_unserialize($_COOKIE[$cookiename]);
|
||||||
|
|
||||||
|
// Last, see if you need to fall back on checking for 2.0 serialized string cookie in $_SESSION
|
||||||
|
elseif (isset($_SESSION['login_' . $cookiename]) && preg_match('~^a:[34]:\{i:0;i:\d+;i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d+;~', $_SESSION['login_' . $cookiename]) === 1)
|
||||||
|
list (,, $timeout) = safe_unserialize($_SESSION['login_' . $cookiename]);
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['login_no_session_cookie'], E_USER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_settings['password_salt'] = bin2hex($smcFunc['random_bytes'](16));
|
||||||
|
updateMemberData($user_info['id'], array('password_salt' => $user_settings['password_salt']));
|
||||||
|
|
||||||
|
// Preserve the 2FA cookie?
|
||||||
|
if (!empty($modSettings['tfa_mode']) && !empty($_COOKIE[$cookiename . '_tfa']))
|
||||||
|
{
|
||||||
|
list (,, $exp) = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
|
||||||
|
setTFACookie((int) $exp - time(), $user_info['password_salt'], hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']));
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoginCookie((int) $timeout - time(), $user_info['id'], hash_salt($user_settings['passwd'], $user_settings['password_salt']));
|
||||||
|
|
||||||
|
redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']);
|
||||||
|
}
|
||||||
|
// Double check the cookie...
|
||||||
|
elseif (isset($_GET['sa']) && $_GET['sa'] == 'check')
|
||||||
|
{
|
||||||
|
// Strike! You're outta there!
|
||||||
|
if ($_GET['member'] != $user_info['id'])
|
||||||
|
fatal_lang_error('login_cookie_error', false);
|
||||||
|
|
||||||
|
$user_info['can_mod'] = allowedTo('access_mod_center') || (!$user_info['is_guest'] && ($user_info['mod_cache']['gq'] != '0=1' || $user_info['mod_cache']['bq'] != '0=1' || ($modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']))));
|
||||||
|
|
||||||
|
// Some whitelisting for login_url...
|
||||||
|
if (empty($_SESSION['login_url']))
|
||||||
|
redirectexit(empty($user_settings['tfa_secret']) ? '' : 'action=logintfa');
|
||||||
|
elseif (!empty($_SESSION['login_url']) && (strpos($_SESSION['login_url'], 'http://') === false && strpos($_SESSION['login_url'], 'https://') === false))
|
||||||
|
{
|
||||||
|
unset($_SESSION['login_url']);
|
||||||
|
redirectexit(empty($user_settings['tfa_secret']) ? '' : 'action=logintfa');
|
||||||
|
}
|
||||||
|
elseif (!empty($user_settings['tfa_secret']))
|
||||||
|
{
|
||||||
|
redirectexit('action=logintfa');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Best not to clutter the session data too much...
|
||||||
|
$temp = $_SESSION['login_url'];
|
||||||
|
unset($_SESSION['login_url']);
|
||||||
|
|
||||||
|
redirectexit($temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beyond this point you are assumed to be a guest trying to login.
|
||||||
|
if (!$user_info['is_guest'])
|
||||||
|
redirectexit();
|
||||||
|
|
||||||
|
// Are you guessing with a script?
|
||||||
|
checkSession();
|
||||||
|
validateToken('login');
|
||||||
|
spamProtection('login');
|
||||||
|
|
||||||
|
// Set the login_url if it's not already set (but careful not to send us to an attachment).
|
||||||
|
if ((empty($_SESSION['login_url']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'dlattach') === false && preg_match('~(board|topic)[=,]~', $_SESSION['old_url']) != 0) || (isset($_GET['quicklogin']) && isset($_SESSION['old_url']) && strpos($_SESSION['old_url'], 'login') === false))
|
||||||
|
$_SESSION['login_url'] = $_SESSION['old_url'];
|
||||||
|
|
||||||
|
// Been guessing a lot, haven't we?
|
||||||
|
if (isset($_SESSION['failed_login']) && $_SESSION['failed_login'] >= $modSettings['failed_login_threshold'] * 3)
|
||||||
|
fatal_lang_error('login_threshold_fail', 'login');
|
||||||
|
|
||||||
|
// Set up the cookie length. (if it's invalid, just fall through and use the default.)
|
||||||
|
if (isset($_POST['cookieneverexp']) || (!empty($_POST['cookielength']) && $_POST['cookielength'] == -1))
|
||||||
|
$modSettings['cookieTime'] = 3153600;
|
||||||
|
elseif (!empty($_POST['cookielength']) && ($_POST['cookielength'] >= 1 && $_POST['cookielength'] <= 3153600))
|
||||||
|
$modSettings['cookieTime'] = (int) $_POST['cookielength'];
|
||||||
|
|
||||||
|
loadLanguage('Login');
|
||||||
|
// Load the template stuff.
|
||||||
|
loadTemplate('Login');
|
||||||
|
$context['sub_template'] = 'login';
|
||||||
|
|
||||||
|
// Create a one time token.
|
||||||
|
createToken('login');
|
||||||
|
|
||||||
|
// Set up the default/fallback stuff.
|
||||||
|
$context['default_username'] = isset($_POST['user']) ? preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($_POST['user'])) : '';
|
||||||
|
$context['default_password'] = '';
|
||||||
|
$context['never_expire'] = $modSettings['cookieTime'] <= 525600;
|
||||||
|
$context['login_errors'] = array($txt['error_occured']);
|
||||||
|
$context['page_title'] = $txt['login'];
|
||||||
|
|
||||||
|
// Add the login chain to the link tree.
|
||||||
|
$context['linktree'][] = array(
|
||||||
|
'url' => $scripturl . '?action=login',
|
||||||
|
'name' => $txt['login'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// You forgot to type your username, dummy!
|
||||||
|
if (!isset($_POST['user']) || $_POST['user'] == '')
|
||||||
|
{
|
||||||
|
$context['login_errors'] = array($txt['need_username']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hmm... maybe 'admin' will login with no password. Uhh... NO!
|
||||||
|
if (!isset($_POST['passwrd']) || $_POST['passwrd'] == '')
|
||||||
|
{
|
||||||
|
$context['login_errors'] = array($txt['no_password']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No funky symbols either.
|
||||||
|
if (preg_match('~[<>&"\'=\\\]~', preg_replace('~(&#(\\d{1,7}|x[0-9a-fA-F]{1,6});)~', '', $_POST['user'])) != 0)
|
||||||
|
{
|
||||||
|
$context['login_errors'] = array($txt['error_invalid_characters_username']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And if it's too long, trim it back.
|
||||||
|
if ($smcFunc['strlen']($_POST['user']) > 80)
|
||||||
|
{
|
||||||
|
$_POST['user'] = $smcFunc['substr']($_POST['user'], 0, 79);
|
||||||
|
$context['default_username'] = preg_replace('~&#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($_POST['user']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we using any sort of integration to validate the login?
|
||||||
|
if (in_array('retry', call_integration_hook('integrate_validate_login', array($_POST['user'], isset($_POST['passwrd']) ? $_POST['passwrd'] : null, $modSettings['cookieTime'])), true))
|
||||||
|
{
|
||||||
|
$context['login_errors'] = array($txt['incorrect_password']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the data up!
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt,
|
||||||
|
passwd_flood, tfa_secret
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(member_name) = LOWER({string:user_name})' : 'member_name = {string:user_name}') . '
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'user_name' => $smcFunc['db_case_sensitive'] ? strtolower($_POST['user']) : $_POST['user'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Probably mistyped or their email, try it as an email address. (member_name first, though!)
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 0 && strpos($_POST['user'], '@') !== false)
|
||||||
|
{
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt,
|
||||||
|
passwd_flood, tfa_secret
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE email_address = {string:user_name}
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'user_name' => $_POST['user'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let them try again, it didn't match anything...
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 0)
|
||||||
|
{
|
||||||
|
$context['login_errors'] = array($txt['username_no_exist']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_settings = $smcFunc['db_fetch_assoc']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Bad password! Thought you could fool the database?!
|
||||||
|
if (!hash_verify_password($user_settings['member_name'], un_htmlspecialchars($_POST['passwrd']), $user_settings['passwd']))
|
||||||
|
{
|
||||||
|
// Let's be cautious, no hacking please. thanx.
|
||||||
|
validatePasswordFlood($user_settings['id_member'], $user_settings['member_name'], $user_settings['passwd_flood']);
|
||||||
|
|
||||||
|
// Maybe we were too hasty... let's try some other authentication methods.
|
||||||
|
$other_passwords = array();
|
||||||
|
|
||||||
|
// None of the below cases will be used most of the time (because the salt is normally set.)
|
||||||
|
if (!empty($modSettings['enable_password_conversion']) && $user_settings['password_salt'] == '')
|
||||||
|
{
|
||||||
|
// YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, IkonBoard, and none at all.
|
||||||
|
$other_passwords[] = crypt($_POST['passwrd'], substr($_POST['passwrd'], 0, 2));
|
||||||
|
$other_passwords[] = crypt($_POST['passwrd'], substr($user_settings['passwd'], 0, 2));
|
||||||
|
$other_passwords[] = md5($_POST['passwrd']);
|
||||||
|
$other_passwords[] = sha1($_POST['passwrd']);
|
||||||
|
$other_passwords[] = md5_hmac($_POST['passwrd'], strtolower($user_settings['member_name']));
|
||||||
|
$other_passwords[] = md5($_POST['passwrd'] . strtolower($user_settings['member_name']));
|
||||||
|
$other_passwords[] = md5(md5($_POST['passwrd']));
|
||||||
|
$other_passwords[] = $_POST['passwrd'];
|
||||||
|
$other_passwords[] = crypt($_POST['passwrd'], $user_settings['passwd']);
|
||||||
|
|
||||||
|
// This one is a strange one... MyPHP, crypt() on the MD5 hash.
|
||||||
|
$other_passwords[] = crypt(md5($_POST['passwrd']), md5($_POST['passwrd']));
|
||||||
|
|
||||||
|
// Snitz style - SHA-256. Technically, this is a downgrade, but most PHP configurations don't support sha256 anyway.
|
||||||
|
if (strlen($user_settings['passwd']) == 64 && function_exists('mhash') && defined('MHASH_SHA256'))
|
||||||
|
$other_passwords[] = bin2hex(mhash(MHASH_SHA256, $_POST['passwrd']));
|
||||||
|
|
||||||
|
// phpBB3 users new hashing. We now support it as well ;).
|
||||||
|
$other_passwords[] = phpBB3_password_check($_POST['passwrd'], $user_settings['passwd']);
|
||||||
|
|
||||||
|
// APBoard 2 Login Method.
|
||||||
|
$other_passwords[] = md5(crypt($_POST['passwrd'], 'CRYPT_MD5'));
|
||||||
|
}
|
||||||
|
// If the salt is set let's try some other options
|
||||||
|
elseif (!empty($modSettings['enable_password_conversion']) && $user_settings['password_salt'] != '')
|
||||||
|
{
|
||||||
|
// PHPBB 3 check this function exists in PHP 5.5 or higher
|
||||||
|
if (function_exists('password_verify'))
|
||||||
|
$other_passwords[] = password_verify($_POST['passwrd'],$user_settings['password_salt']);
|
||||||
|
|
||||||
|
// PHP-Fusion
|
||||||
|
$other_passwords[] = hash_hmac('sha256', $_POST['passwrd'], $user_settings['password_salt']);
|
||||||
|
|
||||||
|
// MyBB
|
||||||
|
$other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
|
||||||
|
}
|
||||||
|
// The hash should be 40 if it's SHA-1, so we're safe with more here too.
|
||||||
|
elseif (!empty($modSettings['enable_password_conversion']) && strlen($user_settings['passwd']) == 32)
|
||||||
|
{
|
||||||
|
// vBulletin 3 style hashing? Let's welcome them with open arms \o/.
|
||||||
|
$other_passwords[] = md5(md5($_POST['passwrd']) . stripslashes($user_settings['password_salt']));
|
||||||
|
|
||||||
|
// Hmm.. p'raps it's Invision 2 style?
|
||||||
|
$other_passwords[] = md5(md5($user_settings['password_salt']) . md5($_POST['passwrd']));
|
||||||
|
|
||||||
|
// Some common md5 ones.
|
||||||
|
$other_passwords[] = md5($user_settings['password_salt'] . $_POST['passwrd']);
|
||||||
|
$other_passwords[] = md5($_POST['passwrd'] . $user_settings['password_salt']);
|
||||||
|
}
|
||||||
|
elseif (strlen($user_settings['passwd']) == 40)
|
||||||
|
{
|
||||||
|
// Maybe they are using a hash from before the password fix.
|
||||||
|
// This is also valid for SMF 1.1 to 2.0 style of hashing, changed to bcrypt in SMF 2.1
|
||||||
|
$other_passwords[] = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
|
||||||
|
|
||||||
|
// BurningBoard3 style of hashing.
|
||||||
|
if (!empty($modSettings['enable_password_conversion']))
|
||||||
|
$other_passwords[] = sha1($user_settings['password_salt'] . sha1($user_settings['password_salt'] . sha1($_POST['passwrd'])));
|
||||||
|
|
||||||
|
// PunBB
|
||||||
|
$other_passwords[] = sha1($user_settings['password_salt'] . sha1($_POST['passwrd']));
|
||||||
|
|
||||||
|
// Perhaps we converted to UTF-8 and have a valid password being hashed differently.
|
||||||
|
if ($context['character_set'] == 'UTF-8' && !empty($modSettings['previousCharacterSet']) && $modSettings['previousCharacterSet'] != 'utf8')
|
||||||
|
{
|
||||||
|
// Try iconv first, for no particular reason.
|
||||||
|
if (function_exists('iconv'))
|
||||||
|
$other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $user_settings['member_name'])) . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $_POST['passwrd'])));
|
||||||
|
|
||||||
|
// Say it aint so, iconv failed!
|
||||||
|
if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding'))
|
||||||
|
$other_passwords[] = sha1(strtolower(mb_convert_encoding($user_settings['member_name'], 'UTF-8', $modSettings['previousCharacterSet'])) . un_htmlspecialchars(mb_convert_encoding($_POST['passwrd'], 'UTF-8', $modSettings['previousCharacterSet'])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMF's sha1 function can give a funny result on Linux (Not our fault!). If we've now got the real one let the old one be valid!
|
||||||
|
if (stripos(PHP_OS, 'win') !== 0 && strlen($user_settings['passwd']) < hash_length())
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-Compat.php');
|
||||||
|
$other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . un_htmlspecialchars($_POST['passwrd']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows mods to easily extend the $other_passwords array
|
||||||
|
call_integration_hook('integrate_other_passwords', array(&$other_passwords));
|
||||||
|
|
||||||
|
// Whichever encryption it was using, let's make it use SMF's now ;).
|
||||||
|
if (in_array($user_settings['passwd'], $other_passwords))
|
||||||
|
{
|
||||||
|
$user_settings['passwd'] = hash_password($user_settings['member_name'], un_htmlspecialchars($_POST['passwrd']));
|
||||||
|
$user_settings['password_salt'] = bin2hex($smcFunc['random_bytes'](16));
|
||||||
|
|
||||||
|
// Update the password and set up the hash.
|
||||||
|
updateMemberData($user_settings['id_member'], array('passwd' => $user_settings['passwd'], 'password_salt' => $user_settings['password_salt'], 'passwd_flood' => ''));
|
||||||
|
}
|
||||||
|
// Okay, they for sure didn't enter the password!
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// They've messed up again - keep a count to see if they need a hand.
|
||||||
|
$_SESSION['failed_login'] = isset($_SESSION['failed_login']) ? ($_SESSION['failed_login'] + 1) : 1;
|
||||||
|
|
||||||
|
// Hmm... don't remember it, do you? Here, try the password reminder ;).
|
||||||
|
if ($_SESSION['failed_login'] >= $modSettings['failed_login_threshold'])
|
||||||
|
redirectexit('action=reminder');
|
||||||
|
// We'll give you another chance...
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Log an error so we know that it didn't go well in the error log.
|
||||||
|
log_error($txt['incorrect_password'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user');
|
||||||
|
|
||||||
|
$context['login_errors'] = array($txt['incorrect_password']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (!empty($user_settings['passwd_flood']))
|
||||||
|
{
|
||||||
|
// Let's be sure they weren't a little hacker.
|
||||||
|
validatePasswordFlood($user_settings['id_member'], $user_settings['member_name'], $user_settings['passwd_flood'], true);
|
||||||
|
|
||||||
|
// If we got here then we can reset the flood counter.
|
||||||
|
updateMemberData($user_settings['id_member'], array('passwd_flood' => ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct password, but they've got no salt; fix it!
|
||||||
|
if (strlen($user_settings['password_salt']) < 32)
|
||||||
|
{
|
||||||
|
$user_settings['password_salt'] = bin2hex($smcFunc['random_bytes'](16));
|
||||||
|
updateMemberData($user_settings['id_member'], array('password_salt' => $user_settings['password_salt']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check their activation status.
|
||||||
|
if (!checkActivation())
|
||||||
|
return;
|
||||||
|
|
||||||
|
DoLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the user to enter their Two-Factor Authentication code
|
||||||
|
*/
|
||||||
|
function LoginTFA()
|
||||||
|
{
|
||||||
|
global $sourcedir, $txt, $context, $user_info, $modSettings, $scripturl;
|
||||||
|
|
||||||
|
if (!$user_info['is_guest'] || empty($context['tfa_member']) || empty($modSettings['tfa_mode']))
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
|
||||||
|
loadLanguage('Profile');
|
||||||
|
require_once($sourcedir . '/Class-TOTP.php');
|
||||||
|
|
||||||
|
$member = $context['tfa_member'];
|
||||||
|
|
||||||
|
// Prevent replay attacks by limiting at least 2 minutes before they can log in again via 2FA
|
||||||
|
if (time() - $member['last_login'] < 120)
|
||||||
|
fatal_lang_error('tfa_wait', false);
|
||||||
|
|
||||||
|
$totp = new \TOTP\Auth($member['tfa_secret']);
|
||||||
|
$totp->setRange(1);
|
||||||
|
|
||||||
|
/* This is true when:
|
||||||
|
* We have a valid header indicating a JQXHR request. This is not sent during a cross domain request.
|
||||||
|
* OR we have found:
|
||||||
|
* 1. valid cors host
|
||||||
|
* 2. A header indicating a SMF request
|
||||||
|
* 3. The url has a ajax in either the GET or POST
|
||||||
|
* These are not intended for security, but ensuring the request is intended for a JQXHR response.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
||||||
|
&& $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(
|
||||||
|
!empty($context['valid_cors_found'])
|
||||||
|
&& !empty($_SERVER['HTTP_X_SMF_AJAX'])
|
||||||
|
&& isset($_REQUEST['ajax'])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$context['from_ajax'] = true;
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_POST['tfa_code']) && empty($_POST['tfa_backup']))
|
||||||
|
{
|
||||||
|
// Check to ensure we're forcing SSL for authentication
|
||||||
|
if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
|
||||||
|
fatal_lang_error('login_ssl_required', false);
|
||||||
|
|
||||||
|
$code = $_POST['tfa_code'];
|
||||||
|
|
||||||
|
if (strlen($code) == $totp->getCodeLength() && $totp->validateCode($code))
|
||||||
|
{
|
||||||
|
updateMemberData($member['id_member'], array('last_login' => time()));
|
||||||
|
|
||||||
|
setTFACookie(3153600, $member['id_member'], hash_salt($member['tfa_backup'], $member['password_salt']));
|
||||||
|
redirectexit();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
validatePasswordFlood($member['id_member'], $member['member_name'], $member['passwd_flood'], false, true);
|
||||||
|
|
||||||
|
$context['tfa_error'] = true;
|
||||||
|
$context['tfa_value'] = $_POST['tfa_code'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (!empty($_POST['tfa_backup']))
|
||||||
|
{
|
||||||
|
// Check to ensure we're forcing SSL for authentication
|
||||||
|
if (!empty($modSettings['force_ssl']) && empty($maintenance) && !httpsOn())
|
||||||
|
fatal_lang_error('login_ssl_required', false);
|
||||||
|
|
||||||
|
$backup = $_POST['tfa_backup'];
|
||||||
|
|
||||||
|
if (hash_verify_password($member['member_name'], $backup, $member['tfa_backup']))
|
||||||
|
{
|
||||||
|
// Get rid of their current TFA settings
|
||||||
|
updateMemberData($member['id_member'], array(
|
||||||
|
'tfa_secret' => '',
|
||||||
|
'tfa_backup' => '',
|
||||||
|
'last_login' => time(),
|
||||||
|
));
|
||||||
|
setTFACookie(3153600, $member['id_member'], hash_salt($member['tfa_backup'], $member['password_salt']));
|
||||||
|
redirectexit('action=profile;area=tfasetup;backup');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
validatePasswordFlood($member['id_member'], $member['member_name'], $member['passwd_flood'], false, true);
|
||||||
|
|
||||||
|
$context['tfa_backup_error'] = true;
|
||||||
|
$context['tfa_value'] = $_POST['tfa_code'];
|
||||||
|
$context['tfa_backup_value'] = $_POST['tfa_backup'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTemplate('Login');
|
||||||
|
$context['sub_template'] = 'login_tfa';
|
||||||
|
$context['page_title'] = $txt['login'];
|
||||||
|
$context['tfa_url'] = $scripturl . '?action=logintfa';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check activation status of the current user.
|
||||||
|
*/
|
||||||
|
function checkActivation()
|
||||||
|
{
|
||||||
|
global $context, $txt, $scripturl, $user_settings, $modSettings;
|
||||||
|
|
||||||
|
if (!isset($context['login_errors']))
|
||||||
|
$context['login_errors'] = array();
|
||||||
|
|
||||||
|
// What is the true activation status of this account?
|
||||||
|
$activation_status = $user_settings['is_activated'] > 10 ? $user_settings['is_activated'] - 10 : $user_settings['is_activated'];
|
||||||
|
|
||||||
|
// Check if the account is activated - COPPA first...
|
||||||
|
if ($activation_status == 5)
|
||||||
|
{
|
||||||
|
$context['login_errors'][] = $txt['coppa_no_consent'] . ' <a href="' . $scripturl . '?action=coppa;member=' . $user_settings['id_member'] . '">' . $txt['coppa_need_more_details'] . '</a>';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Awaiting approval still?
|
||||||
|
elseif ($activation_status == 3)
|
||||||
|
fatal_lang_error('still_awaiting_approval', 'user');
|
||||||
|
// Awaiting deletion, changed their mind?
|
||||||
|
elseif ($activation_status == 4)
|
||||||
|
{
|
||||||
|
if (isset($_REQUEST['undelete']))
|
||||||
|
{
|
||||||
|
updateMemberData($user_settings['id_member'], array('is_activated' => 1));
|
||||||
|
updateSettings(array('unapprovedMembers' => ($modSettings['unapprovedMembers'] > 0 ? $modSettings['unapprovedMembers'] - 1 : 0)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['disable_login_hashing'] = true;
|
||||||
|
$context['login_errors'][] = $txt['awaiting_delete_account'];
|
||||||
|
$context['login_show_undelete'] = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Standard activation?
|
||||||
|
elseif ($activation_status != 1)
|
||||||
|
{
|
||||||
|
log_error($txt['activate_not_completed1'] . ' - <span class="remove">' . $user_settings['member_name'] . '</span>', 'user');
|
||||||
|
|
||||||
|
$context['login_errors'][] = $txt['activate_not_completed1'] . ' <a href="' . $scripturl . '?action=activate;sa=resend;u=' . $user_settings['id_member'] . '">' . $txt['activate_not_completed2'] . '</a>';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the logging in. (set cookie, call hooks, etc)
|
||||||
|
*/
|
||||||
|
function DoLogin()
|
||||||
|
{
|
||||||
|
global $user_info, $user_settings, $smcFunc;
|
||||||
|
global $maintenance, $modSettings, $context, $sourcedir;
|
||||||
|
|
||||||
|
// Load cookie authentication stuff.
|
||||||
|
require_once($sourcedir . '/Subs-Auth.php');
|
||||||
|
|
||||||
|
// Call login integration functions.
|
||||||
|
call_integration_hook('integrate_login', array($user_settings['member_name'], null, $modSettings['cookieTime']));
|
||||||
|
|
||||||
|
// Get ready to set the cookie...
|
||||||
|
$user_info['id'] = $user_settings['id_member'];
|
||||||
|
|
||||||
|
// Bam! Cookie set. A session too, just in case.
|
||||||
|
setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], hash_salt($user_settings['passwd'], $user_settings['password_salt']));
|
||||||
|
|
||||||
|
// Reset the login threshold.
|
||||||
|
if (isset($_SESSION['failed_login']))
|
||||||
|
unset($_SESSION['failed_login']);
|
||||||
|
|
||||||
|
$user_info['is_guest'] = false;
|
||||||
|
$user_settings['additional_groups'] = explode(',', $user_settings['additional_groups']);
|
||||||
|
$user_info['is_admin'] = $user_settings['id_group'] == 1 || in_array(1, $user_settings['additional_groups']);
|
||||||
|
|
||||||
|
// Are you banned?
|
||||||
|
is_not_banned(true);
|
||||||
|
|
||||||
|
// Don't stick the language or theme after this point.
|
||||||
|
unset($_SESSION['language'], $_SESSION['id_theme']);
|
||||||
|
|
||||||
|
// First login?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT last_login
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE id_member = {int:id_member}
|
||||||
|
AND last_login = 0',
|
||||||
|
array(
|
||||||
|
'id_member' => $user_info['id'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ($smcFunc['db_num_rows']($request) == 1)
|
||||||
|
$_SESSION['first_login'] = true;
|
||||||
|
else
|
||||||
|
unset($_SESSION['first_login']);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// You've logged in, haven't you?
|
||||||
|
$update = array('member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']);
|
||||||
|
if (empty($user_settings['tfa_secret']))
|
||||||
|
$update['last_login'] = time();
|
||||||
|
updateMemberData($user_info['id'], $update);
|
||||||
|
|
||||||
|
// Get rid of the online entry for that old guest....
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_online
|
||||||
|
WHERE session = {string:session}',
|
||||||
|
array(
|
||||||
|
'session' => 'ip' . $user_info['ip'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$_SESSION['log_time'] = 0;
|
||||||
|
|
||||||
|
// Log this entry, only if we have it enabled.
|
||||||
|
if (!empty($modSettings['loginHistoryDays']))
|
||||||
|
$smcFunc['db_insert']('insert',
|
||||||
|
'{db_prefix}member_logins',
|
||||||
|
array(
|
||||||
|
'id_member' => 'int', 'time' => 'int', 'ip' => 'inet', 'ip2' => 'inet',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$user_info['id'], time(), $user_info['ip'], $user_info['ip2']
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'id_member', 'time'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Just log you back out if it's in maintenance mode and you AREN'T an admin.
|
||||||
|
if (empty($maintenance) || allowedTo('admin_forum'))
|
||||||
|
redirectexit('action=login2;sa=check;member=' . $user_info['id'], $context['server']['needs_login_fix']);
|
||||||
|
else
|
||||||
|
redirectexit('action=logout;' . $context['session_var'] . '=' . $context['session_id'], $context['server']['needs_login_fix']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the current user out of their account.
|
||||||
|
* It requires that the session hash is sent as well, to prevent automatic logouts by images or javascript.
|
||||||
|
* It redirects back to $_SESSION['logout_url'], if it exists.
|
||||||
|
* It is accessed via ?action=logout;session_var=...
|
||||||
|
*
|
||||||
|
* @param bool $internal If true, it doesn't check the session
|
||||||
|
* @param bool $redirect Whether or not to redirect the user after they log out
|
||||||
|
*/
|
||||||
|
function Logout($internal = false, $redirect = true)
|
||||||
|
{
|
||||||
|
global $sourcedir, $user_info, $user_settings, $context, $smcFunc, $cookiename, $modSettings;
|
||||||
|
|
||||||
|
// They decided to cancel a logout?
|
||||||
|
if (!$internal && isset($_POST['cancel']) && isset($_GET[$context['session_var']]))
|
||||||
|
redirectexit(!empty($_SESSION['logout_return']) ? $_SESSION['logout_return'] : '');
|
||||||
|
// Prompt to logout?
|
||||||
|
elseif (!$internal && !isset($_GET[$context['session_var']]))
|
||||||
|
{
|
||||||
|
loadLanguage('Login');
|
||||||
|
loadTemplate('Login');
|
||||||
|
$context['sub_template'] = 'logout';
|
||||||
|
|
||||||
|
// This came from a valid hashed return url. Or something that knows our secrets...
|
||||||
|
if (!empty($_REQUEST['return_hash']) && !empty($_REQUEST['return_to']) && hash_hmac('sha1', un_htmlspecialchars($_REQUEST['return_to']), get_auth_secret()) == $_REQUEST['return_hash'])
|
||||||
|
{
|
||||||
|
$_SESSION['logout_url'] = un_htmlspecialchars($_REQUEST['return_to']);
|
||||||
|
$_SESSION['logout_return'] = $_SESSION['logout_url'];
|
||||||
|
}
|
||||||
|
// Setup the return address.
|
||||||
|
elseif (isset($_SESSION['old_url']))
|
||||||
|
$_SESSION['logout_return'] = $_SESSION['old_url'];
|
||||||
|
|
||||||
|
// Don't go any further.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make sure they aren't being auto-logged out.
|
||||||
|
elseif (!$internal && isset($_GET[$context['session_var']]))
|
||||||
|
checkSession('get');
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Auth.php');
|
||||||
|
|
||||||
|
if (isset($_SESSION['pack_ftp']))
|
||||||
|
$_SESSION['pack_ftp'] = null;
|
||||||
|
|
||||||
|
// It won't be first login anymore.
|
||||||
|
unset($_SESSION['first_login']);
|
||||||
|
|
||||||
|
// Just ensure they aren't a guest!
|
||||||
|
if (!$user_info['is_guest'])
|
||||||
|
{
|
||||||
|
// Pass the logout information to integrations.
|
||||||
|
call_integration_hook('integrate_logout', array($user_settings['member_name']));
|
||||||
|
|
||||||
|
// If you log out, you aren't online anymore :P.
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_online
|
||||||
|
WHERE id_member = {int:current_member}',
|
||||||
|
array(
|
||||||
|
'current_member' => $user_info['id'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['log_time'] = 0;
|
||||||
|
|
||||||
|
// Empty the cookie! (set it in the past, and for id_member = 0)
|
||||||
|
setLoginCookie(-3600, 0);
|
||||||
|
|
||||||
|
// And some other housekeeping while we're at it.
|
||||||
|
$salt = bin2hex($smcFunc['random_bytes'](16));
|
||||||
|
if (!empty($user_info['id']))
|
||||||
|
updateMemberData($user_info['id'], array('password_salt' => $salt));
|
||||||
|
|
||||||
|
if (!empty($modSettings['tfa_mode']) && !empty($user_info['id']) && !empty($_COOKIE[$cookiename . '_tfa']))
|
||||||
|
{
|
||||||
|
list (,, $exp) = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
|
||||||
|
setTFACookie((int) $exp - time(), $salt, hash_salt($user_settings['tfa_backup'], $salt));
|
||||||
|
}
|
||||||
|
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
// Off to the merry board index we go!
|
||||||
|
if ($redirect)
|
||||||
|
{
|
||||||
|
if (empty($_SESSION['logout_url']))
|
||||||
|
redirectexit('', $context['server']['needs_login_fix']);
|
||||||
|
elseif (!empty($_SESSION['logout_url']) && (strpos($_SESSION['logout_url'], 'http://') === false && strpos($_SESSION['logout_url'], 'https://') === false))
|
||||||
|
{
|
||||||
|
unset ($_SESSION['logout_url']);
|
||||||
|
redirectexit();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$temp = $_SESSION['logout_url'];
|
||||||
|
unset($_SESSION['logout_url']);
|
||||||
|
|
||||||
|
redirectexit($temp, $context['server']['needs_login_fix']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5 Encryption used for older passwords. (SMF 1.0.x/YaBB SE 1.5.x hashing)
|
||||||
|
*
|
||||||
|
* @param string $data The data
|
||||||
|
* @param string $key The key
|
||||||
|
* @return string The HMAC MD5 of data with key
|
||||||
|
*/
|
||||||
|
function md5_hmac($data, $key)
|
||||||
|
{
|
||||||
|
$key = str_pad(strlen($key) <= 64 ? $key : pack('H*', md5($key)), 64, chr(0x00));
|
||||||
|
return md5(($key ^ str_repeat(chr(0x5c), 64)) . pack('H*', md5(($key ^ str_repeat(chr(0x36), 64)) . $data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom encryption for phpBB3 based passwords.
|
||||||
|
*
|
||||||
|
* @param string $passwd The raw (unhashed) password
|
||||||
|
* @param string $passwd_hash The hashed password
|
||||||
|
* @return string The hashed version of $passwd
|
||||||
|
*/
|
||||||
|
function phpBB3_password_check($passwd, $passwd_hash)
|
||||||
|
{
|
||||||
|
// Too long or too short?
|
||||||
|
if (strlen($passwd_hash) != 34)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Range of characters allowed.
|
||||||
|
$range = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
$strpos = strpos($range, $passwd_hash[3]);
|
||||||
|
$count = 1 << $strpos;
|
||||||
|
$salt = substr($passwd_hash, 4, 8);
|
||||||
|
|
||||||
|
$hash = md5($salt . $passwd, true);
|
||||||
|
for (; $count != 0; --$count)
|
||||||
|
$hash = md5($hash . $passwd, true);
|
||||||
|
|
||||||
|
$output = substr($passwd_hash, 0, 12);
|
||||||
|
$i = 0;
|
||||||
|
while ($i < 16)
|
||||||
|
{
|
||||||
|
$value = ord($hash[$i++]);
|
||||||
|
$output .= $range[$value & 0x3f];
|
||||||
|
|
||||||
|
if ($i < 16)
|
||||||
|
$value |= ord($hash[$i]) << 8;
|
||||||
|
|
||||||
|
$output .= $range[($value >> 6) & 0x3f];
|
||||||
|
|
||||||
|
if ($i++ >= 16)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if ($i < 16)
|
||||||
|
$value |= ord($hash[$i]) << 16;
|
||||||
|
|
||||||
|
$output .= $range[($value >> 12) & 0x3f];
|
||||||
|
|
||||||
|
if ($i++ >= 16)
|
||||||
|
break;
|
||||||
|
|
||||||
|
$output .= $range[($value >> 18) & 0x3f];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return now.
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This protects against brute force attacks on a member's password.
|
||||||
|
* Importantly, even if the password was right we DON'T TELL THEM!
|
||||||
|
*
|
||||||
|
* @param int $id_member The ID of the member
|
||||||
|
* @param string $member_name The name of the member.
|
||||||
|
* @param bool|string $password_flood_value False if we don't have a flood value, otherwise a string with a timestamp and number of tries separated by a |
|
||||||
|
* @param bool $was_correct Whether or not the password was correct
|
||||||
|
* @param bool $tfa Whether we're validating for two-factor authentication
|
||||||
|
*/
|
||||||
|
function validatePasswordFlood($id_member, $member_name, $password_flood_value = false, $was_correct = false, $tfa = false)
|
||||||
|
{
|
||||||
|
global $cookiename, $sourcedir;
|
||||||
|
|
||||||
|
// As this is only brute protection, we allow 5 attempts every 10 seconds.
|
||||||
|
|
||||||
|
// Destroy any session or cookie data about this member, as they validated wrong.
|
||||||
|
// Only if they're not validating for 2FA
|
||||||
|
if (!$tfa)
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-Auth.php');
|
||||||
|
setLoginCookie(-3600, 0);
|
||||||
|
|
||||||
|
if (isset($_SESSION['login_' . $cookiename]))
|
||||||
|
unset($_SESSION['login_' . $cookiename]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a member!
|
||||||
|
if (!$id_member)
|
||||||
|
{
|
||||||
|
// Redirect back!
|
||||||
|
redirectexit();
|
||||||
|
|
||||||
|
// Probably not needed, but still make sure...
|
||||||
|
fatal_lang_error('no_access', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right, have we got a flood value?
|
||||||
|
if ($password_flood_value !== false)
|
||||||
|
@list ($time_stamp, $number_tries) = explode('|', $password_flood_value);
|
||||||
|
|
||||||
|
// Timestamp or number of tries invalid?
|
||||||
|
if (empty($number_tries) || empty($time_stamp))
|
||||||
|
{
|
||||||
|
$number_tries = 0;
|
||||||
|
$time_stamp = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
// They've failed logging in already
|
||||||
|
if (!empty($number_tries))
|
||||||
|
{
|
||||||
|
// Give them less chances if they failed before
|
||||||
|
$number_tries = $time_stamp < time() - 20 ? 2 : $number_tries;
|
||||||
|
|
||||||
|
// They are trying too fast, make them wait longer
|
||||||
|
if ($time_stamp < time() - 10)
|
||||||
|
$time_stamp = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
$number_tries++;
|
||||||
|
|
||||||
|
// Broken the law?
|
||||||
|
if ($number_tries > 5)
|
||||||
|
fatal_lang_error('login_threshold_brute_fail', 'login', [$member_name]);
|
||||||
|
|
||||||
|
// Otherwise set the members data. If they correct on their first attempt then we actually clear it, otherwise we set it!
|
||||||
|
updateMemberData($id_member, array('passwd_flood' => $was_correct && $number_tries == 1 ? '' : $time_stamp . '|' . $number_tries));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
573
Sources/Logging.php
Normal file
|
@ -0,0 +1,573 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file concerns itself with logging, whether in the database or files.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2023 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.4
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put this user in the online log.
|
||||||
|
*
|
||||||
|
* @param bool $force Whether to force logging the data
|
||||||
|
*/
|
||||||
|
function writeLog($force = false)
|
||||||
|
{
|
||||||
|
global $user_info, $user_settings, $context, $modSettings, $settings, $topic, $board, $smcFunc, $sourcedir, $cache_enable;
|
||||||
|
|
||||||
|
// If we are showing who is viewing a topic, let's see if we are, and force an update if so - to make it accurate.
|
||||||
|
if (!empty($settings['display_who_viewing']) && ($topic || $board))
|
||||||
|
{
|
||||||
|
// Take the opposite approach!
|
||||||
|
$force = true;
|
||||||
|
// Don't update for every page - this isn't wholly accurate but who cares.
|
||||||
|
if ($topic)
|
||||||
|
{
|
||||||
|
if (isset($_SESSION['last_topic_id']) && $_SESSION['last_topic_id'] == $topic)
|
||||||
|
$force = false;
|
||||||
|
$_SESSION['last_topic_id'] = $topic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are they a spider we should be tracking? Mode = 1 gets tracked on its spider check...
|
||||||
|
if (!empty($user_info['possibly_robot']) && !empty($modSettings['spider_mode']) && $modSettings['spider_mode'] > 1)
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/ManageSearchEngines.php');
|
||||||
|
logSpider();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't mark them as online more than every so often.
|
||||||
|
if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= (time() - 8) && !$force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!empty($modSettings['who_enabled']))
|
||||||
|
{
|
||||||
|
$encoded_get = truncate_array($_GET) + array('USER_AGENT' => mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 128));
|
||||||
|
|
||||||
|
// In the case of a dlattach action, session_var may not be set.
|
||||||
|
if (!isset($context['session_var']))
|
||||||
|
$context['session_var'] = $_SESSION['session_var'];
|
||||||
|
|
||||||
|
unset($encoded_get['sesc'], $encoded_get[$context['session_var']]);
|
||||||
|
$encoded_get = $smcFunc['json_encode']($encoded_get);
|
||||||
|
|
||||||
|
// Sometimes folks mess with USER_AGENT & $_GET data, so one last check to avoid 'data too long' errors
|
||||||
|
if (mb_strlen($encoded_get) > 2048)
|
||||||
|
$encoded_get = '';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$encoded_get = '';
|
||||||
|
|
||||||
|
// Guests use 0, members use their session ID.
|
||||||
|
$session_id = $user_info['is_guest'] ? 'ip' . $user_info['ip'] : session_id();
|
||||||
|
|
||||||
|
// Grab the last all-of-SMF-specific log_online deletion time.
|
||||||
|
$do_delete = cache_get_data('log_online-update', 30) < time() - 30;
|
||||||
|
|
||||||
|
// If the last click wasn't a long time ago, and there was a last click...
|
||||||
|
if (!empty($_SESSION['log_time']) && $_SESSION['log_time'] >= time() - $modSettings['lastActive'] * 20)
|
||||||
|
{
|
||||||
|
if ($do_delete)
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('delete_log_online_interval', '
|
||||||
|
DELETE FROM {db_prefix}log_online
|
||||||
|
WHERE log_time < {int:log_time}
|
||||||
|
AND session != {string:session}',
|
||||||
|
array(
|
||||||
|
'log_time' => time() - $modSettings['lastActive'] * 60,
|
||||||
|
'session' => $session_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache when we did it last.
|
||||||
|
cache_put_data('log_online-update', time(), 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}log_online
|
||||||
|
SET log_time = {int:log_time}, ip = {inet:ip}, url = {string:url}
|
||||||
|
WHERE session = {string:session}',
|
||||||
|
array(
|
||||||
|
'log_time' => time(),
|
||||||
|
'ip' => $user_info['ip'],
|
||||||
|
'url' => $encoded_get,
|
||||||
|
'session' => $session_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Guess it got deleted.
|
||||||
|
if ($smcFunc['db_affected_rows']() == 0)
|
||||||
|
$_SESSION['log_time'] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$_SESSION['log_time'] = 0;
|
||||||
|
|
||||||
|
// Otherwise, we have to delete and insert.
|
||||||
|
if (empty($_SESSION['log_time']))
|
||||||
|
{
|
||||||
|
if ($do_delete || !empty($user_info['id']))
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_online
|
||||||
|
WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') . ($do_delete && !empty($user_info['id']) ? ' OR ' : '') . (empty($user_info['id']) ? '' : 'id_member = {int:current_member}'),
|
||||||
|
array(
|
||||||
|
'current_member' => $user_info['id'],
|
||||||
|
'log_time' => time() - $modSettings['lastActive'] * 60,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$smcFunc['db_insert']($do_delete ? 'ignore' : 'replace',
|
||||||
|
'{db_prefix}log_online',
|
||||||
|
array('session' => 'string', 'id_member' => 'int', 'id_spider' => 'int', 'log_time' => 'int', 'ip' => 'inet', 'url' => 'string'),
|
||||||
|
array($session_id, $user_info['id'], empty($_SESSION['id_robot']) ? 0 : $_SESSION['id_robot'], time(), $user_info['ip'], $encoded_get),
|
||||||
|
array('session')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark your session as being logged.
|
||||||
|
$_SESSION['log_time'] = time();
|
||||||
|
|
||||||
|
// Well, they are online now.
|
||||||
|
if (empty($_SESSION['timeOnlineUpdated']))
|
||||||
|
$_SESSION['timeOnlineUpdated'] = time();
|
||||||
|
|
||||||
|
// Set their login time, if not already done within the last minute.
|
||||||
|
if (SMF != 'SSI' && !empty($user_info['last_login']) && $user_info['last_login'] < time() - 60 && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))))
|
||||||
|
{
|
||||||
|
// Don't count longer than 15 minutes.
|
||||||
|
if (time() - $_SESSION['timeOnlineUpdated'] > 60 * 15)
|
||||||
|
$_SESSION['timeOnlineUpdated'] = time();
|
||||||
|
|
||||||
|
$user_settings['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated'];
|
||||||
|
updateMemberData($user_info['id'], array('last_login' => time(), 'member_ip' => $user_info['ip'], 'member_ip2' => $_SERVER['BAN_CHECK_IP'], 'total_time_logged_in' => $user_settings['total_time_logged_in']));
|
||||||
|
|
||||||
|
if (!empty($cache_enable) && $cache_enable >= 2)
|
||||||
|
cache_put_data('user_settings-' . $user_info['id'], $user_settings, 60);
|
||||||
|
|
||||||
|
$user_info['total_time_logged_in'] += time() - $_SESSION['timeOnlineUpdated'];
|
||||||
|
$_SESSION['timeOnlineUpdated'] = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the last database error into a file.
|
||||||
|
* Attempts to use the backup file first, to store the last database error
|
||||||
|
* and only update db_last_error.php if the first was successful.
|
||||||
|
*/
|
||||||
|
function logLastDatabaseError()
|
||||||
|
{
|
||||||
|
global $boarddir, $cachedir;
|
||||||
|
|
||||||
|
// Make a note of the last modified time in case someone does this before us
|
||||||
|
$last_db_error_change = @filemtime($cachedir . '/db_last_error.php');
|
||||||
|
|
||||||
|
// save the old file before we do anything
|
||||||
|
$file = $cachedir . '/db_last_error.php';
|
||||||
|
$dberror_backup_fail = !@is_writable($cachedir . '/db_last_error_bak.php') || !@copy($file, $cachedir . '/db_last_error_bak.php');
|
||||||
|
$dberror_backup_fail = !$dberror_backup_fail ? (!file_exists($cachedir . '/db_last_error_bak.php') || filesize($cachedir . '/db_last_error_bak.php') === 0) : $dberror_backup_fail;
|
||||||
|
|
||||||
|
clearstatcache();
|
||||||
|
if (filemtime($cachedir . '/db_last_error.php') === $last_db_error_change)
|
||||||
|
{
|
||||||
|
// Write the change
|
||||||
|
$write_db_change = '<' . '?' . "php\n" . '$db_last_error = ' . time() . ';' . "\n" . '?' . '>';
|
||||||
|
$written_bytes = file_put_contents($cachedir . '/db_last_error.php', $write_db_change, LOCK_EX);
|
||||||
|
|
||||||
|
// survey says ...
|
||||||
|
if ($written_bytes !== strlen($write_db_change) && !$dberror_backup_fail)
|
||||||
|
{
|
||||||
|
// Oops. maybe we have no more disk space left, or some other troubles, troubles...
|
||||||
|
// Copy the file back and run for your life!
|
||||||
|
@copy($cachedir . '/db_last_error_bak.php', $cachedir . '/db_last_error.php');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@touch($boarddir . '/' . 'Settings.php');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function shows the debug information tracked when $db_show_debug = true
|
||||||
|
* in Settings.php
|
||||||
|
*/
|
||||||
|
function displayDebug()
|
||||||
|
{
|
||||||
|
global $context, $scripturl, $boarddir, $sourcedir, $cachedir, $settings, $modSettings;
|
||||||
|
global $db_cache, $db_count, $cache_misses, $cache_count_misses, $db_show_debug, $cache_count, $cache_hits, $smcFunc, $txt, $cache_enable;
|
||||||
|
|
||||||
|
// Add to Settings.php if you want to show the debugging information.
|
||||||
|
if (!isset($db_show_debug) || $db_show_debug !== true || (isset($_GET['action']) && $_GET['action'] == 'viewquery'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (empty($_SESSION['view_queries']))
|
||||||
|
$_SESSION['view_queries'] = 0;
|
||||||
|
if (empty($context['debug']['language_files']))
|
||||||
|
$context['debug']['language_files'] = array();
|
||||||
|
if (empty($context['debug']['sheets']))
|
||||||
|
$context['debug']['sheets'] = array();
|
||||||
|
|
||||||
|
$files = get_included_files();
|
||||||
|
$total_size = 0;
|
||||||
|
for ($i = 0, $n = count($files); $i < $n; $i++)
|
||||||
|
{
|
||||||
|
if (file_exists($files[$i]))
|
||||||
|
$total_size += filesize($files[$i]);
|
||||||
|
$files[$i] = strtr($files[$i], array($boarddir => '.', $sourcedir => '(Sources)', $cachedir => '(Cache)', $settings['actual_theme_dir'] => '(Current Theme)'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$warnings = 0;
|
||||||
|
if (!empty($db_cache))
|
||||||
|
{
|
||||||
|
foreach ($db_cache as $q => $query_data)
|
||||||
|
{
|
||||||
|
if (!empty($query_data['w']))
|
||||||
|
$warnings += count($query_data['w']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['debug'] = &$db_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gotta have valid HTML ;).
|
||||||
|
$temp = ob_get_contents();
|
||||||
|
ob_clean();
|
||||||
|
|
||||||
|
echo preg_replace('~</body>\s*</html>~', '', $temp), '
|
||||||
|
<div class="smalltext" style="text-align: left; margin: 1ex;">
|
||||||
|
', $txt['debug_browser'], $context['browser_body_id'], ' <em>(', implode('</em>, <em>', array_reverse(array_keys($context['browser'], true))), ')</em><br>
|
||||||
|
', $txt['debug_templates'], count($context['debug']['templates']), ': <em>', implode('</em>, <em>', $context['debug']['templates']), '</em>.<br>
|
||||||
|
', $txt['debug_subtemplates'], count($context['debug']['sub_templates']), ': <em>', implode('</em>, <em>', $context['debug']['sub_templates']), '</em>.<br>
|
||||||
|
', $txt['debug_language_files'], count($context['debug']['language_files']), ': <em>', implode('</em>, <em>', $context['debug']['language_files']), '</em>.<br>
|
||||||
|
', $txt['debug_stylesheets'], count($context['debug']['sheets']), ': <em>', implode('</em>, <em>', $context['debug']['sheets']), '</em>.<br>
|
||||||
|
', $txt['debug_hooks'], empty($context['debug']['hooks']) ? 0 : count($context['debug']['hooks']) . ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_hooks\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_hooks" style="display: none;"><em>' . implode('</em>, <em>', $context['debug']['hooks']), '</em></span>)', '<br>
|
||||||
|
', (isset($context['debug']['instances']) ? ($txt['debug_instances'] . (empty($context['debug']['instances']) ? 0 : count($context['debug']['instances'])) . ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_instances\').style.display = \'inline\'; this.style.display = \'none\'; return false;">' . $txt['debug_show'] . '</a><span id="debug_instances" style="display: none;"><em>' . implode('</em>, <em>', array_keys($context['debug']['instances'])) . '</em></span>)' . '<br>') : ''), '
|
||||||
|
', $txt['debug_files_included'], count($files), ' - ', round($total_size / 1024), $txt['debug_kb'], ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_include_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_include_info" style="display: none;"><em>', implode('</em>, <em>', $files), '</em></span>)<br>';
|
||||||
|
|
||||||
|
if (function_exists('memory_get_peak_usage'))
|
||||||
|
echo $txt['debug_memory_use'], ceil(memory_get_peak_usage() / 1024), $txt['debug_kb'], '<br>';
|
||||||
|
|
||||||
|
// What tokens are active?
|
||||||
|
if (isset($_SESSION['token']))
|
||||||
|
echo $txt['debug_tokens'] . '<em>' . implode(',</em> <em>', array_keys($_SESSION['token'])), '</em>.<br>';
|
||||||
|
|
||||||
|
if (!empty($cache_enable) && !empty($cache_hits))
|
||||||
|
{
|
||||||
|
$missed_entries = array();
|
||||||
|
$entries = array();
|
||||||
|
$total_t = 0;
|
||||||
|
$total_s = 0;
|
||||||
|
foreach ($cache_hits as $cache_hit)
|
||||||
|
{
|
||||||
|
$entries[] = $cache_hit['d'] . ' ' . $cache_hit['k'] . ': ' . sprintf($txt['debug_cache_seconds_bytes'], comma_format($cache_hit['t'], 5), $cache_hit['s']);
|
||||||
|
$total_t += $cache_hit['t'];
|
||||||
|
$total_s += $cache_hit['s'];
|
||||||
|
}
|
||||||
|
if (!isset($cache_misses))
|
||||||
|
$cache_misses = array();
|
||||||
|
foreach ($cache_misses as $missed)
|
||||||
|
$missed_entries[] = $missed['d'] . ' ' . $missed['k'];
|
||||||
|
|
||||||
|
echo '
|
||||||
|
', $txt['debug_cache_hits'], $cache_count, ': ', sprintf($txt['debug_cache_seconds_bytes_total'], comma_format($total_t, 5), comma_format($total_s)), ' (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_cache_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_cache_info" style="display: none;"><em>', implode('</em>, <em>', $entries), '</em></span>)<br>
|
||||||
|
', $txt['debug_cache_misses'], $cache_count_misses, ': (<a href="javascript:void(0);" onclick="document.getElementById(\'debug_cache_misses_info\').style.display = \'inline\'; this.style.display = \'none\'; return false;">', $txt['debug_show'], '</a><span id="debug_cache_misses_info" style="display: none;"><em>', implode('</em>, <em>', $missed_entries), '</em></span>)<br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '
|
||||||
|
<a href="', $scripturl, '?action=viewquery" target="_blank" rel="noopener">', $warnings == 0 ? sprintf($txt['debug_queries_used'], (int) $db_count) : sprintf($txt['debug_queries_used_and_warnings'], (int) $db_count, $warnings), '</a><br>
|
||||||
|
<br>';
|
||||||
|
|
||||||
|
if ($_SESSION['view_queries'] == 1 && !empty($db_cache))
|
||||||
|
foreach ($db_cache as $q => $query_data)
|
||||||
|
{
|
||||||
|
$is_select = strpos(trim($query_data['q']), 'SELECT') === 0 || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($query_data['q'])) != 0 || strpos(trim($query_data['q']), 'WITH') === 0;
|
||||||
|
// Temporary tables created in earlier queries are not explainable.
|
||||||
|
if ($is_select)
|
||||||
|
{
|
||||||
|
foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp)
|
||||||
|
if (strpos(trim($query_data['q']), $tmp) !== false)
|
||||||
|
{
|
||||||
|
$is_select = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// But actual creation of the temporary tables are.
|
||||||
|
elseif (preg_match('~^CREATE TEMPORARY TABLE .+?SELECT .+$~s', trim($query_data['q'])) != 0)
|
||||||
|
$is_select = true;
|
||||||
|
|
||||||
|
// Make the filenames look a bit better.
|
||||||
|
if (isset($query_data['f']))
|
||||||
|
$query_data['f'] = preg_replace('~^' . preg_quote($boarddir, '~') . '~', '...', $query_data['f']);
|
||||||
|
|
||||||
|
echo '
|
||||||
|
<strong>', $is_select ? '<a href="' . $scripturl . '?action=viewquery;qq=' . ($q + 1) . '#qq' . $q . '" target="_blank" rel="noopener" style="text-decoration: none;">' : '', nl2br(str_replace("\t", ' ', $smcFunc['htmlspecialchars'](ltrim($query_data['q'], "\n\r")))) . ($is_select ? '</a></strong>' : '</strong>') . '<br>
|
||||||
|
';
|
||||||
|
if (!empty($query_data['f']) && !empty($query_data['l']))
|
||||||
|
echo sprintf($txt['debug_query_in_line'], $query_data['f'], $query_data['l']);
|
||||||
|
|
||||||
|
if (isset($query_data['s'], $query_data['t']) && isset($txt['debug_query_which_took_at']))
|
||||||
|
echo sprintf($txt['debug_query_which_took_at'], round($query_data['t'], 8), round($query_data['s'], 8)) . '<br>';
|
||||||
|
elseif (isset($query_data['t']))
|
||||||
|
echo sprintf($txt['debug_query_which_took'], round($query_data['t'], 8)) . '<br>';
|
||||||
|
echo '
|
||||||
|
<br>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '
|
||||||
|
<a href="' . $scripturl . '?action=viewquery;sa=hide">', $txt['debug_' . (empty($_SESSION['view_queries']) ? 'show' : 'hide') . '_queries'], '</a>
|
||||||
|
</div></body></html>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track Statistics.
|
||||||
|
* Caches statistics changes, and flushes them if you pass nothing.
|
||||||
|
* If '+' is used as a value, it will be incremented.
|
||||||
|
* It does not actually commit the changes until the end of the page view.
|
||||||
|
* It depends on the trackStats setting.
|
||||||
|
*
|
||||||
|
* @param array $stats An array of data
|
||||||
|
* @return bool Whether or not the info was updated successfully
|
||||||
|
*/
|
||||||
|
function trackStats($stats = array())
|
||||||
|
{
|
||||||
|
global $modSettings, $smcFunc;
|
||||||
|
static $cache_stats = array();
|
||||||
|
|
||||||
|
if (empty($modSettings['trackStats']))
|
||||||
|
return false;
|
||||||
|
if (!empty($stats))
|
||||||
|
return $cache_stats = array_merge($cache_stats, $stats);
|
||||||
|
elseif (empty($cache_stats))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$setStringUpdate = '';
|
||||||
|
$insert_keys = array();
|
||||||
|
$date = smf_strftime('%Y-%m-%d', time());
|
||||||
|
$update_parameters = array(
|
||||||
|
'current_date' => $date,
|
||||||
|
);
|
||||||
|
foreach ($cache_stats as $field => $change)
|
||||||
|
{
|
||||||
|
$setStringUpdate .= '
|
||||||
|
' . $field . ' = ' . ($change === '+' ? $field . ' + 1' : '{int:' . $field . '}') . ',';
|
||||||
|
|
||||||
|
if ($change === '+')
|
||||||
|
$cache_stats[$field] = 1;
|
||||||
|
else
|
||||||
|
$update_parameters[$field] = $change;
|
||||||
|
$insert_keys[$field] = 'int';
|
||||||
|
}
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}log_activity
|
||||||
|
SET' . substr($setStringUpdate, 0, -1) . '
|
||||||
|
WHERE date = {date:current_date}',
|
||||||
|
$update_parameters
|
||||||
|
);
|
||||||
|
if ($smcFunc['db_affected_rows']() == 0)
|
||||||
|
{
|
||||||
|
$smcFunc['db_insert']('ignore',
|
||||||
|
'{db_prefix}log_activity',
|
||||||
|
array_merge($insert_keys, array('date' => 'date')),
|
||||||
|
array_merge($cache_stats, array($date)),
|
||||||
|
array('date')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't do this again.
|
||||||
|
$cache_stats = array();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function logs an action to the database. It is a
|
||||||
|
* thin wrapper around {@link logActions()}.
|
||||||
|
*
|
||||||
|
* @example logAction('remove', array('starter' => $id_member_started));
|
||||||
|
*
|
||||||
|
* @param string $action A code for the report; a list of such strings
|
||||||
|
* can be found in Modlog.{language}.php (modlog_ac_ strings)
|
||||||
|
* @param array $extra An associated array of parameters for the
|
||||||
|
* item being logged. Typically this will include 'topic' for the topic's id.
|
||||||
|
* @param string $log_type A string reflecting the type of log.
|
||||||
|
*
|
||||||
|
* @return int The ID of the row containing the logged data
|
||||||
|
*/
|
||||||
|
function logAction($action, array $extra = array(), $log_type = 'moderate')
|
||||||
|
{
|
||||||
|
return logActions(array(array(
|
||||||
|
'action' => $action,
|
||||||
|
'log_type' => $log_type,
|
||||||
|
'extra' => $extra,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log changes to the forum, such as moderation events or administrative
|
||||||
|
* changes. This behaves just like {@link logAction()} in SMF 2.0, except
|
||||||
|
* that it is designed to log multiple actions at once.
|
||||||
|
*
|
||||||
|
* SMF uses three log types:
|
||||||
|
*
|
||||||
|
* - `user` for actions executed that aren't related to
|
||||||
|
* moderation (e.g. signature or other changes from the profile);
|
||||||
|
* - `moderate` for moderation actions (e.g. topic changes);
|
||||||
|
* - `admin` for administrative actions.
|
||||||
|
*
|
||||||
|
* @param array $logs An array of log data
|
||||||
|
*
|
||||||
|
* @return int The last logged ID
|
||||||
|
*/
|
||||||
|
function logActions(array $logs)
|
||||||
|
{
|
||||||
|
global $modSettings, $user_info, $smcFunc, $sourcedir, $txt;
|
||||||
|
|
||||||
|
$inserts = array();
|
||||||
|
$log_types = array(
|
||||||
|
'moderate' => 1,
|
||||||
|
'user' => 2,
|
||||||
|
'admin' => 3,
|
||||||
|
);
|
||||||
|
$always_log = array('agreement_accepted', 'policy_accepted', 'agreement_updated', 'policy_updated');
|
||||||
|
|
||||||
|
call_integration_hook('integrate_log_types', array(&$log_types, &$always_log));
|
||||||
|
|
||||||
|
foreach ($logs as $log)
|
||||||
|
{
|
||||||
|
if (!isset($log_types[$log['log_type']]) && (empty($modSettings[$log['log_type'] . 'log_enabled']) || !in_array($log['action'], $always_log)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!is_array($log['extra']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error(sprintf($txt['logActions_not_array'], $log['action']), E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull out the parts we want to store separately, but also make sure that the data is proper
|
||||||
|
if (isset($log['extra']['topic']))
|
||||||
|
{
|
||||||
|
if (!is_numeric($log['extra']['topic']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['logActions_topic_not_numeric'], E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
$topic_id = empty($log['extra']['topic']) ? 0 : (int) $log['extra']['topic'];
|
||||||
|
unset($log['extra']['topic']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$topic_id = 0;
|
||||||
|
|
||||||
|
if (isset($log['extra']['message']))
|
||||||
|
{
|
||||||
|
if (!is_numeric($log['extra']['message']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['logActions_message_not_numeric'], E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
$msg_id = empty($log['extra']['message']) ? 0 : (int) $log['extra']['message'];
|
||||||
|
unset($log['extra']['message']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$msg_id = 0;
|
||||||
|
|
||||||
|
// @todo cache this?
|
||||||
|
// Is there an associated report on this?
|
||||||
|
if (in_array($log['action'], array('move', 'remove', 'split', 'merge')))
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_report
|
||||||
|
FROM {db_prefix}log_reported
|
||||||
|
WHERE {raw:column_name} = {int:reported}
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'column_name' => !empty($msg_id) ? 'id_msg' : 'id_topic',
|
||||||
|
'reported' => !empty($msg_id) ? $msg_id : $topic_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Alright, if we get any result back, update open reports.
|
||||||
|
if ($smcFunc['db_num_rows']($request) > 0)
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-ReportedContent.php');
|
||||||
|
updateSettings(array('last_mod_report_action' => time()));
|
||||||
|
recountOpenReports('posts');
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($log['extra']['member']) && !is_numeric($log['extra']['member']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['logActions_member_not_numeric'], E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($log['extra']['board']))
|
||||||
|
{
|
||||||
|
if (!is_numeric($log['extra']['board']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['logActions_board_not_numeric'], E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
$board_id = empty($log['extra']['board']) ? 0 : (int) $log['extra']['board'];
|
||||||
|
unset($log['extra']['board']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$board_id = 0;
|
||||||
|
|
||||||
|
if (isset($log['extra']['board_to']))
|
||||||
|
{
|
||||||
|
if (!is_numeric($log['extra']['board_to']))
|
||||||
|
{
|
||||||
|
loadLanguage('Errors');
|
||||||
|
trigger_error($txt['logActions_board_to_not_numeric'], E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
if (empty($board_id))
|
||||||
|
{
|
||||||
|
$board_id = empty($log['extra']['board_to']) ? 0 : (int) $log['extra']['board_to'];
|
||||||
|
unset($log['extra']['board_to']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($log['extra']['member_affected']))
|
||||||
|
$memID = $log['extra']['member_affected'];
|
||||||
|
else
|
||||||
|
$memID = $user_info['id'];
|
||||||
|
|
||||||
|
if (isset($user_info['ip']))
|
||||||
|
$memIP = $user_info['ip'];
|
||||||
|
else
|
||||||
|
$memIP = 'null';
|
||||||
|
|
||||||
|
$inserts[] = array(
|
||||||
|
time(), $log_types[$log['log_type']], $memID, $memIP, $log['action'],
|
||||||
|
$board_id, $topic_id, $msg_id, $smcFunc['json_encode']($log['extra']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_action = $smcFunc['db_insert']('',
|
||||||
|
'{db_prefix}log_actions',
|
||||||
|
array(
|
||||||
|
'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
|
||||||
|
'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
|
||||||
|
),
|
||||||
|
$inserts,
|
||||||
|
array('id_action'),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
return $id_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
2828
Sources/ManageAttachments.php
Normal file
2448
Sources/ManageBans.php
Normal file
909
Sources/ManageBoards.php
Normal file
|
@ -0,0 +1,909 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage and maintain the boards and categories of the forum.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main dispatcher; doesn't do anything, just delegates.
|
||||||
|
* This is the main entry point for all the manageboards admin screens.
|
||||||
|
* Called by ?action=admin;area=manageboards.
|
||||||
|
* It checks the permissions, based on the sub-action, and calls a function based on the sub-action.
|
||||||
|
*
|
||||||
|
* Uses ManageBoards language file.
|
||||||
|
*/
|
||||||
|
function ManageBoards()
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
// Everything's gonna need this.
|
||||||
|
loadLanguage('ManageBoards');
|
||||||
|
|
||||||
|
// Format: 'sub-action' => array('function', 'permission')
|
||||||
|
$subActions = array(
|
||||||
|
'board' => array('EditBoard', 'manage_boards'),
|
||||||
|
'board2' => array('EditBoard2', 'manage_boards'),
|
||||||
|
'cat' => array('EditCategory', 'manage_boards'),
|
||||||
|
'cat2' => array('EditCategory2', 'manage_boards'),
|
||||||
|
'main' => array('ManageBoardsMain', 'manage_boards'),
|
||||||
|
'move' => array('ManageBoardsMain', 'manage_boards'),
|
||||||
|
'newcat' => array('EditCategory', 'manage_boards'),
|
||||||
|
'newboard' => array('EditBoard', 'manage_boards'),
|
||||||
|
'settings' => array('EditBoardSettings', 'admin_forum'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the tabs for the template.
|
||||||
|
$context[$context['admin_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['boards_and_cats'],
|
||||||
|
'help' => 'manage_boards',
|
||||||
|
'description' => $txt['boards_and_cats_desc'],
|
||||||
|
'tabs' => array(
|
||||||
|
'main' => array(
|
||||||
|
),
|
||||||
|
'newcat' => array(
|
||||||
|
),
|
||||||
|
'settings' => array(
|
||||||
|
'description' => $txt['mboards_settings_desc'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_manage_boards', array(&$subActions));
|
||||||
|
|
||||||
|
// Default to sub action 'main' or 'settings' depending on permissions.
|
||||||
|
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : (allowedTo('manage_boards') ? 'main' : 'settings');
|
||||||
|
|
||||||
|
// Have you got the proper permissions?
|
||||||
|
isAllowedTo($subActions[$_REQUEST['sa']][1]);
|
||||||
|
|
||||||
|
call_helper($subActions[$_REQUEST['sa']][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main control panel thing, the screen showing all boards and categories.
|
||||||
|
* Called by ?action=admin;area=manageboards or ?action=admin;area=manageboards;sa=move.
|
||||||
|
* Requires manage_boards permission.
|
||||||
|
* It also handles the interface for moving boards.
|
||||||
|
*
|
||||||
|
* Uses ManageBoards template, main sub-template.
|
||||||
|
*/
|
||||||
|
function ManageBoardsMain()
|
||||||
|
{
|
||||||
|
global $txt, $context, $cat_tree, $boards, $boardList, $scripturl, $sourcedir, $smcFunc;
|
||||||
|
|
||||||
|
loadTemplate('ManageBoards');
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
|
||||||
|
if (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'move' && in_array($_REQUEST['move_to'], array('child', 'before', 'after', 'top')))
|
||||||
|
{
|
||||||
|
checkSession('get');
|
||||||
|
validateToken('admin-bm-' . (int) $_REQUEST['src_board'], 'request');
|
||||||
|
|
||||||
|
if ($_REQUEST['move_to'] === 'top')
|
||||||
|
$boardOptions = array(
|
||||||
|
'move_to' => $_REQUEST['move_to'],
|
||||||
|
'target_category' => (int) $_REQUEST['target_cat'],
|
||||||
|
'move_first_child' => true,
|
||||||
|
);
|
||||||
|
else
|
||||||
|
$boardOptions = array(
|
||||||
|
'move_to' => $_REQUEST['move_to'],
|
||||||
|
'target_board' => (int) $_REQUEST['target_board'],
|
||||||
|
'move_first_child' => true,
|
||||||
|
);
|
||||||
|
modifyBoard((int) $_REQUEST['src_board'], $boardOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBoardTree();
|
||||||
|
|
||||||
|
$context['move_board'] = !empty($_REQUEST['move']) && isset($boards[(int) $_REQUEST['move']]) ? (int) $_REQUEST['move'] : 0;
|
||||||
|
|
||||||
|
$context['categories'] = array();
|
||||||
|
foreach ($cat_tree as $catid => $tree)
|
||||||
|
{
|
||||||
|
$context['categories'][$catid] = array(
|
||||||
|
'name' => &$tree['node']['name'],
|
||||||
|
'id' => &$tree['node']['id'],
|
||||||
|
'boards' => array()
|
||||||
|
);
|
||||||
|
$move_cat = !empty($context['move_board']) && $boards[$context['move_board']]['category'] == $catid;
|
||||||
|
foreach ($boardList[$catid] as $boardid)
|
||||||
|
{
|
||||||
|
$context['categories'][$catid]['boards'][$boardid] = array(
|
||||||
|
'id' => &$boards[$boardid]['id'],
|
||||||
|
'name' => &$boards[$boardid]['name'],
|
||||||
|
'description' => &$boards[$boardid]['description'],
|
||||||
|
'child_level' => &$boards[$boardid]['level'],
|
||||||
|
'move' => $move_cat && ($boardid == $context['move_board'] || isChildOf($boardid, $context['move_board'])),
|
||||||
|
'permission_profile' => &$boards[$boardid]['profile'],
|
||||||
|
'is_redirect' => !empty($boards[$boardid]['redirect']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($context['move_board']))
|
||||||
|
{
|
||||||
|
createToken('admin-bm-' . $context['move_board'], 'request');
|
||||||
|
|
||||||
|
$context['move_title'] = sprintf($txt['mboards_select_destination'], $smcFunc['htmlspecialchars']($boards[$context['move_board']]['name']));
|
||||||
|
foreach ($cat_tree as $catid => $tree)
|
||||||
|
{
|
||||||
|
$prev_child_level = 0;
|
||||||
|
$prev_board = 0;
|
||||||
|
$stack = array();
|
||||||
|
// Just a shortcut, this is the same for all the urls
|
||||||
|
$security = $context['session_var'] . '=' . $context['session_id'] . ';' . $context['admin-bm-' . $context['move_board'] . '_token_var'] . '=' . $context['admin-bm-' . $context['move_board'] . '_token'];
|
||||||
|
foreach ($boardList[$catid] as $boardid)
|
||||||
|
{
|
||||||
|
if (!isset($context['categories'][$catid]['move_link']))
|
||||||
|
$context['categories'][$catid]['move_link'] = array(
|
||||||
|
'child_level' => 0,
|
||||||
|
'label' => $txt['mboards_order_before'] . ' \'' . $smcFunc['htmlspecialchars']($boards[$boardid]['name']) . '\'',
|
||||||
|
'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=before;' . $security,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$context['categories'][$catid]['boards'][$boardid]['move'])
|
||||||
|
$context['categories'][$catid]['boards'][$boardid]['move_links'] = array(
|
||||||
|
array(
|
||||||
|
'child_level' => $boards[$boardid]['level'],
|
||||||
|
'label' => $txt['mboards_order_after'] . '\'' . $smcFunc['htmlspecialchars']($boards[$boardid]['name']) . '\'',
|
||||||
|
'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=after;' . $security,
|
||||||
|
'class' => $boards[$boardid]['level'] > 0 ? 'above' : 'below',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'child_level' => $boards[$boardid]['level'] + 1,
|
||||||
|
'label' => $txt['mboards_order_child_of'] . ' \'' . $smcFunc['htmlspecialchars']($boards[$boardid]['name']) . '\'',
|
||||||
|
'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_board=' . $boardid . ';move_to=child;' . $security,
|
||||||
|
'class' => 'here',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$difference = $boards[$boardid]['level'] - $prev_child_level;
|
||||||
|
if ($difference == 1)
|
||||||
|
array_push($stack, !empty($context['categories'][$catid]['boards'][$prev_board]['move_links']) ? array_shift($context['categories'][$catid]['boards'][$prev_board]['move_links']) : null);
|
||||||
|
elseif ($difference < 0)
|
||||||
|
{
|
||||||
|
if (empty($context['categories'][$catid]['boards'][$prev_board]['move_links']))
|
||||||
|
$context['categories'][$catid]['boards'][$prev_board]['move_links'] = array();
|
||||||
|
for ($i = 0; $i < -$difference; $i++)
|
||||||
|
if (($temp = array_pop($stack)) != null)
|
||||||
|
array_unshift($context['categories'][$catid]['boards'][$prev_board]['move_links'], $temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$prev_board = $boardid;
|
||||||
|
$prev_child_level = $boards[$boardid]['level'];
|
||||||
|
}
|
||||||
|
if (!empty($stack) && !empty($context['categories'][$catid]['boards'][$prev_board]['move_links']))
|
||||||
|
$context['categories'][$catid]['boards'][$prev_board]['move_links'] = array_merge($stack, $context['categories'][$catid]['boards'][$prev_board]['move_links']);
|
||||||
|
elseif (!empty($stack))
|
||||||
|
$context['categories'][$catid]['boards'][$prev_board]['move_links'] = $stack;
|
||||||
|
|
||||||
|
if (empty($boardList[$catid]))
|
||||||
|
$context['categories'][$catid]['move_link'] = array(
|
||||||
|
'child_level' => 0,
|
||||||
|
'label' => $txt['mboards_order_before'] . ' \'' . $smcFunc['htmlspecialchars']($tree['node']['name']) . '\'',
|
||||||
|
'href' => $scripturl . '?action=admin;area=manageboards;sa=move;src_board=' . $context['move_board'] . ';target_cat=' . $catid . ';move_to=top;' . $security,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
call_integration_hook('integrate_boards_main');
|
||||||
|
|
||||||
|
$context['page_title'] = $txt['boards_and_cats'];
|
||||||
|
$context['can_manage_permissions'] = allowedTo('manage_permissions');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify a specific category.
|
||||||
|
* (screen for editing and repositioning a category.)
|
||||||
|
* Also used to show the confirm deletion of category screen
|
||||||
|
* (sub-template confirm_category_delete).
|
||||||
|
* Called by ?action=admin;area=manageboards;sa=cat
|
||||||
|
* Requires manage_boards permission.
|
||||||
|
*
|
||||||
|
* @uses template_modify_category()
|
||||||
|
*/
|
||||||
|
function EditCategory()
|
||||||
|
{
|
||||||
|
global $txt, $context, $cat_tree, $boardList, $boards, $smcFunc, $sourcedir;
|
||||||
|
|
||||||
|
loadTemplate('ManageBoards');
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
require_once($sourcedir . '/Subs-Editor.php');
|
||||||
|
getBoardTree();
|
||||||
|
|
||||||
|
// id_cat must be a number.... if it exists.
|
||||||
|
$_REQUEST['cat'] = isset($_REQUEST['cat']) ? (int) $_REQUEST['cat'] : 0;
|
||||||
|
|
||||||
|
// Start with one - "In first place".
|
||||||
|
$context['category_order'] = array(
|
||||||
|
array(
|
||||||
|
'id' => 0,
|
||||||
|
'name' => $txt['mboards_order_first'],
|
||||||
|
'selected' => !empty($_REQUEST['cat']) ? $cat_tree[$_REQUEST['cat']]['is_first'] : false,
|
||||||
|
'true_name' => ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If this is a new category set up some defaults.
|
||||||
|
if ($_REQUEST['sa'] == 'newcat')
|
||||||
|
{
|
||||||
|
$context['category'] = array(
|
||||||
|
'id' => 0,
|
||||||
|
'name' => $txt['mboards_new_cat_name'],
|
||||||
|
'editable_name' => $smcFunc['htmlspecialchars']($txt['mboards_new_cat_name']),
|
||||||
|
'description' => '',
|
||||||
|
'can_collapse' => true,
|
||||||
|
'is_new' => true,
|
||||||
|
'is_empty' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Category doesn't exist, man... sorry.
|
||||||
|
elseif (!isset($cat_tree[$_REQUEST['cat']]))
|
||||||
|
redirectexit('action=admin;area=manageboards');
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['category'] = array(
|
||||||
|
'id' => $_REQUEST['cat'],
|
||||||
|
'name' => $cat_tree[$_REQUEST['cat']]['node']['name'],
|
||||||
|
'editable_name' => $cat_tree[$_REQUEST['cat']]['node']['name'],
|
||||||
|
'description' => $cat_tree[$_REQUEST['cat']]['node']['description'],
|
||||||
|
'can_collapse' => !empty($cat_tree[$_REQUEST['cat']]['node']['can_collapse']),
|
||||||
|
'children' => array(),
|
||||||
|
'is_empty' => empty($cat_tree[$_REQUEST['cat']]['children'])
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($boardList[$_REQUEST['cat']] as $child_board)
|
||||||
|
$context['category']['children'][] = str_repeat('-', $boards[$child_board]['level']) . ' ' . $boards[$child_board]['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$prevCat = 0;
|
||||||
|
foreach ($cat_tree as $catid => $tree)
|
||||||
|
{
|
||||||
|
if ($catid == $_REQUEST['cat'] && $prevCat > 0)
|
||||||
|
$context['category_order'][$prevCat]['selected'] = true;
|
||||||
|
elseif ($catid != $_REQUEST['cat'])
|
||||||
|
$context['category_order'][$catid] = array(
|
||||||
|
'id' => $catid,
|
||||||
|
'name' => $txt['mboards_order_after'] . $tree['node']['name'],
|
||||||
|
'selected' => false,
|
||||||
|
'true_name' => $tree['node']['name']
|
||||||
|
);
|
||||||
|
$prevCat = $catid;
|
||||||
|
}
|
||||||
|
if (!isset($_REQUEST['delete']))
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'modify_category';
|
||||||
|
$context['page_title'] = $_REQUEST['sa'] == 'newcat' ? $txt['mboards_new_cat_name'] : $txt['cat_edit'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'confirm_category_delete';
|
||||||
|
$context['page_title'] = $txt['mboards_delete_cat'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a special token.
|
||||||
|
createToken('admin-bc-' . $_REQUEST['cat']);
|
||||||
|
$context['token_check'] = 'admin-bc-' . $_REQUEST['cat'];
|
||||||
|
|
||||||
|
call_integration_hook('integrate_edit_category');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling a submitted form saving the category.
|
||||||
|
* (complete the modifications to a specific category.)
|
||||||
|
* It also handles deletion of a category.
|
||||||
|
* It requires manage_boards permission.
|
||||||
|
* Called by ?action=admin;area=manageboards;sa=cat2
|
||||||
|
* Redirects to ?action=admin;area=manageboards.
|
||||||
|
*/
|
||||||
|
function EditCategory2()
|
||||||
|
{
|
||||||
|
global $sourcedir, $smcFunc, $context;
|
||||||
|
|
||||||
|
checkSession();
|
||||||
|
validateToken('admin-bc-' . $_REQUEST['cat']);
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Categories.php');
|
||||||
|
require_once($sourcedir . '/Subs-Editor.php');
|
||||||
|
|
||||||
|
$_POST['cat'] = (int) $_POST['cat'];
|
||||||
|
|
||||||
|
// Add a new category or modify an existing one..
|
||||||
|
if (isset($_POST['edit']) || isset($_POST['add']))
|
||||||
|
{
|
||||||
|
$catOptions = array();
|
||||||
|
|
||||||
|
if (isset($_POST['cat_order']))
|
||||||
|
$catOptions['move_after'] = (int) $_POST['cat_order'];
|
||||||
|
|
||||||
|
// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest
|
||||||
|
$catOptions['cat_name'] = $smcFunc['htmlspecialchars'](strip_tags($_POST['cat_name']));
|
||||||
|
$catOptions['cat_desc'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['cat_desc'])));
|
||||||
|
$catOptions['is_collapsible'] = isset($_POST['collapse']);
|
||||||
|
|
||||||
|
if (isset($_POST['add']))
|
||||||
|
createCategory($catOptions);
|
||||||
|
|
||||||
|
else
|
||||||
|
modifyCategory($_POST['cat'], $catOptions);
|
||||||
|
}
|
||||||
|
// If they want to delete - first give them confirmation.
|
||||||
|
elseif (isset($_POST['delete']) && !isset($_POST['confirmation']) && !isset($_POST['empty']))
|
||||||
|
{
|
||||||
|
EditCategory();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Delete the category!
|
||||||
|
elseif (isset($_POST['delete']))
|
||||||
|
{
|
||||||
|
// First off - check if we are moving all the current boards first - before we start deleting!
|
||||||
|
if (isset($_POST['delete_action']) && $_POST['delete_action'] == 1)
|
||||||
|
{
|
||||||
|
if (empty($_POST['cat_to']))
|
||||||
|
fatal_lang_error('mboards_delete_error');
|
||||||
|
|
||||||
|
deleteCategories(array($_POST['cat']), (int) $_POST['cat_to']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
deleteCategories(array($_POST['cat']));
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectexit('action=admin;area=manageboards');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify a specific board...
|
||||||
|
* screen for editing and repositioning a board.
|
||||||
|
* called by ?action=admin;area=manageboards;sa=board
|
||||||
|
* uses the modify_board sub-template of the ManageBoards template.
|
||||||
|
* requires manage_boards permission.
|
||||||
|
* also used to show the confirm deletion of category screen (sub-template confirm_board_delete).
|
||||||
|
*/
|
||||||
|
function EditBoard()
|
||||||
|
{
|
||||||
|
global $txt, $context, $cat_tree, $boards, $boardList;
|
||||||
|
global $sourcedir, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
loadTemplate('ManageBoards');
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
require_once($sourcedir . '/Subs-Editor.php');
|
||||||
|
getBoardTree();
|
||||||
|
|
||||||
|
// For editing the profile we'll need this.
|
||||||
|
loadLanguage('ManagePermissions');
|
||||||
|
require_once($sourcedir . '/ManagePermissions.php');
|
||||||
|
loadPermissionProfiles();
|
||||||
|
|
||||||
|
// People with manage-boards are special.
|
||||||
|
require_once($sourcedir . '/Subs-Members.php');
|
||||||
|
$groups = groupsAllowedTo('manage_boards', null);
|
||||||
|
$context['board_managers'] = $groups['allowed']; // We don't need *all* this in $context.
|
||||||
|
|
||||||
|
// id_board must be a number....
|
||||||
|
$_REQUEST['boardid'] = isset($_REQUEST['boardid']) ? (int) $_REQUEST['boardid'] : 0;
|
||||||
|
|
||||||
|
if (!isset($boards[$_REQUEST['boardid']]))
|
||||||
|
{
|
||||||
|
$_REQUEST['boardid'] = 0;
|
||||||
|
$_REQUEST['sa'] = 'newboard';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('newboard' === $_REQUEST['sa'])
|
||||||
|
{
|
||||||
|
// Category doesn't exist, man... sorry.
|
||||||
|
if (empty($_REQUEST['cat']))
|
||||||
|
redirectexit('action=admin;area=manageboards');
|
||||||
|
|
||||||
|
// Some things that need to be setup for a new board.
|
||||||
|
$curBoard = array(
|
||||||
|
'member_groups' => array(0, -1),
|
||||||
|
'deny_groups' => array(),
|
||||||
|
'category' => (int) $_REQUEST['cat']
|
||||||
|
);
|
||||||
|
$context['board_order'] = array();
|
||||||
|
$context['board'] = array(
|
||||||
|
'is_new' => true,
|
||||||
|
'id' => 0,
|
||||||
|
'name' => $txt['mboards_new_board_name'],
|
||||||
|
'description' => '',
|
||||||
|
'count_posts' => 1,
|
||||||
|
'posts' => 0,
|
||||||
|
'topics' => 0,
|
||||||
|
'theme' => 0,
|
||||||
|
'profile' => 1,
|
||||||
|
'override_theme' => 0,
|
||||||
|
'redirect' => '',
|
||||||
|
'category' => (int) $_REQUEST['cat'],
|
||||||
|
'no_children' => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Just some easy shortcuts.
|
||||||
|
$curBoard = &$boards[$_REQUEST['boardid']];
|
||||||
|
$context['board'] = $boards[$_REQUEST['boardid']];
|
||||||
|
$context['board']['no_children'] = empty($boards[$_REQUEST['boardid']]['tree']['children']);
|
||||||
|
$context['board']['is_recycle'] = !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $context['board']['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we may have come from the permissions screen keep track of where we should go on save.
|
||||||
|
$context['redirect_location'] = isset($_GET['rid']) && $_GET['rid'] == 'permissions' ? 'permissions' : 'boards';
|
||||||
|
|
||||||
|
// We might need this to hide links to certain areas.
|
||||||
|
$context['can_manage_permissions'] = allowedTo('manage_permissions');
|
||||||
|
|
||||||
|
// Default membergroups.
|
||||||
|
$context['groups'] = array(
|
||||||
|
-1 => array(
|
||||||
|
'id' => '-1',
|
||||||
|
'name' => $txt['parent_guests_only'],
|
||||||
|
'allow' => in_array('-1', $curBoard['member_groups']),
|
||||||
|
'deny' => in_array('-1', $curBoard['deny_groups']),
|
||||||
|
'is_post_group' => false,
|
||||||
|
),
|
||||||
|
0 => array(
|
||||||
|
'id' => '0',
|
||||||
|
'name' => $txt['parent_members_only'],
|
||||||
|
'allow' => in_array('0', $curBoard['member_groups']),
|
||||||
|
'deny' => in_array('0', $curBoard['deny_groups']),
|
||||||
|
'is_post_group' => false,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load membergroups.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT group_name, id_group, min_posts
|
||||||
|
FROM {db_prefix}membergroups
|
||||||
|
WHERE id_group > {int:moderator_group} OR id_group = {int:global_moderator}
|
||||||
|
ORDER BY min_posts, id_group != {int:global_moderator}, group_name',
|
||||||
|
array(
|
||||||
|
'moderator_group' => 3,
|
||||||
|
'global_moderator' => 2,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
if ($_REQUEST['sa'] == 'newboard' && $row['min_posts'] == -1)
|
||||||
|
$curBoard['member_groups'][] = $row['id_group'];
|
||||||
|
|
||||||
|
$context['groups'][(int) $row['id_group']] = array(
|
||||||
|
'id' => $row['id_group'],
|
||||||
|
'name' => trim($row['group_name']),
|
||||||
|
'allow' => in_array($row['id_group'], $curBoard['member_groups']),
|
||||||
|
'deny' => in_array($row['id_group'], $curBoard['deny_groups']),
|
||||||
|
'is_post_group' => $row['min_posts'] != -1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Category doesn't exist, man... sorry.
|
||||||
|
if (!isset($boardList[$curBoard['category']]))
|
||||||
|
redirectexit('action=admin;area=manageboards');
|
||||||
|
|
||||||
|
foreach ($boardList[$curBoard['category']] as $boardid)
|
||||||
|
{
|
||||||
|
if ($boardid == $_REQUEST['boardid'])
|
||||||
|
{
|
||||||
|
$context['board_order'][] = array(
|
||||||
|
'id' => $boardid,
|
||||||
|
'name' => str_repeat('-', $boards[$boardid]['level']) . ' (' . $txt['mboards_current_position'] . ')',
|
||||||
|
'children' => $boards[$boardid]['tree']['children'],
|
||||||
|
'no_children' => empty($boards[$boardid]['tree']['children']),
|
||||||
|
'is_child' => false,
|
||||||
|
'selected' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['board_order'][] = array(
|
||||||
|
'id' => $boardid,
|
||||||
|
'name' => str_repeat('-', $boards[$boardid]['level']) . ' ' . $boards[$boardid]['name'],
|
||||||
|
'is_child' => empty($_REQUEST['boardid']) ? false : isChildOf($boardid, $_REQUEST['boardid']),
|
||||||
|
'selected' => false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are there any places to move child boards to in the case where we are confirming a delete?
|
||||||
|
if (!empty($_REQUEST['boardid']))
|
||||||
|
{
|
||||||
|
$context['can_move_children'] = false;
|
||||||
|
$context['children'] = $boards[$_REQUEST['boardid']]['tree']['children'];
|
||||||
|
|
||||||
|
foreach ($context['board_order'] as $lBoard)
|
||||||
|
if ($lBoard['is_child'] == false && $lBoard['selected'] == false)
|
||||||
|
$context['can_move_children'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get other available categories.
|
||||||
|
$context['categories'] = array();
|
||||||
|
foreach ($cat_tree as $catID => $tree)
|
||||||
|
$context['categories'][] = array(
|
||||||
|
'id' => $catID == $curBoard['category'] ? 0 : $catID,
|
||||||
|
'name' => $tree['node']['name'],
|
||||||
|
'selected' => $catID == $curBoard['category']
|
||||||
|
);
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT mem.id_member, mem.real_name
|
||||||
|
FROM {db_prefix}moderators AS mods
|
||||||
|
INNER JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
|
||||||
|
WHERE mods.id_board = {int:current_board}',
|
||||||
|
array(
|
||||||
|
'current_board' => $_REQUEST['boardid'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['board']['moderators'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$context['board']['moderators'][$row['id_member']] = $row['real_name'];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$context['board']['moderator_list'] = empty($context['board']['moderators']) ? '' : '"' . implode('", "', $context['board']['moderators']) . '"';
|
||||||
|
|
||||||
|
if (!empty($context['board']['moderators']))
|
||||||
|
list ($context['board']['last_moderator_id']) = array_slice(array_keys($context['board']['moderators']), -1);
|
||||||
|
|
||||||
|
// Get all the groups assigned as moderators
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_group
|
||||||
|
FROM {db_prefix}moderator_groups
|
||||||
|
WHERE id_board = {int:current_board}',
|
||||||
|
array(
|
||||||
|
'current_board' => $_REQUEST['boardid'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['board']['moderator_groups'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$context['board']['moderator_groups'][$row['id_group']] = $context['groups'][$row['id_group']]['name'];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$context['board']['moderator_groups_list'] = empty($context['board']['moderator_groups']) ? '' : '"' . implode('", &qout;', $context['board']['moderator_groups']) . '"';
|
||||||
|
|
||||||
|
if (!empty($context['board']['moderator_groups']))
|
||||||
|
list ($context['board']['last_moderator_group_id']) = array_slice(array_keys($context['board']['moderator_groups']), -1);
|
||||||
|
|
||||||
|
// Get all the themes...
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_theme AS id, value AS name
|
||||||
|
FROM {db_prefix}themes
|
||||||
|
WHERE variable = {literal:name}
|
||||||
|
AND id_theme IN ({array_int:enable_themes})',
|
||||||
|
array(
|
||||||
|
'enable_themes' => explode(',', $modSettings['enableThemes']),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['themes'] = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$context['themes'][] = $row;
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
if (!isset($_REQUEST['delete']))
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'modify_board';
|
||||||
|
$context['page_title'] = $txt['boards_edit'];
|
||||||
|
loadJavaScriptFile('suggest.js', array('defer' => false, 'minimize' => true), 'smf_suggest');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context['sub_template'] = 'confirm_board_delete';
|
||||||
|
$context['page_title'] = $txt['mboards_delete_board'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a special token.
|
||||||
|
createToken('admin-be-' . $_REQUEST['boardid']);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_edit_board');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make changes to/delete a board.
|
||||||
|
* (function for handling a submitted form saving the board.)
|
||||||
|
* It also handles deletion of a board.
|
||||||
|
* Called by ?action=admin;area=manageboards;sa=board2
|
||||||
|
* Redirects to ?action=admin;area=manageboards.
|
||||||
|
* It requires manage_boards permission.
|
||||||
|
*/
|
||||||
|
function EditBoard2()
|
||||||
|
{
|
||||||
|
global $sourcedir, $smcFunc, $context;
|
||||||
|
|
||||||
|
$_POST['boardid'] = (int) $_POST['boardid'];
|
||||||
|
checkSession();
|
||||||
|
validateToken('admin-be-' . $_REQUEST['boardid']);
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
require_once($sourcedir . '/Subs-Editor.php');
|
||||||
|
|
||||||
|
// Mode: modify aka. don't delete.
|
||||||
|
if (isset($_POST['edit']) || isset($_POST['add']))
|
||||||
|
{
|
||||||
|
$boardOptions = array();
|
||||||
|
|
||||||
|
// Move this board to a new category?
|
||||||
|
if (!empty($_POST['new_cat']))
|
||||||
|
{
|
||||||
|
$boardOptions['move_to'] = 'bottom';
|
||||||
|
$boardOptions['target_category'] = (int) $_POST['new_cat'];
|
||||||
|
}
|
||||||
|
// Change the boardorder of this board?
|
||||||
|
elseif (!empty($_POST['placement']) && !empty($_POST['board_order']))
|
||||||
|
{
|
||||||
|
if (!in_array($_POST['placement'], array('before', 'after', 'child')))
|
||||||
|
fatal_lang_error('mangled_post', false);
|
||||||
|
|
||||||
|
$boardOptions['move_to'] = $_POST['placement'];
|
||||||
|
$boardOptions['target_board'] = (int) $_POST['board_order'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkboxes....
|
||||||
|
$boardOptions['posts_count'] = isset($_POST['count']);
|
||||||
|
$boardOptions['override_theme'] = isset($_POST['override_theme']);
|
||||||
|
$boardOptions['board_theme'] = (int) $_POST['boardtheme'];
|
||||||
|
$boardOptions['access_groups'] = array();
|
||||||
|
$boardOptions['deny_groups'] = array();
|
||||||
|
|
||||||
|
if (!empty($_POST['groups']))
|
||||||
|
foreach ($_POST['groups'] as $group => $action)
|
||||||
|
{
|
||||||
|
if ($action == 'allow')
|
||||||
|
$boardOptions['access_groups'][] = (int) $group;
|
||||||
|
elseif ($action == 'deny')
|
||||||
|
$boardOptions['deny_groups'][] = (int) $group;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(implode(',', $boardOptions['access_groups'])) > 255 || strlen(implode(',', $boardOptions['deny_groups'])) > 255)
|
||||||
|
fatal_lang_error('too_many_groups', false);
|
||||||
|
|
||||||
|
// Try and get any valid HTML to BBC first, add a naive attempt to strip it off, htmlspecialchars for the rest
|
||||||
|
$boardOptions['board_name'] = $smcFunc['htmlspecialchars'](strip_tags($_POST['board_name']));
|
||||||
|
$boardOptions['board_description'] = $smcFunc['htmlspecialchars'](strip_tags(html_to_bbc($_POST['desc'])));
|
||||||
|
|
||||||
|
$boardOptions['moderator_string'] = $_POST['moderators'];
|
||||||
|
|
||||||
|
if (isset($_POST['moderator_list']) && is_array($_POST['moderator_list']))
|
||||||
|
{
|
||||||
|
$moderators = array();
|
||||||
|
foreach ($_POST['moderator_list'] as $moderator)
|
||||||
|
$moderators[(int) $moderator] = (int) $moderator;
|
||||||
|
|
||||||
|
$boardOptions['moderators'] = $moderators;
|
||||||
|
}
|
||||||
|
|
||||||
|
$boardOptions['moderator_group_string'] = $_POST['moderator_groups'];
|
||||||
|
|
||||||
|
if (isset($_POST['moderator_group_list']) && is_array($_POST['moderator_group_list']))
|
||||||
|
{
|
||||||
|
$moderator_groups = array();
|
||||||
|
foreach ($_POST['moderator_group_list'] as $moderator_group)
|
||||||
|
$moderator_groups[(int) $moderator_group] = (int) $moderator_group;
|
||||||
|
$boardOptions['moderator_groups'] = $moderator_groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are they doing redirection?
|
||||||
|
$boardOptions['redirect'] = !empty($_POST['redirect_enable']) && isset($_POST['redirect_address']) && trim($_POST['redirect_address']) != '' ? normalize_iri(trim($_POST['redirect_address'])) : '';
|
||||||
|
|
||||||
|
// Profiles...
|
||||||
|
$boardOptions['profile'] = $_POST['profile'] == -1 ? 1 : $_POST['profile'];
|
||||||
|
$boardOptions['inherit_permissions'] = $_POST['profile'] == -1;
|
||||||
|
|
||||||
|
// We need to know what used to be case in terms of redirection.
|
||||||
|
if (!empty($_POST['boardid']))
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT redirect, num_posts, id_cat
|
||||||
|
FROM {db_prefix}boards
|
||||||
|
WHERE id_board = {int:current_board}',
|
||||||
|
array(
|
||||||
|
'current_board' => $_POST['boardid'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($oldRedirect, $numPosts, $old_id_cat) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// If we're turning redirection on check the board doesn't have posts in it - if it does don't make it a redirection board.
|
||||||
|
if ($boardOptions['redirect'] && empty($oldRedirect) && $numPosts)
|
||||||
|
unset($boardOptions['redirect']);
|
||||||
|
|
||||||
|
// Reset the redirection count when switching on/off.
|
||||||
|
elseif (empty($boardOptions['redirect']) != empty($oldRedirect))
|
||||||
|
$boardOptions['num_posts'] = 0;
|
||||||
|
|
||||||
|
// Resetting the count?
|
||||||
|
elseif ($boardOptions['redirect'] && !empty($_POST['reset_redirect']))
|
||||||
|
$boardOptions['num_posts'] = 0;
|
||||||
|
|
||||||
|
$boardOptions['old_id_cat'] = $old_id_cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new board...
|
||||||
|
if (isset($_POST['add']))
|
||||||
|
{
|
||||||
|
// New boards by default go to the bottom of the category.
|
||||||
|
if (empty($_POST['new_cat']))
|
||||||
|
$boardOptions['target_category'] = (int) $_POST['cur_cat'];
|
||||||
|
if (!isset($boardOptions['move_to']))
|
||||||
|
$boardOptions['move_to'] = 'bottom';
|
||||||
|
|
||||||
|
createBoard($boardOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...or update an existing board.
|
||||||
|
else
|
||||||
|
modifyBoard($_POST['boardid'], $boardOptions);
|
||||||
|
}
|
||||||
|
elseif (isset($_POST['delete']) && !isset($_POST['confirmation']) && !isset($_POST['no_children']))
|
||||||
|
{
|
||||||
|
EditBoard();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
elseif (isset($_POST['delete']))
|
||||||
|
{
|
||||||
|
// First off - check if we are moving all the current child boards first - before we start deleting!
|
||||||
|
if (isset($_POST['delete_action']) && $_POST['delete_action'] == 1)
|
||||||
|
{
|
||||||
|
if (empty($_POST['board_to']))
|
||||||
|
fatal_lang_error('mboards_delete_board_error');
|
||||||
|
|
||||||
|
deleteBoards(array($_POST['boardid']), (int) $_POST['board_to']);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
deleteBoards(array($_POST['boardid']), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_REQUEST['rid']) && $_REQUEST['rid'] == 'permissions')
|
||||||
|
redirectexit('action=admin;area=permissions;sa=board;' . $context['session_var'] . '=' . $context['session_id']);
|
||||||
|
else
|
||||||
|
redirectexit('action=admin;area=manageboards');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to retrieve data for modifying a board category
|
||||||
|
*/
|
||||||
|
function ModifyCat()
|
||||||
|
{
|
||||||
|
global $boards, $sourcedir, $smcFunc;
|
||||||
|
|
||||||
|
// Get some information about the boards and the cats.
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
getBoardTree();
|
||||||
|
|
||||||
|
// Allowed sub-actions...
|
||||||
|
$allowed_sa = array('add', 'modify', 'cut');
|
||||||
|
|
||||||
|
// Check our input.
|
||||||
|
$_POST['id'] = empty($_POST['id']) ? array_keys(current($boards)) : (int) $_POST['id'];
|
||||||
|
$_POST['id'] = substr($_POST['id'][1], 0, 3);
|
||||||
|
|
||||||
|
// Select the stuff we need from the DB.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT CONCAT({string:post_id}, {string:feline_clause}, {string:subact})
|
||||||
|
FROM {db_prefix}categories
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'post_id' => $_POST['id'] . 's ar',
|
||||||
|
'feline_clause' => 'e,o ',
|
||||||
|
'subact' => $allowed_sa[2] . 'e, ',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($cat) = $smcFunc['db_fetch_row']($request);
|
||||||
|
|
||||||
|
// Free resources.
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// This would probably never happen, but just to be sure.
|
||||||
|
if ($cat .= $allowed_sa[1])
|
||||||
|
die(str_replace(',', ' to', $cat));
|
||||||
|
|
||||||
|
redirectexit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A screen to set a few general board and category settings.
|
||||||
|
*
|
||||||
|
* @uses template_show_settings()
|
||||||
|
* @param bool $return_config Whether to return the $config_vars array (used for admin search)
|
||||||
|
* @return void|array Returns nothing or the array of config vars if $return_config is true
|
||||||
|
*/
|
||||||
|
function EditBoardSettings($return_config = false)
|
||||||
|
{
|
||||||
|
global $context, $txt, $sourcedir, $scripturl, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// Load the boards list - for the recycle bin!
|
||||||
|
$request = $smcFunc['db_query']('order_by_board_order', '
|
||||||
|
SELECT b.id_board, b.name AS board_name, c.name AS cat_name
|
||||||
|
FROM {db_prefix}boards AS b
|
||||||
|
LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
|
||||||
|
WHERE redirect = {string:empty_string}',
|
||||||
|
array(
|
||||||
|
'empty_string' => '',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$recycle_boards[$row['id_board']] = $row['cat_name'] . ' - ' . $row['board_name'];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
if (!empty($recycle_boards))
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
sortBoards($recycle_boards);
|
||||||
|
$recycle_boards = array('') + $recycle_boards;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$recycle_boards = array('');
|
||||||
|
|
||||||
|
// If this setting is missing, set it to 1
|
||||||
|
if (empty($modSettings['boardindex_max_depth']))
|
||||||
|
$modSettings['boardindex_max_depth'] = 1;
|
||||||
|
|
||||||
|
// Here and the board settings...
|
||||||
|
$config_vars = array(
|
||||||
|
array('title', 'settings'),
|
||||||
|
// Inline permissions.
|
||||||
|
array('permissions', 'manage_boards'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// Other board settings.
|
||||||
|
array('int', 'boardindex_max_depth', 'step' => 1, 'min' => 1, 'max' => 100),
|
||||||
|
array('check', 'countChildPosts'),
|
||||||
|
array('check', 'recycle_enable', 'onclick' => 'document.getElementById(\'recycle_board\').disabled = !this.checked;'),
|
||||||
|
array('select', 'recycle_board', $recycle_boards),
|
||||||
|
array('check', 'allow_ignore_boards'),
|
||||||
|
array('check', 'deny_boards_access'),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_modify_board_settings', array(&$config_vars));
|
||||||
|
|
||||||
|
if ($return_config)
|
||||||
|
return $config_vars;
|
||||||
|
|
||||||
|
// Needed for the settings template.
|
||||||
|
require_once($sourcedir . '/ManageServer.php');
|
||||||
|
|
||||||
|
$context['post_url'] = $scripturl . '?action=admin;area=manageboards;save;sa=settings';
|
||||||
|
|
||||||
|
$context['page_title'] = $txt['boards_and_cats'] . ' - ' . $txt['settings'];
|
||||||
|
|
||||||
|
loadTemplate('ManageBoards');
|
||||||
|
$context['sub_template'] = 'show_settings';
|
||||||
|
|
||||||
|
// Add some javascript stuff for the recycle box.
|
||||||
|
addInlineJavaScript('
|
||||||
|
document.getElementById("recycle_board").disabled = !document.getElementById("recycle_enable").checked;', true);
|
||||||
|
|
||||||
|
// Warn the admin against selecting the recycle topic without selecting a board.
|
||||||
|
$context['force_form_onsubmit'] = 'if(document.getElementById(\'recycle_enable\').checked && document.getElementById(\'recycle_board\').value == 0) { return confirm(\'' . $txt['recycle_board_unselected_notice'] . '\');} return true;';
|
||||||
|
|
||||||
|
// Doing a save?
|
||||||
|
if (isset($_GET['save']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
call_integration_hook('integrate_save_board_settings');
|
||||||
|
|
||||||
|
saveDBSettings($config_vars);
|
||||||
|
$_SESSION['adm-save'] = true;
|
||||||
|
redirectexit('action=admin;area=manageboards;sa=settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this for the in-line permissions
|
||||||
|
createToken('admin-mp');
|
||||||
|
|
||||||
|
// Prepare the settings...
|
||||||
|
prepareDBSettingContext($config_vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
416
Sources/ManageCalendar.php
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file allows you to manage the calendar.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main controlling function doesn't have much to do... yet.
|
||||||
|
* Just check permissions and delegate to the rest.
|
||||||
|
*
|
||||||
|
* Uses ManageCalendar language file.
|
||||||
|
*/
|
||||||
|
function ManageCalendar()
|
||||||
|
{
|
||||||
|
global $context, $txt, $modSettings;
|
||||||
|
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
// Everything's gonna need this.
|
||||||
|
loadLanguage('ManageCalendar');
|
||||||
|
|
||||||
|
// Little short on the ground of functions here... but things can and maybe will change...
|
||||||
|
if (!empty($modSettings['cal_enabled']))
|
||||||
|
{
|
||||||
|
$subActions = array(
|
||||||
|
'editholiday' => 'EditHoliday',
|
||||||
|
'holidays' => 'ModifyHolidays',
|
||||||
|
'settings' => 'ModifyCalendarSettings'
|
||||||
|
);
|
||||||
|
$default = 'holidays';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$subActions = array(
|
||||||
|
'settings' => 'ModifyCalendarSettings'
|
||||||
|
);
|
||||||
|
$default = 'settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the two tabs here...
|
||||||
|
$context[$context['admin_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['manage_calendar'],
|
||||||
|
'help' => 'calendar',
|
||||||
|
'description' => $txt['calendar_settings_desc'],
|
||||||
|
);
|
||||||
|
if (!empty($modSettings['cal_enabled']))
|
||||||
|
$context[$context['admin_menu_name']]['tab_data']['tabs'] = array(
|
||||||
|
'holidays' => array(
|
||||||
|
'description' => $txt['manage_holidays_desc'],
|
||||||
|
),
|
||||||
|
'settings' => array(
|
||||||
|
'description' => $txt['calendar_settings_desc'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_manage_calendar', array(&$subActions));
|
||||||
|
|
||||||
|
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : $default;
|
||||||
|
|
||||||
|
call_helper($subActions[$_REQUEST['sa']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function that handles adding, and deleting holiday data
|
||||||
|
*/
|
||||||
|
function ModifyHolidays()
|
||||||
|
{
|
||||||
|
global $sourcedir, $scripturl, $txt, $context, $modSettings;
|
||||||
|
|
||||||
|
// Submitting something...
|
||||||
|
if (isset($_REQUEST['delete']) && !empty($_REQUEST['holiday']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
validateToken('admin-mc');
|
||||||
|
|
||||||
|
foreach ($_REQUEST['holiday'] as $id => $value)
|
||||||
|
$_REQUEST['holiday'][$id] = (int) $id;
|
||||||
|
|
||||||
|
// Now the IDs are "safe" do the delete...
|
||||||
|
require_once($sourcedir . '/Subs-Calendar.php');
|
||||||
|
removeHolidays($_REQUEST['holiday']);
|
||||||
|
}
|
||||||
|
|
||||||
|
createToken('admin-mc');
|
||||||
|
$listOptions = array(
|
||||||
|
'id' => 'holiday_list',
|
||||||
|
'title' => $txt['current_holidays'],
|
||||||
|
'items_per_page' => $modSettings['defaultMaxListItems'],
|
||||||
|
'base_href' => $scripturl . '?action=admin;area=managecalendar;sa=holidays',
|
||||||
|
'default_sort_col' => 'name',
|
||||||
|
'get_items' => array(
|
||||||
|
'file' => $sourcedir . '/Subs-Calendar.php',
|
||||||
|
'function' => 'list_getHolidays',
|
||||||
|
),
|
||||||
|
'get_count' => array(
|
||||||
|
'file' => $sourcedir . '/Subs-Calendar.php',
|
||||||
|
'function' => 'list_getNumHolidays',
|
||||||
|
),
|
||||||
|
'no_items_label' => $txt['holidays_no_entries'],
|
||||||
|
'columns' => array(
|
||||||
|
'name' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['holidays_title'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'sprintf' => array(
|
||||||
|
'format' => '<a href="' . $scripturl . '?action=admin;area=managecalendar;sa=editholiday;holiday=%1$d">%2$s</a>',
|
||||||
|
'params' => array(
|
||||||
|
'id_holiday' => false,
|
||||||
|
'title' => false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'title ASC, event_date ASC',
|
||||||
|
'reverse' => 'title DESC, event_date ASC',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'date' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['date'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData) use ($txt)
|
||||||
|
{
|
||||||
|
// Recurring every year or just a single year?
|
||||||
|
$year = $rowData['year'] == '1004' ? sprintf('(%1$s)', $txt['every_year']) : $rowData['year'];
|
||||||
|
|
||||||
|
// Construct the date.
|
||||||
|
return sprintf('%1$d %2$s %3$s', $rowData['day'], $txt['months'][(int) $rowData['month']], $year);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'event_date',
|
||||||
|
'reverse' => 'event_date DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'check' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
|
||||||
|
'class' => 'centercol',
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'sprintf' => array(
|
||||||
|
'format' => '<input type="checkbox" name="holiday[%1$d]">',
|
||||||
|
'params' => array(
|
||||||
|
'id_holiday' => false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'class' => 'centercol',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'form' => array(
|
||||||
|
'href' => $scripturl . '?action=admin;area=managecalendar;sa=holidays',
|
||||||
|
'token' => 'admin-mc',
|
||||||
|
),
|
||||||
|
'additional_rows' => array(
|
||||||
|
array(
|
||||||
|
'position' => 'above_column_headers',
|
||||||
|
'value' => '<input type="submit" name="delete" value="' . $txt['quickmod_delete_selected'] . '" class="button">
|
||||||
|
<a class="button" href="' . $scripturl . '?action=admin;area=managecalendar;sa=editholiday">' . $txt['holidays_add'] . '</a>',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'position' => 'below_table_data',
|
||||||
|
'value' => '<input type="submit" name="delete" value="' . $txt['quickmod_delete_selected'] . '" class="button">
|
||||||
|
<a class="button" href="' . $scripturl . '?action=admin;area=managecalendar;sa=editholiday">' . $txt['holidays_add'] . '</a>',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-List.php');
|
||||||
|
createList($listOptions);
|
||||||
|
|
||||||
|
//loadTemplate('ManageCalendar');
|
||||||
|
$context['page_title'] = $txt['manage_holidays'];
|
||||||
|
|
||||||
|
// Since the list is the only thing to show, use the default list template.
|
||||||
|
$context['default_list'] = 'holiday_list';
|
||||||
|
$context['sub_template'] = 'show_list';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used for adding/editing a specific holiday
|
||||||
|
*/
|
||||||
|
function EditHoliday()
|
||||||
|
{
|
||||||
|
global $txt, $context, $smcFunc;
|
||||||
|
|
||||||
|
loadTemplate('ManageCalendar');
|
||||||
|
|
||||||
|
$context['is_new'] = !isset($_REQUEST['holiday']);
|
||||||
|
$context['page_title'] = $context['is_new'] ? $txt['holidays_add'] : $txt['holidays_edit'];
|
||||||
|
$context['sub_template'] = 'edit_holiday';
|
||||||
|
|
||||||
|
// Cast this for safety...
|
||||||
|
if (isset($_REQUEST['holiday']))
|
||||||
|
$_REQUEST['holiday'] = (int) $_REQUEST['holiday'];
|
||||||
|
|
||||||
|
// Submitting?
|
||||||
|
if (isset($_POST[$context['session_var']]) && (isset($_REQUEST['delete']) || $_REQUEST['title'] != ''))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
validateToken('admin-eh');
|
||||||
|
|
||||||
|
// Not too long good sir?
|
||||||
|
$_REQUEST['title'] = $smcFunc['substr']($smcFunc['normalize']($_REQUEST['title']), 0, 60);
|
||||||
|
$_REQUEST['holiday'] = isset($_REQUEST['holiday']) ? (int) $_REQUEST['holiday'] : 0;
|
||||||
|
|
||||||
|
if (isset($_REQUEST['delete']))
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}calendar_holidays
|
||||||
|
WHERE id_holiday = {int:selected_holiday}',
|
||||||
|
array(
|
||||||
|
'selected_holiday' => $_REQUEST['holiday'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$date = smf_strftime($_REQUEST['year'] <= 1004 ? '1004-%m-%d' : '%Y-%m-%d', mktime(0, 0, 0, $_REQUEST['month'], $_REQUEST['day'], $_REQUEST['year']));
|
||||||
|
if (isset($_REQUEST['edit']))
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
UPDATE {db_prefix}calendar_holidays
|
||||||
|
SET event_date = {date:holiday_date}, title = {string:holiday_title}
|
||||||
|
WHERE id_holiday = {int:selected_holiday}',
|
||||||
|
array(
|
||||||
|
'holiday_date' => $date,
|
||||||
|
'selected_holiday' => $_REQUEST['holiday'],
|
||||||
|
'holiday_title' => $_REQUEST['title'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
else
|
||||||
|
$smcFunc['db_insert']('',
|
||||||
|
'{db_prefix}calendar_holidays',
|
||||||
|
array(
|
||||||
|
'event_date' => 'date', 'title' => 'string-60',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
$date, $_REQUEST['title'],
|
||||||
|
),
|
||||||
|
array('id_holiday')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings(array(
|
||||||
|
'calendar_updated' => time(),
|
||||||
|
'settings_updated' => time(),
|
||||||
|
));
|
||||||
|
|
||||||
|
redirectexit('action=admin;area=managecalendar;sa=holidays');
|
||||||
|
}
|
||||||
|
|
||||||
|
createToken('admin-eh');
|
||||||
|
|
||||||
|
// Default states...
|
||||||
|
if ($context['is_new'])
|
||||||
|
$context['holiday'] = array(
|
||||||
|
'id' => 0,
|
||||||
|
'day' => date('d'),
|
||||||
|
'month' => date('m'),
|
||||||
|
'year' => '0000',
|
||||||
|
'title' => ''
|
||||||
|
);
|
||||||
|
// If it's not new load the data.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title
|
||||||
|
FROM {db_prefix}calendar_holidays
|
||||||
|
WHERE id_holiday = {int:selected_holiday}
|
||||||
|
LIMIT 1',
|
||||||
|
array(
|
||||||
|
'selected_holiday' => $_REQUEST['holiday'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$context['holiday'] = array(
|
||||||
|
'id' => $row['id_holiday'],
|
||||||
|
'day' => $row['day'],
|
||||||
|
'month' => $row['month'],
|
||||||
|
'year' => $row['year'] <= 4 ? 0 : $row['year'],
|
||||||
|
'title' => $row['title']
|
||||||
|
);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last day for the drop down?
|
||||||
|
$context['holiday']['last_day'] = (int) smf_strftime('%d', mktime(0, 0, 0, $context['holiday']['month'] == 12 ? 1 : $context['holiday']['month'] + 1, 0, $context['holiday']['month'] == 12 ? $context['holiday']['year'] + 1 : $context['holiday']['year']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show and allow to modify calendar settings. Obviously.
|
||||||
|
*
|
||||||
|
* @param bool $return_config Whether to return the $config_vars array (used for admin search)
|
||||||
|
* @return void|array Returns nothing or returns $config_vars if $return_config is true
|
||||||
|
*/
|
||||||
|
function ModifyCalendarSettings($return_config = false)
|
||||||
|
{
|
||||||
|
global $context, $txt, $sourcedir, $scripturl, $smcFunc, $modSettings;
|
||||||
|
|
||||||
|
// Load the boards list.
|
||||||
|
$boards = array('');
|
||||||
|
$request = $smcFunc['db_query']('order_by_board_order', '
|
||||||
|
SELECT b.id_board, b.name AS board_name, c.name AS cat_name
|
||||||
|
FROM {db_prefix}boards AS b
|
||||||
|
LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$boards[$row['id_board']] = $row['cat_name'] . ' - ' . $row['board_name'];
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-Boards.php');
|
||||||
|
sortBoards($boards);
|
||||||
|
|
||||||
|
// Look, all the calendar settings - of which there are many!
|
||||||
|
if (!empty($modSettings['cal_enabled']))
|
||||||
|
$config_vars = array(
|
||||||
|
array('check', 'cal_enabled'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// All the permissions:
|
||||||
|
array('permissions', 'calendar_view'),
|
||||||
|
array('permissions', 'calendar_post'),
|
||||||
|
array('permissions', 'calendar_edit_own'),
|
||||||
|
array('permissions', 'calendar_edit_any'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// How many days to show on board index, and where to display events etc?
|
||||||
|
array('int', 'cal_days_for_index', 'help' => 'cal_maxdays_advance', 6, 'postinput' => $txt['days_word']),
|
||||||
|
array('select', 'cal_showholidays', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])),
|
||||||
|
array('select', 'cal_showbdays', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])),
|
||||||
|
array('select', 'cal_showevents', array(0 => $txt['setting_cal_show_never'], 1 => $txt['setting_cal_show_cal'], 3 => $txt['setting_cal_show_index'], 2 => $txt['setting_cal_show_all'])),
|
||||||
|
array('check', 'cal_export'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// Linking events etc...
|
||||||
|
array('select', 'cal_defaultboard', $boards),
|
||||||
|
array('check', 'cal_daysaslink', 'help' => 'cal_link_postevent'),
|
||||||
|
array('check', 'cal_allow_unlinked', 'help' => 'cal_allow_unlinkedevents'),
|
||||||
|
array('check', 'cal_showInTopic'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// Dates of calendar...
|
||||||
|
array('int', 'cal_minyear', 'help' => 'cal_min_year'),
|
||||||
|
array('int', 'cal_maxyear', 'help' => 'cal_max_year'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// Calendar spanning...
|
||||||
|
array('int', 'cal_maxspan', 6, 'postinput' => $txt['days_word'], 'subtext' => $txt['zero_for_no_limit'], 'help' => 'cal_maxevent_span'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// Miscellaneous layout settings...
|
||||||
|
array('check', 'cal_disable_prev_next'),
|
||||||
|
array('select', 'cal_week_links', array(0 => $txt['setting_cal_week_links_none'], 1 => $txt['setting_cal_week_links_mini'], 2 => $txt['setting_cal_week_links_main'], 3 => $txt['setting_cal_week_links_both'])),
|
||||||
|
array('check', 'cal_prev_next_links'),
|
||||||
|
array('check', 'cal_short_days'),
|
||||||
|
array('check', 'cal_short_months'),
|
||||||
|
);
|
||||||
|
else
|
||||||
|
$config_vars = array(
|
||||||
|
array('check', 'cal_enabled'),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_modify_calendar_settings', array(&$config_vars));
|
||||||
|
if ($return_config)
|
||||||
|
return $config_vars;
|
||||||
|
|
||||||
|
// Get the settings template fired up.
|
||||||
|
require_once($sourcedir . '/ManageServer.php');
|
||||||
|
|
||||||
|
// Some important context stuff
|
||||||
|
$context['page_title'] = $txt['calendar_settings'];
|
||||||
|
$context['sub_template'] = 'show_settings';
|
||||||
|
|
||||||
|
// Get the final touches in place.
|
||||||
|
$context['post_url'] = $scripturl . '?action=admin;area=managecalendar;save;sa=settings';
|
||||||
|
$context['settings_title'] = $txt['calendar_settings'];
|
||||||
|
|
||||||
|
// Saving the settings?
|
||||||
|
if (isset($_GET['save']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
call_integration_hook('integrate_save_calendar_settings');
|
||||||
|
saveDBSettings($config_vars);
|
||||||
|
|
||||||
|
// Update the stats in case.
|
||||||
|
updateSettings(array(
|
||||||
|
'calendar_updated' => time(),
|
||||||
|
));
|
||||||
|
|
||||||
|
$_SESSION['adm-save'] = true;
|
||||||
|
redirectexit('action=admin;area=managecalendar;sa=settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need this for the inline permissions
|
||||||
|
createToken('admin-mp');
|
||||||
|
|
||||||
|
// Prepare the settings...
|
||||||
|
prepareDBSettingContext($config_vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
481
Sources/ManageErrors.php
Normal file
|
@ -0,0 +1,481 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main purpose of this file is to show a list of all errors that were
|
||||||
|
* logged on the forum, and allow filtering and deleting them.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.3
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View the forum's error log.
|
||||||
|
* This function sets all the context up to show the error log for maintenance.
|
||||||
|
* It requires the maintain_forum permission.
|
||||||
|
* It is accessed from ?action=admin;area=logs;sa=errorlog.
|
||||||
|
*
|
||||||
|
* @uses template_error_log()
|
||||||
|
*/
|
||||||
|
function ViewErrorLog()
|
||||||
|
{
|
||||||
|
global $scripturl, $txt, $context, $modSettings, $user_profile, $filter, $smcFunc;
|
||||||
|
|
||||||
|
// Viewing contents of a file?
|
||||||
|
if (isset($_GET['file']))
|
||||||
|
return ViewFile();
|
||||||
|
|
||||||
|
// Viewing contents of a backtrace?
|
||||||
|
if (isset($_GET['backtrace']))
|
||||||
|
return ViewBacktrace();
|
||||||
|
|
||||||
|
// Check for the administrative permission to do this.
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
// Templates, etc...
|
||||||
|
loadLanguage('ManageMaintenance');
|
||||||
|
loadTemplate('Errors');
|
||||||
|
|
||||||
|
// You can filter by any of the following columns:
|
||||||
|
$filters = array(
|
||||||
|
'id_member' => array(
|
||||||
|
'txt' => $txt['username'],
|
||||||
|
'operator' => '=',
|
||||||
|
'datatype' => 'int',
|
||||||
|
),
|
||||||
|
'ip' => array(
|
||||||
|
'txt' => $txt['ip_address'],
|
||||||
|
'operator' => '=',
|
||||||
|
'datatype' => 'inet',
|
||||||
|
),
|
||||||
|
'session' => array(
|
||||||
|
'txt' => $txt['session'],
|
||||||
|
'operator' => 'LIKE',
|
||||||
|
'datatype' => 'string',
|
||||||
|
),
|
||||||
|
'url' => array(
|
||||||
|
'txt' => $txt['error_url'],
|
||||||
|
'operator' => 'LIKE',
|
||||||
|
'datatype' => 'string',
|
||||||
|
),
|
||||||
|
'message' => array(
|
||||||
|
'txt' => $txt['error_message'],
|
||||||
|
'operator' => 'LIKE',
|
||||||
|
'datatype' => 'string',
|
||||||
|
),
|
||||||
|
'error_type' => array(
|
||||||
|
'txt' => $txt['error_type'],
|
||||||
|
'operator' => 'LIKE',
|
||||||
|
'datatype' => 'string',
|
||||||
|
),
|
||||||
|
'file' => array(
|
||||||
|
'txt' => $txt['file'],
|
||||||
|
'operator' => 'LIKE',
|
||||||
|
'datatype' => 'string',
|
||||||
|
),
|
||||||
|
'line' => array(
|
||||||
|
'txt' => $txt['line'],
|
||||||
|
'operator' => '=',
|
||||||
|
'datatype' => 'int',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up the filtering...
|
||||||
|
if (isset($_GET['value'], $_GET['filter']) && isset($filters[$_GET['filter']]))
|
||||||
|
$filter = array(
|
||||||
|
'variable' => $_GET['filter'],
|
||||||
|
'value' => array(
|
||||||
|
'sql' => in_array($_GET['filter'], array('message', 'url', 'file')) ? base64_decode(strtr($_GET['value'], array(' ' => '+'))) : $smcFunc['db_escape_wildcard_string']($_GET['value']),
|
||||||
|
),
|
||||||
|
'href' => ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'],
|
||||||
|
'entity' => $filters[$_GET['filter']]['txt']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deleting, are we?
|
||||||
|
if (isset($_POST['delall']) || isset($_POST['delete']))
|
||||||
|
deleteErrors();
|
||||||
|
|
||||||
|
// Just how many errors are there?
|
||||||
|
$result = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}log_errors' . (isset($filter) ? '
|
||||||
|
WHERE ' . $filter['variable'] . ' ' . $filters[$_GET['filter']]['operator'] . ' {' . $filters[$_GET['filter']]['datatype'] . ':filter}' : ''),
|
||||||
|
array(
|
||||||
|
'filter' => isset($filter) ? $filter['value']['sql'] : '',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($num_errors) = $smcFunc['db_fetch_row']($result);
|
||||||
|
$smcFunc['db_free_result']($result);
|
||||||
|
|
||||||
|
// If this filter is empty...
|
||||||
|
if ($num_errors == 0 && isset($filter))
|
||||||
|
redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : ''));
|
||||||
|
|
||||||
|
// Clean up start.
|
||||||
|
if (!isset($_GET['start']) || $_GET['start'] < 0)
|
||||||
|
$_GET['start'] = 0;
|
||||||
|
|
||||||
|
// Do we want to reverse error listing?
|
||||||
|
$context['sort_direction'] = isset($_REQUEST['desc']) ? 'down' : 'up';
|
||||||
|
|
||||||
|
// Set the page listing up.
|
||||||
|
$context['page_index'] = constructPageIndex($scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : '') . (isset($filter) ? $filter['href'] : ''), $_GET['start'], $num_errors, $modSettings['defaultMaxListItems']);
|
||||||
|
$context['start'] = $_GET['start'];
|
||||||
|
|
||||||
|
// Update the error count
|
||||||
|
if (!isset($filter))
|
||||||
|
$context['num_errors'] = $num_errors;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We want all errors, not just the number of filtered messages...
|
||||||
|
$query = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM {db_prefix}log_errors',
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
|
||||||
|
$smcFunc['db_free_result']($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and sort out the errors.
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_error, id_member, ip, url, log_time, message, session, error_type, file, line
|
||||||
|
FROM {db_prefix}log_errors' . (isset($filter) ? '
|
||||||
|
WHERE ' . $filter['variable'] . ' ' . $filters[$_GET['filter']]['operator'] . ' {' . $filters[$_GET['filter']]['datatype'] . ':filter}' : '') . '
|
||||||
|
ORDER BY id_error ' . ($context['sort_direction'] == 'down' ? 'DESC' : '') . '
|
||||||
|
LIMIT {int:start}, {int:max}',
|
||||||
|
array(
|
||||||
|
'filter' => isset($filter) ? $filter['value']['sql'] : '',
|
||||||
|
'start' => $_GET['start'],
|
||||||
|
'max' => $modSettings['defaultMaxListItems'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$context['errors'] = array();
|
||||||
|
$members = array();
|
||||||
|
|
||||||
|
for ($i = 0; $row = $smcFunc['db_fetch_assoc']($request); $i++)
|
||||||
|
{
|
||||||
|
$search_message = preg_replace('~<span class="remove">(.+?)</span>~', '%', $smcFunc['db_escape_wildcard_string']($row['message']));
|
||||||
|
|
||||||
|
if (isset($filter) && $search_message == $filter['value']['sql'])
|
||||||
|
$search_message = $smcFunc['db_escape_wildcard_string']($row['message']);
|
||||||
|
|
||||||
|
$show_message = strtr(strtr(preg_replace('~<span class="remove">(.+?)</span>~', '$1', $row['message']), array("\r" => '', '<br>' => "\n", '<' => '<', '>' => '>', '"' => '"')), array("\n" => '<br>'));
|
||||||
|
|
||||||
|
$context['errors'][$row['id_error']] = array(
|
||||||
|
'member' => array(
|
||||||
|
'id' => $row['id_member'],
|
||||||
|
'ip' => inet_dtop($row['ip']),
|
||||||
|
'session' => $row['session']
|
||||||
|
),
|
||||||
|
'time' => timeformat($row['log_time']),
|
||||||
|
'timestamp' => $row['log_time'],
|
||||||
|
'url' => array(
|
||||||
|
'html' => $smcFunc['htmlspecialchars'](strpos($row['url'], 'cron.php') === false ? (substr($row['url'], 0, 1) == '?' ? $scripturl : '') . $row['url'] : $row['url']),
|
||||||
|
'href' => base64_encode($smcFunc['db_escape_wildcard_string']($row['url']))
|
||||||
|
),
|
||||||
|
'message' => array(
|
||||||
|
'html' => $show_message,
|
||||||
|
'href' => base64_encode($search_message)
|
||||||
|
),
|
||||||
|
'id' => $row['id_error'],
|
||||||
|
'error_type' => array(
|
||||||
|
'type' => $row['error_type'],
|
||||||
|
'name' => isset($txt['errortype_' . $row['error_type']]) ? $txt['errortype_' . $row['error_type']] : $row['error_type'],
|
||||||
|
),
|
||||||
|
'file' => array(),
|
||||||
|
);
|
||||||
|
if (!empty($row['file']) && !empty($row['line']))
|
||||||
|
{
|
||||||
|
// Eval'd files rarely point to the right location and cause havoc for linking, so don't link them.
|
||||||
|
$linkfile = strpos($row['file'], 'eval') === false || strpos($row['file'], '?') === false; // De Morgan's Law. Want this true unless both are present.
|
||||||
|
|
||||||
|
$context['errors'][$row['id_error']]['file'] = array(
|
||||||
|
'file' => $row['file'],
|
||||||
|
'line' => $row['line'],
|
||||||
|
'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'],
|
||||||
|
'link' => $linkfile ? '<a href="' . $scripturl . '?action=admin;area=logs;sa=errorlog;file=' . base64_encode($row['file']) . ';line=' . $row['line'] . '" onclick="return reqWin(this.href, 600, 480, false);">' . $row['file'] . '</a>' : $row['file'],
|
||||||
|
'search' => base64_encode($row['file']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a list of members to load later.
|
||||||
|
$members[$row['id_member']] = $row['id_member'];
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Load the member data.
|
||||||
|
if (!empty($members))
|
||||||
|
{
|
||||||
|
// Get some additional member info...
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT id_member, member_name, real_name
|
||||||
|
FROM {db_prefix}members
|
||||||
|
WHERE id_member IN ({array_int:member_list})
|
||||||
|
LIMIT {int:members}',
|
||||||
|
array(
|
||||||
|
'member_list' => $members,
|
||||||
|
'members' => count($members),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
$members[$row['id_member']] = $row;
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// This is a guest...
|
||||||
|
$members[0] = array(
|
||||||
|
'id_member' => 0,
|
||||||
|
'member_name' => '',
|
||||||
|
'real_name' => $txt['guest_title']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go through each error and tack the data on.
|
||||||
|
foreach ($context['errors'] as $id => $dummy)
|
||||||
|
{
|
||||||
|
$memID = $context['errors'][$id]['member']['id'];
|
||||||
|
$context['errors'][$id]['member']['username'] = $members[$memID]['member_name'];
|
||||||
|
$context['errors'][$id]['member']['name'] = $members[$memID]['real_name'];
|
||||||
|
$context['errors'][$id]['member']['href'] = empty($memID) ? '' : $scripturl . '?action=profile;u=' . $memID;
|
||||||
|
$context['errors'][$id]['member']['link'] = empty($memID) ? $txt['guest_title'] : '<a href="' . $scripturl . '?action=profile;u=' . $memID . '">' . $context['errors'][$id]['member']['name'] . '</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtering anything?
|
||||||
|
if (isset($filter))
|
||||||
|
{
|
||||||
|
$context['filter'] = &$filter;
|
||||||
|
|
||||||
|
// Set the filtering context.
|
||||||
|
if ($filter['variable'] == 'id_member')
|
||||||
|
{
|
||||||
|
$id = $filter['value']['sql'];
|
||||||
|
loadMemberData($id, false, 'minimal');
|
||||||
|
$context['filter']['value']['html'] = '<a href="' . $scripturl . '?action=profile;u=' . $id . '">' . (isset($user_profile[$id]['real_name']) ? $user_profile[$id]['real_name'] : $txt['guest']) . '</a>';
|
||||||
|
}
|
||||||
|
elseif ($filter['variable'] == 'url')
|
||||||
|
$context['filter']['value']['html'] = '\'' . strtr($smcFunc['htmlspecialchars']((substr($filter['value']['sql'], 0, 1) == '?' ? $scripturl : '') . $filter['value']['sql']), array('\_' => '_')) . '\'';
|
||||||
|
elseif ($filter['variable'] == 'message')
|
||||||
|
{
|
||||||
|
$context['filter']['value']['html'] = '\'' . strtr($smcFunc['htmlspecialchars']($filter['value']['sql']), array("\n" => '<br>', '<br />' => '<br>', "\t" => ' ', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\'';
|
||||||
|
$context['filter']['value']['html'] = preg_replace('~&lt;span class=&quot;remove&quot;&gt;(.+?)&lt;/span&gt;~', '$1', $context['filter']['value']['html']);
|
||||||
|
}
|
||||||
|
elseif ($filter['variable'] == 'error_type')
|
||||||
|
{
|
||||||
|
$context['filter']['value']['html'] = '\'' . strtr($smcFunc['htmlspecialchars']($filter['value']['sql']), array("\n" => '<br>', '<br />' => '<br>', "\t" => ' ', '\_' => '_', '\\%' => '%', '\\\\' => '\\')) . '\'';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$context['filter']['value']['html'] = &$filter['value']['sql'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['error_types'] = array();
|
||||||
|
|
||||||
|
$context['error_types']['all'] = array(
|
||||||
|
'label' => $txt['errortype_all'],
|
||||||
|
'error_type' => 'all',
|
||||||
|
'description' => isset($txt['errortype_all_desc']) ? $txt['errortype_all_desc'] : '',
|
||||||
|
'url' => $scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : ''),
|
||||||
|
'is_selected' => empty($filter),
|
||||||
|
);
|
||||||
|
|
||||||
|
$sum = 0;
|
||||||
|
// What type of errors do we have and how many do we have?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT error_type, COUNT(*) AS num_errors
|
||||||
|
FROM {db_prefix}log_errors
|
||||||
|
GROUP BY error_type
|
||||||
|
ORDER BY error_type = {string:critical_type} DESC, error_type ASC',
|
||||||
|
array(
|
||||||
|
'critical_type' => 'critical',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
// Total errors so far?
|
||||||
|
$sum += $row['num_errors'];
|
||||||
|
|
||||||
|
$context['error_types'][$sum] = array(
|
||||||
|
'label' => (isset($txt['errortype_' . $row['error_type']]) ? $txt['errortype_' . $row['error_type']] : $row['error_type']) . ' (' . $row['num_errors'] . ')',
|
||||||
|
'error_type' => $row['error_type'],
|
||||||
|
'description' => isset($txt['errortype_' . $row['error_type'] . '_desc']) ? $txt['errortype_' . $row['error_type'] . '_desc'] : '',
|
||||||
|
'url' => $scripturl . '?action=admin;area=logs;sa=errorlog' . ($context['sort_direction'] == 'down' ? ';desc' : '') . ';filter=error_type;value=' . $row['error_type'],
|
||||||
|
'is_selected' => isset($filter) && $filter['value']['sql'] == $smcFunc['db_escape_wildcard_string']($row['error_type']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
// Update the all errors tab with the total number of errors
|
||||||
|
$context['error_types']['all']['label'] .= ' (' . $sum . ')';
|
||||||
|
|
||||||
|
// Finally, work out what is the last tab!
|
||||||
|
if (isset($context['error_types'][$sum]))
|
||||||
|
$context['error_types'][$sum]['is_last'] = true;
|
||||||
|
else
|
||||||
|
$context['error_types']['all']['is_last'] = true;
|
||||||
|
|
||||||
|
// And this is pretty basic ;).
|
||||||
|
$context['page_title'] = $txt['errorlog'];
|
||||||
|
$context['has_filter'] = isset($filter);
|
||||||
|
$context['sub_template'] = 'error_log';
|
||||||
|
|
||||||
|
createToken('admin-el');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all or some of the errors in the error log.
|
||||||
|
* It applies any necessary filters to deletion.
|
||||||
|
* This should only be called by ViewErrorLog().
|
||||||
|
* It attempts to TRUNCATE the table to reset the auto_increment.
|
||||||
|
* Redirects back to the error log when done.
|
||||||
|
*/
|
||||||
|
function deleteErrors()
|
||||||
|
{
|
||||||
|
global $filter, $smcFunc;
|
||||||
|
|
||||||
|
// Make sure the session exists and is correct; otherwise, might be a hacker.
|
||||||
|
checkSession();
|
||||||
|
validateToken('admin-el');
|
||||||
|
|
||||||
|
// Delete all or just some?
|
||||||
|
if (isset($_POST['delall']) && !isset($filter))
|
||||||
|
$smcFunc['db_query']('truncate_table', '
|
||||||
|
TRUNCATE {db_prefix}log_errors',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Deleting all with a filter?
|
||||||
|
elseif (isset($_POST['delall']) && isset($filter))
|
||||||
|
{
|
||||||
|
// ip need a different placeholder type
|
||||||
|
$filter_type = $filter['variable'] == 'ip'? 'inet' : 'string';
|
||||||
|
$filter_op = $filter['variable'] == 'ip'? '=' : 'LIKE';
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_errors
|
||||||
|
WHERE ' . $filter['variable'] . ' ' . $filter_op . ' {' . $filter_type . ':filter}',
|
||||||
|
array(
|
||||||
|
'filter' => $filter['value']['sql'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Just specific errors?
|
||||||
|
elseif (!empty($_POST['delete']))
|
||||||
|
{
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}log_errors
|
||||||
|
WHERE id_error IN ({array_int:error_list})',
|
||||||
|
array(
|
||||||
|
'error_list' => array_unique($_POST['delete']),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Go back to where we were.
|
||||||
|
redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '') . ';start=' . $_GET['start'] . (isset($filter) ? ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'] : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back to the error log!
|
||||||
|
redirectexit('action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View a file specified in $_REQUEST['file'], with php highlighting on it
|
||||||
|
* Preconditions:
|
||||||
|
* - file must be readable,
|
||||||
|
* - full file path must be base64 encoded,
|
||||||
|
* - user must have admin_forum permission.
|
||||||
|
* The line number number is specified by $_REQUEST['line']...
|
||||||
|
* The function will try to get the 20 lines before and after the specified line.
|
||||||
|
*/
|
||||||
|
function ViewFile()
|
||||||
|
{
|
||||||
|
global $context, $boarddir, $sourcedir, $cachedir, $smcFunc;
|
||||||
|
|
||||||
|
// Check for the administrative permission to do this.
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
// Decode the file and get the line
|
||||||
|
$file = realpath(base64_decode($_REQUEST['file']));
|
||||||
|
$real_board = realpath($boarddir);
|
||||||
|
$real_source = realpath($sourcedir);
|
||||||
|
$real_cache = realpath($cachedir);
|
||||||
|
$basename = strtolower(basename($file));
|
||||||
|
$ext = strrchr($basename, '.');
|
||||||
|
$line = isset($_REQUEST['line']) ? (int) $_REQUEST['line'] : 0;
|
||||||
|
|
||||||
|
// Make sure the file we are looking for is one they are allowed to look at
|
||||||
|
if ($ext != '.php' || (strpos($file, $real_board) === false && strpos($file, $real_source) === false) || ($basename == 'settings.php' || $basename == 'settings_bak.php') || strpos($file, $real_cache) !== false || !is_readable($file))
|
||||||
|
fatal_lang_error('error_bad_file', true, array($smcFunc['htmlspecialchars']($file)));
|
||||||
|
|
||||||
|
// get the min and max lines
|
||||||
|
$min = $line - 20 <= 0 ? 1 : $line - 20;
|
||||||
|
$max = $line + 21; // One additional line to make everything work out correctly
|
||||||
|
|
||||||
|
if ($max <= 0 || $min >= $max)
|
||||||
|
fatal_lang_error('error_bad_line');
|
||||||
|
|
||||||
|
$file_data = explode('<br />', highlight_php_code($smcFunc['htmlspecialchars'](implode('', file($file)))));
|
||||||
|
|
||||||
|
// We don't want to slice off too many so lets make sure we stop at the last one
|
||||||
|
$max = min($max, max(array_keys($file_data)));
|
||||||
|
|
||||||
|
$file_data = array_slice($file_data, $min - 1, $max - $min);
|
||||||
|
|
||||||
|
$context['file_data'] = array(
|
||||||
|
'contents' => $file_data,
|
||||||
|
'min' => $min,
|
||||||
|
'target' => $line,
|
||||||
|
'file' => strtr($file, array('"' => '\\"')),
|
||||||
|
);
|
||||||
|
|
||||||
|
loadTemplate('Errors');
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
$context['sub_template'] = 'show_file';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View a backtrace specified in $_REQUEST['backtrace'], with php highlighting on it
|
||||||
|
* Preconditions:
|
||||||
|
* - user must have admin_forum permission.
|
||||||
|
*/
|
||||||
|
function ViewBacktrace()
|
||||||
|
{
|
||||||
|
global $context, $smcFunc, $scripturl;
|
||||||
|
|
||||||
|
// Check for the administrative permission to do this.
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
$id_error = (int) $_REQUEST['backtrace'];
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT backtrace, error_type, message, file, line, url
|
||||||
|
FROM {db_prefix}log_errors
|
||||||
|
WHERE id_error = {int:id_error}',
|
||||||
|
array(
|
||||||
|
'id_error' => $id_error,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
$context['error_info'] = $row;
|
||||||
|
$context['error_info']['url'] = $scripturl . $row['url'];
|
||||||
|
$context['error_info']['backtrace'] = $smcFunc['json_decode']($row['backtrace']);
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
loadCSSFile('admin.css', array(), 'smf_admin');
|
||||||
|
loadTemplate('Errors');
|
||||||
|
loadLanguage('ManageMaintenance');
|
||||||
|
$context['template_layers'] = array();
|
||||||
|
$context['sub_template'] = 'show_backtrace';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1763
Sources/ManageLanguages.php
Normal file
526
Sources/ManageMail.php
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is all about mail, how we love it so. In particular it handles the admin side of
|
||||||
|
* mail configuration, as well as reviewing the mail queue - if enabled.
|
||||||
|
*
|
||||||
|
* @todo refactor as controller-model.
|
||||||
|
*
|
||||||
|
* Simple Machines Forum (SMF)
|
||||||
|
*
|
||||||
|
* @package SMF
|
||||||
|
* @author Simple Machines https://www.simplemachines.org
|
||||||
|
* @copyright 2022 Simple Machines and individual contributors
|
||||||
|
* @license https://www.simplemachines.org/about/smf/license.php BSD
|
||||||
|
*
|
||||||
|
* @version 2.1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SMF'))
|
||||||
|
die('No direct access...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main dispatcher. This function checks permissions and passes control through to the relevant section.
|
||||||
|
*/
|
||||||
|
function ManageMail()
|
||||||
|
{
|
||||||
|
global $context, $txt, $sourcedir;
|
||||||
|
|
||||||
|
// You need to be an admin to edit settings!
|
||||||
|
isAllowedTo('admin_forum');
|
||||||
|
|
||||||
|
loadLanguage('Help');
|
||||||
|
loadLanguage('ManageMail');
|
||||||
|
|
||||||
|
// We'll need the utility functions from here.
|
||||||
|
require_once($sourcedir . '/ManageServer.php');
|
||||||
|
|
||||||
|
$context['page_title'] = $txt['mailqueue_title'];
|
||||||
|
$context['sub_template'] = 'show_settings';
|
||||||
|
|
||||||
|
$subActions = array(
|
||||||
|
'browse' => 'BrowseMailQueue',
|
||||||
|
'clear' => 'ClearMailQueue',
|
||||||
|
'settings' => 'ModifyMailSettings',
|
||||||
|
'test' => 'TestMailSend',
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_manage_mail', array(&$subActions));
|
||||||
|
|
||||||
|
// By default we want to browse
|
||||||
|
$_REQUEST['sa'] = isset($_REQUEST['sa']) && isset($subActions[$_REQUEST['sa']]) ? $_REQUEST['sa'] : 'browse';
|
||||||
|
$context['sub_action'] = $_REQUEST['sa'];
|
||||||
|
|
||||||
|
// Load up all the tabs...
|
||||||
|
$context[$context['admin_menu_name']]['tab_data'] = array(
|
||||||
|
'title' => $txt['mailqueue_title'],
|
||||||
|
'help' => '',
|
||||||
|
'description' => $txt['mailqueue_desc'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call the right function for this sub-action.
|
||||||
|
call_helper($subActions[$_REQUEST['sa']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the mail queue...
|
||||||
|
*/
|
||||||
|
function BrowseMailQueue()
|
||||||
|
{
|
||||||
|
global $scripturl, $context, $txt, $smcFunc;
|
||||||
|
global $sourcedir, $modSettings;
|
||||||
|
|
||||||
|
// First, are we deleting something from the queue?
|
||||||
|
if (isset($_REQUEST['delete']))
|
||||||
|
{
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
$smcFunc['db_query']('', '
|
||||||
|
DELETE FROM {db_prefix}mail_queue
|
||||||
|
WHERE id_mail IN ({array_int:mail_ids})',
|
||||||
|
array(
|
||||||
|
'mail_ids' => $_REQUEST['delete'],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// How many items do we have?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*) AS queue_size, MIN(time_sent) AS oldest
|
||||||
|
FROM {db_prefix}mail_queue',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($mailQueueSize, $mailOldest) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
$context['oldest_mail'] = empty($mailOldest) ? $txt['mailqueue_oldest_not_available'] : time_since(time() - $mailOldest);
|
||||||
|
$context['mail_queue_size'] = comma_format($mailQueueSize);
|
||||||
|
|
||||||
|
$listOptions = array(
|
||||||
|
'id' => 'mail_queue',
|
||||||
|
'title' => $txt['mailqueue_browse'],
|
||||||
|
'items_per_page' => $modSettings['defaultMaxListItems'],
|
||||||
|
'base_href' => $scripturl . '?action=admin;area=mailqueue',
|
||||||
|
'default_sort_col' => 'age',
|
||||||
|
'no_items_label' => $txt['mailqueue_no_items'],
|
||||||
|
'get_items' => array(
|
||||||
|
'function' => 'list_getMailQueue',
|
||||||
|
),
|
||||||
|
'get_count' => array(
|
||||||
|
'function' => 'list_getMailQueueSize',
|
||||||
|
),
|
||||||
|
'columns' => array(
|
||||||
|
'subject' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mailqueue_subject'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData) use ($smcFunc)
|
||||||
|
{
|
||||||
|
return $smcFunc['strlen']($rowData['subject']) > 50 ? sprintf('%1$s...', $smcFunc['htmlspecialchars']($smcFunc['substr']($rowData['subject'], 0, 47))) : $smcFunc['htmlspecialchars']($rowData['subject']);
|
||||||
|
},
|
||||||
|
'class' => 'smalltext',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'subject',
|
||||||
|
'reverse' => 'subject DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'recipient' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mailqueue_recipient'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'sprintf' => array(
|
||||||
|
'format' => '<a href="mailto:%1$s">%1$s</a>',
|
||||||
|
'params' => array(
|
||||||
|
'recipient' => true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'class' => 'smalltext',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'recipient',
|
||||||
|
'reverse' => 'recipient DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'priority' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mailqueue_priority'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData) use ($txt)
|
||||||
|
{
|
||||||
|
// We probably have a text label with your priority.
|
||||||
|
$txtKey = sprintf('mq_mpriority_%1$s', $rowData['priority']);
|
||||||
|
|
||||||
|
// But if not, revert to priority 0.
|
||||||
|
return isset($txt[$txtKey]) ? $txt[$txtKey] : $txt['mq_mpriority_1'];
|
||||||
|
},
|
||||||
|
'class' => 'smalltext',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'priority',
|
||||||
|
'reverse' => 'priority DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'age' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => $txt['mailqueue_age'],
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData)
|
||||||
|
{
|
||||||
|
return time_since(time() - $rowData['time_sent']);
|
||||||
|
},
|
||||||
|
'class' => 'smalltext',
|
||||||
|
),
|
||||||
|
'sort' => array(
|
||||||
|
'default' => 'time_sent',
|
||||||
|
'reverse' => 'time_sent DESC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'check' => array(
|
||||||
|
'header' => array(
|
||||||
|
'value' => '<input type="checkbox" onclick="invertAll(this, this.form);">',
|
||||||
|
),
|
||||||
|
'data' => array(
|
||||||
|
'function' => function($rowData)
|
||||||
|
{
|
||||||
|
return '<input type="checkbox" name="delete[]" value="' . $rowData['id_mail'] . '">';
|
||||||
|
},
|
||||||
|
'class' => 'smalltext',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'form' => array(
|
||||||
|
'href' => $scripturl . '?action=admin;area=mailqueue',
|
||||||
|
'include_start' => true,
|
||||||
|
'include_sort' => true,
|
||||||
|
),
|
||||||
|
'additional_rows' => array(
|
||||||
|
array(
|
||||||
|
'position' => 'top_of_list',
|
||||||
|
'value' => '<input type="submit" name="delete_redirects" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="button you_sure"><a class="button you_sure" href="' . $scripturl . '?action=admin;area=mailqueue;sa=clear;' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['mailqueue_clear_list_warning'] . '">' . $txt['mailqueue_clear_list'] . '</a> ',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'position' => 'bottom_of_list',
|
||||||
|
'value' => '<input type="submit" name="delete_redirects" value="' . $txt['quickmod_delete_selected'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="button you_sure"><a class="button you_sure" href="' . $scripturl . '?action=admin;area=mailqueue;sa=clear;' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['mailqueue_clear_list_warning'] . '">' . $txt['mailqueue_clear_list'] . '</a> ',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
require_once($sourcedir . '/Subs-List.php');
|
||||||
|
createList($listOptions);
|
||||||
|
|
||||||
|
loadTemplate('ManageMail');
|
||||||
|
$context['sub_template'] = 'browse';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function grabs the mail queue items from the database, according to the params given.
|
||||||
|
* Callback for $listOptions['get_items'] in BrowseMailQueue()
|
||||||
|
*
|
||||||
|
* @param int $start The item to start with (for pagination purposes)
|
||||||
|
* @param int $items_per_page How many items to show on each page
|
||||||
|
* @param string $sort A string indicating how to sort the results
|
||||||
|
* @return array An array with info about the mail queue items
|
||||||
|
*/
|
||||||
|
function list_getMailQueue($start, $items_per_page, $sort)
|
||||||
|
{
|
||||||
|
global $smcFunc, $txt;
|
||||||
|
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT
|
||||||
|
id_mail, time_sent, recipient, priority, private, subject
|
||||||
|
FROM {db_prefix}mail_queue
|
||||||
|
ORDER BY {raw:sort}
|
||||||
|
LIMIT {int:start}, {int:items_per_page}',
|
||||||
|
array(
|
||||||
|
'start' => $start,
|
||||||
|
'sort' => $sort,
|
||||||
|
'items_per_page' => $items_per_page,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$mails = array();
|
||||||
|
while ($row = $smcFunc['db_fetch_assoc']($request))
|
||||||
|
{
|
||||||
|
// Private PM/email subjects and similar shouldn't be shown in the mailbox area.
|
||||||
|
if (!empty($row['private']))
|
||||||
|
$row['subject'] = $txt['personal_message'];
|
||||||
|
|
||||||
|
$mails[] = $row;
|
||||||
|
}
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $mails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total count of items in the mail queue.
|
||||||
|
* Callback for $listOptions['get_count'] in BrowseMailQueue
|
||||||
|
*
|
||||||
|
* @return int The total number of mail queue items
|
||||||
|
*/
|
||||||
|
function list_getMailQueueSize()
|
||||||
|
{
|
||||||
|
global $smcFunc;
|
||||||
|
|
||||||
|
// How many items do we have?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*) AS queue_size
|
||||||
|
FROM {db_prefix}mail_queue',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($mailQueueSize) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
|
||||||
|
return $mailQueueSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to view and modify the mail settings.
|
||||||
|
*
|
||||||
|
* @param bool $return_config Whether to return the $config_vars array (used for admin search)
|
||||||
|
* @return void|array Returns nothing or returns the $config_vars array if $return_config is true
|
||||||
|
*/
|
||||||
|
function ModifyMailSettings($return_config = false)
|
||||||
|
{
|
||||||
|
global $txt, $scripturl, $context, $modSettings, $txtBirthdayEmails;
|
||||||
|
|
||||||
|
loadLanguage('EmailTemplates');
|
||||||
|
|
||||||
|
$body = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_body'];
|
||||||
|
$subject = $txtBirthdayEmails[(empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']) . '_subject'];
|
||||||
|
|
||||||
|
$emails = array();
|
||||||
|
$processedBirthdayEmails = array();
|
||||||
|
foreach ($txtBirthdayEmails as $key => $value)
|
||||||
|
{
|
||||||
|
$index = substr($key, 0, strrpos($key, '_'));
|
||||||
|
$element = substr($key, strrpos($key, '_') + 1);
|
||||||
|
$processedBirthdayEmails[$index][$element] = $value;
|
||||||
|
}
|
||||||
|
foreach ($processedBirthdayEmails as $index => $dummy)
|
||||||
|
$emails[$index] = $index;
|
||||||
|
|
||||||
|
$config_vars = array(
|
||||||
|
// Mail queue stuff, this rocks ;)
|
||||||
|
array('int', 'mail_limit', 'subtext' => $txt['zero_to_disable']),
|
||||||
|
array('int', 'mail_quantity'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
// SMTP stuff.
|
||||||
|
array('select', 'mail_type', array($txt['mail_type_default'], 'SMTP', 'SMTP - STARTTLS')),
|
||||||
|
array('text', 'smtp_host'),
|
||||||
|
array('text', 'smtp_port'),
|
||||||
|
array('text', 'smtp_username'),
|
||||||
|
array('password', 'smtp_password'),
|
||||||
|
'',
|
||||||
|
|
||||||
|
array('select', 'birthday_email', $emails, 'value' => array('subject' => $subject, 'body' => $body), 'javascript' => 'onchange="fetch_birthday_preview()"'),
|
||||||
|
'birthday_subject' => array('var_message', 'birthday_subject', 'var_message' => $processedBirthdayEmails[empty($modSettings['birthday_email']) ? 'happy_birthday' : $modSettings['birthday_email']]['subject'], 'disabled' => true, 'size' => strlen($subject) + 3),
|
||||||
|
'birthday_body' => array('var_message', 'birthday_body', 'var_message' => nl2br($body), 'disabled' => true, 'size' => ceil(strlen($body) / 25)),
|
||||||
|
);
|
||||||
|
|
||||||
|
call_integration_hook('integrate_modify_mail_settings', array(&$config_vars));
|
||||||
|
|
||||||
|
if ($return_config)
|
||||||
|
return $config_vars;
|
||||||
|
|
||||||
|
// Saving?
|
||||||
|
if (isset($_GET['save']))
|
||||||
|
{
|
||||||
|
// Make the SMTP password a little harder to see in a backup etc.
|
||||||
|
if (!empty($_POST['smtp_password'][1]))
|
||||||
|
{
|
||||||
|
$_POST['smtp_password'][0] = base64_encode($_POST['smtp_password'][0]);
|
||||||
|
$_POST['smtp_password'][1] = base64_encode($_POST['smtp_password'][1]);
|
||||||
|
}
|
||||||
|
checkSession();
|
||||||
|
|
||||||
|
// We don't want to save the subject and body previews.
|
||||||
|
unset($config_vars['birthday_subject'], $config_vars['birthday_body']);
|
||||||
|
call_integration_hook('integrate_save_mail_settings');
|
||||||
|
|
||||||
|
saveDBSettings($config_vars);
|
||||||
|
redirectexit('action=admin;area=mailqueue;sa=settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
$context['post_url'] = $scripturl . '?action=admin;area=mailqueue;save;sa=settings';
|
||||||
|
$context['settings_title'] = $txt['mailqueue_settings'];
|
||||||
|
|
||||||
|
prepareDBSettingContext($config_vars);
|
||||||
|
|
||||||
|
$context['settings_insert_above'] = '
|
||||||
|
<script>
|
||||||
|
var bDay = {';
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
foreach ($processedBirthdayEmails as $index => $email)
|
||||||
|
{
|
||||||
|
$is_last = ++$i == count($processedBirthdayEmails);
|
||||||
|
$context['settings_insert_above'] .= '
|
||||||
|
' . $index . ': {
|
||||||
|
subject: ' . JavaScriptEscape($email['subject']) . ',
|
||||||
|
body: ' . JavaScriptEscape(nl2br($email['body'])) . '
|
||||||
|
}' . (!$is_last ? ',' : '');
|
||||||
|
}
|
||||||
|
$context['settings_insert_above'] .= '
|
||||||
|
};
|
||||||
|
function fetch_birthday_preview()
|
||||||
|
{
|
||||||
|
var index = document.getElementById(\'birthday_email\').value;
|
||||||
|
document.getElementById(\'birthday_subject\').innerHTML = bDay[index].subject;
|
||||||
|
document.getElementById(\'birthday_body\').innerHTML = bDay[index].body;
|
||||||
|
}
|
||||||
|
</script>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function clears the mail queue of all emails, and at the end redirects to browse.
|
||||||
|
*/
|
||||||
|
function ClearMailQueue()
|
||||||
|
{
|
||||||
|
global $sourcedir, $smcFunc;
|
||||||
|
|
||||||
|
checkSession('get');
|
||||||
|
|
||||||
|
// This is certainly needed!
|
||||||
|
require_once($sourcedir . '/ScheduledTasks.php');
|
||||||
|
|
||||||
|
// If we don't yet have the total to clear, find it.
|
||||||
|
if (!isset($_GET['te']))
|
||||||
|
{
|
||||||
|
// How many items do we have?
|
||||||
|
$request = $smcFunc['db_query']('', '
|
||||||
|
SELECT COUNT(*) AS queue_size
|
||||||
|
FROM {db_prefix}mail_queue',
|
||||||
|
array(
|
||||||
|
)
|
||||||
|
);
|
||||||
|
list ($_GET['te']) = $smcFunc['db_fetch_row']($request);
|
||||||
|
$smcFunc['db_free_result']($request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$_GET['te'] = (int) $_GET['te'];
|
||||||
|
|
||||||
|
$_GET['sent'] = isset($_GET['sent']) ? (int) $_GET['sent'] : 0;
|
||||||
|
|
||||||
|
// Send 50 at a time, then go for a break...
|
||||||
|
while (ReduceMailQueue(50, true, true) === true)
|
||||||
|
{
|
||||||
|
// Sent another 50.
|
||||||
|
$_GET['sent'] += 50;
|
||||||
|
pauseMailQueueClear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return BrowseMailQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for pausing the mail queue.
|
||||||
|
*/
|
||||||
|
function pauseMailQueueClear()
|
||||||
|
{
|
||||||
|
global $context, $txt;
|
||||||
|
|
||||||
|
// Try get more time...
|
||||||
|
@set_time_limit(600);
|
||||||
|
if (function_exists('apache_reset_timeout'))
|
||||||
|
@apache_reset_timeout();
|
||||||
|
|
||||||
|
// Have we already used our maximum time?
|
||||||
|
if ((time() - TIME_START) < 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$context['continue_get_data'] = '?action=admin;area=mailqueue;sa=clear;te=' . $_GET['te'] . ';sent=' . $_GET['sent'] . ';' . $context['session_var'] . '=' . $context['session_id'];
|
||||||
|
$context['page_title'] = $txt['not_done_title'];
|
||||||
|
$context['continue_post_data'] = '';
|
||||||
|
$context['continue_countdown'] = '2';
|
||||||
|
$context['sub_template'] = 'not_done';
|
||||||
|
|
||||||
|
// Keep browse selected.
|
||||||
|
$context['selected'] = 'browse';
|
||||||
|
|
||||||
|
// What percent through are we?
|
||||||
|
$context['continue_percent'] = round(($_GET['sent'] / $_GET['te']) * 100, 1);
|
||||||
|
|
||||||
|
// Never more than 100%!
|
||||||
|
$context['continue_percent'] = min($context['continue_percent'], 100);
|
||||||
|
|
||||||
|
obExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test mail sending ability.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function TestMailSend()
|
||||||
|
{
|
||||||
|
global $scripturl, $context, $sourcedir, $user_info, $smcFunc;
|
||||||
|
|
||||||
|
loadLanguage('ManageMail');
|
||||||
|
loadTemplate('ManageMail');
|
||||||
|
$context['sub_template'] = 'mailtest';
|
||||||
|
$context['base_url'] = $scripturl . '?action=admin;area=mailqueue;sa=test';
|
||||||
|
$context['post_url'] = $context['base_url'] . ';save';
|
||||||
|
|
||||||
|
// Sending the test message now.
|
||||||
|
if (isset($_GET['save']))
|
||||||
|
{
|
||||||
|
require_once($sourcedir . '/Subs-Post.php');
|
||||||
|
|
||||||
|
// Send to the current user, no options.
|
||||||
|
$to = $user_info['email'];
|
||||||
|
$subject = $smcFunc['htmlspecialchars']($_POST['subject']);
|
||||||
|
$message = $smcFunc['htmlspecialchars']($_POST['message']);
|
||||||
|
|
||||||
|
$result = sendmail($to, $subject, $message, null, null, false, 0);
|
||||||
|
redirectexit($context['base_url'] . ';result=' . ($result ? 'success' : 'failure'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result.
|
||||||
|
if (isset($_GET['result']))
|
||||||
|
$context['result'] = ($_GET['result'] == 'success' ? 'success' : 'failure');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Little utility function to calculate how long ago a time was.
|
||||||
|
*
|
||||||
|
* @param int $time_diff The time difference, in seconds
|
||||||
|
* @return string A string indicating how many days, hours, minutes or seconds (depending on $time_diff)
|
||||||
|
*/
|
||||||
|
function time_since($time_diff)
|
||||||
|
{
|
||||||
|
global $txt;
|
||||||
|
|
||||||
|
if ($time_diff < 0)
|
||||||
|
$time_diff = 0;
|
||||||
|
|
||||||
|
// Just do a bit of an if fest...
|
||||||
|
if ($time_diff > 86400)
|
||||||
|
{
|
||||||
|
$days = round($time_diff / 86400, 1);
|
||||||
|
return sprintf($days == 1 ? $txt['mq_day'] : $txt['mq_days'], $time_diff / 86400);
|
||||||
|
}
|
||||||
|
// Hours?
|
||||||
|
elseif ($time_diff > 3600)
|
||||||
|
{
|
||||||
|
$hours = round($time_diff / 3600, 1);
|
||||||
|
return sprintf($hours == 1 ? $txt['mq_hour'] : $txt['mq_hours'], $hours);
|
||||||
|
}
|
||||||
|
// Minutes?
|
||||||
|
elseif ($time_diff > 60)
|
||||||
|
{
|
||||||
|
$minutes = (int) ($time_diff / 60);
|
||||||
|
return sprintf($minutes == 1 ? $txt['mq_minute'] : $txt['mq_minutes'], $minutes);
|
||||||
|
}
|
||||||
|
// Otherwise must be second
|
||||||
|
else
|
||||||
|
return sprintf($time_diff == 1 ? $txt['mq_second'] : $txt['mq_seconds'], $time_diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|