388 changes: 268 additions & 120 deletions Sources/Calendar.php

Large diffs are not rendered by default.

129 changes: 83 additions & 46 deletions Sources/Class-BrowserDetect.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @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
Expand All @@ -23,21 +24,18 @@
* - 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
{
/**
* Holds all browsers information. Its contents will be placed into $context['browser'].
*
* @var array
* @var array Holds all the browser information. Its contents will be placed into $context['browser']
*/
private $_browsers = null;

/**
* Holds if the detected device may be a mobile one
*
* @var boolean
* @var boolean Whether or not this might be a mobile device
*/
private $_is_mobile = null;

Expand All @@ -59,6 +57,9 @@ function detectBrowser()
// 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();
Expand Down Expand Up @@ -92,6 +93,9 @@ function detectBrowser()
// 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();

Expand All @@ -100,9 +104,10 @@ function detectBrowser()
}

/**
* Determine if the browser is Opera or not
* @return boolean true if the browser is Opera otherwise false
*/
* Determine if the browser is Opera or not
*
* @return boolean Whether or not this is Opera
*/
function isOpera()
{
if (!isset($this->_browsers['is_opera']))
Expand All @@ -111,9 +116,10 @@ function isOpera()
}

/**
* Determine if the browser is IE or not
* @return boolean true if the browser is IE otherwise false
*/
* 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.
Expand All @@ -123,22 +129,36 @@ function isIe()
}

/**
* Determine if the browser is IE11 or not
* @return boolean true if the browser is IE11 otherwise false
*/
* 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 a Webkit based one or not
* @return boolean true if the browser is Webkit based otherwise false
*/
* 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']))
Expand All @@ -147,9 +167,10 @@ function isWebkit()
}

/**
* Determine if the browser is Firefox or one of its variants
* @return boolean true if the browser is Firefox otherwise false
*/
* 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']))
Expand All @@ -158,9 +179,10 @@ function isFirefox()
}

/**
* Determine if the browser is WebTv or not
* @return boolean true if the browser is WebTv otherwise false
*/
* 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']))
Expand All @@ -169,9 +191,10 @@ function isWebTv()
}

/**
* Determine if the browser is konqueror or not
* @return boolean true if the browser is konqueror otherwise false
*/
* Determine if the browser is konqueror or not
*
* @return boolean Whether or not this is Konqueror
*/
function isKonqueror()
{
if (!isset($this->_browsers['is_konqueror']))
Expand All @@ -180,9 +203,10 @@ function isKonqueror()
}

/**
* Determine if the browser is Gecko or not
* @return boolean true if the browser is Gecko otherwise false
*/
* 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']))
Expand All @@ -191,9 +215,10 @@ function isGecko()
}

/**
* Determine if the browser is OperaMini or not
* @return boolean true if the browser is OperaMini otherwise false
*/
* 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']))
Expand All @@ -204,9 +229,10 @@ function isOperaMini()
}

/**
* Determine if the browser is OperaMobi or not
* @return boolean true if the browser is OperaMobi otherwise false
*/
* 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']))
Expand Down Expand Up @@ -258,7 +284,7 @@ private function setupWebkit()
* 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 compatabilty view
* - attempts to distinguish between IE and IE in compatibility view
* - checks for old IE on macs as well, since we can
*/
private function setupIe()
Expand All @@ -273,12 +299,12 @@ private function setupIe()
$this->_browsers['is_ie' . $msie_match[1]] = true;
}

// "modern" ie uses trident 4=ie8, 5=ie9, 6=ie10, 7=ie11 even in compatability view
// "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 compatablity view
// 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;
}
Expand Down Expand Up @@ -336,11 +362,20 @@ private function setupOpera()
$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 compatablity
* - only sets browsers that may need some help via css for compatibility
*/
private function setupBrowserPriority()
{
Expand All @@ -359,6 +394,7 @@ private function setupBrowserPriority()
'is_ie10' => 'ie10',
'is_ie11' => 'ie11',
'is_ie' => 'ie',
'is_edge' => 'edge',
'is_firefox' => 'firefox',
'is_chrome' => 'chrome',
'is_safari' => 'safari',
Expand Down Expand Up @@ -407,7 +443,8 @@ function fillInformation()
'is_android' => false,
'is_chrome' => false,
'is_safari' => false,
'is_gecko' => false,
'is_gecko' => false,
'is_edge' => false,
'is_ie8' => false,
'is_ie7' => false,
'is_ie6' => false,
Expand Down
241 changes: 151 additions & 90 deletions Sources/Class-CurlFetchWeb.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,76 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @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
* ### Load class
* Initiate as
* - $fetch_data = new cURL_fetch_web_data();
* - optionaly pass an array of cURL options and redirect count
* - cURL_fetch_web_data(cURL options array, Max redirects);
* - $fetch_data = new cURL_fetch_web_data(array(CURLOPT_SSL_VERIFYPEER => 1), 5);
* ```
* $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_data('http://www.simplemachines.org'); // fetch a page
* - $fetch_data('http://www.simplemachines.org', array('user' => 'name', 'password' => 'password')); // post to a page
* - $fetch_data('http://www.simplemachines.org', parameter1&parameter2&parameter3); // post to a page
* ### 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&parameter2&parameter3);
* ```
*
* Get the data
* - $fetch_data->result('body'); // just the page content
* - $fetch_data->result(); // an array of results, body, header, http result codes
* - $fetch_data->result_raw(); // show all results of all calls (in the event of a redirect)
* - $fetch_data->result_raw(0); // show all results of call x
* ### 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
* @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 => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko Firefox/11.0', // set a normal looking useragent
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
Expand All @@ -58,12 +83,47 @@ class curl_fetch_web_data
);

/**
* Start the curl object
* - allow for user override values
*
* @param array $options cURL options as an array
* @param int $max_redirect use to overide the default of 3
*/
* @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
Expand All @@ -72,15 +132,16 @@ public function __construct($options = array(), $max_redirect = 3)
}

/**
* 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
*/
* 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?
Expand All @@ -97,14 +158,14 @@ public function get_url_data($url, $post_data = array())
}

/**
* 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 site to fetch
* @param boolean $redirect flag to indicate if this was a redirect request or not
* @return boolean
*/
* 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
Expand Down Expand Up @@ -156,17 +217,17 @@ private function curl_request($url, $redirect = false)
}

/**
* Used if being redirected to ensure we have a fully qualified address
*
* @param string $last_url where we went to
* @param string $new_url where we were redirected to
* @return string new url location
*/
* 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);
$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'];
Expand All @@ -179,13 +240,13 @@ private function get_redirect_url($last_url = '', $new_url = '')
}

/**
* 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
*/
* 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;
Expand All @@ -198,13 +259,13 @@ public function result($area = '')
}

/**
* 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
* @return array|string The entire response array or just the specified response
*/
* 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))
Expand All @@ -217,13 +278,13 @@ public function result_raw($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
* @return string A string of post data
*/
* 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))
Expand All @@ -238,15 +299,15 @@ private function build_post_data($post_data)
}
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
*
*/
* 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
Expand All @@ -271,12 +332,12 @@ private function set_options()
}

/**
* 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
* @param string $referer_url
*/
* 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
Expand All @@ -286,13 +347,13 @@ private function redirect($target_url, $referer_url)
}

/**
* Callback function to parse returned headers
* - lowercases everything to make it consistent
*
* @param type $cr
* @param string $header
* @return int The length of the header
*/
* 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);
Expand Down
226 changes: 114 additions & 112 deletions Sources/Class-Graphics.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @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
Expand All @@ -36,17 +38,17 @@ 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->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);
$this->Buf = range(0, 279);
}

public function decompress($data, &$datLen)
{
$stLen = strlen($data);
$stLen = strlen($data);
$datLen = 0;
$ret = '';
$ret = '';

$this->LZWCommand($data, true);

Expand All @@ -68,10 +70,10 @@ public function LZWCommand(&$data, $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->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);
Expand Down Expand Up @@ -99,7 +101,7 @@ public function LZWCommand(&$data, $bInit)
do
{
$this->FirstCode = $this->GetCode($data, $bInit);
$this->OldCode = $this->FirstCode;
$this->OldCode = $this->FirstCode;
}
while ($this->FirstCode == $this->ClearCode);

Expand Down Expand Up @@ -128,12 +130,12 @@ public function LZWCommand(&$data, $bInit)
$this->Vals[$i] = 0;
}

$this->CodeSize = $this->SetCodeSize + 1;
$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;
$this->MaxCode = $this->ClearCode + 2;
$this->sp = 0;
$this->FirstCode = $this->GetCode($data, $bInit);
$this->OldCode = $this->FirstCode;

return $this->FirstCode;
}
Expand Down Expand Up @@ -192,9 +194,9 @@ public function GetCode(&$data, $bInit)
{
if ($bInit)
{
$this->CurBit = 0;
$this->LastBit = 0;
$this->Done = 0;
$this->CurBit = 0;
$this->LastBit = 0;
$this->Done = 0;
$this->LastByte = 2;

return 1;
Expand All @@ -215,12 +217,12 @@ public function GetCode(&$data, $bInit)
$this->Buf[1] = $this->Buf[$this->LastByte - 1];

$count = ord($data[0]);
$data = substr($data, 1);
$data = substr($data, 1);

if ($count)
{
for ($i = 0; $i < $count; $i++)
$this->Buf[2 + $i] = ord($data{$i});
$this->Buf[2 + $i] = ord($data[$i]);

$data = substr($data, $count);
}
Expand All @@ -229,7 +231,7 @@ public function GetCode(&$data, $bInit)

$this->LastByte = 2 + $count;
$this->CurBit = ($this->CurBit - $this->LastBit) + 16;
$this->LastBit = (2 + $count) << 3;
$this->LastBit = (2 + $count) << 3;
}

$iRet = 0;
Expand All @@ -253,7 +255,7 @@ public function __construct()

public function load($lpData, $num)
{
$this->m_nColors = 0;
$this->m_nColors = 0;
$this->m_arColors = array();

for ($i = 0; $i < $num; $i++)
Expand All @@ -276,8 +278,8 @@ public function toString()
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] & 0x000000FF)) . // R
chr(($this->m_arColors[$i] & 0x0000FF00) >> 8) . // G
chr(($this->m_arColors[$i] & 0x00FF0000) >> 16); // B
}

Expand All @@ -286,18 +288,19 @@ public function toString()

public function colorIndex($rgb)
{
$rgb = intval($rgb) & 0xFFFFFF;
$r1 = ($rgb & 0x0000FF);
$g1 = ($rgb & 0x00FF00) >> 8;
$b1 = ($rgb & 0xFF0000) >> 16;
$idx = -1;
$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;
$g2 = ($this->m_arColors[$i] & 0x0000FF00) >> 8;
$b2 = ($this->m_arColors[$i] & 0x00FF0000) >> 16;
$d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1);
$d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1);

if (($idx == -1) || ($d < $dif))
{
Expand Down Expand Up @@ -336,11 +339,11 @@ public function load($lpData, &$hdrLen)
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_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;

Expand Down Expand Up @@ -379,9 +382,9 @@ public function load($lpData, &$hdrLen)
return false;

$b = ord($lpData[8]);
$this->m_bLocalClr = ($b & 0x80) ? true : false;
$this->m_bLocalClr = ($b & 0x80) ? true : false;
$this->m_bInterlace = ($b & 0x40) ? true : false;
$this->m_bSorted = ($b & 0x20) ? true : false;
$this->m_bSorted = ($b & 0x20) ? true : false;
$this->m_nTableSize = 2 << ($b & 0x07);
$hdrLen = 9;

Expand Down Expand Up @@ -422,41 +425,40 @@ public function load($data, &$datLen)

switch ($b)
{
// Extension...
case 0x21:
$len = 0;
if (!$this->skipExt($data, $len))
return false;
// Extension...
case 0x21:
$len = 0;
if (!$this->skipExt($data, $len))
return false;

$datLen += $len;
break;
$datLen += $len;
break;

// Image...
case 0x2C:
// Load the header and color table.
$len = 0;
if (!$this->m_gih->load($data, $len))
return false;
// 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;
$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;
// Decompress the data, and ride on home ;).
$len = 0;
if (!($this->m_data = $this->m_lzw->decompress($data, $len)))
return false;

$data = substr($data, $len);
$datLen += $len;
$datLen += $len;

if ($this->m_gih->m_bInterlace)
$this->deInterlace();
if ($this->m_gih->m_bInterlace)
$this->deInterlace();

return true;
return true;

case 0x3B: // EOF
default:
return false;
case 0x3B: // EOF
default:
return false;
}
}
return false;
Expand All @@ -472,28 +474,28 @@ public function skipExt(&$data, &$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;
// 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.
Expand All @@ -504,7 +506,7 @@ public function skipExt(&$data, &$extLen)
{
$data = substr($data, $b);
$extLen += $b;
$b = ord($data[0]);
$b = ord($data[0]);
$data = substr($data, 1);
$extLen++;
}
Expand All @@ -519,25 +521,25 @@ public function deInterlace()
{
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;
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)
Expand Down Expand Up @@ -644,7 +646,7 @@ public function get_png_data($background_color)
{
// 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};
$bmp .= $data[$i];
// Otherwise, this is background...
else
$bmp .= chr($background_color);
Expand Down
200 changes: 123 additions & 77 deletions Sources/Class-Package.php

Large diffs are not rendered by default.

611 changes: 611 additions & 0 deletions Sources/Class-Punycode.php

Large diffs are not rendered by default.

145 changes: 105 additions & 40 deletions Sources/Class-SearchAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.0
*/

/**
* Interface search_api_interface
*/
interface search_api_interface
{
/**
Expand All @@ -19,17 +22,17 @@ interface search_api_interface
* they need not be declared
*
* @access public
* @param string $methodName
* @param array $query_params
* @return boolean
* @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
* @return bool Whether or not this method is valid
*/
public function isValid();

Expand All @@ -41,51 +44,53 @@ public function isValid();
* @access public
* @param string $a Word A
* @param string $b Word B
* @return int
* @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
* @param array $wordsSearch
* @param array $wordsExclude
* @param bool $isExcluded
* @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
* @param array $search_data
* @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()}
*
* @see createPost()
*
* @access public
* @param array $msgOptions
* @param array $topicOptions
* @param array $posterOptions
* @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()}
*
* @see modifyPost()
*
* @access public
* @param array $msgOptions
* @param array $topicOptions
* @param array $posterOptions
* @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);
Expand All @@ -94,7 +99,7 @@ public function postModified(array &$msgOptions, array &$topicOptions, array &$p
* Callback when a post is removed
*
* @access public
* @param int $id_msg
* @param int $id_msg The ID of the post that was removed
* @return void
*/
public function postRemoved($id_msg);
Expand All @@ -103,7 +108,7 @@ public function postRemoved($id_msg);
* Callback when a topic is removed
*
* @access public
* @param array $topics
* @param array $topics The ID(s) of the removed topic(s)
* @return void
*/
public function topicsRemoved(array $topics);
Expand All @@ -112,8 +117,8 @@ public function topicsRemoved(array $topics);
* Callback when a topic is moved
*
* @access public
* @param array $topics
* @param int $board_to
* @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);
Expand All @@ -122,36 +127,54 @@ public function topicsMoved(array $topics, $board_to);
* Callback for actually performing the search query
*
* @access public
* @param array $query_params
* @param array $searchWords
* @param array $excludedIndexWords
* @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);
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
{
/**
* This is the last version of SMF that this was tested on, to protect against API changes.
* @var type
* @var string The maximum SMF version that this will work with.
*/
public $version_compatible = 'SMF 2.1 Beta 1';
public $version_compatible = '2.1.999';

/**
* This won't work with versions of SMF less than this.
* @var type
* @var string The minimum SMF version that this will work with.
*/
public $min_smf_version = 'SMF 2.1 Beta 1';
public $min_smf_version = '2.1 RC1';

/**
* Is it supported?
* @var type
* @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}
*/
Expand Down Expand Up @@ -199,6 +222,48 @@ public function postModified(array &$msgOptions, array &$topicOptions, array &$p
*/
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,
)
);
}

/**
Expand All @@ -218,7 +283,7 @@ public function topicsMoved(array $topics, $board_to)
/**
* {@inheritDoc}
*/
public function searchQuery(array $query_params, array $searchWords, array $excludedIndexWords, array $participants, array $searchArray)
public function searchQuery(array $query_params, array $searchWords, array $excludedIndexWords, array &$participants, array &$searchArray)
{
}
}
Expand Down
371 changes: 371 additions & 0 deletions Sources/Class-TOTP.php
Original file line number Diff line number Diff line change
@@ -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;
}
}

?>
186 changes: 83 additions & 103 deletions Sources/DbExtra-mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.0
*/

if (!defined('SMF'))
Expand All @@ -27,15 +27,17 @@ function db_extra_init()
$smcFunc += array(
'db_backup_table' => 'smf_db_backup_table',
'db_optimize_table' => 'smf_db_optimize_table',
'db_insert_sql' => 'smf_db_insert_sql',
'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
Expand All @@ -60,7 +62,8 @@ function smf_db_backup_table($table, $backup_table)
array(
'backup_table' => $backup_table,
'table' => $table
));
)
);
// If this failed, we go old school.
if ($result)
{
Expand All @@ -71,7 +74,8 @@ function smf_db_backup_table($table, $backup_table)
array(
'backup_table' => $backup_table,
'table' => $table
));
)
);

// Old school or no school?
if ($request)
Expand Down Expand Up @@ -169,6 +173,7 @@ function smf_db_backup_table($table, $backup_table)

/**
* This function optimizes a table.
*
* @param string $table The table to be optimized
* @return int How much space was gained
*/
Expand All @@ -180,31 +185,31 @@ function smf_db_optimize_table($table)

// Get how much overhead there is.
$request = $smcFunc['db_query']('', '
SHOW TABLE STATUS LIKE {string:table_name}',
array(
'table_name' => str_replace('_', '\_', $table),
)
);
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,
)
);
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),
)
);
SHOW TABLE STATUS LIKE {string:table}',
array(
'table' => str_replace('_', '\_', $table),
)
);
$row = $smcFunc['db_fetch_assoc']($request);
$smcFunc['db_free_result']($request);

Expand Down Expand Up @@ -246,85 +251,9 @@ function smf_db_list_tables($db = false, $filter = false)
return $tables;
}

/**
* Gets all the necessary INSERTs for the table named table_name.
* It goes in 250 row segments.
*
* @param string $tableName The table to create the inserts for.
* @param boolean $new_table Whether or not this is a new table (resets $start and $limit)
* @return string The query to insert the data back in, or an empty string if the table was empty.
*/
function smf_db_insert_sql($tableName, $new_table = false)
{
global $smcFunc, $db_prefix;
static $start = 0, $num_rows, $fields, $limit;

if ($new_table)
{
$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
$start = 0;
}

$data = '';
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);

// This will be handy...
$crlf = "\r\n";

$result = $smcFunc['db_query']('', '
SELECT /*!40001 SQL_NO_CACHE */ *
FROM `' . $tableName . '`
LIMIT ' . $start . ', ' . $limit,
array(
'security_override' => true,
)
);

// The number of rows, just for record keeping and breaking INSERTs up.
$num_rows = $smcFunc['db_num_rows']($result);

if ($num_rows == 0)
return '';

if ($new_table)
{
$fields = array_keys($smcFunc['db_fetch_assoc']($result));
$smcFunc['db_data_seek']($result, 0);
}

// Start it off with the basic INSERT INTO.
$data = 'INSERT INTO `' . $tableName . '`' . $crlf . "\t" . '(`' . implode('`, `', $fields) . '`)' . $crlf . 'VALUES ';

// Loop through each row.
while ($row = $smcFunc['db_fetch_assoc']($result))
{
// Get the fields in this row...
$field_list = array();

foreach ($row as $item)
{
// Try to figure out the type of each field. (NULL, number, or 'string'.)
if (!isset($item))
$field_list[] = 'NULL';
elseif (is_numeric($item) && (int) $item == $item)
$field_list[] = $item;
else
$field_list[] = '\'' . $smcFunc['db_escape_string']($item) . '\'';
}

$data .= '(' . implode(', ', $field_list) . ')' . ',' . $crlf . "\t";
}

$smcFunc['db_free_result']($result);
$data = substr(trim($data), 0, -1) . ';' . $crlf . $crlf;

$start += $limit;

return $data;
}

/**
* 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
Expand Down Expand Up @@ -414,10 +343,10 @@ function smf_db_table_sql($tableName)
// Ensure the columns are in proper order.
ksort($columns);

$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode($columns, ', ') . ')';
$schema_create .= ',' . $crlf . ' ' . $keyname . ' (' . implode(', ', $columns) . ')';
}

// Now just get the comment and type... (MyISAM, etc.)
// Now just get the comment and engine... (MyISAM, etc.)
$result = $smcFunc['db_query']('', '
SHOW TABLE STATUS
LIKE {string:table}',
Expand All @@ -429,17 +358,23 @@ function smf_db_table_sql($tableName)
$smcFunc['db_free_result']($result);

// Probably MyISAM.... and it might have a comment.
$schema_create .= $crlf . ') ENGINE=' . (isset($row['Type']) ? $row['Type'] : $row['Engine']) . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');
$schema_create .= $crlf . ') ENGINE=' . $row['Engine'] . ($row['Comment'] != '' ? ' COMMENT="' . $row['Comment'] . '"' : '');

return $schema_create;
}

/**
* Get the version number.
* @return string The version
*
* @return string The version
*/
function smf_db_get_version()
{
static $ver;

if (!empty($ver))
return $ver;

global $smcFunc;

$request = $smcFunc['db_query']('', '
Expand All @@ -453,4 +388,49 @@ function smf_db_get_version()
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;
}

?>
219 changes: 107 additions & 112 deletions Sources/DbExtra-postgresql.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.0
*/

if (!defined('SMF'))
Expand All @@ -27,15 +27,17 @@ function db_extra_init()
$smcFunc += array(
'db_backup_table' => 'smf_db_backup_table',
'db_optimize_table' => 'smf_db_optimize_table',
'db_insert_sql' => 'smf_db_insert_sql',
'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
Expand Down Expand Up @@ -82,6 +84,7 @@ function smf_db_backup_table($table, $backup_table)

/**
* This function optimizes a table.
*
* @param string $table The table to be optimized
* @return int How much space was gained
*/
Expand All @@ -91,20 +94,52 @@ function smf_db_optimize_table($table)

$table = str_replace('{db_prefix}', $db_prefix, $table);

$pg_tables = array('pg_catalog', 'information_schema');

$request = $smcFunc['db_query']('', '
VACUUM ANALYZE {raw:table}',
array(
'table' => $table,
)
);
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['Data_free']))
return $row['Data_free'] / 1024;
if (isset($row['size']))
return ($old_size - $row['size']) / 1024;
else
return 0;
}
Expand Down Expand Up @@ -141,87 +176,9 @@ function smf_db_list_tables($db = false, $filter = false)
return $tables;
}

/**
* Gets all the necessary INSERTs for the table named table_name.
* It goes in 250 row segments.
*
* @param string $tableName The table to create the inserts for.
* @param boolean $new_table Whether or not this a new table (resets $start and $limit)
* @return string The query to insert the data back in, or an empty string if the table was empty.
*/
function smf_db_insert_sql($tableName, $new_table = false)
{
global $smcFunc, $db_prefix;
static $start = 0, $num_rows, $fields, $limit;

if ($new_table)
{
$limit = strstr($tableName, 'log_') !== false ? 500 : 250;
$start = 0;
}

$data = '';
$tableName = str_replace('{db_prefix}', $db_prefix, $tableName);

// This will be handy...
$crlf = "\r\n";

$result = $smcFunc['db_query']('', '
SELECT *
FROM ' . $tableName . '
LIMIT ' . $start . ', ' . $limit,
array(
'security_override' => true,
)
);

// The number of rows, just for record keeping and breaking INSERTs up.
$num_rows = $smcFunc['db_num_rows']($result);

if ($num_rows == 0)
return '';

if ($new_table)
{
$fields = array_keys($smcFunc['db_fetch_assoc']($result));
$smcFunc['db_data_seek']($result, 0);
}

// Start it off with the basic INSERT INTO.
$data = '';
$insert_msg = $crlf . 'INSERT INTO ' . $tableName . $crlf . "\t" . '(' . implode(', ', $fields) . ')' . $crlf . 'VALUES ' . $crlf . "\t";

// Loop through each row.
while ($row = $smcFunc['db_fetch_assoc']($result))
{
// Get the fields in this row...
$field_list = array();

foreach ($row as $key => $item)
{
// Try to figure out the type of each field. (NULL, number, or 'string'.)
if (!isset($item))
$field_list[] = 'NULL';
elseif (is_numeric($item) && (int) $item == $item)
$field_list[] = $item;
else
$field_list[] = '\'' . $smcFunc['db_escape_string']($item) . '\'';
}

// 'Insert' the data.
$data .= $insert_msg . '(' . implode(', ', $field_list) . ');' . $crlf;
}
$smcFunc['db_free_result']($result);

$data .= $crlf;

$start += $limit;

return $data;
}

/**
* 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
Expand All @@ -235,8 +192,11 @@ function smf_db_table_sql($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;
$schema_create .= 'CREATE TABLE ' . $tableName . ' (' . $crlf;
$index_create = '';
$seq_create = '';

Expand Down Expand Up @@ -294,28 +254,40 @@ function smf_db_table_sql($tableName)
$schema_create = substr($schema_create, 0, -strlen($crlf) - 1);

$result = $smcFunc['db_query']('', '
SELECT CASE WHEN i.indisprimary THEN 1 ELSE 0 END AS is_primary, pg_get_indexdef(i.indexrelid) AS inddef
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}',
WHERE c.relname = {string:table} AND i.indisprimary is {raw:pk}',
array(
'table' => $tableName,
'pk' => 'false',
)
);
$indexes = array();

while ($row = $smcFunc['db_fetch_assoc']($result))
{
if ($row['is_primary'])
{
if (preg_match('~\(([^\)]+?)\)~i', $row['inddef'], $matches) == 0)
continue;
$index_create .= $crlf . $row['inddef'] . ';';
}

$index_create .= $crlf . 'ALTER TABLE ' . $tableName . ' ADD PRIMARY KEY ("' . $matches[1] . '");';
}
else
$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!
Expand All @@ -326,21 +298,44 @@ function smf_db_table_sql($tableName)

/**
* Get the version number.
* @return string The version
*
* @return string The version
*/
function smf_db_get_version()
{
global $smcFunc;
global $db_connection;
static $ver;

$request = $smcFunc['db_query']('', '
SHOW server_version',
array(
)
);
list ($ver) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);
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;
}

?>
430 changes: 341 additions & 89 deletions Sources/DbPackages-mysql.php

Large diffs are not rendered by default.

467 changes: 297 additions & 170 deletions Sources/DbPackages-postgresql.php

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions Sources/DbSearch-mysql.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.4
*/

if (!defined('SMF'))
Expand All @@ -30,6 +30,10 @@ function db_search_init()
'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', '>=');
}

/**
Expand Down
109 changes: 83 additions & 26 deletions Sources/DbSearch-postgresql.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.4
*/

if (!defined('SMF'))
Expand All @@ -29,7 +29,13 @@ function db_search_init()
'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;
}

/**
Expand All @@ -40,7 +46,7 @@ function db_search_init()
*/
function smf_db_search_support($search_type)
{
$supported_types = array('custom');
$supported_types = array('custom', 'fulltext');

return in_array($search_type, $supported_types);
}
Expand All @@ -60,44 +66,58 @@ function smf_db_search_query($identifier, $db_string, $db_values = array(), $con

$replacements = array(
'create_tmp_log_search_topics' => array(
'~mediumint\(\d\)~i' => 'int',
'~unsigned~i' => '',
'~ENGINE=MEMORY~i' => '',
),
'create_tmp_log_search_messages' => array(
'~mediumint\(\d\)' => 'int',
'~unsigned~i' => '',
'~ENGINE=MEMORY~i' => '',
),
'drop_tmp_log_search_topics' => array(
'~IF\sEXISTS~i' => '',
),
'drop_tmp_log_search_messages' => array(
'~IF\sEXISTS~i' => '',
),
'insert_into_log_messages_fulltext' => array(
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => '~NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_results_subject' => array(
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => 'NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
'/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);
elseif (preg_match('~^\s*INSERT\sIGNORE~i', $db_string) != 0)
if (preg_match('~^\s*INSERT\sIGNORE~i', $db_string) != 0)
{
$db_string = preg_replace('~^\s*INSERT\sIGNORE~i', 'INSERT', $db_string);
// Don't error on multi-insert.
$db_values['db_error_skip'] = true;
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
);
Expand Down Expand Up @@ -129,4 +149,41 @@ function smf_db_create_word_search($size)
);
}

/**
* 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;
}

?>
1,126 changes: 466 additions & 660 deletions Sources/Display.php

Large diffs are not rendered by default.

144 changes: 87 additions & 57 deletions Sources/Drafts.php

Large diffs are not rendered by default.

227 changes: 151 additions & 76 deletions Sources/Errors.php

Large diffs are not rendered by default.

237 changes: 85 additions & 152 deletions Sources/Groups.php

Large diffs are not rendered by default.

92 changes: 38 additions & 54 deletions Sources/Help.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @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 Beta 1
* @version 2.1.3
*/

if (!defined('SMF'))
Expand All @@ -20,7 +20,8 @@
* 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.
*
* Uses Help template and Manual language file.
*/
function ShowHelp()
{
Expand All @@ -29,7 +30,6 @@ function ShowHelp()

$subActions = array(
'index' => 'HelpIndex',
'rules' => 'HelpRules',
);

// CRUD $subActions as needed.
Expand All @@ -39,12 +39,15 @@ function ShowHelp()
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'] = 'http://wiki.simplemachines.org/smf';
$context['wiki_url'] = 'https://wiki.simplemachines.org/smf';
$context['wiki_prefix'] = 'SMF2.1:';

$context['canonical_url'] = $scripturl . '?action=help';
Expand Down Expand Up @@ -74,53 +77,19 @@ function HelpIndex()
$context['sub_template'] = 'manual';
}

function HelpRules()
{
global $context, $txt, $boarddir, $user_info, $scripturl;

// Build the link tree.
$context['linktree'][] = array(
'url' => $scripturl . '?action=help',
'name' => $txt['help'],
);
$context['linktree'][] = array(
'url' => $scripturl . '?action=help;sa=rules',
'name' => $txt['terms_and_rules'],
);

// Have we got a localized one?
if (file_exists($boarddir . '/agreement.' . $user_info['language'] . '.txt'))
$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.' . $user_info['language'] . '.txt'), true, 'agreement_' . $user_info['language']);
elseif (file_exists($boarddir . '/agreement.txt'))
$context['agreement'] = parse_bbc(file_get_contents($boarddir . '/agreement.txt'), true, 'agreement');
else
$context['agreement'] = '';

// Nothing to show, so let's get out of here
if (empty($context['agreement']))
{
// No file found or a blank file! Just leave...
redirectexit();
}

$context['canonical_url'] = $scripturl . '?action=help;sa=rules';

$context['page_title'] = $txt['terms_and_rules'];
$context['sub_template'] = 'terms';
}

/**
* 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 Help template, popup sub template, no layers.
*
* Uses ManagePermissions language file, if the help starts with permissionhelp.
* @uses template_popup() with no layers.
*/
function ShowAdminHelp()
{
global $txt, $helptxt, $context, $scripturl;
global $txt, $helptxt, $context, $scripturl, $boarddir, $boardurl;

if (!isset($_GET['help']) || !is_string($_GET['help']))
fatal_lang_error('no_access', false);
Expand All @@ -138,26 +107,41 @@ function ShowAdminHelp()
loadTemplate('Help');

// Allow mods to load their own language file here
call_integration_hook('integrate_helpadmin');

// 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';
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
$context['help_text'] = $_GET['help'];
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';
}

?>
121 changes: 72 additions & 49 deletions Sources/Likes.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,72 @@
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines http://www.simplemachines.org
* @copyright 2014 Simple Machines and individual contributors
* @license http://www.simplemachines.org/about/smf/license.php BSD
* @author Simple Machines https://www.simplemachines.org
* @copyright 2024 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1 Beta 1
* @version 2.1.5
*/

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.
* @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]
* @var string The sub action sent in $_GET['sa'].
*/
protected $_sa = null;

/**
* @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.
* @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'].
* @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.
* @var integer a valid ID to identify your like content.
*/
protected $_content = 0;

/**
*@var integer The number of times your content has been liked.
* @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.
* @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_see' boolean|string whether or not the current user can see the like.
* => 'can_like' boolean|string whether or not the current user can actually like your content.
* for both can_like and can_see: 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.
* 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_see' => false,
'can_like' => false,
'redirect' => '',
'type' => '',
Expand Down Expand Up @@ -114,8 +120,6 @@ public function __construct()
*
* 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
* @param
* @return
*/
public function call()
{
Expand Down Expand Up @@ -160,13 +164,14 @@ public function call()
*
* 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.
* @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;
$property = '_' . $property;
return property_exists($this, $property) ? $this->$property : false;
}

Expand Down Expand Up @@ -201,8 +206,7 @@ protected function check()
$request = $smcFunc['db_query']('', '
SELECT m.id_topic, m.id_member
FROM {db_prefix}messages AS m
INNER JOIN {db_prefix}boards AS b ON (m.id_board = b.id_board)
WHERE {query_see_board}
WHERE {query_see_message_board}
AND m.id_msg = {int:msg}',
array(
'msg' => $this->_content,
Expand All @@ -220,7 +224,6 @@ protected function check()
$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_see'] = allowedTo('likes_view') ? true : 'cannot_view_likes';

$this->_validLikes['can_like'] = ($this->_user['id'] == $topicOwner ? 'cannot_like_content' : (allowedTo('likes_like') ? true : 'cannot_like_content'));
}
Expand All @@ -230,7 +233,7 @@ protected function check()
// 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 cannot see it, return the appropriate key (can_see) as false. If the user can see it and can like it, you MUST return your type in the 'type' key back.
// 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));

Expand Down Expand Up @@ -259,13 +262,9 @@ protected function check()
return $this->_error = 'cannot_';
}

// Does the user can see this?
if (isset($this->_validLikes['can_see']) && is_string($this->_validLikes['can_see']))
return $this->_error = $this->_validLikes['can_see'];

// 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'];
if ($this->_sa != 'view' && isset($this->_validLikes['can_like']) && is_string($this->_validLikes['can_like']))
return $this->_error = $this->_validLikes['can_like'];
}

/**
Expand All @@ -292,6 +291,39 @@ protected function delete()
// 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' => '-'));
}
}

/**
Expand All @@ -308,10 +340,11 @@ protected function insert()
$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',
$smcFunc['db_insert']('ignore',
'{db_prefix}user_likes',
array('content_id' => 'int', 'content_type' => 'string-6', 'id_member' => 'int', 'like_time' => 'int'),
array($content, $type, $user['id'], $time),
Expand All @@ -324,7 +357,7 @@ protected function insert()
$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', serialize(array(
array('$sourcedir/tasks/Likes-Notify.php', 'Likes_Notify_Background', $smcFunc['json_encode'](array(
'content_id' => $content,
'content_type' => $type,
'sender_id' => $user['id'],
Expand All @@ -349,7 +382,7 @@ protected function _count()
global $smcFunc;

$request = $smcFunc['db_query']('', '
SELECT COUNT(id_member)
SELECT COUNT(*)
FROM {db_prefix}user_likes
WHERE content_id = {int:like_content}
AND content_type = {string:like_type}',
Expand Down Expand Up @@ -432,7 +465,6 @@ protected function like()
'id_content' => $this->_content,
'count' => $this->_numLikes,
'can_like' => $this->_validLikes['can_like'],
'can_see' => $this->_validLikes['can_see'],
'already_liked' => empty($this->_alreadyLiked),
'type' => $this->_type,
);
Expand Down Expand Up @@ -569,7 +601,7 @@ protected function response()

// Nope? then just do a redirect to whatever URL was provided.
else
redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] .';error='. $this->_error : '');
redirectexit(!empty($this->_validLikes['redirect']) ? $this->_validLikes['redirect'] . ';error=' . $this->_error : '');

return;
}
Expand All @@ -586,7 +618,7 @@ protected function response()
if (in_array($this->_sa, $generic))
{
$context['sub_template'] = 'generic';
$context['data'] = isset($txt['like_'. $this->_data]) ? $txt['like_'. $this->_data] : $this->_data;
$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.
Expand All @@ -598,21 +630,12 @@ protected function response()
}
}

/**
* Outputs a JSON-encoded response
*/
protected function jsonResponse()
{
global $modSettings;

// Kill anything else.
ob_end_clean();

if (!empty($modSettings['CompressedOutput']))
@ob_start('ob_gzhandler');

else
ob_start();

// Send the header.
header('Content-Type: application/json');
global $smcFunc;

$print = array(
'data' => $this->_data,
Expand All @@ -631,7 +654,7 @@ protected function jsonResponse()
call_integration_hook('integrate_likes_json_response', array(&$print));

// Print the data.
echo json_encode($print);
smf_serverResponse($smcFunc['json_encode']($print));
die;
}
}
Expand All @@ -647,7 +670,7 @@ function BookOfUnknown()
<html', $context['right_to_left'] ? ' dir="rtl"' : '', '>
<head>
<title>The Book of Unknown, ', @$_GET['verse'] == '2:18' ? '2:18' : '4:16', '</title>
<style type="text/css">
<style>
em
{
font-size: 1.3em;
Expand Down
Loading