Removed pocketmine subdirectory, map PSR-4 style

This commit is contained in:
Dylan K. Taylor
2019-07-30 19:14:57 +01:00
parent 7a77d3dc30
commit 5499ac620c
1044 changed files with 3 additions and 3 deletions

152
src/utils/Color.php Normal file
View File

@@ -0,0 +1,152 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function count;
use function intdiv;
final class Color{
/** @var int */
protected $a;
/** @var int */
protected $r;
/** @var int */
protected $g;
/** @var int */
protected $b;
public function __construct(int $r, int $g, int $b, int $a = 0xff){
$this->r = $r & 0xff;
$this->g = $g & 0xff;
$this->b = $b & 0xff;
$this->a = $a & 0xff;
}
/**
* Returns the alpha (opacity) value of this colour.
* @return int
*/
public function getA() : int{
return $this->a;
}
/**
* Retuns the red value of this colour.
* @return int
*/
public function getR() : int{
return $this->r;
}
/**
* Returns the green value of this colour.
* @return int
*/
public function getG() : int{
return $this->g;
}
/**
* Returns the blue value of this colour.
* @return int
*/
public function getB() : int{
return $this->b;
}
/**
* Mixes the supplied list of colours together to produce a result colour.
*
* @param Color ...$colors
*
* @return Color
*/
public static function mix(Color ...$colors) : Color{
$count = count($colors);
if($count < 1){
throw new \ArgumentCountError("No colors given");
}
$a = $r = $g = $b = 0;
foreach($colors as $color){
$a += $color->a;
$r += $color->r;
$g += $color->g;
$b += $color->b;
}
return new Color(intdiv($r, $count), intdiv($g, $count), intdiv($b, $count), intdiv($a, $count));
}
/**
* Returns a Color from the supplied RGB colour code (24-bit)
*
* @param int $code
*
* @return Color
*/
public static function fromRGB(int $code) : Color{
return new Color(($code >> 16) & 0xff, ($code >> 8) & 0xff, $code & 0xff);
}
/**
* Returns a Color from the supplied ARGB colour code (32-bit)
*
* @param int $code
*
* @return Color
*/
public static function fromARGB(int $code) : Color{
return new Color(($code >> 16) & 0xff, ($code >> 8) & 0xff, $code & 0xff, ($code >> 24) & 0xff);
}
/**
* Returns an ARGB 32-bit colour value.
* @return int
*/
public function toARGB() : int{
return ($this->a << 24) | ($this->r << 16) | ($this->g << 8) | $this->b;
}
/**
* Returns a Color from the supplied RGBA colour code (32-bit)
*
* @param int $c
*
* @return Color
*/
public static function fromRGBA(int $c) : Color{
return new Color(($c >> 24) & 0xff, ($c >> 16) & 0xff, ($c >> 8) & 0xff, $c & 0xff);
}
/**
* Returns an RGBA 32-bit colour value.
* @return int
*/
public function toRGBA() : int{
return ($this->r << 24) | ($this->g << 16) | ($this->b << 8) | $this->a;
}
}

574
src/utils/Config.php Normal file
View File

@@ -0,0 +1,574 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function array_change_key_case;
use function array_keys;
use function array_pop;
use function array_shift;
use function basename;
use function count;
use function date;
use function explode;
use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function implode;
use function is_array;
use function is_bool;
use function json_decode;
use function json_encode;
use function preg_match_all;
use function preg_replace;
use function serialize;
use function str_replace;
use function strlen;
use function strtolower;
use function substr;
use function trim;
use function unserialize;
use function yaml_emit;
use function yaml_parse;
use const CASE_LOWER;
use const JSON_BIGINT_AS_STRING;
use const JSON_PRETTY_PRINT;
/**
* Config Class for simple config manipulation of multiple formats.
*/
class Config{
public const DETECT = -1; //Detect by file extension
public const PROPERTIES = 0; // .properties
public const CNF = Config::PROPERTIES; // .cnf
public const JSON = 1; // .js, .json
public const YAML = 2; // .yml, .yaml
//const EXPORT = 3; // .export, .xport
public const SERIALIZED = 4; // .sl
public const ENUM = 5; // .txt, .list, .enum
public const ENUMERATION = Config::ENUM;
/** @var array */
private $config = [];
private $nestedCache = [];
/** @var string */
private $file;
/** @var int */
private $type = Config::DETECT;
/** @var int */
private $jsonOptions = JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING;
/** @var bool */
private $changed = false;
public static $formats = [
"properties" => Config::PROPERTIES,
"cnf" => Config::CNF,
"conf" => Config::CNF,
"config" => Config::CNF,
"json" => Config::JSON,
"js" => Config::JSON,
"yml" => Config::YAML,
"yaml" => Config::YAML,
//"export" => Config::EXPORT,
//"xport" => Config::EXPORT,
"sl" => Config::SERIALIZED,
"serialize" => Config::SERIALIZED,
"txt" => Config::ENUM,
"list" => Config::ENUM,
"enum" => Config::ENUM
];
/**
* @param string $file Path of the file to be loaded
* @param int $type Config type to load, -1 by default (detect)
* @param array $default Array with the default values that will be written to the file if it did not exist
*/
public function __construct(string $file, int $type = Config::DETECT, array $default = []){
$this->load($file, $type, $default);
}
/**
* Removes all the changes in memory and loads the file again
*/
public function reload() : void{
$this->config = [];
$this->nestedCache = [];
$this->load($this->file, $this->type);
}
public function hasChanged() : bool{
return $this->changed;
}
public function setChanged(bool $changed = true) : void{
$this->changed = $changed;
}
/**
* @param string $str
*
* @return string
*/
public static function fixYAMLIndexes(string $str) : string{
return preg_replace("#^( *)(y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF)( *)\:#m", "$1\"$2\"$3:", $str);
}
/**
* @param string $file
* @param int $type
* @param array $default
*
* @throws \InvalidArgumentException if config type could not be auto-detected
* @throws \InvalidStateException if config type is invalid
*/
private function load(string $file, int $type = Config::DETECT, array $default = []) : void{
$this->file = $file;
$this->type = $type;
if($this->type === Config::DETECT){
$extension = explode(".", basename($this->file));
$extension = strtolower(trim(array_pop($extension)));
if(isset(Config::$formats[$extension])){
$this->type = Config::$formats[$extension];
}else{
throw new \InvalidArgumentException("Cannot detect config type of " . $this->file);
}
}
if(!file_exists($file)){
$this->config = $default;
$this->save();
}else{
$content = file_get_contents($this->file);
switch($this->type){
case Config::PROPERTIES:
$this->parseProperties($content);
break;
case Config::JSON:
$this->config = json_decode($content, true);
break;
case Config::YAML:
$content = self::fixYAMLIndexes($content);
$this->config = yaml_parse($content);
break;
case Config::SERIALIZED:
$this->config = unserialize($content);
break;
case Config::ENUM:
$this->parseList($content);
break;
default:
throw new \InvalidStateException("Config type is unknown");
}
if(!is_array($this->config)){
$this->config = $default;
}
if($this->fillDefaults($default, $this->config) > 0){
$this->save();
}
}
}
/**
* Returns the path of the config.
*
* @return string
*/
public function getPath() : string{
return $this->file;
}
/**
* Flushes the config to disk in the appropriate format.
*
* @throws \InvalidStateException if config type is not valid
*/
public function save() : void{
$content = null;
switch($this->type){
case Config::PROPERTIES:
$content = $this->writeProperties();
break;
case Config::JSON:
$content = json_encode($this->config, $this->jsonOptions);
break;
case Config::YAML:
$content = yaml_emit($this->config, YAML_UTF8_ENCODING);
break;
case Config::SERIALIZED:
$content = serialize($this->config);
break;
case Config::ENUM:
$content = implode("\r\n", array_keys($this->config));
break;
default:
throw new \InvalidStateException("Config type is unknown, has not been set or not detected");
}
file_put_contents($this->file, $content);
$this->changed = false;
}
/**
* Sets the options for the JSON encoding when saving
*
* @param int $options
*
* @return Config $this
* @throws \RuntimeException if the Config is not in JSON
* @see json_encode
*/
public function setJsonOptions(int $options) : Config{
if($this->type !== Config::JSON){
throw new \RuntimeException("Attempt to set JSON options for non-JSON config");
}
$this->jsonOptions = $options;
$this->changed = true;
return $this;
}
/**
* Enables the given option in addition to the currently set JSON options
*
* @param int $option
*
* @return Config $this
* @throws \RuntimeException if the Config is not in JSON
* @see json_encode
*/
public function enableJsonOption(int $option) : Config{
if($this->type !== Config::JSON){
throw new \RuntimeException("Attempt to enable JSON option for non-JSON config");
}
$this->jsonOptions |= $option;
$this->changed = true;
return $this;
}
/**
* Disables the given option for the JSON encoding when saving
*
* @param int $option
*
* @return Config $this
* @throws \RuntimeException if the Config is not in JSON
* @see json_encode
*/
public function disableJsonOption(int $option) : Config{
if($this->type !== Config::JSON){
throw new \RuntimeException("Attempt to disable JSON option for non-JSON config");
}
$this->jsonOptions &= ~$option;
$this->changed = true;
return $this;
}
/**
* Returns the options for the JSON encoding when saving
*
* @return int
* @throws \RuntimeException if the Config is not in JSON
* @see json_encode
*/
public function getJsonOptions() : int{
if($this->type !== Config::JSON){
throw new \RuntimeException("Attempt to get JSON options for non-JSON config");
}
return $this->jsonOptions;
}
/**
* @param string $k
*
* @return bool|mixed
*/
public function __get($k){
return $this->get($k);
}
/**
* @param string $k
* @param mixed $v
*/
public function __set($k, $v){
$this->set($k, $v);
}
/**
* @param string $k
*
* @return bool
*/
public function __isset($k){
return $this->exists($k);
}
/**
* @param string $k
*/
public function __unset($k){
$this->remove($k);
}
/**
* @param string $key
* @param mixed $value
*/
public function setNested($key, $value) : void{
$vars = explode(".", $key);
$base = array_shift($vars);
if(!isset($this->config[$base])){
$this->config[$base] = [];
}
$base =& $this->config[$base];
while(count($vars) > 0){
$baseKey = array_shift($vars);
if(!isset($base[$baseKey])){
$base[$baseKey] = [];
}
$base =& $base[$baseKey];
}
$base = $value;
$this->nestedCache = [];
$this->changed = true;
}
/**
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function getNested($key, $default = null){
if(isset($this->nestedCache[$key])){
return $this->nestedCache[$key];
}
$vars = explode(".", $key);
$base = array_shift($vars);
if(isset($this->config[$base])){
$base = $this->config[$base];
}else{
return $default;
}
while(count($vars) > 0){
$baseKey = array_shift($vars);
if(is_array($base) and isset($base[$baseKey])){
$base = $base[$baseKey];
}else{
return $default;
}
}
return $this->nestedCache[$key] = $base;
}
public function removeNested(string $key) : void{
$this->nestedCache = [];
$this->changed = true;
$vars = explode(".", $key);
$currentNode =& $this->config;
while(count($vars) > 0){
$nodeName = array_shift($vars);
if(isset($currentNode[$nodeName])){
if(empty($vars)){ //final node
unset($currentNode[$nodeName]);
}elseif(is_array($currentNode[$nodeName])){
$currentNode =& $currentNode[$nodeName];
}
}else{
break;
}
}
}
/**
* @param string $k
* @param mixed $default
*
* @return bool|mixed
*/
public function get($k, $default = false){
return $this->config[$k] ?? $default;
}
/**
* @param string $k key to be set
* @param mixed $v value to set key
*/
public function set($k, $v = true) : void{
$this->config[$k] = $v;
$this->changed = true;
foreach($this->nestedCache as $nestedKey => $nvalue){
if(substr($nestedKey, 0, strlen($k) + 1) === ($k . ".")){
unset($this->nestedCache[$nestedKey]);
}
}
}
/**
* @param array $v
*/
public function setAll(array $v) : void{
$this->config = $v;
$this->changed = true;
}
/**
* @param string $k
* @param bool $lowercase If set, searches Config in single-case / lowercase.
*
* @return bool
*/
public function exists($k, bool $lowercase = false) : bool{
if($lowercase){
$k = strtolower($k); //Convert requested key to lower
$array = array_change_key_case($this->config, CASE_LOWER); //Change all keys in array to lower
return isset($array[$k]); //Find $k in modified array
}else{
return isset($this->config[$k]);
}
}
/**
* @param string $k
*/
public function remove($k) : void{
unset($this->config[$k]);
$this->changed = true;
}
/**
* @param bool $keys
*
* @return array
*/
public function getAll(bool $keys = false) : array{
return ($keys ? array_keys($this->config) : $this->config);
}
/**
* @param array $defaults
*/
public function setDefaults(array $defaults) : void{
$this->fillDefaults($defaults, $this->config);
}
/**
* @param array $default
* @param array &$data
*
* @return int
*/
private function fillDefaults(array $default, &$data) : int{
$changed = 0;
foreach($default as $k => $v){
if(is_array($v)){
if(!isset($data[$k]) or !is_array($data[$k])){
$data[$k] = [];
}
$changed += $this->fillDefaults($v, $data[$k]);
}elseif(!isset($data[$k])){
$data[$k] = $v;
++$changed;
}
}
if($changed > 0){
$this->changed = true;
}
return $changed;
}
/**
* @param string $content
*/
private function parseList(string $content) : void{
foreach(explode("\n", trim(str_replace("\r\n", "\n", $content))) as $v){
$v = trim($v);
if($v == ""){
continue;
}
$this->config[$v] = true;
}
}
/**
* @return string
*/
private function writeProperties() : string{
$content = "#Properties Config file\r\n#" . date("D M j H:i:s T Y") . "\r\n";
foreach($this->config as $k => $v){
if(is_bool($v)){
$v = $v ? "on" : "off";
}elseif(is_array($v)){
$v = implode(";", $v);
}
$content .= $k . "=" . $v . "\r\n";
}
return $content;
}
/**
* @param string $content
*/
private function parseProperties(string $content) : void{
if(preg_match_all('/^\s*([a-zA-Z0-9\-_\.]+)[ \t]*=([^\r\n]*)/um', $content, $matches) > 0){ //false or 0 matches
foreach($matches[1] as $i => $k){
$v = trim($matches[2][$i]);
switch(strtolower($v)){
case "on":
case "true":
case "yes":
$v = true;
break;
case "off":
case "false":
case "no":
$v = false;
break;
}
if(isset($this->config[$k])){
\GlobalLogger::get()->debug("[Config] Repeated property " . $k . " on file " . $this->file);
}
$this->config[$k] = $v;
}
}
}
}

120
src/utils/EnumTrait.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function preg_match;
trait EnumTrait{
use RegistryTrait;
/**
* Registers the given object as an enum member.
*
* @param self $member
*
* @throws \InvalidArgumentException
*/
protected static function register(self $member) : void{
self::_registryRegister($member->name(), $member);
}
/**
* Returns an array of enum members to be registered.
*
* (This ought to be private, but traits suck too much for that.)
*
* @return self[]|iterable
*/
abstract protected static function setup() : iterable;
/**
* @internal Lazy-inits the enum if necessary.
*
* @throws \InvalidArgumentException
*/
protected static function checkInit() : void{
if(self::$members === null){
self::$members = [];
foreach(self::setup() as $item){
self::register($item);
}
}
}
/**
* Returns all members of the enum.
* This is overridden to change the return typehint.
*
* @return self[]
*/
public static function getAll() : array{
return self::_registryGetAll();
}
/**
* Returns the enum member matching the given name.
* This is overridden to change the return typehint.
*
* @param string $name
*
* @return self
* @throws \InvalidArgumentException if no member matches.
*/
public static function fromString(string $name) : self{
return self::_registryFromString($name);
}
/** @var string */
private $enumName;
/**
* @param string $enumName
* @throws \InvalidArgumentException
*/
private function __construct(string $enumName){
static $pattern = '/^\D[A-Za-z\d_]+$/u';
if(preg_match($pattern, $enumName, $matches) === 0){
throw new \InvalidArgumentException("Invalid enum member name \"$enumName\", should only contain letters, numbers and underscores, and must not start with a number");
}
$this->enumName = $enumName;
}
/**
* @return string
*/
public function name() : string{
return $this->enumName;
}
/**
* Returns whether the two objects are equivalent.
*
* @param self $other
*
* @return bool
*/
public function equals(self $other) : bool{
return $this->enumName === $other->enumName;
}
}

245
src/utils/Internet.php Normal file
View File

@@ -0,0 +1,245 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function array_merge;
use function curl_close;
use function curl_error;
use function curl_exec;
use function curl_getinfo;
use function curl_init;
use function curl_setopt_array;
use function explode;
use function preg_match;
use function socket_close;
use function socket_connect;
use function socket_create;
use function socket_getsockname;
use function socket_last_error;
use function socket_strerror;
use function strip_tags;
use function strtolower;
use function substr;
use function trim;
use const AF_INET;
use const CURLINFO_HEADER_SIZE;
use const CURLINFO_HTTP_CODE;
use const CURLOPT_AUTOREFERER;
use const CURLOPT_CONNECTTIMEOUT_MS;
use const CURLOPT_FOLLOWLOCATION;
use const CURLOPT_FORBID_REUSE;
use const CURLOPT_FRESH_CONNECT;
use const CURLOPT_HEADER;
use const CURLOPT_HTTPHEADER;
use const CURLOPT_POST;
use const CURLOPT_POSTFIELDS;
use const CURLOPT_RETURNTRANSFER;
use const CURLOPT_SSL_VERIFYHOST;
use const CURLOPT_SSL_VERIFYPEER;
use const CURLOPT_TIMEOUT_MS;
use const SOCK_DGRAM;
use const SOL_UDP;
class Internet{
public static $ip = false;
public static $online = true;
/**
* Gets the External IP using an external service, it is cached
*
* @param bool $force default false, force IP check even when cached
*
* @return string|bool
*/
public static function getIP(bool $force = false){
if(!self::$online){
return false;
}elseif(self::$ip !== false and !$force){
return self::$ip;
}
$ip = self::getURL("http://api.ipify.org/");
if($ip !== false){
return self::$ip = $ip;
}
$ip = self::getURL("http://checkip.dyndns.org/");
if($ip !== false and preg_match('#Current IP Address\: ([0-9a-fA-F\:\.]*)#', trim(strip_tags($ip)), $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://www.checkip.org/");
if($ip !== false and preg_match('#">([0-9a-fA-F\:\.]*)</span>#', $ip, $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://checkmyip.org/");
if($ip !== false and preg_match('#Your IP address is ([0-9a-fA-F\:\.]*)#', $ip, $matches) > 0){
return self::$ip = $matches[1];
}
$ip = self::getURL("http://ifconfig.me/ip");
if($ip !== false and trim($ip) != ""){
return self::$ip = trim($ip);
}
return false;
}
/**
* Returns the machine's internal network IP address. If the machine is not behind a router, this may be the same
* as the external IP.
*
* @return string
* @throws InternetException
*/
public static function getInternalIP() : string{
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
try{
if(!@socket_connect($sock, "8.8.8.8", 65534)){
throw new InternetException("Failed to get internal IP: " . trim(socket_strerror(socket_last_error($sock))));
}
if(!@socket_getsockname($sock, $name)){
throw new InternetException("Failed to get internal IP: " . trim(socket_strerror(socket_last_error($sock))));
}
return $name;
}finally{
socket_close($sock);
}
}
/**
* GETs an URL using cURL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param int $timeout default 10
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @param array[] &$headers
* @param int &$httpCode
*
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders);
return $ret;
}catch(InternetException $ex){
$err = $ex->getMessage();
return false;
}
}
/**
* POSTs data to an URL
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param array|string $args
* @param int $timeout
* @param array $extraHeaders
* @param string &$err Will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation.
* @param array[] &$headers
* @param int &$httpCode
*
* @return bool|mixed false if an error occurred, mixed data if successful.
*/
public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){
try{
list($ret, $headers, $httpCode) = self::simpleCurl($page, $timeout, $extraHeaders, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $args
]);
return $ret;
}catch(InternetException $ex){
$err = $ex->getMessage();
return false;
}
}
/**
* General cURL shorthand function.
* NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread.
*
* @param string $page
* @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms.
* @param string[] $extraHeaders extra headers to send as a plain string array
* @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map
* @param \Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle.
*
* @return array a plain array of three [result body : string, headers : array[], HTTP response code : int]. Headers are grouped by requests with strtolower(header name) as keys and header value as values
*
* @throws InternetException if a cURL error occurs
*/
public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], ?\Closure $onSuccess = null) : array{
if(!self::$online){
throw new InternetException("Cannot execute web request while offline");
}
$ch = curl_init($page);
curl_setopt_array($ch, $extraOpts + [
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_AUTOREFERER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_TIMEOUT_MS => (int) ($timeout * 1000),
CURLOPT_HTTPHEADER => array_merge(["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 " . \pocketmine\NAME], $extraHeaders),
CURLOPT_HEADER => true
]);
try{
$raw = curl_exec($ch);
$error = curl_error($ch);
if($error !== ""){
throw new InternetException($error);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$headers = [];
foreach(explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup){
$headerGroup = [];
foreach(explode("\r\n", $rawHeaderGroup) as $line){
$nameValue = explode(":", $line, 2);
if(isset($nameValue[1])){
$headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]);
}
}
$headers[] = $headerGroup;
}
if($onSuccess !== null){
$onSuccess($ch);
}
return [$body, $headers, $httpCode];
}finally{
curl_close($ch);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
class InternetException extends \RuntimeException{
}

292
src/utils/MainLogger.php Normal file
View File

@@ -0,0 +1,292 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use LogLevel;
use pocketmine\thread\Thread;
use pocketmine\thread\Worker;
use function fclose;
use function fopen;
use function fwrite;
use function get_class;
use function is_resource;
use function preg_replace;
use function sprintf;
use function touch;
use function trim;
use const PHP_EOL;
use const PTHREADS_INHERIT_NONE;
class MainLogger extends \AttachableThreadedLogger{
/** @var string */
protected $logFile;
/** @var \Threaded */
protected $logStream;
/** @var bool */
protected $shutdown;
/** @var bool */
protected $logDebug;
/** @var bool */
private $syncFlush = false;
/** @var string */
private $format = TextFormat::AQUA . "[%s] " . TextFormat::RESET . "%s[%s/%s]: %s" . TextFormat::RESET;
/** @var bool */
private $mainThreadHasFormattingCodes = false;
/** @var string */
private $timezone;
/**
* @param string $logFile
* @param bool $logDebug
*
* @throws \RuntimeException
*/
public function __construct(string $logFile, bool $logDebug = false){
parent::__construct();
touch($logFile);
$this->logFile = $logFile;
$this->logDebug = $logDebug;
$this->logStream = new \Threaded;
//Child threads may not inherit command line arguments, so if there's an override it needs to be recorded here
$this->mainThreadHasFormattingCodes = Terminal::hasFormattingCodes();
$this->timezone = Timezone::get();
$this->start(PTHREADS_INHERIT_NONE);
}
/**
* Returns the current logger format used for console output.
*
* @return string
*/
public function getFormat() : string{
return $this->format;
}
/**
* Sets the logger format to use for outputting text to the console.
* It should be an sprintf()able string accepting 5 string arguments:
* - time
* - color
* - thread name
* - prefix (debug, info etc)
* - message
*
* @see http://php.net/manual/en/function.sprintf.php
*
* @param string $format
*/
public function setFormat(string $format) : void{
$this->format = $format;
}
public function emergency($message){
$this->send($message, \LogLevel::EMERGENCY, "EMERGENCY", TextFormat::RED);
}
public function alert($message){
$this->send($message, \LogLevel::ALERT, "ALERT", TextFormat::RED);
}
public function critical($message){
$this->send($message, \LogLevel::CRITICAL, "CRITICAL", TextFormat::RED);
}
public function error($message){
$this->send($message, \LogLevel::ERROR, "ERROR", TextFormat::DARK_RED);
}
public function warning($message){
$this->send($message, \LogLevel::WARNING, "WARNING", TextFormat::YELLOW);
}
public function notice($message){
$this->send($message, \LogLevel::NOTICE, "NOTICE", TextFormat::AQUA);
}
public function info($message){
$this->send($message, \LogLevel::INFO, "INFO", TextFormat::WHITE);
}
public function debug($message, bool $force = false){
if(!$this->logDebug and !$force){
return;
}
$this->send($message, \LogLevel::DEBUG, "DEBUG", TextFormat::GRAY);
}
/**
* @param bool $logDebug
*/
public function setLogDebug(bool $logDebug) : void{
$this->logDebug = $logDebug;
}
/**
* @param \Throwable $e
* @param array|null $trace
*/
public function logException(\Throwable $e, $trace = null){
if($trace === null){
$trace = $e->getTrace();
}
$errstr = $e->getMessage();
$errfile = $e->getFile();
$errno = $e->getCode();
$errline = $e->getLine();
try{
$errno = \ErrorUtils::errorTypeToString($errno);
}catch(\InvalidArgumentException $e){
//pass
}
$errstr = preg_replace('/\s+/', ' ', trim($errstr));
$errfile = Utils::cleanPath($errfile);
$message = get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
$stack = Utils::printableTrace($trace);
$this->synchronized(function() use ($message, $stack) : void{
$this->log(LogLevel::CRITICAL, $message);
foreach($stack as $line){
$this->debug($line, true);
}
});
$this->syncFlushBuffer();
}
public function log($level, $message){
switch($level){
case LogLevel::EMERGENCY:
$this->emergency($message);
break;
case LogLevel::ALERT:
$this->alert($message);
break;
case LogLevel::CRITICAL:
$this->critical($message);
break;
case LogLevel::ERROR:
$this->error($message);
break;
case LogLevel::WARNING:
$this->warning($message);
break;
case LogLevel::NOTICE:
$this->notice($message);
break;
case LogLevel::INFO:
$this->info($message);
break;
case LogLevel::DEBUG:
$this->debug($message);
break;
}
}
public function shutdown() : void{
$this->shutdown = true;
$this->notify();
}
protected function send($message, $level, $prefix, $color) : void{
$time = new \DateTime('now', new \DateTimeZone($this->timezone));
$thread = \Thread::getCurrentThread();
if($thread === null){
$threadName = "Server thread";
}elseif($thread instanceof Thread or $thread instanceof Worker){
$threadName = $thread->getThreadName() . " thread";
}else{
$threadName = (new \ReflectionClass($thread))->getShortName() . " thread";
}
$message = sprintf($this->format, $time->format("H:i:s.v"), $color, $threadName, $prefix, TextFormat::clean($message, false));
if(!Terminal::isInit()){
Terminal::init($this->mainThreadHasFormattingCodes); //lazy-init colour codes because we don't know if they've been registered on this thread
}
$this->synchronized(function() use ($message, $level, $time) : void{
Terminal::writeLine($message);
foreach($this->attachments as $attachment){
$attachment->call($level, $message);
}
$this->logStream[] = $time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL;
});
}
public function syncFlushBuffer() : void{
$this->syncFlush = true;
$this->synchronized(function(){
$this->notify(); //write immediately
while($this->syncFlush){
$this->wait(); //block until it's all been written to disk
}
});
}
/**
* @param resource $logResource
*/
private function writeLogStream($logResource) : void{
while($this->logStream->count() > 0){
$chunk = $this->logStream->shift();
fwrite($logResource, $chunk);
}
if($this->syncFlush){
$this->syncFlush = false;
$this->notify(); //if this was due to a sync flush, tell the caller to stop waiting
}
}
public function run() : void{
$this->shutdown = false;
$logResource = fopen($this->logFile, "ab");
if(!is_resource($logResource)){
throw new \RuntimeException("Couldn't open log file");
}
while(!$this->shutdown){
$this->writeLogStream($logResource);
$this->synchronized(function(){
$this->wait(25000);
});
}
$this->writeLogStream($logResource);
fclose($logResource);
}
}

171
src/utils/Process.php Normal file
View File

@@ -0,0 +1,171 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use pocketmine\thread\ThreadManager;
use function count;
use function exec;
use function fclose;
use function file;
use function file_get_contents;
use function function_exists;
use function hexdec;
use function memory_get_usage;
use function preg_match;
use function proc_close;
use function proc_open;
use function stream_get_contents;
use function strpos;
use function trim;
final class Process{
private function __construct(){
//NOOP
}
/**
* @param bool $advanced
*
* @return int[]|int
*/
public static function getMemoryUsage(bool $advanced = false){
$reserved = memory_get_usage();
$VmSize = null;
$VmRSS = null;
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
$status = file_get_contents("/proc/self/status");
if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmRSS = $matches[1] * 1024;
}
if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmSize = $matches[1] * 1024;
}
}
//TODO: more OS
if($VmRSS === null){
$VmRSS = memory_get_usage();
}
if(!$advanced){
return $VmRSS;
}
if($VmSize === null){
$VmSize = memory_get_usage(true);
}
return [$reserved, $VmRSS, $VmSize];
}
/**
* @return int[]
*/
public static function getRealMemoryUsage() : array{
$stack = 0;
$heap = 0;
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
$mappings = file("/proc/self/maps");
foreach($mappings as $line){
if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){
if(strpos($matches[3], "heap") === 0){
$heap += hexdec($matches[2]) - hexdec($matches[1]);
}elseif(strpos($matches[3], "stack") === 0){
$stack += hexdec($matches[2]) - hexdec($matches[1]);
}
}
}
}
return [$heap, $stack];
}
public static function getThreadCount() : int{
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){
return (int) $matches[1];
}
}
//TODO: more OS
return count(ThreadManager::getInstance()->getAll()) + 3; //RakLib + MainLogger + Main Thread
}
public static function kill($pid) : void{
$logger = \GlobalLogger::get();
if($logger instanceof MainLogger){
$logger->syncFlushBuffer();
}
switch(Utils::getOS()){
case "win":
exec("taskkill.exe /F /PID " . ((int) $pid) . " > NUL");
break;
case "mac":
case "linux":
default:
if(function_exists("posix_kill")){
posix_kill($pid, 9); //SIGKILL
}else{
exec("kill -9 " . ((int) $pid) . " > /dev/null 2>&1");
}
}
}
/**
* @param string $command Command to execute
* @param string|null &$stdout Reference parameter to write stdout to
* @param string|null &$stderr Reference parameter to write stderr to
*
* @return int process exit code
*/
public static function execute(string $command, string &$stdout = null, string &$stderr = null) : int{
$process = proc_open($command, [
["pipe", "r"],
["pipe", "w"],
["pipe", "w"]
], $pipes);
if($process === false){
$stderr = "Failed to open process";
$stdout = "";
return -1;
}
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
foreach($pipes as $p){
fclose($p);
}
return proc_close($process);
}
}

155
src/utils/Random.php Normal file
View File

@@ -0,0 +1,155 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function time;
/**
* XorShift128Engine Random Number Noise, used for fast seeded values
* Most of the code in this class was adapted from the XorShift128Engine in the php-random library.
*/
class Random{
public const X = 123456789;
public const Y = 362436069;
public const Z = 521288629;
public const W = 88675123;
/**
* @var int
*/
private $x;
/**
* @var int
*/
private $y;
/**
* @var int
*/
private $z;
/**
* @var int
*/
private $w;
/** @var int */
protected $seed;
/**
* @param int $seed Integer to be used as seed.
*/
public function __construct(int $seed = -1){
if($seed === -1){
$seed = time();
}
$this->setSeed($seed);
}
/**
* @param int $seed Integer to be used as seed.
*/
public function setSeed(int $seed) : void{
$this->seed = $seed;
$this->x = self::X ^ $seed;
$this->y = self::Y ^ ($seed << 17) | (($seed >> 15) & 0x7fffffff) & 0xffffffff;
$this->z = self::Z ^ ($seed << 31) | (($seed >> 1) & 0x7fffffff) & 0xffffffff;
$this->w = self::W ^ ($seed << 18) | (($seed >> 14) & 0x7fffffff) & 0xffffffff;
}
public function getSeed() : int{
return $this->seed;
}
/**
* Returns an 31-bit integer (not signed)
*
* @return int
*/
public function nextInt() : int{
return $this->nextSignedInt() & 0x7fffffff;
}
/**
* Returns a 32-bit integer (signed)
*
* @return int
*/
public function nextSignedInt() : int{
$t = ($this->x ^ ($this->x << 11)) & 0xffffffff;
$this->x = $this->y;
$this->y = $this->z;
$this->z = $this->w;
$this->w = ($this->w ^ (($this->w >> 19) & 0x7fffffff)
^ ($t ^ (($t >> 8) & 0x7fffffff))) & 0xffffffff;
return $this->w;
}
/**
* Returns a float between 0.0 and 1.0 (inclusive)
*
* @return float
*/
public function nextFloat() : float{
return $this->nextInt() / 0x7fffffff;
}
/**
* Returns a float between -1.0 and 1.0 (inclusive)
*
* @return float
*/
public function nextSignedFloat() : float{
return $this->nextSignedInt() / 0x7fffffff;
}
/**
* Returns a random boolean
*
* @return bool
*/
public function nextBoolean() : bool{
return ($this->nextSignedInt() & 0x01) === 0;
}
/**
* Returns a random integer between $start and $end
*
* @param int $start default 0
* @param int $end default 0x7fffffff
*
* @return int
*/
public function nextRange(int $start = 0, int $end = 0x7fffffff) : int{
return $start + ($this->nextInt() % ($end + 1 - $start));
}
public function nextBoundedInt(int $bound) : int{
return $this->nextInt() % $bound;
}
}

168
src/utils/RegistryTrait.php Normal file
View File

@@ -0,0 +1,168 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function count;
use function get_class;
use function implode;
use function sprintf;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
trait RegistryTrait{
/** @var object[] */
private static $members = null;
/**
* Adds the given object to the registry.
*
* @param string $name
* @param object $member
*
* @throws \InvalidArgumentException
*/
private static function _registryRegister(string $name, object $member) : void{
$name = strtoupper($name);
if(isset(self::$members[$name])){
throw new \InvalidArgumentException("\"$name\" is already reserved");
}
self::$members[strtoupper($name)] = $member;
}
/**
* Inserts default entries into the registry.
*
* (This ought to be private, but traits suck too much for that.)
*/
abstract protected static function setup() : void;
/**
* @internal Lazy-inits the enum if necessary.
*
* @throws \InvalidArgumentException
*/
protected static function checkInit() : void{
if(self::$members === null){
self::$members = [];
self::setup();
}
}
/**
* @param string $name
*
* @return object
* @throws \InvalidArgumentException
*/
private static function _registryFromString(string $name) : object{
self::checkInit();
$name = strtoupper($name);
if(!isset(self::$members[$name])){
throw new \InvalidArgumentException("No such registry member: " . self::class . "::" . $name);
}
return self::$members[$name];
}
/**
* @param string $name
* @param array $arguments
*
* @return object
*/
public static function __callStatic($name, $arguments){
if(!empty($arguments)){
throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
}
try{
return self::fromString($name);
}catch(\InvalidArgumentException $e){
throw new \Error($e->getMessage(), 0, $e);
}
}
/**
* @return object[]
*/
private static function _registryGetAll() : array{
self::checkInit();
return self::$members;
}
/**
* Generates code for static methods for all known registry members.
*
* @return string
*/
public static function _generateGetters() : string{
$lines = [];
static $fnTmpl = '
public static function %1$s() : %2$s{
return self::fromString("%1$s");
}';
foreach(self::getAll() as $name => $member){
$lines[] = sprintf($fnTmpl, $name, '\\' . get_class($member));
}
return "//region auto-generated code\n" . implode("\n", $lines) . "\n\n//endregion\n";
}
/**
* Generates a block of @ method annotations for accessors for this registry's known members.
*
* @return string
*/
public static function _generateMethodAnnotations() : string{
$traitName = (new \ReflectionClass(__TRAIT__))->getShortName();
$fnName = (new \ReflectionMethod(__METHOD__))->getShortName();
$lines = ["/**"];
$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
$lines[] = " * This must be regenerated whenever registry members are added, removed or changed.";
$lines[] = " * @see $traitName::$fnName()";
$lines[] = " *";
static $lineTmpl = " * @method static %2\$s %s()";
$thisNamespace = (new \ReflectionClass(__CLASS__))->getNamespaceName();
foreach(self::getAll() as $name => $member){
$reflect = new \ReflectionClass($member);
while($reflect !== false and $reflect->isAnonymous()){
$reflect = $reflect->getParentClass();
}
if($reflect === false){
$typehint = "object";
}elseif($reflect->getName() === __CLASS__){
$typehint = "self";
}elseif(strpos($reflect->getName(), $thisNamespace) === 0){
$typehint = substr($reflect->getName(), strlen($thisNamespace . '\\'));
}else{
$typehint = '\\' . $reflect->getName();
}
$lines[] = sprintf($lineTmpl, $name, $typehint);
}
$lines[] = " */\n";
return implode("\n", $lines);
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
class ReversePriorityQueue extends \SplPriorityQueue{
public function compare($priority1, $priority2){
return (int) -($priority1 - $priority2);
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
class ServerException extends \RuntimeException{
}

View File

@@ -0,0 +1,65 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use pocketmine\thread\Thread;
use function getmypid;
use function time;
class ServerKiller extends Thread{
public $time;
/** @var bool */
private $stopped = false;
public function __construct($time = 15){
$this->time = $time;
}
protected function onRun() : void{
$start = time();
$this->synchronized(function(){
if(!$this->stopped){
$this->wait($this->time * 1000000);
}
});
if(time() - $start >= $this->time){
echo "\nTook too long to stop, server was killed forcefully!\n";
@Process::kill(getmypid());
}
}
public function quit() : void{
$this->synchronized(function() : void{
$this->stopped = true;
$this->notify();
});
parent::quit();
}
public function getThreadName() : string{
return "Server Killer";
}
}

286
src/utils/Terminal.php Normal file
View File

@@ -0,0 +1,286 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function fclose;
use function fopen;
use function function_exists;
use function getenv;
use function is_array;
use function stream_isatty;
use const PHP_EOL;
abstract class Terminal{
public static $FORMAT_BOLD = "";
public static $FORMAT_OBFUSCATED = "";
public static $FORMAT_ITALIC = "";
public static $FORMAT_UNDERLINE = "";
public static $FORMAT_STRIKETHROUGH = "";
public static $FORMAT_RESET = "";
public static $COLOR_BLACK = "";
public static $COLOR_DARK_BLUE = "";
public static $COLOR_DARK_GREEN = "";
public static $COLOR_DARK_AQUA = "";
public static $COLOR_DARK_RED = "";
public static $COLOR_PURPLE = "";
public static $COLOR_GOLD = "";
public static $COLOR_GRAY = "";
public static $COLOR_DARK_GRAY = "";
public static $COLOR_BLUE = "";
public static $COLOR_GREEN = "";
public static $COLOR_AQUA = "";
public static $COLOR_RED = "";
public static $COLOR_LIGHT_PURPLE = "";
public static $COLOR_YELLOW = "";
public static $COLOR_WHITE = "";
/** @var bool|null */
private static $formattingCodes = null;
public static function hasFormattingCodes() : bool{
if(self::$formattingCodes === null){
throw new \InvalidStateException("Formatting codes have not been initialized");
}
return self::$formattingCodes;
}
private static function detectFormattingCodesSupport() : bool{
$stdout = fopen("php://stdout", "w");
$result = (
stream_isatty($stdout) and //STDOUT isn't being piped
(
getenv('TERM') !== false or //Console says it supports colours
(function_exists('sapi_windows_vt100_support') and sapi_windows_vt100_support($stdout)) //we're on windows and have vt100 support
)
);
fclose($stdout);
return $result;
}
protected static function getFallbackEscapeCodes() : void{
self::$FORMAT_BOLD = "\x1b[1m";
self::$FORMAT_OBFUSCATED = "";
self::$FORMAT_ITALIC = "\x1b[3m";
self::$FORMAT_UNDERLINE = "\x1b[4m";
self::$FORMAT_STRIKETHROUGH = "\x1b[9m";
self::$FORMAT_RESET = "\x1b[m";
self::$COLOR_BLACK = "\x1b[38;5;16m";
self::$COLOR_DARK_BLUE = "\x1b[38;5;19m";
self::$COLOR_DARK_GREEN = "\x1b[38;5;34m";
self::$COLOR_DARK_AQUA = "\x1b[38;5;37m";
self::$COLOR_DARK_RED = "\x1b[38;5;124m";
self::$COLOR_PURPLE = "\x1b[38;5;127m";
self::$COLOR_GOLD = "\x1b[38;5;214m";
self::$COLOR_GRAY = "\x1b[38;5;145m";
self::$COLOR_DARK_GRAY = "\x1b[38;5;59m";
self::$COLOR_BLUE = "\x1b[38;5;63m";
self::$COLOR_GREEN = "\x1b[38;5;83m";
self::$COLOR_AQUA = "\x1b[38;5;87m";
self::$COLOR_RED = "\x1b[38;5;203m";
self::$COLOR_LIGHT_PURPLE = "\x1b[38;5;207m";
self::$COLOR_YELLOW = "\x1b[38;5;227m";
self::$COLOR_WHITE = "\x1b[38;5;231m";
}
protected static function getEscapeCodes() : void{
self::$FORMAT_BOLD = `tput bold`;
self::$FORMAT_OBFUSCATED = `tput smacs`;
self::$FORMAT_ITALIC = `tput sitm`;
self::$FORMAT_UNDERLINE = `tput smul`;
self::$FORMAT_STRIKETHROUGH = "\x1b[9m"; //`tput `;
self::$FORMAT_RESET = `tput sgr0`;
$colors = (int) `tput colors`;
if($colors > 8){
self::$COLOR_BLACK = $colors >= 256 ? `tput setaf 16` : `tput setaf 0`;
self::$COLOR_DARK_BLUE = $colors >= 256 ? `tput setaf 19` : `tput setaf 4`;
self::$COLOR_DARK_GREEN = $colors >= 256 ? `tput setaf 34` : `tput setaf 2`;
self::$COLOR_DARK_AQUA = $colors >= 256 ? `tput setaf 37` : `tput setaf 6`;
self::$COLOR_DARK_RED = $colors >= 256 ? `tput setaf 124` : `tput setaf 1`;
self::$COLOR_PURPLE = $colors >= 256 ? `tput setaf 127` : `tput setaf 5`;
self::$COLOR_GOLD = $colors >= 256 ? `tput setaf 214` : `tput setaf 3`;
self::$COLOR_GRAY = $colors >= 256 ? `tput setaf 145` : `tput setaf 7`;
self::$COLOR_DARK_GRAY = $colors >= 256 ? `tput setaf 59` : `tput setaf 8`;
self::$COLOR_BLUE = $colors >= 256 ? `tput setaf 63` : `tput setaf 12`;
self::$COLOR_GREEN = $colors >= 256 ? `tput setaf 83` : `tput setaf 10`;
self::$COLOR_AQUA = $colors >= 256 ? `tput setaf 87` : `tput setaf 14`;
self::$COLOR_RED = $colors >= 256 ? `tput setaf 203` : `tput setaf 9`;
self::$COLOR_LIGHT_PURPLE = $colors >= 256 ? `tput setaf 207` : `tput setaf 13`;
self::$COLOR_YELLOW = $colors >= 256 ? `tput setaf 227` : `tput setaf 11`;
self::$COLOR_WHITE = $colors >= 256 ? `tput setaf 231` : `tput setaf 15`;
}else{
self::$COLOR_BLACK = self::$COLOR_DARK_GRAY = `tput setaf 0`;
self::$COLOR_RED = self::$COLOR_DARK_RED = `tput setaf 1`;
self::$COLOR_GREEN = self::$COLOR_DARK_GREEN = `tput setaf 2`;
self::$COLOR_YELLOW = self::$COLOR_GOLD = `tput setaf 3`;
self::$COLOR_BLUE = self::$COLOR_DARK_BLUE = `tput setaf 4`;
self::$COLOR_LIGHT_PURPLE = self::$COLOR_PURPLE = `tput setaf 5`;
self::$COLOR_AQUA = self::$COLOR_DARK_AQUA = `tput setaf 6`;
self::$COLOR_GRAY = self::$COLOR_WHITE = `tput setaf 7`;
}
}
public static function init(?bool $enableFormatting = null) : void{
self::$formattingCodes = $enableFormatting ?? self::detectFormattingCodesSupport();
if(!self::$formattingCodes){
return;
}
switch(Utils::getOS()){
case "linux":
case "mac":
case "bsd":
self::getEscapeCodes();
return;
case "win":
case "android":
self::getFallbackEscapeCodes();
return;
}
//TODO: iOS
}
public static function isInit() : bool{
return self::$formattingCodes !== null;
}
/**
* Returns a string with colorized ANSI Escape codes for the current terminal
* Note that this is platform-dependent and might produce different results depending on the terminal type and/or OS.
*
* @param string|array $string
*
* @return string
*/
public static function toANSI($string) : string{
if(!is_array($string)){
$string = TextFormat::tokenize($string);
}
$newString = "";
foreach($string as $token){
switch($token){
case TextFormat::BOLD:
$newString .= Terminal::$FORMAT_BOLD;
break;
case TextFormat::OBFUSCATED:
$newString .= Terminal::$FORMAT_OBFUSCATED;
break;
case TextFormat::ITALIC:
$newString .= Terminal::$FORMAT_ITALIC;
break;
case TextFormat::UNDERLINE:
$newString .= Terminal::$FORMAT_UNDERLINE;
break;
case TextFormat::STRIKETHROUGH:
$newString .= Terminal::$FORMAT_STRIKETHROUGH;
break;
case TextFormat::RESET:
$newString .= Terminal::$FORMAT_RESET;
break;
//Colors
case TextFormat::BLACK:
$newString .= Terminal::$COLOR_BLACK;
break;
case TextFormat::DARK_BLUE:
$newString .= Terminal::$COLOR_DARK_BLUE;
break;
case TextFormat::DARK_GREEN:
$newString .= Terminal::$COLOR_DARK_GREEN;
break;
case TextFormat::DARK_AQUA:
$newString .= Terminal::$COLOR_DARK_AQUA;
break;
case TextFormat::DARK_RED:
$newString .= Terminal::$COLOR_DARK_RED;
break;
case TextFormat::DARK_PURPLE:
$newString .= Terminal::$COLOR_PURPLE;
break;
case TextFormat::GOLD:
$newString .= Terminal::$COLOR_GOLD;
break;
case TextFormat::GRAY:
$newString .= Terminal::$COLOR_GRAY;
break;
case TextFormat::DARK_GRAY:
$newString .= Terminal::$COLOR_DARK_GRAY;
break;
case TextFormat::BLUE:
$newString .= Terminal::$COLOR_BLUE;
break;
case TextFormat::GREEN:
$newString .= Terminal::$COLOR_GREEN;
break;
case TextFormat::AQUA:
$newString .= Terminal::$COLOR_AQUA;
break;
case TextFormat::RED:
$newString .= Terminal::$COLOR_RED;
break;
case TextFormat::LIGHT_PURPLE:
$newString .= Terminal::$COLOR_LIGHT_PURPLE;
break;
case TextFormat::YELLOW:
$newString .= Terminal::$COLOR_YELLOW;
break;
case TextFormat::WHITE:
$newString .= Terminal::$COLOR_WHITE;
break;
default:
$newString .= $token;
break;
}
}
return $newString;
}
/**
* Emits a string containing Minecraft colour codes to the console formatted with native colours.
*
* @param string $line
*/
public static function write(string $line) : void{
echo self::toANSI($line);
}
/**
* Emits a string containing Minecraft colour codes to the console formatted with native colours, followed by a
* newline character.
*
* @param string $line
*/
public static function writeLine(string $line) : void{
echo self::toANSI($line) . self::$FORMAT_RESET . PHP_EOL;
}
}

412
src/utils/TextFormat.php Normal file
View File

@@ -0,0 +1,412 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function is_array;
use function json_encode;
use function mb_scrub;
use function preg_quote;
use function preg_replace;
use function preg_split;
use function str_repeat;
use function str_replace;
use const JSON_UNESCAPED_SLASHES;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
/**
* Class used to handle Minecraft chat format, and convert it to other formats like HTML
*/
abstract class TextFormat{
public const ESCAPE = "\xc2\xa7"; //§
public const EOL = "\n";
public const BLACK = TextFormat::ESCAPE . "0";
public const DARK_BLUE = TextFormat::ESCAPE . "1";
public const DARK_GREEN = TextFormat::ESCAPE . "2";
public const DARK_AQUA = TextFormat::ESCAPE . "3";
public const DARK_RED = TextFormat::ESCAPE . "4";
public const DARK_PURPLE = TextFormat::ESCAPE . "5";
public const GOLD = TextFormat::ESCAPE . "6";
public const GRAY = TextFormat::ESCAPE . "7";
public const DARK_GRAY = TextFormat::ESCAPE . "8";
public const BLUE = TextFormat::ESCAPE . "9";
public const GREEN = TextFormat::ESCAPE . "a";
public const AQUA = TextFormat::ESCAPE . "b";
public const RED = TextFormat::ESCAPE . "c";
public const LIGHT_PURPLE = TextFormat::ESCAPE . "d";
public const YELLOW = TextFormat::ESCAPE . "e";
public const WHITE = TextFormat::ESCAPE . "f";
public const OBFUSCATED = TextFormat::ESCAPE . "k";
public const BOLD = TextFormat::ESCAPE . "l";
public const STRIKETHROUGH = TextFormat::ESCAPE . "m";
public const UNDERLINE = TextFormat::ESCAPE . "n";
public const ITALIC = TextFormat::ESCAPE . "o";
public const RESET = TextFormat::ESCAPE . "r";
/**
* Splits the string by Format tokens
*
* @param string $string
*
* @return array
*/
public static function tokenize(string $string) : array{
return preg_split("/(" . TextFormat::ESCAPE . "[0-9a-fk-or])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
}
/**
* Cleans the string from Minecraft codes, ANSI Escape Codes and invalid UTF-8 characters
*
* @param string $string
* @param bool $removeFormat
*
* @return string valid clean UTF-8
*/
public static function clean(string $string, bool $removeFormat = true) : string{
$string = mb_scrub($string, 'UTF-8');
$string = preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console)
if($removeFormat){
$string = str_replace(TextFormat::ESCAPE, "", preg_replace("/" . TextFormat::ESCAPE . "[0-9a-fk-or]/u", "", $string));
}
return str_replace("\x1b", "", preg_replace("/\x1b[\\(\\][[0-9;\\[\\(]+[Bm]/u", "", $string));
}
/**
* Replaces placeholders of § with the correct character. Only valid codes (as in the constants of the TextFormat class) will be converted.
*
* @param string $string
* @param string $placeholder default "&"
*
* @return string
*/
public static function colorize(string $string, string $placeholder = "&") : string{
return preg_replace('/' . preg_quote($placeholder, "/") . '([0-9a-fk-or])/u', TextFormat::ESCAPE . '$1', $string);
}
/**
* Returns an JSON-formatted string with colors/markup
*
* @param string|array $string
*
* @return string
*/
public static function toJSON($string) : string{
if(!is_array($string)){
$string = self::tokenize($string);
}
$newString = [];
$pointer =& $newString;
$color = "white";
$bold = false;
$italic = false;
$underlined = false;
$strikethrough = false;
$obfuscated = false;
$index = 0;
foreach($string as $token){
if(isset($pointer["text"])){
if(!isset($newString["extra"])){
$newString["extra"] = [];
}
$newString["extra"][$index] = [];
$pointer =& $newString["extra"][$index];
if($color !== "white"){
$pointer["color"] = $color;
}
if($bold){
$pointer["bold"] = true;
}
if($italic){
$pointer["italic"] = true;
}
if($underlined){
$pointer["underlined"] = true;
}
if($strikethrough){
$pointer["strikethrough"] = true;
}
if($obfuscated){
$pointer["obfuscated"] = true;
}
++$index;
}
switch($token){
case TextFormat::BOLD:
if(!$bold){
$pointer["bold"] = true;
$bold = true;
}
break;
case TextFormat::OBFUSCATED:
if(!$obfuscated){
$pointer["obfuscated"] = true;
$obfuscated = true;
}
break;
case TextFormat::ITALIC:
if(!$italic){
$pointer["italic"] = true;
$italic = true;
}
break;
case TextFormat::UNDERLINE:
if(!$underlined){
$pointer["underlined"] = true;
$underlined = true;
}
break;
case TextFormat::STRIKETHROUGH:
if(!$strikethrough){
$pointer["strikethrough"] = true;
$strikethrough = true;
}
break;
case TextFormat::RESET:
if($color !== "white"){
$pointer["color"] = "white";
$color = "white";
}
if($bold){
$pointer["bold"] = false;
$bold = false;
}
if($italic){
$pointer["italic"] = false;
$italic = false;
}
if($underlined){
$pointer["underlined"] = false;
$underlined = false;
}
if($strikethrough){
$pointer["strikethrough"] = false;
$strikethrough = false;
}
if($obfuscated){
$pointer["obfuscated"] = false;
$obfuscated = false;
}
break;
//Colors
case TextFormat::BLACK:
$pointer["color"] = "black";
$color = "black";
break;
case TextFormat::DARK_BLUE:
$pointer["color"] = "dark_blue";
$color = "dark_blue";
break;
case TextFormat::DARK_GREEN:
$pointer["color"] = "dark_green";
$color = "dark_green";
break;
case TextFormat::DARK_AQUA:
$pointer["color"] = "dark_aqua";
$color = "dark_aqua";
break;
case TextFormat::DARK_RED:
$pointer["color"] = "dark_red";
$color = "dark_red";
break;
case TextFormat::DARK_PURPLE:
$pointer["color"] = "dark_purple";
$color = "dark_purple";
break;
case TextFormat::GOLD:
$pointer["color"] = "gold";
$color = "gold";
break;
case TextFormat::GRAY:
$pointer["color"] = "gray";
$color = "gray";
break;
case TextFormat::DARK_GRAY:
$pointer["color"] = "dark_gray";
$color = "dark_gray";
break;
case TextFormat::BLUE:
$pointer["color"] = "blue";
$color = "blue";
break;
case TextFormat::GREEN:
$pointer["color"] = "green";
$color = "green";
break;
case TextFormat::AQUA:
$pointer["color"] = "aqua";
$color = "aqua";
break;
case TextFormat::RED:
$pointer["color"] = "red";
$color = "red";
break;
case TextFormat::LIGHT_PURPLE:
$pointer["color"] = "light_purple";
$color = "light_purple";
break;
case TextFormat::YELLOW:
$pointer["color"] = "yellow";
$color = "yellow";
break;
case TextFormat::WHITE:
$pointer["color"] = "white";
$color = "white";
break;
default:
$pointer["text"] = $token;
break;
}
}
if(isset($newString["extra"])){
foreach($newString["extra"] as $k => $d){
if(!isset($d["text"])){
unset($newString["extra"][$k]);
}
}
}
return json_encode($newString, JSON_UNESCAPED_SLASHES);
}
/**
* Returns an HTML-formatted string with colors/markup
*
* @param string|array $string
*
* @return string
*/
public static function toHTML($string) : string{
if(!is_array($string)){
$string = self::tokenize($string);
}
$newString = "";
$tokens = 0;
foreach($string as $token){
switch($token){
case TextFormat::BOLD:
$newString .= "<span style=font-weight:bold>";
++$tokens;
break;
case TextFormat::OBFUSCATED:
//$newString .= "<span style=text-decoration:line-through>";
//++$tokens;
break;
case TextFormat::ITALIC:
$newString .= "<span style=font-style:italic>";
++$tokens;
break;
case TextFormat::UNDERLINE:
$newString .= "<span style=text-decoration:underline>";
++$tokens;
break;
case TextFormat::STRIKETHROUGH:
$newString .= "<span style=text-decoration:line-through>";
++$tokens;
break;
case TextFormat::RESET:
$newString .= str_repeat("</span>", $tokens);
$tokens = 0;
break;
//Colors
case TextFormat::BLACK:
$newString .= "<span style=color:#000>";
++$tokens;
break;
case TextFormat::DARK_BLUE:
$newString .= "<span style=color:#00A>";
++$tokens;
break;
case TextFormat::DARK_GREEN:
$newString .= "<span style=color:#0A0>";
++$tokens;
break;
case TextFormat::DARK_AQUA:
$newString .= "<span style=color:#0AA>";
++$tokens;
break;
case TextFormat::DARK_RED:
$newString .= "<span style=color:#A00>";
++$tokens;
break;
case TextFormat::DARK_PURPLE:
$newString .= "<span style=color:#A0A>";
++$tokens;
break;
case TextFormat::GOLD:
$newString .= "<span style=color:#FA0>";
++$tokens;
break;
case TextFormat::GRAY:
$newString .= "<span style=color:#AAA>";
++$tokens;
break;
case TextFormat::DARK_GRAY:
$newString .= "<span style=color:#555>";
++$tokens;
break;
case TextFormat::BLUE:
$newString .= "<span style=color:#55F>";
++$tokens;
break;
case TextFormat::GREEN:
$newString .= "<span style=color:#5F5>";
++$tokens;
break;
case TextFormat::AQUA:
$newString .= "<span style=color:#5FF>";
++$tokens;
break;
case TextFormat::RED:
$newString .= "<span style=color:#F55>";
++$tokens;
break;
case TextFormat::LIGHT_PURPLE:
$newString .= "<span style=color:#F5F>";
++$tokens;
break;
case TextFormat::YELLOW:
$newString .= "<span style=color:#FF5>";
++$tokens;
break;
case TextFormat::WHITE:
$newString .= "<span style=color:#FFF>";
++$tokens;
break;
default:
$newString .= $token;
break;
}
}
$newString .= str_repeat("</span>", $tokens);
return $newString;
}
}

217
src/utils/Timezone.php Normal file
View File

@@ -0,0 +1,217 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function abs;
use function date_default_timezone_set;
use function date_parse;
use function exec;
use function file_exists;
use function file_get_contents;
use function implode;
use function ini_get;
use function ini_set;
use function is_link;
use function json_decode;
use function parse_ini_file;
use function preg_match;
use function readlink;
use function str_replace;
use function strpos;
use function substr;
use function timezone_abbreviations_list;
use function timezone_name_from_abbr;
use function trim;
abstract class Timezone{
public static function get() : string{
return ini_get('date.timezone');
}
public static function init() : void{
$timezone = ini_get("date.timezone");
if($timezone !== ""){
/*
* This is here so that people don't come to us complaining and fill up the issue tracker when they put
* an incorrect timezone abbreviation in php.ini apparently.
*/
if(strpos($timezone, "/") === false){
$default_timezone = timezone_name_from_abbr($timezone);
if($default_timezone !== false){
ini_set("date.timezone", $default_timezone);
date_default_timezone_set($default_timezone);
return;
}
//Bad php.ini value, try another method to detect timezone
\GlobalLogger::get()->warning("Timezone \"$timezone\" could not be parsed as a valid timezone from php.ini, falling back to auto-detection");
}else{
date_default_timezone_set($timezone);
return;
}
}
if(($timezone = self::detectSystemTimezone()) and date_default_timezone_set($timezone)){
//Success! Timezone has already been set and validated in the if statement.
//This here is just for redundancy just in case some program wants to read timezone data from the ini.
ini_set("date.timezone", $timezone);
return;
}
if($response = Internet::getURL("http://ip-api.com/json") //If system timezone detection fails or timezone is an invalid value.
and $ip_geolocation_data = json_decode($response, true)
and $ip_geolocation_data['status'] !== 'fail'
and date_default_timezone_set($ip_geolocation_data['timezone'])
){
//Again, for redundancy.
ini_set("date.timezone", $ip_geolocation_data['timezone']);
return;
}
ini_set("date.timezone", "UTC");
date_default_timezone_set("UTC");
\GlobalLogger::get()->warning("Timezone could not be automatically determined or was set to an invalid value. An incorrect timezone will result in incorrect timestamps on console logs. It has been set to \"UTC\" by default. You can change it on the php.ini file.");
}
public static function detectSystemTimezone(){
switch(Utils::getOS()){
case 'win':
$regex = '/(UTC)(\+*\-*\d*\d*\:*\d*\d*)/';
/*
* wmic timezone get Caption
* Get the timezone offset
*
* Sample Output var_dump
* array(3) {
* [0] =>
* string(7) "Caption"
* [1] =>
* string(20) "(UTC+09:30) Adelaide"
* [2] =>
* string(0) ""
* }
*/
exec("wmic timezone get Caption", $output);
$string = trim(implode("\n", $output));
//Detect the Time Zone string
preg_match($regex, $string, $matches);
if(!isset($matches[2])){
return false;
}
$offset = $matches[2];
if($offset == ""){
return "UTC";
}
return self::parseOffset($offset);
case 'linux':
// Ubuntu / Debian.
if(file_exists('/etc/timezone')){
$data = file_get_contents('/etc/timezone');
if($data){
return trim($data);
}
}
// RHEL / CentOS
if(file_exists('/etc/sysconfig/clock')){
$data = parse_ini_file('/etc/sysconfig/clock');
if(!empty($data['ZONE'])){
return trim($data['ZONE']);
}
}
//Portable method for incompatible linux distributions.
$offset = trim(exec('date +%:z'));
if($offset == "+00:00"){
return "UTC";
}
return self::parseOffset($offset);
case 'mac':
if(is_link('/etc/localtime')){
$filename = readlink('/etc/localtime');
if(strpos($filename, '/usr/share/zoneinfo/') === 0){
$timezone = substr($filename, 20);
return trim($timezone);
}
}
return false;
default:
return false;
}
}
/**
* @param string $offset In the format of +09:00, +02:00, -04:00 etc.
*
* @return string|bool
*/
private static function parseOffset($offset){
//Make signed offsets unsigned for date_parse
if(strpos($offset, '-') !== false){
$negative_offset = true;
$offset = str_replace('-', '', $offset);
}else{
if(strpos($offset, '+') !== false){
$negative_offset = false;
$offset = str_replace('+', '', $offset);
}else{
return false;
}
}
$parsed = date_parse($offset);
$offset = $parsed['hour'] * 3600 + $parsed['minute'] * 60 + $parsed['second'];
//After date_parse is done, put the sign back
if($negative_offset == true){
$offset = -abs($offset);
}
//And then, look the offset up.
//timezone_name_from_abbr is not used because it returns false on some(most) offsets because it's mapping function is weird.
//That's been a bug in PHP since 2008!
foreach(timezone_abbreviations_list() as $zones){
foreach($zones as $timezone){
if($timezone['offset'] == $offset){
return $timezone['timezone_id'];
}
}
}
return false;
}
}

132
src/utils/UUID.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function bin2hex;
use function getmypid;
use function getmyuid;
use function hash;
use function hex2bin;
use function implode;
use function mt_rand;
use function str_replace;
use function strlen;
use function substr;
use function time;
use function trim;
final class UUID{
/** @var int[] */
private $parts;
/** @var int */
private $version;
public function __construct(int $part1 = 0, int $part2 = 0, int $part3 = 0, int $part4 = 0, ?int $version = null){
$this->parts = [$part1, $part2, $part3, $part4];
$this->version = $version ?? ($this->parts[1] & 0xf000) >> 12;
}
public function getVersion() : int{
return $this->version;
}
public function equals(UUID $uuid) : bool{
return $uuid->parts === $this->parts;
}
/**
* Creates an UUID from an hexadecimal representation
*
* @param string $uuid
* @param int $version
*
* @return UUID
*/
public static function fromString(string $uuid, ?int $version = null) : UUID{
return self::fromBinary(hex2bin(str_replace("-", "", trim($uuid))), $version);
}
/**
* Creates an UUID from a binary representation
*
* @param string $uuid
* @param int $version
*
* @return UUID
*
* @throws \InvalidArgumentException
*/
public static function fromBinary(string $uuid, ?int $version = null) : UUID{
if(strlen($uuid) !== 16){
throw new \InvalidArgumentException("Must have exactly 16 bytes");
}
return new UUID(Binary::readInt(substr($uuid, 0, 4)), Binary::readInt(substr($uuid, 4, 4)), Binary::readInt(substr($uuid, 8, 4)), Binary::readInt(substr($uuid, 12, 4)), $version);
}
/**
* Creates an UUIDv3 from binary data or list of binary data
*
* @param string ...$data
*
* @return UUID
*/
public static function fromData(string ...$data) : UUID{
$hash = hash("md5", implode($data), true);
return self::fromBinary($hash, 3);
}
public static function fromRandom() : UUID{
return self::fromData(Binary::writeInt(time()), Binary::writeShort(getmypid()), Binary::writeShort(getmyuid()), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)), Binary::writeInt(mt_rand(-0x7fffffff, 0x7fffffff)));
}
public function toBinary() : string{
return Binary::writeInt($this->parts[0]) . Binary::writeInt($this->parts[1]) . Binary::writeInt($this->parts[2]) . Binary::writeInt($this->parts[3]);
}
public function toString() : string{
$hex = bin2hex($this->toBinary());
//xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx 8-4-4-4-12
return substr($hex, 0, 8) . "-" . substr($hex, 8, 4) . "-" . substr($hex, 12, 4) . "-" . substr($hex, 16, 4) . "-" . substr($hex, 20, 12);
}
public function __toString() : string{
return $this->toString();
}
public function getPart(int $partNumber){
if($partNumber < 0 or $partNumber > 3){
throw new \InvalidArgumentException("Invalid UUID part index $partNumber");
}
return $this->parts[$partNumber];
}
public function getParts() : array{
return $this->parts;
}
}

562
src/utils/Utils.php Normal file
View File

@@ -0,0 +1,562 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
/**
* Various Utilities used around the code
*/
namespace pocketmine\utils;
use DaveRandom\CallbackValidator\CallbackType;
use function array_combine;
use function array_map;
use function array_reverse;
use function array_values;
use function base64_decode;
use function bin2hex;
use function chunk_split;
use function count;
use function debug_zval_dump;
use function dechex;
use function exec;
use function explode;
use function file;
use function file_exists;
use function file_get_contents;
use function function_exists;
use function get_current_user;
use function get_loaded_extensions;
use function getenv;
use function gettype;
use function implode;
use function is_array;
use function is_dir;
use function is_file;
use function is_object;
use function is_readable;
use function is_string;
use function json_decode;
use function json_last_error_msg;
use function ob_end_clean;
use function ob_get_contents;
use function ob_start;
use function ord;
use function php_uname;
use function phpversion;
use function preg_grep;
use function preg_match;
use function preg_match_all;
use function preg_replace;
use function rmdir;
use function scandir;
use function str_pad;
use function str_replace;
use function str_split;
use function stripos;
use function strlen;
use function strpos;
use function substr;
use function sys_get_temp_dir;
use function trim;
use function unlink;
use function xdebug_get_function_stack;
use const PHP_EOL;
use const PHP_INT_MAX;
use const PHP_INT_SIZE;
use const PHP_MAXPATHLEN;
use const SCANDIR_SORT_NONE;
use const STR_PAD_LEFT;
use const STR_PAD_RIGHT;
/**
* Big collection of functions
*/
class Utils{
/** @var string */
private static $os;
/** @var UUID|null */
private static $serverUniqueId = null;
/**
* Returns a readable identifier for the given Closure, including file and line.
*
* @param \Closure $closure
*
* @return string
* @throws \ReflectionException
*/
public static function getNiceClosureName(\Closure $closure) : string{
$func = new \ReflectionFunction($closure);
if(substr($func->getName(), -strlen('{closure}')) !== '{closure}'){
//closure wraps a named function, can be done with reflection or fromCallable()
//isClosure() is useless here because it just tells us if $func is reflecting a Closure object
$scope = $func->getClosureScopeClass();
if($scope !== null){ //class method
return
$scope->getName() .
($func->getClosureThis() !== null ? "->" : "::") .
$func->getName(); //name doesn't include class in this case
}
//non-class function
return $func->getName();
}
return "closure@" . self::cleanPath($func->getFileName()) . "#L" . $func->getStartLine();
}
/**
* Returns a readable identifier for the class of the given object. Sanitizes class names for anonymous classes.
*
* @param object $obj
*
* @return string
* @throws \ReflectionException
*/
public static function getNiceClassName(object $obj) : string{
$reflect = new \ReflectionClass($obj);
if($reflect->isAnonymous()){
return "anonymous@" . self::cleanPath($reflect->getFileName()) . "#L" . $reflect->getStartLine();
}
return $reflect->getName();
}
public static function cloneCallback() : \Closure{
return static function(object $o){
return clone $o;
};
}
public static function cloneObjectArray(array $array) : array{
return array_map(self::cloneCallback(), $array);
}
/**
* Gets this machine / server instance unique ID
* Returns a hash, the first 32 characters (or 16 if raw)
* will be an identifier that won't change frequently.
* The rest of the hash will change depending on other factors.
*
* @param string $extra optional, additional data to identify the machine
*
* @return UUID
*/
public static function getMachineUniqueId(string $extra = "") : UUID{
if(self::$serverUniqueId !== null and $extra === ""){
return self::$serverUniqueId;
}
$machine = php_uname("a");
$machine .= file_exists("/proc/cpuinfo") ? implode(preg_grep("/(model name|Processor|Serial)/", file("/proc/cpuinfo"))) : "";
$machine .= sys_get_temp_dir();
$machine .= $extra;
$os = Utils::getOS();
if($os === "win"){
@exec("ipconfig /ALL", $mac);
$mac = implode("\n", $mac);
if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches)){
foreach($matches[1] as $i => $v){
if($v == "00-00-00-00-00-00"){
unset($matches[1][$i]);
}
}
$machine .= implode(" ", $matches[1]); //Mac Addresses
}
}elseif($os === "linux"){
if(file_exists("/etc/machine-id")){
$machine .= file_get_contents("/etc/machine-id");
}else{
@exec("ifconfig 2>/dev/null", $mac);
$mac = implode("\n", $mac);
if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches)){
foreach($matches[1] as $i => $v){
if($v == "00:00:00:00:00:00"){
unset($matches[1][$i]);
}
}
$machine .= implode(" ", $matches[1]); //Mac Addresses
}
}
}elseif($os === "android"){
$machine .= @file_get_contents("/system/build.prop");
}elseif($os === "mac"){
$machine .= `system_profiler SPHardwareDataType | grep UUID`;
}
$data = $machine . PHP_MAXPATHLEN;
$data .= PHP_INT_MAX;
$data .= PHP_INT_SIZE;
$data .= get_current_user();
foreach(get_loaded_extensions() as $ext){
$data .= $ext . ":" . phpversion($ext);
}
$uuid = UUID::fromData($machine, $data);
if($extra === ""){
self::$serverUniqueId = $uuid;
}
return $uuid;
}
/**
* Returns the current Operating System
* Windows => win
* MacOS => mac
* iOS => ios
* Android => android
* Linux => Linux
* BSD => bsd
* Other => other
*
* @param bool $recalculate
*
* @return string
*/
public static function getOS(bool $recalculate = false) : string{
if(self::$os === null or $recalculate){
$uname = php_uname("s");
if(stripos($uname, "Darwin") !== false){
if(strpos(php_uname("m"), "iP") === 0){
self::$os = "ios";
}else{
self::$os = "mac";
}
}elseif(stripos($uname, "Win") !== false or $uname === "Msys"){
self::$os = "win";
}elseif(stripos($uname, "Linux") !== false){
if(@file_exists("/system/build.prop")){
self::$os = "android";
}else{
self::$os = "linux";
}
}elseif(stripos($uname, "BSD") !== false or $uname === "DragonFly"){
self::$os = "bsd";
}else{
self::$os = "other";
}
}
return self::$os;
}
/**
* @param bool $recalculate
*
* @return int
*/
public static function getCoreCount(bool $recalculate = false) : int{
static $processors = 0;
if($processors > 0 and !$recalculate){
return $processors;
}else{
$processors = 0;
}
switch(Utils::getOS()){
case "linux":
case "android":
if(file_exists("/proc/cpuinfo")){
foreach(file("/proc/cpuinfo") as $l){
if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){
++$processors;
}
}
}elseif(is_readable("/sys/devices/system/cpu/present")){
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){
$processors = (int) ($matches[2] - $matches[1]);
}
}
break;
case "bsd":
case "mac":
$processors = (int) `sysctl -n hw.ncpu`;
break;
case "win":
$processors = (int) getenv("NUMBER_OF_PROCESSORS");
break;
}
return $processors;
}
/**
* Returns a prettified hexdump
*
* @param string $bin
*
* @return string
*/
public static function hexdump(string $bin) : string{
$output = "";
$bin = str_split($bin, 16);
foreach($bin as $counter => $line){
$hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32, " ", STR_PAD_RIGHT), 2, " "), 24, " ");
$ascii = preg_replace('#([^\x20-\x7E])#', ".", $line);
$output .= str_pad(dechex($counter << 4), 4, "0", STR_PAD_LEFT) . " " . $hex . " " . $ascii . PHP_EOL;
}
return $output;
}
/**
* Returns a string that can be printed, replaces non-printable characters
*
* @param mixed $str
*
* @return string
*/
public static function printable($str) : string{
if(!is_string($str)){
return gettype($str);
}
return preg_replace('#([^\x20-\x7E])#', '.', $str);
}
/*
public static function angle3D($pos1, $pos2){
$X = $pos1["x"] - $pos2["x"];
$Z = $pos1["z"] - $pos2["z"];
$dXZ = sqrt(pow($X, 2) + pow($Z, 2));
$Y = $pos1["y"] - $pos2["y"];
$hAngle = rad2deg(atan2($Z, $X) - M_PI_2);
$vAngle = rad2deg(-atan2($Y, $dXZ));
return array("yaw" => $hAngle, "pitch" => $vAngle);
}*/
public static function javaStringHash(string $string) : int{
$hash = 0;
for($i = 0, $len = strlen($string); $i < $len; $i++){
$ord = ord($string[$i]);
if($ord & 0x80){
$ord -= 0x100;
}
$hash = 31 * $hash + $ord;
while($hash > 0x7FFFFFFF){
$hash -= 0x100000000;
}
while($hash < -0x80000000){
$hash += 0x100000000;
}
$hash &= 0xFFFFFFFF;
}
return $hash;
}
/**
* @param string $token
*
* @return array of claims
*
* @throws \UnexpectedValueException
*/
public static function getJwtClaims(string $token) : array{
$v = explode(".", $token);
if(count($v) !== 3){
throw new \UnexpectedValueException("Expected exactly 3 JWT parts, got " . count($v));
}
$payloadB64 = $v[1];
$payloadJSON = base64_decode(strtr($payloadB64, '-_', '+/'), true);
if($payloadJSON === false){
throw new \UnexpectedValueException("Invalid base64 JWT payload");
}
$result = json_decode($payloadJSON, true);
if(!is_array($result)){
throw new \UnexpectedValueException("Failed to decode JWT payload JSON: " . json_last_error_msg());
}
return $result;
}
/**
* @param object $value
* @param bool $includeCurrent
*
* @return int
*/
public static function getReferenceCount($value, bool $includeCurrent = true) : int{
ob_start();
debug_zval_dump($value);
$ret = explode("\n", ob_get_contents());
ob_end_clean();
if(count($ret) >= 1 and preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
return ((int) $m[1]) - ($includeCurrent ? 3 : 4); //$value + zval call + extra call
}
return -1;
}
/**
* @param array $trace
* @param int $maxStringLength
*
* @return array
*/
public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
$messages = [];
for($i = 0; isset($trace[$i]); ++$i){
$params = "";
if(isset($trace[$i]["args"]) or isset($trace[$i]["params"])){
if(isset($trace[$i]["args"])){
$args = $trace[$i]["args"];
}else{
$args = $trace[$i]["params"];
}
$params = implode(", ", array_map(function($value) use($maxStringLength){
if(is_object($value)){
return "object " . self::getNiceClassName($value);
}
if(is_array($value)){
return "array[" . count($value) . "]";
}
if(is_string($value)){
return "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength);
}
return gettype($value) . " " . Utils::printable((string) $value);
}, $args));
}
$messages[] = "#$i " . (isset($trace[$i]["file"]) ? self::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")";
}
return $messages;
}
/**
* @param int $skipFrames
*
* @return array
*/
public static function currentTrace(int $skipFrames = 0) : array{
++$skipFrames; //omit this frame from trace, in addition to other skipped frames
if(function_exists("xdebug_get_function_stack")){
$trace = array_reverse(xdebug_get_function_stack());
}else{
$e = new \Exception();
$trace = $e->getTrace();
}
for($i = 0; $i < $skipFrames; ++$i){
unset($trace[$i]);
}
return array_values($trace);
}
/**
* @param int $skipFrames
*
* @return array
*/
public static function printableCurrentTrace(int $skipFrames = 0) : array{
return self::printableTrace(self::currentTrace(++$skipFrames));
}
public static function cleanPath($path){
$result = str_replace(["\\", ".php", "phar://"], ["/", "", ""], $path);
//remove relative paths
//TODO: make these paths dynamic so they can be unit-tested against
static $cleanPaths = [
\pocketmine\PLUGIN_PATH => "plugins", //this has to come BEFORE \pocketmine\PATH because it's inside that by default on src installations
\pocketmine\PATH => ""
];
foreach($cleanPaths as $cleanPath => $replacement){
$cleanPath = rtrim(str_replace(["\\", "phar://"], ["/", ""], $cleanPath), "/");
if(strpos($result, $cleanPath) === 0){
$result = ltrim(str_replace($cleanPath, $replacement, $result), "/");
}
}
return $result;
}
/**
* Extracts one-line tags from the doc-comment
*
* @param string $docComment
*
* @return string[] an array of tagName => tag value. If the tag has no value, an empty string is used as the value.
*/
public static function parseDocComment(string $docComment) : array{
preg_match_all('/(*ANYCRLF)^[\t ]*\* @([a-zA-Z]+)(?:[\t ]+(.+))?[\t ]*$/m', $docComment, $matches);
return array_combine($matches[1], $matches[2]);
}
public static function testValidInstance(string $className, string $baseName) : void{
try{
$base = new \ReflectionClass($baseName);
}catch(\ReflectionException $e){
throw new \InvalidArgumentException("Base class $baseName does not exist");
}
try{
$class = new \ReflectionClass($className);
}catch(\ReflectionException $e){
throw new \InvalidArgumentException("Class $className does not exist");
}
if(!$class->isSubclassOf($baseName)){
throw new \InvalidArgumentException("Class $className does not " . ($base->isInterface() ? "implement" : "extend") . " " . $baseName);
}
if(!$class->isInstantiable()){
throw new \InvalidArgumentException("Class $className cannot be constructed");
}
}
/**
* Verifies that the given callable is compatible with the desired signature. Throws a TypeError if they are
* incompatible.
*
* @param callable $signature Dummy callable with the required parameters and return type
* @param callable $subject Callable to check the signature of
*
* @throws \DaveRandom\CallbackValidator\InvalidCallbackException
* @throws \TypeError
*/
public static function validateCallableSignature(callable $signature, callable $subject) : void{
if(!($sigType = CallbackType::createFromCallable($signature))->isSatisfiedBy($subject)){
throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($subject) . "` must be compatible with `" . $sigType . "`");
}
}
public static function recursiveUnlink(string $dir) : void{
if(is_dir($dir)){
$objects = scandir($dir, SCANDIR_SORT_NONE);
foreach($objects as $object){
if($object !== "." and $object !== ".."){
if(is_dir($dir . "/" . $object)){
self::recursiveUnlink($dir . "/" . $object);
}else{
unlink($dir . "/" . $object);
}
}
}
rmdir($dir);
}elseif(is_file($dir)){
unlink($dir);
}
}
}

146
src/utils/VersionString.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\utils;
use function count;
use function preg_match;
/**
* Manages PocketMine-MP version strings, and compares them
*/
class VersionString{
/** @var string */
private $baseVersion;
/** @var string */
private $suffix;
/** @var int */
private $major;
/** @var int */
private $minor;
/** @var int */
private $patch;
/** @var int */
private $build;
/** @var bool */
private $development = false;
/**
* @param string $baseVersion
* @param bool $isDevBuild
* @param int $buildNumber
*/
public function __construct(string $baseVersion, bool $isDevBuild = false, int $buildNumber = 0){
$this->baseVersion = $baseVersion;
$this->development = $isDevBuild;
$this->build = $buildNumber;
preg_match('/(\d+)\.(\d+)\.(\d+)(?:-(.*))?$/', $this->baseVersion, $matches);
if(count($matches) < 4){
throw new \InvalidArgumentException("Invalid base version \"$baseVersion\", should contain at least 3 version digits");
}
$this->major = (int) $matches[1];
$this->minor = (int) $matches[2];
$this->patch = (int) $matches[3];
$this->suffix = $matches[4] ?? "";
}
public function getNumber() : int{
return (($this->major << 9) | ($this->minor << 5) | $this->patch);
}
public function getBaseVersion() : string{
return $this->baseVersion;
}
public function getFullVersion(bool $build = false) : string{
$retval = $this->baseVersion;
if($this->development){
$retval .= "+dev";
if($build and $this->build > 0){
$retval .= "." . $this->build;
}
}
return $retval;
}
public function getMajor() : int{
return $this->major;
}
public function getMinor() : int{
return $this->minor;
}
public function getPatch() : int{
return $this->patch;
}
public function getSuffix() : string{
return $this->suffix;
}
public function getBuild() : int{
return $this->build;
}
public function isDev() : bool{
return $this->development;
}
public function __toString() : string{
return $this->getFullVersion();
}
/**
* @param VersionString $target
* @param bool $diff
*
* @return int
*/
public function compare(VersionString $target, bool $diff = false) : int{
$number = $this->getNumber();
$tNumber = $target->getNumber();
if($diff){
return $tNumber - $number;
}
if($number > $tNumber){
return -1; //Target is older
}elseif($number < $tNumber){
return 1; //Target is newer
}elseif($target->isDev() and !$this->isDev()){
return -1; //Dev builds of the same version are always considered older than a release
}elseif($target->getBuild() > $this->getBuild()){
return 1;
}elseif($target->getBuild() < $this->getBuild()){
return -1;
}else{
return 0; //Same version
}
}
}