2015-04-01 19:34:36 +02:00
< ? php
/*
* Main Class
*/
2015-08-24 00:08:36 +02:00
2015-04-01 19:34:36 +02:00
namespace Sakura ;
2015-06-04 12:41:55 +00:00
use Parsedown ;
use PHPMailer ;
2015-04-01 19:34:36 +02:00
class Main {
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
// 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
2015-05-29 19:27:45 +00:00
Database :: init ( Configuration :: getLocalConfig ( 'database' , 'driver' ));
2015-04-01 19:34:36 +02:00
// "Dynamic" Configuration
Configuration :: initDB ();
// Create new session
Session :: init ();
}
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-08-24 00:08:36 +02:00
return ( new Parsedown ()) -> text ( $text );
2015-04-01 19:45:05 +02:00
}
2015-07-30 20:51:24 +02:00
// Get bbcodes
public static function getBBcodes () {
return Database :: fetch ( 'bbcodes' );
}
2015-06-21 15:41:07 +02:00
// Parse bbcodes
public static function bbParse ( $text ) {
// Get bbcode regex from the database
$bbcodes = Database :: fetch ( 'bbcodes' );
// Split the regex
$regex = array_map ( function ( $arr ) {
return $arr [ 'regex' ];
}, $bbcodes );
// Split the replacement
$replace = array_map ( function ( $arr ) {
return $arr [ 'replace' ];
}, $bbcodes );
// Do the replacement
$text = preg_replace ( $regex , $replace , $text );
// Return the parsed text
return $text ;
}
2015-07-30 20:51:24 +02:00
// Get emoticons
public static function getEmotes () {
return Database :: fetch ( 'emoticons' );
}
2015-07-05 17:03:58 +02:00
// Parsing emoticons
public static function parseEmotes ( $text ) {
// Get emoticons from the database
$emotes = Database :: fetch ( 'emoticons' );
// Do the replacements
2015-08-09 22:50:03 +02:00
foreach ( $emotes as $emote ) {
$text = str_replace ( $emote [ 'emote_string' ], '<img src="' . $emote [ 'emote_path' ] . '" class="emoticon" alt="' . $emote [ 'emote_string' ] . '" />' , $text );
}
2015-07-05 17:03:58 +02:00
// Return the parsed text
return $text ;
}
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
2015-08-09 22:50:03 +02:00
if ( ! $resp ) {
2015-06-04 12:41:55 +00:00
return false ;
2015-04-01 19:34:36 +02:00
2015-08-09 22:50:03 +02:00
}
2015-04-01 19:34:36 +02:00
// 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-05-29 19:27:45 +00:00
ob_end_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
2015-06-04 12:41:55 +00:00
$mail = new PHPMailer ();
2015-04-18 18:26:52 +00:00
// 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
2015-08-09 22:50:03 +02:00
$htmlMail = str_replace ( '{{ sitename }}' , Configuration :: getConfig ( 'sitename' ), $htmlMail );
$htmlMail = str_replace ( '{{ siteurl }}' , '//' . Configuration :: getConfig ( 'url_main' ), $htmlMail );
$htmlMail = str_replace ( '{{ contents }}' , self :: mdParse ( $body ), $htmlMail );
2015-04-18 18:26:52 +00:00
// 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
// 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-06-29 14:40:00 +02:00
$string = htmlentities ( $string , ENT_NOQUOTES | ENT_HTML401 , Configuration :: getConfig ( 'charset' ));
2015-04-18 18:32:41 +00:00
$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 ;
}
2015-07-19 22:10:17 +02:00
// Generate disqus hmac (https://github.com/disqus/DISQUS-API-Recipes/blob/master/sso/php/sso.php)
public static function dsqHmacSha1 ( $data , $key ) {
$blocksize = 64 ;
if ( strlen ( $key ) > $blocksize ) {
$key = pack ( 'H*' , sha1 ( $key ));
}
$key = str_pad ( $key , $blocksize , chr ( 0x00 ));
$ipad = str_repeat ( chr ( 0x36 ), $blocksize );
$opad = str_repeat ( chr ( 0x5c ), $blocksize );
$hmac = pack (
'H*' , sha1 (
( $key ^ $opad ) . pack (
'H*' , sha1 (
( $key ^ $ipad ) . $data
)
)
)
);
return bin2hex ( $hmac );
}
2015-04-01 19:34:36 +02:00
// 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
2015-05-29 19:27:45 +00:00
$cfhosts = file_get_contents ( ROOT . '_sakura/' . Configuration :: getLocalConfig ( 'data' , 'cfipv' . ( self :: ipVersion ( $ip ))));
2015-04-14 14:27:37 +00:00
// 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
2015-07-05 02:03:15 +02:00
$iso3166 = json_decode ( utf8_encode ( file_get_contents ( ROOT . '_sakura/' . Configuration :: getLocalConfig ( 'data' , 'iso3166' ))), true );
2015-04-27 00:41:59 +00:00
// 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-05-23 03:31:42 +00:00
// 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 ;
}
2015-06-04 12:41:55 +00:00
// Indent JSON
2015-07-03 19:33:02 +02:00
public static function jsonPretty ( $json ) {
2015-06-04 12:41:55 +00:00
// Defines
$tab = ' ' ;
$out = '' ;
$lvl = 0 ;
$str = false ;
$obj = json_decode ( $json );
// Validate the object
if ( $obj === false )
return false ;
// Re-encode the json and get the length
$json = json_encode ( $obj );
$len = strlen ( $json );
// Go over the entries
for ( $c = 0 ; $c < $len ; $c ++ ) {
// Get the current character
$char = $json [ $c ];
switch ( $char ) {
case '[' :
case '{' :
if ( $str ) {
$out .= $char ;
} else {
$out .= $char . " \r \n " . str_repeat ( $tab , $lvl + 1 );
$lvl ++ ;
}
break ;
case ']' :
case '}' :
if ( $str ) {
$out .= $char ;
2015-05-24 22:06:53 +00:00
2015-06-04 12:41:55 +00:00
} else {
2015-05-24 22:06:53 +00:00
2015-06-04 12:41:55 +00:00
$lvl -- ;
$out .= " \r \n " . str_repeat ( $tab , $lvl ) . $char ;
}
break ;
case ',' :
if ( $str ) {
$out .= $char ;
} else {
$out .= " , \r \n " . str_repeat ( $tab , $lvl );
}
break ;
case ':' :
if ( $str ) {
$out .= $char ;
} else {
$out .= " : " ;
}
break ;
default :
$out .= $char ;
break ;
}
}
2015-05-24 22:06:53 +00:00
2015-06-04 12:41:55 +00:00
// Return the indented JSON
return $out ;
2015-05-24 22:06:53 +00:00
}
2015-07-05 02:03:15 +02:00
// Time elapsed
public static function timeElapsed ( $timestamp ) {
// Subtract the entered timestamp from the current timestamp
$time = time () - $timestamp ;
// If the new timestamp is below 1 return a standard string
if ( $time < 1 )
return 'Just now' ;
// Array containing time "types"
$times = [
365 * 24 * 60 * 60 => 'year' ,
30 * 24 * 60 * 60 => 'month' ,
24 * 60 * 60 => 'day' ,
60 * 60 => 'hour' ,
60 => 'minute' ,
1 => 'second'
];
foreach ( $times as $secs => $str ) {
// Do a devision to check if the given timestamp fits in the current "type"
$calc = $time / $secs ;
if ( $calc >= 1 ) {
// Round the number
$round = round ( $calc );
// Return the string
return $round . ' ' . $times [ $secs ] . ( $round == 1 ? '' : 's' ) . ' ago' ;
2015-08-24 00:08:36 +02:00
}
2015-07-05 02:03:15 +02:00
}
}
2015-08-09 20:26:01 +02:00
// Get the byte symbol from a value
public static function getByteSymbol ( $bytes ) {
// Return nothing if the input was 0
if ( ! $bytes )
return ;
// Array with byte symbols
$symbols = [ 'B' , 'KiB' , 'MiB' , 'GiB' , 'TiB' , 'PiB' , 'EiB' , 'ZiB' , 'YiB' ];
// Calculate byte entity
$exp = floor ( log ( $bytes ) / log ( 1024 ));
// Format the things
$bytes = sprintf ( " %.2f " . $symbols [ $exp ], ( $bytes / pow ( 1024 , floor ( $exp ))));
// Return the formatted string
return $bytes ;
}
2015-04-01 19:34:36 +02:00
}