2015-04-01 19:34:36 +02:00
< ? php
/*
* Main Class
*/
namespace Sakura ;
class Main {
2015-04-30 23:01:01 +00:00
public static $_MD ; // Markdown class container
public static $_MANAGE_MODE = false ; // Management mode
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
// Constructor
public static function init ( $config ) {
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
// Stop the execution if the PHP Version is older than 5.4.0
if ( version_compare ( phpversion (), '5.4.0' , '<' ))
die ( '<h3>Upgrade your PHP Version to at least PHP 5.4!</h3>' );
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
// Configuration Management and local configuration
Configuration :: init ( $config );
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
// Database
Database :: init ();
2015-04-01 19:34:36 +02:00
// "Dynamic" Configuration
Configuration :: initDB ();
// Create new session
Session :: init ();
2015-04-30 23:01:01 +00:00
// Check if management mode was requested
self :: $_MANAGE_MODE = defined ( 'SAKURA_MANAGE' );
2015-04-01 19:34:36 +02:00
// Templating engine
2015-04-30 23:01:01 +00:00
Templates :: init ( self :: $_MANAGE_MODE ? Configuration :: getConfig ( 'manage_style' ) : Configuration :: getConfig ( 'site_style' ));
2015-04-18 18:26:52 +00:00
// Assign servers file to whois class
Whois :: setServers ( Configuration :: getLocalConfig ( 'etc' , 'whoisservers' ));
2015-04-01 19:34:36 +02:00
// Markdown Parser
2015-04-02 15:41:05 +02:00
self :: initMD ();
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
}
2015-04-01 19:34:36 +02:00
// Initialise Parsedown
2015-04-02 15:41:05 +02:00
private static function initMD () {
2015-04-01 19:34:36 +02:00
self :: $_MD = new \Parsedown ();
}
2015-04-01 19:45:05 +02:00
// Parse markdown
2015-04-01 19:46:02 +02:00
public static function mdParse ( $text ) {
2015-04-01 19:45:05 +02:00
2015-04-02 15:30:05 +02:00
return self :: $_MD -> text ( $text );
2015-04-01 19:45:05 +02:00
}
2015-04-01 19:34:36 +02:00
// 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 ;
}
2015-04-18 18:32:41 +00:00
// Error Handler
2015-04-30 23:01:01 +00:00
public static function errorHandler ( $errno , $errstr , $errfile , $errline ) {
2015-04-01 19:34:36 +02:00
// Set some variables to work with including A HUGE fallback hackjob for the templates folder
2015-04-18 18:26:52 +00:00
$errstr = str_replace ( ROOT , '' , $errstr );
$errfile = str_replace ( ROOT , '' , $errfile );
$templates = ROOT . '_sakura/templates/' ;
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
switch ( $errno ) {
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
case E_ERROR :
case E_USER_ERROR :
$error = '<b>FATAL ERROR</b>: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile ;
2015-04-01 19:34:36 +02:00
break ;
2015-04-18 18:32:41 +00:00
case E_WARNING :
case E_USER_WARNING :
$error = '<b>WARNING</b>: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile ;
2015-04-01 19:34:36 +02:00
break ;
2015-04-18 18:32:41 +00:00
case E_NOTICE :
case E_USER_NOTICE :
$error = '<b>NOTICE</b>: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile ;
2015-04-01 19:34:36 +02:00
break ;
2015-04-18 18:32:41 +00:00
default :
$error = '<b>Unknown error type</b> [' . $errno . ']: ' . $errstr . ' on line ' . $errline . ' in ' . $errfile ;
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
}
2015-04-01 19:34:36 +02:00
// 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 );
2015-04-18 18:32:41 +00:00
// Truncate all previous outputs
ob_clean ();
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
// Die and display error message
die ( $error );
2015-04-01 19:34:36 +02:00
2015-04-18 18:32:41 +00:00
}
2015-04-01 19:34:36 +02:00
2015-04-18 18:26:52 +00:00
// 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 ;
}
2015-04-01 19:34:36 +02:00
// 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
2015-04-27 00:41:59 +00:00
public static function cleanString ( $string , $lower = false , $nospecial = false ) {
2015-04-01 19:34:36 +02:00
2015-04-27 00:41:59 +00:00
// Run common sanitisation function over string
2015-04-18 18:32:41 +00:00
$string = htmlentities ( $string , ENT_QUOTES | ENT_IGNORE , Configuration :: getConfig ( 'charset' ));
$string = stripslashes ( $string );
$string = strip_tags ( $string );
2015-04-27 00:41:59 +00:00
// If set also make the string lowercase
2015-04-01 19:34:36 +02:00
if ( $lower )
$string = strtolower ( $string );
2015-04-27 00:41:59 +00:00
// 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
2015-04-18 18:32:41 +00:00
return $string ;
2015-04-01 19:34:36 +02:00
}
// Getting news posts
2015-04-05 18:24:07 +02:00
public static function getNewsPosts ( $limit = null , $pid = false ) {
2015-04-01 19:34:36 +02:00
// Get news posts
2015-04-05 18:24:07 +02:00
$newsPosts = Database :: fetch ( 'news' , true , ( $pid ? [ 'id' => [ $limit , '=' ]] : null ), [ 'id' , true ], ( $limit && ! $pid ? [ $limit ] : null ));
2015-04-01 19:34:36 +02:00
// Get user data
foreach ( $newsPosts as $newsId => $newsPost ) {
2015-04-25 20:08:44 +00:00
2015-04-02 15:27:21 +02:00
$newsPosts [ $newsId ][ 'parsed' ] = self :: mdParse ( $newsPost [ 'content' ]);
$newsPosts [ $newsId ][ 'udata' ] = Users :: getUser ( $newsPost [ 'uid' ]);
2015-04-06 20:06:33 +00:00
$newsPosts [ $newsId ][ 'rdata' ] = Users :: getRank ( $newsPosts [ $newsId ][ 'udata' ][ 'rank_main' ]);
2015-04-25 20:08:44 +00:00
// 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' ];
2015-04-01 19:34:36 +02:00
}
// Return posts
return $newsPosts ;
}
// Loading info pages
public static function loadInfoPage ( $id ) {
// Get contents from the database
$infopage = Database :: fetch ( 'infopages' , false , [ 'shorthand' => [ $id , '=' ]]);
2015-04-01 19:45:05 +02:00
// Return the data if there is any else just return false
return count ( $infopage ) ? $infopage : false ;
2015-04-01 19:34:36 +02:00
}
2015-04-14 14:27:37 +00:00
// Validate MX records
public static function checkMXRecord ( $email ) {
2015-04-19 13:00:32 +00:00
// Get the domain from the e-mail address
$domain = substr ( strstr ( $email , '@' ), 1 );
2015-04-14 14:27:37 +00:00
// 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 ;
}
2015-04-18 18:26:52 +00:00
// Get country code from CloudFlare header (which just returns EU if not found)
public static function getCountryCode () {
2015-04-27 17:04:27 +00:00
// Check if the required header is set and return it
if ( isset ( $_SERVER [ 'HTTP_CF_IPCOUNTRY' ]))
return $_SERVER [ 'HTTP_CF_IPCOUNTRY' ];
2015-04-18 18:26:52 +00:00
// Return EU as a fallback
return 'EU' ;
}
2015-04-19 13:00:32 +00:00
// 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' ]];
}
2015-04-24 17:48:56 +00:00
// 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 );
}
2015-04-27 00:41:59 +00:00
// 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
}
2015-04-30 14:12:49 +00:00
// Get FAQ data
public static function getFaqData () {
// Do database call
$faq = Database :: fetch ( 'faq' , true , null , [ 'id' ]);
// Return FAQ data
return $faq ;
}
2015-04-01 19:34:36 +02:00
}