Upgrade your PHP Version to at least PHP 5.4!');
// Configuration Management and local configuration
Configuration::init($config);
// Database
Database::init();
// "Dynamic" Configuration
Configuration::initDB();
// Create new session
Session::init();
// Check if management mode was requested
self::$_MANAGE_MODE = defined('SAKURA_MANAGE');
// Templating engine
Templates::init(self::$_MANAGE_MODE ? Configuration::getConfig('manage_style') : Configuration::getConfig('site_style'));
// Assign servers file to whois class
Whois::setServers(Configuration::getLocalConfig('etc', 'whoisservers'));
// Markdown Parser
self::initMD();
}
// Initialise Parsedown
private static function initMD() {
self::$_MD = new \Parsedown();
}
// Parse markdown
public static function mdParse($text) {
return self::$_MD->text($text);
}
// Verify ReCAPTCHA
public static function verifyCaptcha($response) {
// Attempt to get the response
$resp = @file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='. Configuration::getConfig('recaptcha_private') .'&response='. $response);
// In the highly unlikely case that it failed to get anything forge a false
if(!$resp)
return array('success' => false, 'error-codes' => array('Could not connect to the ReCAPTCHA server.'));
// Decode the response JSON from the servers
$resp = json_decode($resp, true);
// Return shit
return $resp;
}
// Error Handler
public static function errorHandler($errno, $errstr, $errfile, $errline) {
// Set some variables to work with including A HUGE fallback hackjob for the templates folder
$errstr = str_replace(ROOT, '', $errstr);
$errfile = str_replace(ROOT, '', $errfile);
$templates = ROOT .'_sakura/templates/';
switch ($errno) {
case E_ERROR:
case E_USER_ERROR:
$error = 'FATAL ERROR: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile;
break;
case E_WARNING:
case E_USER_WARNING:
$error = 'WARNING: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile;
break;
case E_NOTICE:
case E_USER_NOTICE:
$error = 'NOTICE: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile;
break;
default:
$error = 'Unknown error type [' . $errno . ']: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile;
}
// Use file_get_contents instead of Twig in case the problem is related to twig
$errorPage = file_get_contents($templates. 'errorPage.tpl');
// str_replace {{ error }} on the error page with the error data
$error = str_replace('{{ error }}', $error, $errorPage);
// Truncate all previous outputs
ob_clean();
// Die and display error message
die($error);
}
// Send emails
public static function sendMail($to, $subject, $body) {
// Initialise PHPMailer
$mail = new \PHPMailer();
// Set to SMTP
$mail->IsSMTP();
// Set the SMTP server host
$mail->Host = Configuration::getConfig('smtp_server');
// Do we require authentication?
$mail->SMTPAuth = Configuration::getConfig('smtp_auth');
// Do we encrypt as well?
$mail->SMTPSecure = Configuration::getConfig('smtp_secure');
// Set the port to the SMTP server
$mail->Port = Configuration::getConfig('smtp_port');
// If authentication is required log in as well
if(Configuration::getConfig('smtp_auth')) {
$mail->Username = Configuration::getConfig('smtp_username');
$mail->Password = base64_decode(Configuration::getConfig('smtp_password'));
}
// Add a reply-to header
$mail->AddReplyTo(Configuration::getConfig('smtp_replyto_mail'), Configuration::getConfig('smtp_replyto_name'));
// Set a from address as well
$mail->SetFrom(Configuration::getConfig('smtp_from_email'), Configuration::getConfig('smtp_from_name'));
// Set the addressee
foreach($to as $email => $name)
$mail->AddBCC($email, $name);
// Subject line
$mail->Subject = $subject;
// Set the mail type to HTML
$mail->isHTML(true);
// Set email contents
$htmlMail = file_get_contents(ROOT .'_sakura/templates/htmlEmail.tpl');
// Replace template tags
$htmlMail = str_replace('{{ sitename }}', Configuration::getConfig('sitename'), $htmlMail);
$htmlMail = str_replace('{{ siteurl }}', '//'. Configuration::getLocalConfig('urls', 'main'), $htmlMail);
$htmlMail = str_replace('{{ contents }}', self::mdParse($body), $htmlMail);
// Set HTML body
$mail->Body = $htmlMail;
// Set fallback body
$mail->AltBody = $body;
// Send the message
$send = $mail->Send();
// Clear the addressee list
$mail->ClearAddresses();
// If we got an error return the error
if(!$send)
return $mail->ErrorInfo;
// Else just return whatever
return $send;
}
// Legacy password hashing to be able to validate passwords from users on the old backend.
public static function legacyPasswordHash($data) {
return hash('sha512', strrev(hash('sha512', $data)));
}
// Cleaning strings
public static function cleanString($string, $lower = false, $nospecial = false) {
// Run common sanitisation function over string
$string = htmlentities($string, ENT_QUOTES | ENT_IGNORE, Configuration::getConfig('charset'));
$string = stripslashes($string);
$string = strip_tags($string);
// If set also make the string lowercase
if($lower)
$string = strtolower($string);
// If set remove all characters that aren't a-z or 0-9
if($nospecial)
$string = preg_replace('/[^a-z0-9]/', '', $string);
// Return clean string
return $string;
}
// Getting news posts
public static function getNewsPosts($limit = null, $pid = false) {
// Get news posts
$newsPosts = Database::fetch('news', true, ($pid ? ['id' => [$limit, '=']] : null), ['id', true], ($limit && !$pid ? [$limit] : null));
// Get user data
foreach($newsPosts as $newsId => $newsPost) {
$newsPosts[$newsId]['parsed'] = self::mdParse($newsPost['content']);
$newsPosts[$newsId]['udata'] = Users::getUser($newsPost['uid']);
$newsPosts[$newsId]['rdata'] = Users::getRank($newsPosts[$newsId]['udata']['rank_main']);
// Check if a custom name colour is set and if so overwrite the rank colour
if($newsPosts[$newsId]['udata']['name_colour'] != null)
$newsPosts[$newsId]['rdata']['colour'] = $newsPosts[$newsId]['udata']['name_colour'];
}
// Return posts
return $newsPosts;
}
// Loading info pages
public static function loadInfoPage($id) {
// Get contents from the database
$infopage = Database::fetch('infopages', false, ['shorthand' => [$id, '=']]);
// Return the data if there is any else just return false
return count($infopage) ? $infopage : false;
}
// Validate MX records
public static function checkMXRecord($email) {
// Get the domain from the e-mail address
$domain = substr(strstr($email, '@'), 1);
// Check the MX record
$record = checkdnsrr($domain, 'MX');
// Return the record data
return $record;
}
// Check IP version
public static function ipVersion($ip) {
// Check if var is IP
if(filter_var($ip, FILTER_VALIDATE_IP)) {
// IPv4
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
return 4;
// IPv6
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
return 6;
}
// Not an IP or unknown type
return 0;
}
// Convert inet_pton to string with bits
public static function inetToBits($inet) {
// Unpack string
$unpacked = unpack('A16', $inet);
// Split the string
$unpacked = str_split($unpacked[1]);
// Define variable
$binaryIP = null;
// "Build" binary IP
foreach($unpacked as $char)
$binaryIP .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
// Return IP
return $binaryIP;
}
// Match IP subnets
public static function matchSubnet($ip, $range) {
// Use the proper IP type
switch(self::ipVersion($ip)) {
case 4:
// Break the range up in parts
list($subnet, $bits) = explode('/', $range);
// Convert IP and Subnet to long
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
// In case the supplied subnet wasn't correctly aligned
$subnet &= $mask;
// Return true if IP is in subnet
return ($ip & $mask) == $subnet;
case 6:
// Break the range up in parts
list($subnet, $bits) = explode('/', $range);
// Convert subnet to packed address and convert it to binary
$subnet = inet_pton($subnet);
$binarySubnet = self::inetToBits($subnet);
// Convert IPv6 to packed address and convert it to binary as well
$ip = inet_pton($ip);
$binaryIP = self::inetToBits($ip);
// Return bits of the strings according to the bits
$ipBits = substr($binaryIP, 0, $bits);
$subnetBits = substr($binarySubnet, 0, $bits);
return ($ipBits === $subnetBits);
default:
return 0;
}
}
// Check if IP is a CloudFlare IP
public static function checkCFIP($ip) {
// Get CloudFlare Subnet list
$cfhosts = file_get_contents(Configuration::getLocalConfig('etc', 'cfhosts'));
// Replace \r\n with \n
$cfhosts = str_replace("\r\n", "\n", $cfhosts);
// Explode the file into an array
$cfhosts = explode("\n", $cfhosts);
// Check if IP is in a CloudFlare subnet
foreach($cfhosts as $subnet) {
// Return true if found
if(self::matchSubnet($ip, $subnet))
return true;
}
// Return false if fails
return false;
}
// Gets IP of current visitor
public static function getRemoteIP() {
// Assign REMOTE_ADDR to a variables
$ip = $_SERVER['REMOTE_ADDR'];
// Check if the IP is a CloudFlare IP
if(self::checkCFIP($ip)) {
// If it is check if the CloudFlare IP header is set and if it is assign it to the ip variable
if(isset($_SERVER['HTTP_CF_CONNECTING_IP']))
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
// Return the correct IP
return $ip;
}
// Get country code from CloudFlare header (which just returns EU if not found)
public static function getCountryCode() {
// Check if the required header is set and return it
if(isset($_SERVER['HTTP_CF_IPCOUNTRY']))
return $_SERVER['HTTP_CF_IPCOUNTRY'];
// Return EU as a fallback
return 'EU';
}
// Create a new action code
public static function newActionCode($action, $userid, $instruct) {
// Make sure the user we're working with exists
if(Users::getUser($userid)['id'] == 0)
return false;
// Convert the instruction array to a JSON
$instruct = json_encode($instruct);
// Generate a key
$key = sha1(date("r") . time() . $userid . $action . rand(0, 9999));
// Insert the key into the database
Database::insert('actioncodes', [
'action' => $action,
'userid' => $userid,
'actkey' => $key,
'instruction' => $instruct
]);
// Return the key
return $key;
}
// Use an action code
public static function useActionCode($action, $key, $uid = 0) {
// Retrieve the row from the database
$keyRow = Database::fetch('actioncodes', false, [
'actkey' => [$key, '='],
'action' => [$action, '=']
]);
// Check if the code exists
if(count($keyRow) <= 1)
return [0, 'INVALID_CODE'];
// Check if the code was intended for the user that's using this code
if($keyRow['userid'] != 0) {
if($keyRow['userid'] != $uid)
return [0, 'INVALID_USER'];
}
// Remove the key from the database
Database::delete('actioncodes', [
'id' => [$keyRow['id'], '=']
]);
// Return success
return [1, 'SUCCESS', $keyRow['instruction']];
}
// Calculate password entropy
public static function pwdEntropy($pw) {
// Decode utf-8 chars
$pw = utf8_decode($pw);
// Count the amount of unique characters in the password string and calculate the entropy
return count(count_chars($pw, 1)) * log(256, 2);
}
// Get country name from ISO 3166 code
public static function getCountryName($code) {
// Parse JSON file
$iso3166 = json_decode(file_get_contents(Configuration::getLocalConfig('etc', 'iso3166')), true);
// Check if key exists
if(array_key_exists($code, $iso3166))
return $iso3166[$code]; // If entry found return the full name
else
return 'Unknown'; // Else return unknown
}
// Get FAQ data
public static function getFaqData() {
// Do database call
$faq = Database::fetch('faq', true, null, ['id']);
// Return FAQ data
return $faq;
}
// Get log type string
public static function getLogStringFromType($type) {
// Query the database
$return = Database::fetch('logtypes', false, ['id' => [$type, '=']]);
// Check if type exists and else return a unformattable string
if(count($return) < 2)
return 'Unknown action.';
// Return the string
return $return['string'];
}
// Get formatted logs
public static function getUserLogs($uid = 0) {
// Check if a user is specified
$conditions = ($uid ? ['uid' => [$uid, '=']] : null);
// Get data from database
$logsDB = Database::fetch('logs', true, $conditions, ['id', true]);
// Storage array
$logs = array();
// Iterate over entries
foreach($logsDB as $log) {
// Store usable data
$logs[$log['id']] = [
'user' => $_USER = Users::getUser($log['uid']),
'rank' => Users::getRank($_USER['rank_main']),
'string' => vsprintf(self::getLogStringFromType($log['action']), json_decode($log['attribs'], true))
];
}
// Return new logs
return $logs;
}
}