migration and the software itself works!
114
app/BBcode.php
|
@ -7,12 +7,8 @@
|
|||
|
||||
namespace Sakura;
|
||||
|
||||
use JBBCode\CodeDefinitionBuilder;
|
||||
use JBBCode\DefaultCodeDefinitionSet;
|
||||
use JBBCode\Parser;
|
||||
|
||||
/**
|
||||
* Sakura wrapper for JBBCode.
|
||||
* BBcode handler.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
|
@ -20,22 +16,17 @@ use JBBCode\Parser;
|
|||
class BBcode
|
||||
{
|
||||
/**
|
||||
* The container for JBBCode.
|
||||
* BBcodes, also for backwards compatibility.
|
||||
*
|
||||
* @var Parser
|
||||
* @var array
|
||||
*/
|
||||
private static $bbcode = null;
|
||||
protected static $bbcodes = [];
|
||||
|
||||
/**
|
||||
* Initialiser.
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
// Create new parser class
|
||||
self::$bbcode = new Parser();
|
||||
|
||||
// Add the standard definitions
|
||||
self::loadStandardCodes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +44,7 @@ class BBcode
|
|||
|
||||
// Parse all emoticons
|
||||
foreach ($emotes as $emote) {
|
||||
$image = "<img src='{$emote->emote_path}' alt='{$emote->emote_string}' class='emoticon' />";
|
||||
$image = "<img src='{$emote->emote_path}' alt='{$emote->emote_string}' class='emoticon'>";
|
||||
$icon = preg_quote($emote->emote_string, '#');
|
||||
$text = preg_replace("#$icon#", $image, $text);
|
||||
}
|
||||
|
@ -62,52 +53,6 @@ class BBcode
|
|||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the standard BBcode.
|
||||
*/
|
||||
public static function loadStandardCodes()
|
||||
{
|
||||
// Add the standard definitions
|
||||
self::$bbcode->addCodeDefinitionSet(new DefaultCodeDefinitionSet());
|
||||
|
||||
$simpleCodes = [
|
||||
['header', '<h1>{param}</h1>'],
|
||||
['s', '<del>{param}</del>'],
|
||||
['spoiler', '<span class="spoiler">{param}</span>'],
|
||||
['box', '<div class="spoiler-box-container">
|
||||
<div class="spoiler-box-title" onclick="Sakura.toggleClass(this.parentNode.children[1], \'hidden\');">'
|
||||
. 'Click to open</div><div class="spoiler-box-content hidden">{param}</div></div>'],
|
||||
['box', '<div class="spoiler-box-container"><div class="spoiler-box-title"'
|
||||
. ' onclick="Sakura.toggleClass(this.parentNode.children[1], \'hidden\');">{option}</div>'
|
||||
. '<div class="spoiler-box-content hidden">{param}</div></div>'],
|
||||
['quote', '<blockquote><div class="quotee">Quote</div><div class="quote">{param}</div></blockquote>'],
|
||||
];
|
||||
|
||||
foreach ($simpleCodes as $code) {
|
||||
$builder = new CodeDefinitionBuilder($code[0], $code[1]);
|
||||
|
||||
if (strstr($code[1], '{option}')) {
|
||||
$builder->setUseOption(true);
|
||||
}
|
||||
|
||||
self::$bbcode->addCodeDefinition($builder->build());
|
||||
}
|
||||
|
||||
// Add special definitions (PHP files MUST have the same name as the definition class
|
||||
foreach (glob(ROOT . 'libraries/BBcodeDefinitions/*.php') as $ext) {
|
||||
// Clean the file path
|
||||
$ext = str_replace(ROOT . 'libraries/', '', $ext);
|
||||
$ext = str_replace('.php', '', $ext);
|
||||
$ext = str_replace('/', '\\', $ext);
|
||||
|
||||
// Build the classname
|
||||
$className = __NAMESPACE__ . '\\' . $ext;
|
||||
|
||||
// Add the BBcode definition
|
||||
self::$bbcode->addCodeDefinition(new $className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text to parse.
|
||||
*
|
||||
|
@ -115,12 +60,7 @@ class BBcode
|
|||
*/
|
||||
public static function text($text)
|
||||
{
|
||||
// Check if $bbcode is still null
|
||||
if (!self::$bbcode) {
|
||||
self::init();
|
||||
}
|
||||
|
||||
self::$bbcode->parse($text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,17 +72,19 @@ class BBcode
|
|||
*/
|
||||
public static function toHTML($text = null)
|
||||
{
|
||||
// Check if text isn't null
|
||||
if ($text !== null) {
|
||||
self::text($text);
|
||||
}
|
||||
// // Check if text isn't null
|
||||
// if ($text !== null) {
|
||||
// self::text($text);
|
||||
// }
|
||||
|
||||
$parsed = nl2br(self::$bbcode->getAsHtml());
|
||||
// $parsed = nl2br(self::$bbcode->getAsHtml());
|
||||
|
||||
$parsed = self::fixCodeTags($parsed);
|
||||
$parsed = self::parseEmoticons($parsed);
|
||||
// $parsed = self::fixCodeTags($parsed);
|
||||
// $parsed = self::parseEmoticons($parsed);
|
||||
|
||||
return $parsed;
|
||||
// return $parsed;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,12 +96,14 @@ class BBcode
|
|||
*/
|
||||
public static function toEditor($text = null)
|
||||
{
|
||||
// Check if text isn't null
|
||||
if ($text !== null) {
|
||||
self::text($text);
|
||||
}
|
||||
// // Check if text isn't null
|
||||
// if ($text !== null) {
|
||||
// self::text($text);
|
||||
// }
|
||||
|
||||
return self::$bbcode->getAsBBCode();
|
||||
// return self::$bbcode->getAsBBCode();
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,16 +115,18 @@ class BBcode
|
|||
*/
|
||||
public static function toPlain($text = null)
|
||||
{
|
||||
// Check if text isn't null
|
||||
if ($text !== null) {
|
||||
self::text($text);
|
||||
}
|
||||
// // Check if text isn't null
|
||||
// if ($text !== null) {
|
||||
// self::text($text);
|
||||
// }
|
||||
|
||||
return self::$bbcode->getAsText();
|
||||
// return self::$bbcode->getAsText();
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the contents of <code> tags.
|
||||
* See if this can be deprecated with a custom implementation!
|
||||
*
|
||||
* @param string $text Dirty
|
||||
*
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the text alignment bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
|
||||
/**
|
||||
* Text alignment bbcode for JBBCode
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Align extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("align");
|
||||
$this->setUseOption(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates compiled HTML from the align bbcode.
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string Compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$alignments = [
|
||||
'left',
|
||||
'center',
|
||||
'right',
|
||||
];
|
||||
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= $child->getAsHTML();
|
||||
}
|
||||
|
||||
$alignment = $el->getAttribute()['align'];
|
||||
|
||||
if (!in_array($alignment, $alignments)) {
|
||||
return $el->getAsBBCode();
|
||||
}
|
||||
|
||||
return "<div style='text-align: {$alignment};'>{$content}</div>";
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the code format bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
|
||||
/**
|
||||
* Code bbcode for JBBCode
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Code extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("code");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the code bbcode to HTML.
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return mixed The compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= $child->getAsBBCode();
|
||||
}
|
||||
|
||||
return "<pre class='code'><code>{$content}</code></pre>";
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the list bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
|
||||
/**
|
||||
* Implements a [list] code definition that provides the following syntax:
|
||||
*
|
||||
* [list]
|
||||
* [*] first item
|
||||
* [*] second item
|
||||
* [*] third item
|
||||
* [/list]
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Jackson Owens <jackson_owens@alumni.brown.edu>
|
||||
*/
|
||||
class Lists extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->parseContent = true;
|
||||
$this->useOption = false;
|
||||
$this->setTagName('list');
|
||||
$this->nestLimit = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the list bbcode to HTML.
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string The compiled HTML list.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$bodyHtml = '';
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$bodyHtml .= $child->getAsHTML();
|
||||
}
|
||||
|
||||
$listPieces = explode('[*]', $bodyHtml);
|
||||
|
||||
unset($listPieces[0]);
|
||||
|
||||
$listPieces = array_map(function ($li) {
|
||||
return "<li>{$li}</li>";
|
||||
}, $listPieces);
|
||||
|
||||
$list = implode('', $listPieces);
|
||||
|
||||
return "<ul>{$list}</ul>";
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the forum post quoting bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\Forum\Forum;
|
||||
use Sakura\Forum\Post;
|
||||
use Sakura\Perms\Forum as ForumPerms;
|
||||
use Sakura\Router;
|
||||
|
||||
/**
|
||||
* Quote BBcode for JBBCode.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Quote extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("quote");
|
||||
$this->setUseOption(true);
|
||||
$this->setParseContent(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the user bbcode to HTML
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string The compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$attr = $el->getAttribute()['quote'];
|
||||
|
||||
if (substr($attr, 0, 1) === '#') {
|
||||
$postId = substr($attr, 1);
|
||||
$post = new Post($postId);
|
||||
$forum = new Forum($post->forum);
|
||||
|
||||
if ($post->id !== 0
|
||||
&& $forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) {
|
||||
$postLink = Router::route('forums.post', $post->id);
|
||||
|
||||
$content = "<blockquote><div class='quotee'><a href='{$postLink}' style='color: inherit;'>"
|
||||
. "<span style='color: {$post->poster->colour}'>"
|
||||
. "{$post->poster->username}</span> wrote</a></div>"
|
||||
. "<div class='quote'>{$post->parsed}</div></blockquote>";
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= $child->getAsHTML();
|
||||
}
|
||||
|
||||
return "<blockquote><div class='quotee'>{$attr} wrote</div>
|
||||
<div class='quote'>{$content}</div></blockquote>";
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the font size bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
|
||||
/**
|
||||
* Size BBcode for JBBCode.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Size extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("size");
|
||||
$this->setUseOption(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the size bbcode to HTML
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string The compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$minSize = 0;
|
||||
$maxSize = 200;
|
||||
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= $child->getAsHTML();
|
||||
}
|
||||
|
||||
$size = $el->getAttribute()['size'];
|
||||
|
||||
if ($size < $minSize || $size > $maxSize) {
|
||||
return $el->getAsBBCode();
|
||||
}
|
||||
|
||||
$size = $size / 100;
|
||||
|
||||
return "<span style='font-size: {$size}em;'>{$content}</span>";
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the username linking bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
use Sakura\Router;
|
||||
use Sakura\User as SakuraUser;
|
||||
|
||||
/**
|
||||
* Username BBcode for JBBCode.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class User extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("user");
|
||||
$this->setUseOption(false);
|
||||
$this->setParseContent(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the user bbcode to HTML
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string The compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= clean_string($child->getAsText(), true);
|
||||
}
|
||||
|
||||
$user = SakuraUser::construct($content);
|
||||
$profile = Router::route('user.profile', $user->id);
|
||||
|
||||
return "<a class='default username' href='{$profile} style='color: {$user->colour};
|
||||
text-shadow: 0 0 .3em {$user->colour}; font-weight: bold;'>{$user->username}</a>";
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the YouTube embed bbcode class.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\BBcodeDefinitions;
|
||||
|
||||
use JBBCode\CodeDefinition;
|
||||
use JBBCode\ElementNode;
|
||||
|
||||
/**
|
||||
* YouTube video embedding bbcode for JBBCode
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class YouTube extends CodeDefinition
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setTagName("youtube");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the YouTube bbcode to HTML
|
||||
*
|
||||
* @param ElementNode $el The JBBCode element node.
|
||||
*
|
||||
* @return string The compiled HTML.
|
||||
*/
|
||||
public function asHtml(ElementNode $el)
|
||||
{
|
||||
$content = "";
|
||||
|
||||
foreach ($el->getChildren() as $child) {
|
||||
$content .= $child->getAsBBCode();
|
||||
}
|
||||
|
||||
$foundMatch = preg_match('/^([A-z0-9=\-]+?)$/i', $content, $matches);
|
||||
|
||||
if (!$foundMatch) {
|
||||
return $el->getAsBBCode();
|
||||
} else {
|
||||
return "<iframe width='640' height='390' src='https://www.youtube.com/embed/{$matches[1]}'
|
||||
frameborder='0' allowfullscreen></iframe>";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
namespace Sakura;
|
||||
|
||||
use Sakura\Hashing;
|
||||
|
||||
/**
|
||||
* Used to generate and validate CSRF tokens.
|
||||
*
|
||||
|
@ -56,7 +54,7 @@ class CSRF
|
|||
*/
|
||||
public static function generate()
|
||||
{
|
||||
return bin2hex(\mcrypt_create_iv(self::RANDOM_SIZE, MCRYPT_DEV_URANDOM));
|
||||
return bin2hex(random_bytes(self::RANDOM_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +75,6 @@ class CSRF
|
|||
return false;
|
||||
}
|
||||
|
||||
// Use the slowEquals function from the hashing lib to validate
|
||||
return Hashing::slowEquals($token, $_SESSION[$id]);
|
||||
return hash_equals($token, $_SESSION[$id]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,8 @@ class Application extends \CLIFramework\Application
|
|||
*/
|
||||
const VERSION = SAKURA_VERSION;
|
||||
|
||||
/**
|
||||
* CLI initialiser
|
||||
/*
|
||||
* Enable command autoloading
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Execute the original init function
|
||||
parent::init();
|
||||
|
||||
// Add commands with class reference because the autoloader is retarded
|
||||
$this->command('serve', Command\ServeCommand::class);
|
||||
}
|
||||
protected $commandAutoloadEnabled = true;
|
||||
}
|
||||
|
|
26
app/Console/Command/DatabaseInstallCommand.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the migration repository installer command controller.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Console\Command;
|
||||
|
||||
use CLIFramework\Command;
|
||||
use Sakura\DB;
|
||||
|
||||
class DatabaseInstallCommand extends Command
|
||||
{
|
||||
public function brief()
|
||||
{
|
||||
return 'Create the migration repository';
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$repository = DB::getMigrationRepository();
|
||||
$repository->createRepository();
|
||||
$this->getLogger()->writeln("Created the migration repository!");
|
||||
}
|
||||
}
|
40
app/Console/Command/DatabaseMigrateCommand.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
/**
|
||||
* Holds the migration command controller.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura\Console\Command;
|
||||
|
||||
use CLIFramework\Command;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Sakura\DB;
|
||||
|
||||
class DatabaseMigrateCommand extends Command
|
||||
{
|
||||
const MIGRATIONS = "database/";
|
||||
|
||||
public function brief()
|
||||
{
|
||||
return 'Run the database migrations';
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$repository = DB::getMigrationRepository();
|
||||
$migrator = new Migrator($repository, $repository->getConnectionResolver(), new Filesystem);
|
||||
|
||||
if (!$migrator->repositoryExists()) {
|
||||
$this->getLogger()->writeln("Run 'database-install' first!");
|
||||
return;
|
||||
}
|
||||
|
||||
$migrator->run(ROOT . self::MIGRATIONS);
|
||||
|
||||
foreach ($migrator->getNotes() as $note) {
|
||||
$this->getLogger()->writeln($note);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
namespace Sakura\Console\Command;
|
||||
|
||||
use CLIFramework\Command;
|
||||
use Sakura\Config;
|
||||
|
||||
class ServeCommand extends Command
|
||||
{
|
||||
|
|
|
@ -11,7 +11,6 @@ use Sakura\ActionCode;
|
|||
use Sakura\ActiveUser;
|
||||
use Sakura\Config;
|
||||
use Sakura\DB;
|
||||
use Sakura\Hashing;
|
||||
use Sakura\Net;
|
||||
use Sakura\Perms\Site;
|
||||
use Sakura\Router;
|
||||
|
@ -125,30 +124,20 @@ class AuthController extends Controller
|
|||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Validate password
|
||||
switch ($user->passwordAlgo) {
|
||||
// Disabled
|
||||
case 'disabled':
|
||||
$this->touchRateLimit($user->id);
|
||||
$message = 'Logging into this account is disabled.';
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
if (strlen($user->password) < 1) {
|
||||
$message = 'Your password expired.';
|
||||
$redirect = Router::route('auth.resetpassword');
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Default hashing method
|
||||
default:
|
||||
if (!Hashing::validatePassword($password, [
|
||||
$user->passwordAlgo,
|
||||
$user->passwordIter,
|
||||
$user->passwordSalt,
|
||||
$user->passwordHash,
|
||||
])) {
|
||||
$this->touchRateLimit($user->id);
|
||||
$message = 'The password you entered was invalid.';
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
if (!password_verify($password, $user->password)) {
|
||||
$this->touchRateLimit($user->id);
|
||||
$message = 'The password you entered was invalid.';
|
||||
Template::vars(compact('message', 'redirect'));
|
||||
|
||||
return Template::render('global/information');
|
||||
}
|
||||
return Template::render('global/information');
|
||||
}
|
||||
|
||||
// Check if the user has the required privs to log in
|
||||
|
@ -564,16 +553,13 @@ class AuthController extends Controller
|
|||
}
|
||||
|
||||
// Hash the password
|
||||
$pw = Hashing::createHash($password);
|
||||
$password = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Update the user
|
||||
DB::table('users')
|
||||
->where('user_id', $user->id)
|
||||
->update([
|
||||
'password_hash' => $pw[3],
|
||||
'password_salt' => $pw[2],
|
||||
'password_algo' => $pw[0],
|
||||
'password_iter' => $pw[1],
|
||||
'password' => $password,
|
||||
'password_chan' => time(),
|
||||
]);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class FileController extends Controller
|
|||
*/
|
||||
public function avatar($id = 0)
|
||||
{
|
||||
$noAvatar = ROOT . str_replace(
|
||||
$noAvatar = ROOT . 'public/' . str_replace(
|
||||
'%tplname%',
|
||||
Template::$name,
|
||||
config('user.avatar_none')
|
||||
|
@ -56,18 +56,7 @@ class FileController extends Controller
|
|||
'mime' => getimagesizefromstring($noAvatar)['mime'],
|
||||
];
|
||||
|
||||
$deactivePath = ROOT . str_replace(
|
||||
'%tplname%',
|
||||
Template::$name,
|
||||
config('user.avatar_inactive')
|
||||
);
|
||||
$deactive = [
|
||||
'name' => basename($deactivePath),
|
||||
'data' => file_get_contents($deactivePath),
|
||||
'mime' => getimagesizefromstring($deactivePath)['mime'],
|
||||
];
|
||||
|
||||
$bannedPath = ROOT . str_replace(
|
||||
$bannedPath = ROOT . 'public/' . str_replace(
|
||||
'%tplname%',
|
||||
Template::$name,
|
||||
config('user.avatar_ban')
|
||||
|
@ -80,15 +69,11 @@ class FileController extends Controller
|
|||
|
||||
$user = User::construct($id);
|
||||
|
||||
if ($user->permission(Site::DEACTIVATED)) {
|
||||
return $this->serve($deactive['data'], $deactive['mime'], $deactive['name']);
|
||||
}
|
||||
|
||||
if ($user->permission(Site::RESTRICTED)) {
|
||||
return $this->serve($banned['data'], $banned['mime'], $banned['name']);
|
||||
}
|
||||
|
||||
if (!$user->avatar) {
|
||||
if ($user->id < 1 || !$user->avatar || $user->permission(Site::DEACTIVATED)) {
|
||||
return $this->serve($none['data'], $none['mime'], $none['name']);
|
||||
}
|
||||
|
||||
|
@ -108,7 +93,7 @@ class FileController extends Controller
|
|||
*/
|
||||
public function background($id = 0)
|
||||
{
|
||||
$noBg = ROOT . "public/content/pixel.png";
|
||||
$noBg = ROOT . "public/images/pixel.png";
|
||||
$none = [
|
||||
'name' => basename($noBg),
|
||||
'data' => file_get_contents($noBg),
|
||||
|
@ -143,7 +128,7 @@ class FileController extends Controller
|
|||
*/
|
||||
public function header($id = 0)
|
||||
{
|
||||
$noHeader = ROOT . "public/content/pixel.png";
|
||||
$noHeader = ROOT . "public/images/pixel.png";
|
||||
$none = [
|
||||
'name' => basename($noHeader),
|
||||
'data' => file_get_contents($noHeader),
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace Sakura\Controllers\Settings;
|
|||
use Sakura\ActiveUser;
|
||||
use Sakura\Config;
|
||||
use Sakura\DB;
|
||||
use Sakura\Hashing;
|
||||
use Sakura\Perms\Site;
|
||||
use Sakura\Router;
|
||||
use Sakura\Template;
|
||||
|
@ -237,12 +236,7 @@ class AccountController extends Controller
|
|||
}
|
||||
|
||||
// Check current password
|
||||
if (!Hashing::validatePassword($current, [
|
||||
ActiveUser::$user->passwordAlgo,
|
||||
ActiveUser::$user->passwordIter,
|
||||
ActiveUser::$user->passwordSalt,
|
||||
ActiveUser::$user->passwordHash,
|
||||
])) {
|
||||
if (!password_verify($current, ActiveUser::$user->password)) {
|
||||
$message = "Your password was invalid!";
|
||||
Template::vars(compact('redirect', 'message'));
|
||||
return Template::render('global/information');
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace Sakura\Controllers\Settings;
|
|||
|
||||
use Sakura\ActiveUser;
|
||||
use Sakura\DB;
|
||||
use Sakura\Hashing;
|
||||
use Sakura\Perms\Site;
|
||||
use Sakura\Router;
|
||||
use Sakura\Template;
|
||||
|
@ -115,12 +114,7 @@ class AdvancedController extends Controller
|
|||
}
|
||||
|
||||
// Check password
|
||||
if (!Hashing::validatePassword($password, [
|
||||
ActiveUser::$user->passwordAlgo,
|
||||
ActiveUser::$user->passwordIter,
|
||||
ActiveUser::$user->passwordSalt,
|
||||
ActiveUser::$user->passwordHash,
|
||||
])) {
|
||||
if (!password_verify($password, ActiveUser::$user->password)) {
|
||||
$message = "Your password was invalid!";
|
||||
Template::vars(compact('redirect', 'message'));
|
||||
return Template::render('global/information');
|
||||
|
|
17
app/DB.php
|
@ -7,7 +7,9 @@
|
|||
|
||||
namespace Sakura;
|
||||
|
||||
use \Illuminate\Database\Capsule\Manager;
|
||||
use Illuminate\Database\Capsule\Manager;
|
||||
use Illuminate\Database\ConnectionResolver;
|
||||
use Illuminate\Database\Migrations\DatabaseMigrationRepository;
|
||||
|
||||
/**
|
||||
* The Illuminate (Laravel) database wrapper.
|
||||
|
@ -17,5 +19,16 @@ use \Illuminate\Database\Capsule\Manager;
|
|||
*/
|
||||
class DB extends Manager
|
||||
{
|
||||
// This class solely exists as an alias
|
||||
public static function getMigrationRepository()
|
||||
{
|
||||
$resolver = new ConnectionResolver(['database' => self::connection()]);
|
||||
$repository = new DatabaseMigrationRepository($resolver, 'migrations');
|
||||
$repository->setSource('database');
|
||||
return $repository;
|
||||
}
|
||||
|
||||
public static function getSchemaBuilder()
|
||||
{
|
||||
return self::connection()->getSchemaBuilder();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,35 +78,35 @@ class Forum
|
|||
*
|
||||
* @var Post
|
||||
*/
|
||||
private $_firstPost = null;
|
||||
private $firstPostCache = null;
|
||||
|
||||
/**
|
||||
* A cached instance of the last post in this forum.
|
||||
*
|
||||
* @var Post
|
||||
*/
|
||||
private $_lastPost = null;
|
||||
private $lastPostCache = null;
|
||||
|
||||
/**
|
||||
* Cached instances of the subforums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_forums = [];
|
||||
private $forumsCache = [];
|
||||
|
||||
/**
|
||||
* Cached instances of the threads in this forum.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_threads = [];
|
||||
private $threadsCache = [];
|
||||
|
||||
/**
|
||||
* The permission container.
|
||||
*
|
||||
* @var Perms
|
||||
*/
|
||||
private $_permissions;
|
||||
private $permissionsCache;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -121,7 +121,7 @@ class Forum
|
|||
->get();
|
||||
|
||||
// Create permissions object
|
||||
$this->_permissions = new Perms(Perms::FORUM);
|
||||
$this->permissionsCache = new Perms(Perms::FORUM);
|
||||
|
||||
// Populate the variables
|
||||
if ($forumRow) {
|
||||
|
@ -159,9 +159,9 @@ class Forum
|
|||
}
|
||||
|
||||
// Bitwise OR it with the permissions for this forum
|
||||
$perm = $perm | $this->_permissions->user($user, ['forum_id' => [$this->id, '=']]);
|
||||
$perm = $perm | $this->permissionsCache->user($user, ['forum_id' => [$this->id, '=']]);
|
||||
|
||||
return $raw ? $perm : $this->_permissions->check($flag, $perm);
|
||||
return $raw ? $perm : $this->permissionsCache->check($flag, $perm);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,8 +171,8 @@ class Forum
|
|||
*/
|
||||
public function forums()
|
||||
{
|
||||
// Check if _forums is populated
|
||||
if (!count($this->_forums)) {
|
||||
// Check if forumsCache is populated
|
||||
if (!count($this->forumsCache)) {
|
||||
// Get all rows with the category id set to the forum id
|
||||
$forumRows = DB::table('forums')
|
||||
->where('forum_category', $this->id)
|
||||
|
@ -187,9 +187,9 @@ class Forum
|
|||
$forums[$forum->forum_id] = new Forum($forum->forum_id);
|
||||
}
|
||||
|
||||
$this->_forums = $forums;
|
||||
$this->forumsCache = $forums;
|
||||
} else {
|
||||
$forums = $this->_forums;
|
||||
$forums = $this->forumsCache;
|
||||
}
|
||||
|
||||
// Return the forum objects
|
||||
|
@ -203,8 +203,8 @@ class Forum
|
|||
*/
|
||||
public function threads()
|
||||
{
|
||||
// Check if _threads is populated
|
||||
if (!count($this->_threads)) {
|
||||
// Check if threadsCache is populated
|
||||
if (!count($this->threadsCache)) {
|
||||
// Get all rows with the forum id for this forum
|
||||
$threadRows = DB::table('topics')
|
||||
->where('forum_id', $this->id)
|
||||
|
@ -220,9 +220,9 @@ class Forum
|
|||
$threads[$thread->topic_id] = new Thread($thread->topic_id);
|
||||
}
|
||||
|
||||
$this->_threads = $threads;
|
||||
$this->threadsCache = $threads;
|
||||
} else {
|
||||
$threads = $this->_threads;
|
||||
$threads = $this->threadsCache;
|
||||
}
|
||||
|
||||
// Return the thread objects
|
||||
|
@ -236,8 +236,8 @@ class Forum
|
|||
*/
|
||||
public function firstPost()
|
||||
{
|
||||
// Check if _firstPost is set
|
||||
if ($this->_firstPost === null) {
|
||||
// Check if firstPostCache is set
|
||||
if ($this->firstPostCache === null) {
|
||||
// Get the row
|
||||
$firstPost = DB::table('posts')
|
||||
->where('forum_id', $this->id)
|
||||
|
@ -249,12 +249,12 @@ class Forum
|
|||
$post = new Post(empty($firstPost) ? 0 : $firstPost[0]->post_id);
|
||||
|
||||
// Assign it to a "cache" variable
|
||||
$this->_firstPost = $post;
|
||||
$this->firstPostCache = $post;
|
||||
|
||||
// Return the post object
|
||||
return $post;
|
||||
} else {
|
||||
return $this->_firstPost;
|
||||
return $this->firstPostCache;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,8 +265,8 @@ class Forum
|
|||
*/
|
||||
public function lastPost()
|
||||
{
|
||||
// Check if _lastPost is set
|
||||
if ($this->_lastPost === null) {
|
||||
// Check if lastPostCache is set
|
||||
if ($this->lastPostCache === null) {
|
||||
// Get the row
|
||||
$lastPost = DB::table('posts')
|
||||
->where('forum_id', $this->id)
|
||||
|
@ -278,12 +278,12 @@ class Forum
|
|||
$post = new Post(empty($lastPost) ? 0 : $lastPost[0]->post_id);
|
||||
|
||||
// Assign it to a "cache" variable
|
||||
$this->_lastPost = $post;
|
||||
$this->lastPostCache = $post;
|
||||
|
||||
// Return the post object
|
||||
return $post;
|
||||
} else {
|
||||
return $this->_lastPost;
|
||||
return $this->lastPostCache;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,21 +104,21 @@ class Thread
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_posts = [];
|
||||
private $postsCache = [];
|
||||
|
||||
/**
|
||||
* A cached instance of opening post.
|
||||
*
|
||||
* @var Post
|
||||
*/
|
||||
private $_firstPost = null;
|
||||
private $firstPostCache = null;
|
||||
|
||||
/**
|
||||
* A cached instance of the last reply.
|
||||
*
|
||||
* @var Post
|
||||
*/
|
||||
private $_lastPost = null;
|
||||
private $lastPostCache = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -244,8 +244,8 @@ class Thread
|
|||
*/
|
||||
public function posts()
|
||||
{
|
||||
// Check if _posts is something
|
||||
if (!count($this->_posts)) {
|
||||
// Check if postsCache is something
|
||||
if (!count($this->postsCache)) {
|
||||
// Get all rows with the thread id
|
||||
$postRows = DB::table('posts')
|
||||
->where('topic_id', $this->id)
|
||||
|
@ -259,9 +259,9 @@ class Thread
|
|||
$posts[$post->post_id] = new Post($post->post_id);
|
||||
}
|
||||
|
||||
$this->_posts = $posts;
|
||||
$this->postsCache = $posts;
|
||||
} else {
|
||||
$posts = $this->_posts;
|
||||
$posts = $this->postsCache;
|
||||
}
|
||||
|
||||
// Return the post objects
|
||||
|
@ -276,8 +276,8 @@ class Thread
|
|||
public function firstPost()
|
||||
{
|
||||
// Check if the cache var is set
|
||||
if ($this->_firstPost !== null) {
|
||||
return $this->_firstPost;
|
||||
if ($this->firstPostCache !== null) {
|
||||
return $this->firstPostCache;
|
||||
}
|
||||
|
||||
// Get the row from the database
|
||||
|
@ -291,7 +291,7 @@ class Thread
|
|||
$post = new Post($post ? $post[0]->post_id : 0);
|
||||
|
||||
// Assign it to the cache var
|
||||
$this->_firstPost = $post;
|
||||
$this->firstPostCache = $post;
|
||||
|
||||
// Return
|
||||
return $post;
|
||||
|
@ -305,8 +305,8 @@ class Thread
|
|||
public function lastPost()
|
||||
{
|
||||
// Check if the cache var is set
|
||||
if ($this->_lastPost !== null) {
|
||||
return $this->_lastPost;
|
||||
if ($this->lastPostCache !== null) {
|
||||
return $this->lastPostCache;
|
||||
}
|
||||
|
||||
// Get the row from the database
|
||||
|
@ -320,7 +320,7 @@ class Thread
|
|||
$post = new Post($post ? $post[0]->post_id : 0);
|
||||
|
||||
// Assign it to the cache var
|
||||
$this->_lastPost = $post;
|
||||
$this->lastPostCache = $post;
|
||||
|
||||
// Return
|
||||
return $post;
|
||||
|
|
226
app/Hashing.php
|
@ -1,226 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Password Hashing With PBKDF2 (https://defuse.ca/php-pbkdf2.htm).
|
||||
* Copyright (c) 2013, Taylor Hornby
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @package Sakura
|
||||
*/
|
||||
|
||||
namespace Sakura;
|
||||
|
||||
/**
|
||||
* PBKDF2 password hashing implementation.
|
||||
*
|
||||
* @package Sakura
|
||||
* @author Taylor Hornby <havoc@defuse.ca>
|
||||
* @author Julian van de Groep <me@flash.moe>
|
||||
*/
|
||||
class Hashing
|
||||
{
|
||||
/**
|
||||
* Hashing algorithm that should be used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $hashAlgorithm = 'sha256';
|
||||
|
||||
/**
|
||||
* Iterations.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $iterations = 1000;
|
||||
|
||||
/**
|
||||
* The amount of bytes the salt should be.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $saltBytes = 24;
|
||||
|
||||
/**
|
||||
* The amount of bytes the hash should be.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static $hashBytes = 24;
|
||||
|
||||
/**
|
||||
* Creates a hash.
|
||||
*
|
||||
* @param string $pass The password that should be hashed.
|
||||
*
|
||||
* @return array An array containing the algorithm, iterations, salt and hash.
|
||||
*/
|
||||
public static function createHash($pass)
|
||||
{
|
||||
$salt = base64_encode(
|
||||
\mcrypt_create_iv(
|
||||
self::$saltBytes,
|
||||
MCRYPT_DEV_URANDOM
|
||||
)
|
||||
);
|
||||
|
||||
$hash = base64_encode(
|
||||
self::pbkdf2(
|
||||
self::$hashAlgorithm,
|
||||
$pass,
|
||||
$salt,
|
||||
self::$iterations,
|
||||
self::$hashBytes,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
$passwordData = [
|
||||
self::$hashAlgorithm,
|
||||
self::$iterations,
|
||||
$salt,
|
||||
$hash,
|
||||
];
|
||||
|
||||
return $passwordData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a password.
|
||||
*
|
||||
* @param string $password The password that is being validated.
|
||||
* @param array $params The parametres in the order of algorithm, iterations, salt and hash.
|
||||
*
|
||||
* @return bool Correct?
|
||||
*/
|
||||
public static function validatePassword($password, $params)
|
||||
{
|
||||
if (count($params) < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pbkdf2 = base64_decode($params[3]);
|
||||
|
||||
$validate = self::slowEquals(
|
||||
$pbkdf2,
|
||||
self::pbkdf2(
|
||||
$params[0],
|
||||
$password,
|
||||
$params[2],
|
||||
(int) $params[1],
|
||||
strlen($pbkdf2),
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
return $validate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two strings $a and $b in length-constant time.
|
||||
*
|
||||
* @param string $a String A.
|
||||
* @param string $b String B.
|
||||
*
|
||||
* @return bool Boolean indicating difference.
|
||||
*/
|
||||
public static function slowEquals($a, $b)
|
||||
{
|
||||
$diff = strlen($a) ^ strlen($b);
|
||||
|
||||
for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
|
||||
$diff |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
|
||||
return $diff === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
|
||||
*
|
||||
* This implementation of PBKDF2 was originally created by https://defuse.ca
|
||||
* With improvements by http://www.variations-of-shadow.com
|
||||
*
|
||||
* @param mixed $algorithm The hash algorithm to use. Recommended: SHA256.
|
||||
* @param mixed $password The password.
|
||||
* @param mixed $salt A salt that is unique to the password.
|
||||
* @param mixed $count Iteration count. Higher is better, but slower. Recommended: At least 1000.
|
||||
* @param mixed $key_length The length of the derived key in bytes.
|
||||
* @param mixed $raw_output A $key_length-byte key derived from the password and salt.
|
||||
*
|
||||
* @return string The PBKDF2 derivation.
|
||||
*/
|
||||
private static function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
|
||||
{
|
||||
$algorithm = strtolower($algorithm);
|
||||
|
||||
if (!in_array($algorithm, hash_algos(), true)) {
|
||||
trigger_error(
|
||||
'PBKDF2 ERROR: Invalid hash algorithm.',
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if ($count <= 0 || $key_length <= 0) {
|
||||
trigger_error(
|
||||
'PBKDF2 ERROR: Invalid parameters.',
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if (function_exists('hash_pbkdf2')) {
|
||||
// The output length is in NIBBLES (4-bits) if $raw_output is false!
|
||||
if (!$raw_output) {
|
||||
$key_length = $key_length * 2;
|
||||
}
|
||||
|
||||
return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
|
||||
}
|
||||
|
||||
$hash_length = strlen(hash($algorithm, '', true));
|
||||
$block_count = ceil($key_length / $hash_length);
|
||||
|
||||
$output = '';
|
||||
|
||||
for ($i = 1; $i <= $block_count; $i++) {
|
||||
// $i encoded as 4 bytes, big endian.
|
||||
$last = $salt . pack('N', $i);
|
||||
|
||||
// First iteration
|
||||
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
|
||||
|
||||
// Perform the other $count - 1 interations
|
||||
for ($j = 1; $j < $count; $j++) {
|
||||
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
|
||||
}
|
||||
|
||||
$output .= $xorsum;
|
||||
|
||||
if ($raw_output) {
|
||||
return substr($output, 0, $key_length);
|
||||
}
|
||||
|
||||
return bin2hex(substr($output, 0, $key_length));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,13 +42,6 @@ class Template
|
|||
*/
|
||||
public static $name;
|
||||
|
||||
/**
|
||||
* The path to the client side resources
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $resources;
|
||||
|
||||
/**
|
||||
* The file extension used by template files
|
||||
*/
|
||||
|
@ -64,9 +57,6 @@ class Template
|
|||
// Set variables
|
||||
self::$name = $name;
|
||||
|
||||
// Set reources path
|
||||
self::$resources = '/content/data/' . self::$name;
|
||||
|
||||
// Reinitialise
|
||||
self::init();
|
||||
}
|
||||
|
@ -77,14 +67,14 @@ class Template
|
|||
public static function init()
|
||||
{
|
||||
// Initialise Twig Filesystem Loader
|
||||
$twigLoader = new Twig_Loader_Filesystem(ROOT . 'views/' . self::$name);
|
||||
$twigLoader = new Twig_Loader_Filesystem(ROOT . 'resources/views/' . self::$name);
|
||||
|
||||
// Environment variable
|
||||
$twigEnv = [];
|
||||
|
||||
// Enable caching
|
||||
if (config("performance.template_cache")) {
|
||||
$twigEnv['cache'] = ROOT . config("performance.cache_dir") . 'twig';
|
||||
$twigEnv['cache'] = ROOT . config("performance.cache_dir") . 'views';
|
||||
}
|
||||
|
||||
// And now actually initialise the templating engine
|
||||
|
@ -94,18 +84,11 @@ class Template
|
|||
self::$engine->addExtension(new Twig_Extension_StringLoader());
|
||||
|
||||
// Add route function
|
||||
self::$engine->addFunction(new Twig_SimpleFunction('route', function ($name, $args = null) {
|
||||
return Router::route($name, $args);
|
||||
}));
|
||||
self::$engine->addFunction(new Twig_SimpleFunction('route', 'route'));
|
||||
|
||||
// Add config function
|
||||
self::$engine->addFunction(new Twig_SimpleFunction('config', 'config'));
|
||||
|
||||
// Add resource function
|
||||
self::$engine->addFunction(new Twig_SimpleFunction('resource', function ($path = "") {
|
||||
return self::$resources . "/{$path}";
|
||||
}));
|
||||
|
||||
// Method of getting the currently active session id
|
||||
self::$engine->addFunction(new Twig_SimpleFunction('session_id', 'session_id'));
|
||||
|
||||
|
@ -135,10 +118,6 @@ class Template
|
|||
*/
|
||||
public static function render($file)
|
||||
{
|
||||
try {
|
||||
return self::$engine->render($file . self::FILE_EXT, self::$vars);
|
||||
} catch (\Exception $e) {
|
||||
return trigger_error($e->getMessage(), E_USER_ERROR);
|
||||
}
|
||||
return self::$engine->render($file . self::FILE_EXT, self::$vars);
|
||||
}
|
||||
}
|
||||
|
|
84
app/User.php
|
@ -219,7 +219,7 @@ class User
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_userCache = [];
|
||||
protected static $userCache = [];
|
||||
|
||||
/**
|
||||
* Cached constructor.
|
||||
|
@ -232,13 +232,13 @@ class User
|
|||
public static function construct($uid, $forceRefresh = false)
|
||||
{
|
||||
// Check if a user object isn't present in cache
|
||||
if ($forceRefresh || !array_key_exists($uid, self::$_userCache)) {
|
||||
if ($forceRefresh || !array_key_exists($uid, self::$userCache)) {
|
||||
// If not create a new object and cache it
|
||||
self::$_userCache[$uid] = new User($uid);
|
||||
self::$userCache[$uid] = new User($uid);
|
||||
}
|
||||
|
||||
// Return the cached object
|
||||
return self::$_userCache[$uid];
|
||||
return self::$userCache[$uid];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,17 +256,14 @@ class User
|
|||
// Set a few variables
|
||||
$usernameClean = clean_string($username, true);
|
||||
$emailClean = clean_string($email, true);
|
||||
$password = Hashing::createHash($password);
|
||||
$password = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Insert the user into the database and get the id
|
||||
$userId = DB::table('users')
|
||||
->insertGetId([
|
||||
'username' => $username,
|
||||
'username_clean' => $usernameClean,
|
||||
'password_hash' => $password[3],
|
||||
'password_salt' => $password[2],
|
||||
'password_algo' => $password[0],
|
||||
'password_iter' => $password[1],
|
||||
'password' => $password,
|
||||
'email' => $emailClean,
|
||||
'rank_main' => 0,
|
||||
'register_ip' => Net::pton(Net::ip()),
|
||||
|
@ -308,11 +305,7 @@ class User
|
|||
$this->id = $userRow->user_id;
|
||||
$this->username = $userRow->username;
|
||||
$this->usernameClean = $userRow->username_clean;
|
||||
$this->passwordHash = $userRow->password_hash;
|
||||
$this->passwordSalt = $userRow->password_salt;
|
||||
$this->passwordAlgo = $userRow->password_algo;
|
||||
$this->passwordIter = $userRow->password_iter;
|
||||
$this->passwordChan = $userRow->password_chan;
|
||||
$this->password = $userRow->password;
|
||||
$this->email = $userRow->email;
|
||||
$this->mainRankId = $userRow->rank_main;
|
||||
$this->colour = $userRow->user_colour;
|
||||
|
@ -1015,62 +1008,6 @@ class User
|
|||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the open warnings on this user.
|
||||
*
|
||||
* @return array The warnings.
|
||||
*/
|
||||
public function getWarnings()
|
||||
{
|
||||
// Do the database query
|
||||
$getWarnings = DB::table('warnings')
|
||||
->where('user_id', $this->id)
|
||||
->get();
|
||||
|
||||
// Storage array
|
||||
$warnings = [];
|
||||
|
||||
// Add special stuff
|
||||
foreach ($getWarnings as $warning) {
|
||||
// Check if it hasn't expired
|
||||
if ($warning->warning_expires < time()) {
|
||||
DB::table('warnings')
|
||||
->where('warning_id', $warning['warning_id'])
|
||||
->delete();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Text action
|
||||
switch ($warning->warning_action) {
|
||||
default:
|
||||
case '0':
|
||||
$warning->warning_action_text = 'Warning';
|
||||
break;
|
||||
case '1':
|
||||
$warning->warning_action_text = 'Silence';
|
||||
break;
|
||||
case '2':
|
||||
$warning->warning_action_text = 'Restriction';
|
||||
break;
|
||||
case '3':
|
||||
$warning->warning_action_text = 'Ban';
|
||||
break;
|
||||
case '4':
|
||||
$warning->warning_action_text = 'Abyss';
|
||||
break;
|
||||
}
|
||||
|
||||
// Text expiration
|
||||
$warning->warning_length = round(($warning->warning_expires - $warning->warning_issued) / 60);
|
||||
|
||||
// Add to array
|
||||
$warnings[$warning->warning_id] = $warning;
|
||||
}
|
||||
|
||||
// Return all the warnings
|
||||
return $warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the user's userpage.
|
||||
*
|
||||
|
@ -1155,16 +1092,13 @@ class User
|
|||
public function setPassword($password)
|
||||
{
|
||||
// Create hash
|
||||
$password = Hashing::createHash($password);
|
||||
$password = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Update userrow
|
||||
DB::table('users')
|
||||
->where('user_id', $this->id)
|
||||
->update([
|
||||
'password_hash' => $password[3],
|
||||
'password_salt' => $password[2],
|
||||
'password_algo' => $password[0],
|
||||
'password_iter' => $password[1],
|
||||
'password' => $password,
|
||||
'password_chan' => time(),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -12,13 +12,15 @@
|
|||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"twig/twig": "*",
|
||||
"phpmailer/phpmailer": "*",
|
||||
"paypal/rest-api-sdk-php": "*",
|
||||
"jbbcode/jbbcode": "*",
|
||||
"corneltek/cliframework": "*",
|
||||
"phroute/phroute": "^2.1",
|
||||
"illuminate/database": "5.2.*",
|
||||
"doctrine/dbal": "~2.4"
|
||||
"doctrine/dbal": "~2.4",
|
||||
"golonka/bbcodeparser": "^2.2",
|
||||
"nesbot/carbon": "^1.21",
|
||||
"swiftmailer/swiftmailer": "^5.4",
|
||||
"corneltek/cliframework": "^3.0",
|
||||
"illuminate/filesystem": "^5.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
263
composer.lock
generated
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "765ea465939a65048ac736e4fbc6c167",
|
||||
"content-hash": "1af681873ad63e53d42dfd445d67b388",
|
||||
"hash": "1f0887a5e8183bc1ca24e7c50e054ba1",
|
||||
"content-hash": "271f4f1bcfb9fdc9446336132938b762",
|
||||
"packages": [
|
||||
{
|
||||
"name": "corneltek/class-template",
|
||||
|
@ -725,6 +725,56 @@
|
|||
],
|
||||
"time": "2014-09-09 13:34:57"
|
||||
},
|
||||
{
|
||||
"name": "golonka/bbcodeparser",
|
||||
"version": "v2.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/golonka/BBCodeParser.git",
|
||||
"reference": "769c4ebe6207ffa20298b84b90eafca87ce2fb95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/golonka/BBCodeParser/zipball/769c4ebe6207ffa20298b84b90eafca87ce2fb95",
|
||||
"reference": "769c4ebe6207ffa20298b84b90eafca87ce2fb95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4",
|
||||
"squizlabs/php_codesniffer": "~2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Golonka\\BBCode\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joseph Landberg",
|
||||
"email": "joseph.landberg@gmail.com",
|
||||
"homepage": "http://github.com/golonka/"
|
||||
}
|
||||
],
|
||||
"description": "Parse your BBCode easy with this library.",
|
||||
"homepage": "http://github.com/golonka/bbcodeparser",
|
||||
"keywords": [
|
||||
"PSR-1",
|
||||
"PSR-2",
|
||||
"PSR-4",
|
||||
"bbcode",
|
||||
"laravel",
|
||||
"parser"
|
||||
],
|
||||
"time": "2016-03-03 09:56:19"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/container",
|
||||
"version": "v5.2.37",
|
||||
|
@ -870,6 +920,56 @@
|
|||
],
|
||||
"time": "2016-06-06 13:12:46"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"version": "v5.2.37",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/filesystem.git",
|
||||
"reference": "e9c3ba4fce5853f559f805a5626b18517a55b8b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/e9c3ba4fce5853f559f805a5626b18517a55b8b3",
|
||||
"reference": "e9c3ba4fce5853f559f805a5626b18517a55b8b3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "5.2.*",
|
||||
"illuminate/support": "5.2.*",
|
||||
"php": ">=5.5.9",
|
||||
"symfony/finder": "2.8.*|3.0.*"
|
||||
},
|
||||
"suggest": {
|
||||
"league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).",
|
||||
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
|
||||
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Filesystem\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylorotwell@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Filesystem package.",
|
||||
"homepage": "http://laravel.com",
|
||||
"time": "2016-05-31 15:08:27"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v5.2.37",
|
||||
|
@ -926,52 +1026,6 @@
|
|||
"homepage": "http://laravel.com",
|
||||
"time": "2016-05-30 02:40:53"
|
||||
},
|
||||
{
|
||||
"name": "jbbcode/jbbcode",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jbowens/jBBCode.git",
|
||||
"reference": "645b6a1c0afa92b7d029d3417ebd8b60a5c578b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jbowens/jBBCode/zipball/645b6a1c0afa92b7d029d3417ebd8b60a5c578b3",
|
||||
"reference": "645b6a1c0afa92b7d029d3417ebd8b60a5c578b3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"JBBCode": "."
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jackson Owens",
|
||||
"email": "jackson_owens@alumni.brown.edu",
|
||||
"homepage": "http://jbowens.org/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A lightweight but extensible BBCode parser written in PHP 5.3.",
|
||||
"homepage": "http://jbbcode.com/",
|
||||
"keywords": [
|
||||
"BB",
|
||||
"bbcode"
|
||||
],
|
||||
"time": "2014-07-06 05:48:20"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "1.21.0",
|
||||
|
@ -1116,66 +1170,6 @@
|
|||
],
|
||||
"time": "2016-07-15 20:42:18"
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v5.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "1d85f9ef3ecfc42bbc4f3c70d5e37ca9a65f629a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/1d85f9ef3ecfc42bbc4f3c70d5e37ca9a65f629a",
|
||||
"reference": "1d85f9ef3ecfc42bbc4f3c70d5e37ca9a65f629a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpdocumentor/phpdocumentor": "*",
|
||||
"phpunit/phpunit": "4.7.*"
|
||||
},
|
||||
"suggest": {
|
||||
"league/oauth2-google": "Needed for Google XOAUTH2 authentication"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"class.phpmailer.php",
|
||||
"class.phpmaileroauth.php",
|
||||
"class.phpmaileroauthgoogle.php",
|
||||
"class.smtp.php",
|
||||
"class.pop3.php",
|
||||
"extras/EasyPeasyICS.php",
|
||||
"extras/ntlm_sasl_client.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marcus Bointon",
|
||||
"email": "phpmailer@synchromedia.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Andy Prevost",
|
||||
"email": "codeworxtech@users.sourceforge.net"
|
||||
},
|
||||
{
|
||||
"name": "Brent R. Matzelle"
|
||||
}
|
||||
],
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"time": "2016-06-06 09:09:37"
|
||||
},
|
||||
{
|
||||
"name": "phroute/phroute",
|
||||
"version": "v2.1.0",
|
||||
|
@ -1304,6 +1298,59 @@
|
|||
],
|
||||
"time": "2012-12-21 11:40:51"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v5.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/4cc92842069c2bbc1f28daaaf1d2576ec4dfe153",
|
||||
"reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "~0.9.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/swift_required.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Corbyn"
|
||||
},
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
}
|
||||
],
|
||||
"description": "Swiftmailer, free feature-rich PHP mailer",
|
||||
"homepage": "http://swiftmailer.org",
|
||||
"keywords": [
|
||||
"email",
|
||||
"mail",
|
||||
"mailer"
|
||||
],
|
||||
"time": "2016-07-08 11:51:25"
|
||||
},
|
||||
{
|
||||
"name": "symfony/class-loader",
|
||||
"version": "v3.0.0",
|
||||
|
|
|
@ -1,38 +1,41 @@
|
|||
; Database configuration according to https://laravel.com/docs/5.2/database#introduction
|
||||
; Put some here in advance, uncomment the one you need.
|
||||
[database]
|
||||
;; mysql
|
||||
;driver = mysql
|
||||
;host = localhost
|
||||
;port = 3306
|
||||
;username = sakura
|
||||
;password = password
|
||||
;prefix = sakura_
|
||||
;database = sakura-development
|
||||
;charset = utf8
|
||||
;collation = utf8_unicode_ci
|
||||
; ; mysql
|
||||
; driver = mysql
|
||||
; host = localhost
|
||||
; port = 3306
|
||||
; username = sakura
|
||||
; password = password
|
||||
; prefix = sakura_
|
||||
; database = sakura-development
|
||||
; charset = utf8
|
||||
; collation = utf8_unicode_ci
|
||||
|
||||
;; sqlite
|
||||
;driver = sqlite
|
||||
;database = sakura.sq3
|
||||
;prefix = sakura_
|
||||
; ; sqlite
|
||||
; driver = sqlite
|
||||
; database = sakura.sq3
|
||||
; prefix = sakura_
|
||||
|
||||
;; postgres
|
||||
;driver = pgsql
|
||||
;host = localhost
|
||||
;port = 5432
|
||||
;username = sakura
|
||||
;password = password
|
||||
;prefix = sakura_
|
||||
;database = sakura-development
|
||||
;charset = utf8
|
||||
;schema = public
|
||||
; ; postgres
|
||||
; driver = pgsql
|
||||
; host = localhost
|
||||
; port = 5432
|
||||
; username = sakura
|
||||
; password = password
|
||||
; prefix = sakura_
|
||||
; database = sakura-development
|
||||
; charset = utf8
|
||||
; schema = public
|
||||
|
||||
; General site settings
|
||||
[general]
|
||||
; Name of the site
|
||||
name = Sakura
|
||||
|
||||
; Logo of the site
|
||||
logo =
|
||||
|
||||
; Description of the site
|
||||
description = Test site
|
||||
|
||||
|
@ -48,6 +51,9 @@ cover =
|
|||
; Close the site for maintenance
|
||||
maintenance = false
|
||||
|
||||
; URL of the sakurako chat (full path) without trailing slash
|
||||
chat = http://chat.localghost
|
||||
|
||||
; Cookie settings
|
||||
[cookie]
|
||||
prefix = sakura_
|
||||
|
@ -90,7 +96,7 @@ username =
|
|||
password =
|
||||
server =
|
||||
port = 25
|
||||
secure = true
|
||||
secure = tls
|
||||
|
||||
; File settings
|
||||
[file]
|
||||
|
@ -116,10 +122,9 @@ max_width = 2048
|
|||
|
||||
; User settings
|
||||
[user]
|
||||
; Avatars
|
||||
avatar_ban = public/content/data/%tplname%/images/banned-av.png
|
||||
avatar_none = public/content/data/%tplname%/images/no-av.png
|
||||
avatar_inactive = public/content/data/%tplname%/images/deactivated-av.png
|
||||
; Avatars (relative to public/)
|
||||
avatar_ban = images/%tplname%-ban.png
|
||||
avatar_none = images/%tplname%-none.png
|
||||
|
||||
; Username constraints
|
||||
name_min = 3
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
var
|
||||
elixir = require('laravel-elixir'),
|
||||
elixirTypscript = require('elixir-typescript'),
|
||||
nodePath = '../../node_modules/';
|
||||
|
||||
elixir.config.assetsPath = './assets';
|
||||
nodePath = '../../../node_modules/';
|
||||
|
||||
elixir(function(mix) {
|
||||
|
||||
mix
|
||||
.less('aitemu/master.less', 'public/css/aitemu.css')
|
||||
.less('yuuno/master.less', 'public/css/yuuno.css')
|
||||
|
@ -14,6 +11,6 @@ elixir(function(mix) {
|
|||
.typescript('aitemu/**/*.ts', 'public/js/aitemu.js')
|
||||
.typescript('yuuno/**/*.ts', 'public/js/yuuno.js')
|
||||
.scripts([
|
||||
nodePath + 'turbolinks/dist/turbolinks.js'
|
||||
nodePath + 'turbolinks/dist/turbolinks.js',
|
||||
], 'public/js/libraries.js');
|
||||
});
|
||||
|
|
22
mahou
|
@ -13,23 +13,15 @@ namespace Sakura;
|
|||
use Sakura\Console\Application;
|
||||
use GetOptionKit\Exception\InvalidOptionException;
|
||||
|
||||
// Define that this page won't require templating
|
||||
define('SAKURA_NO_TPL', true);
|
||||
|
||||
// Include components
|
||||
require_once 'sakura.php';
|
||||
|
||||
// Check if we're using console
|
||||
if (php_sapi_name() === 'cli') {
|
||||
// Create an instance
|
||||
$console = new Application;
|
||||
// Create an instance
|
||||
$console = new Application;
|
||||
|
||||
// Attempt to run
|
||||
try {
|
||||
$console->run($argv);
|
||||
} catch (InvalidOptionException $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
} else {
|
||||
echo 'Why would you even try to run a console app through a browser?';
|
||||
// Attempt to run
|
||||
try {
|
||||
$console->run($argv);
|
||||
} catch (InvalidOptionException $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
"dev": "gulp watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"elixir-typescript": "^2.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"laravel-elixir": "^5.0.0",
|
||||
"elixir-typescript": "^2.0.0",
|
||||
"turbolinks": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
width: 90;
|
||||
}
|
||||
|
||||
html {
|
||||
background: url('/content/images/satori-error.png') top right no-repeat #FFF;
|
||||
font-family: 'verdana', sans-serif;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 2em;
|
||||
}
|
||||
|
||||
#wrap {
|
||||
max-width: 34em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h1 img {
|
||||
margin: 0 .5em -.75em 0;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 2em 0;
|
||||
line-height: 1.33em;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1.9em 0;
|
||||
background: #BBB;
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: .75em 0 0 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0 0 .8em 3.46em;
|
||||
line-height: 1.32em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
|
||||
img+a:before {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 2.5em 0;
|
||||
}
|
||||
|
||||
li:nth-child(3) img {
|
||||
margin: -0.2em 0;
|
||||
}
|
||||
|
||||
li:nth-child(4) img {
|
||||
margin: -0.5em 0;
|
||||
}
|
||||
|
||||
table {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
table,
|
||||
tr,
|
||||
td {
|
||||
background: rgba(0, 0, 0, .2);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table img {
|
||||
border-radius: 32px;
|
||||
box-shadow: 0 4px 32px #888;
|
||||
}
|
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 223 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 398 B |
Before Width: | Height: | Size: 496 B |
Before Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 649 B |
Before Width: | Height: | Size: 652 B |
5
public/content/libraries/d3.js
vendored
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
|
||||
Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #23241f;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-tag,
|
||||
.hljs-subst {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-strong,
|
||||
.hljs-emphasis {
|
||||
color: #a8a8a2;
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-quote,
|
||||
.hljs-number,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-link {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-selector-class {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-name,
|
||||
.hljs-attr {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-attribute {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.hljs-params,
|
||||
.hljs-class .hljs-title {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #75715e;
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
/*
|
||||
* Shared client side code
|
||||
*/
|
||||
// Meta functions
|
||||
var Sakura = (function () {
|
||||
function Sakura() {
|
||||
}
|
||||
// Get or set a cookie value
|
||||
Sakura.cookie = function (name, value) {
|
||||
if (value === void 0) { value = null; }
|
||||
// If value is null only get the cookie's value
|
||||
if (value) {
|
||||
// Delete the old instance
|
||||
document.cookie = this.cookiePrefix + name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT; path=' + this.cookiePath;
|
||||
// Assign the cookie
|
||||
document.cookie = this.cookiePrefix + name + '=' + value + '; path=' + this.cookiePath;
|
||||
// Pass the value through
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
// Perform a regex on document.cookie
|
||||
var get = new RegExp('(^|; )' + encodeURIComponent(this.cookiePrefix + name) + '=([^;]*)').exec(document.cookie);
|
||||
// If anything was returned return it (professional phrasing)
|
||||
return get ? get[2] : '';
|
||||
}
|
||||
};
|
||||
// Unix timestamp
|
||||
Sakura.epoch = function () {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
};
|
||||
// Toggle a class
|
||||
Sakura.toggleClass = function (element, name) {
|
||||
// Check if the class already exists and if not add it
|
||||
if (element.className.indexOf(name) < 0) {
|
||||
element.className += ' ' + name;
|
||||
}
|
||||
else {
|
||||
element.className = element.className.replace(name, '').trim();
|
||||
}
|
||||
};
|
||||
// Remove every element with a specific class name
|
||||
Sakura.removeByClass = function (name) {
|
||||
// Get the elements
|
||||
var objs = document.getElementsByClassName(name);
|
||||
// Use a while loop to remove each element
|
||||
while (objs.length > 0) {
|
||||
objs[0].parentNode.removeChild(objs[0]);
|
||||
}
|
||||
};
|
||||
// Remove a single element with a specific id
|
||||
Sakura.removeById = function (id) {
|
||||
// Get the element
|
||||
var obj = document.getElementById(id);
|
||||
// If the element exists use the parent node to remove it
|
||||
if (typeof (obj) != "undefined" && obj !== null) {
|
||||
obj.parentNode.removeChild(obj);
|
||||
}
|
||||
};
|
||||
// Alternative for Math.log2() since it's still experimental
|
||||
Sakura.log2 = function (num) {
|
||||
return Math.log(num) / Math.log(2);
|
||||
};
|
||||
// Get the number of unique characters in a string
|
||||
Sakura.unique = function (string) {
|
||||
// Store the already found character
|
||||
var used = [];
|
||||
// The amount of characters we've already found
|
||||
var count = 0;
|
||||
// Count the amount of unique characters
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
// Check if we already counted this character
|
||||
if (used.indexOf(string[i]) == -1) {
|
||||
// Push the character into the used array
|
||||
used.push(string[i]);
|
||||
// Up the count
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// Return the count
|
||||
return count;
|
||||
};
|
||||
// Calculate password entropy
|
||||
Sakura.entropy = function (string) {
|
||||
// Decode utf-8 encoded characters
|
||||
string = utf8.decode(string);
|
||||
// Count the unique characters in the string
|
||||
var unique = this.unique(string);
|
||||
// Do the entropy calculation
|
||||
return unique * this.log2(256);
|
||||
};
|
||||
// Validate string lengths
|
||||
Sakura.stringLength = function (string, minimum, maximum) {
|
||||
// Get length of string
|
||||
var length = string.length;
|
||||
// Check if it meets the minimum/maximum
|
||||
if (length < minimum || length > maximum) {
|
||||
return false;
|
||||
}
|
||||
// If it passes both return true
|
||||
return true;
|
||||
};
|
||||
// Validate email address formats
|
||||
Sakura.validateEmail = function (email) {
|
||||
// RFC compliant e-mail address regex
|
||||
var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/;
|
||||
// Test it on the email var which'll return a boolean
|
||||
return re.test(email);
|
||||
};
|
||||
// Calculate the time that has elapsed since a certain data (doesn't take leap years in account).
|
||||
Sakura.timeElapsed = function (timestamp, append, none) {
|
||||
if (append === void 0) { append = ' ago'; }
|
||||
if (none === void 0) { none = 'Just now'; }
|
||||
// Subtract the entered timestamp from the current timestamp
|
||||
var time = this.epoch() - timestamp;
|
||||
// If the new timestamp is below 1 return a standard string
|
||||
if (time < 1) {
|
||||
return none;
|
||||
}
|
||||
// Times array
|
||||
var times = {
|
||||
31536000: ['year', 'a'],
|
||||
2592000: ['month', 'a'],
|
||||
604800: ['week', 'a'],
|
||||
86400: ['day', 'a'],
|
||||
3600: ['hour', 'an'],
|
||||
60: ['minute', 'a'],
|
||||
1: ['second', 'a']
|
||||
};
|
||||
//
|
||||
var timeKeys = Object.keys(times).reverse();
|
||||
// Iterate over the times
|
||||
for (var i in timeKeys) {
|
||||
// Do a devision to check if the given timestamp fits in the current "type"
|
||||
var calc = time / parseInt(timeKeys[i]);
|
||||
// Check if we have a match
|
||||
if (calc >= 1) {
|
||||
// Round the number
|
||||
var display = Math.floor(calc);
|
||||
// Return the formatted string
|
||||
return (display === 1 ? times[timeKeys[i]][1] : display) + " " + times[timeKeys[i]][0] + (display === 1 ? '' : 's') + append;
|
||||
}
|
||||
}
|
||||
// If everything fails just return none
|
||||
return none;
|
||||
};
|
||||
Sakura.cookiePrefix = ""; // Cookie prefix, gets prepended to cookie names
|
||||
Sakura.cookiePath = "/"; // Cookie path, can in most cases be left untouched
|
||||
return Sakura;
|
||||
})();
|
||||
// UTF-8 functions
|
||||
var utf8 = (function () {
|
||||
function utf8() {
|
||||
}
|
||||
// Encode a utf-8 string
|
||||
utf8.encode = function (string) {
|
||||
return unescape(encodeURIComponent(string));
|
||||
};
|
||||
// Decode a utf-8 string
|
||||
utf8.decode = function (string) {
|
||||
return decodeURIComponent(escape(string));
|
||||
};
|
||||
return utf8;
|
||||
})();
|
||||
// HTTP methods
|
||||
var HTTPMethods;
|
||||
(function (HTTPMethods) {
|
||||
HTTPMethods[HTTPMethods["GET"] = 0] = "GET";
|
||||
HTTPMethods[HTTPMethods["HEAD"] = 1] = "HEAD";
|
||||
HTTPMethods[HTTPMethods["POST"] = 2] = "POST";
|
||||
HTTPMethods[HTTPMethods["PUT"] = 3] = "PUT";
|
||||
HTTPMethods[HTTPMethods["DELETE"] = 4] = "DELETE";
|
||||
})(HTTPMethods || (HTTPMethods = {}));
|
||||
// AJAX functions
|
||||
var AJAX = (function () {
|
||||
// Prepares the XMLHttpRequest and stuff
|
||||
function AJAX() {
|
||||
this.send = null;
|
||||
this.request = new XMLHttpRequest();
|
||||
this.callbacks = new Object();
|
||||
this.headers = new Object();
|
||||
}
|
||||
// Start
|
||||
AJAX.prototype.start = function (method) {
|
||||
var _this = this;
|
||||
// Open the connection
|
||||
this.request.open(HTTPMethods[method], this.url, true);
|
||||
// Set headers
|
||||
this.prepareHeaders();
|
||||
// Watch the ready state
|
||||
this.request.onreadystatechange = function () {
|
||||
// Only invoke when complete
|
||||
if (_this.request.readyState === 4) {
|
||||
// Check if a callback if present
|
||||
if ((typeof _this.callbacks[_this.request.status]).toLowerCase() === 'function') {
|
||||
_this.callbacks[_this.request.status]();
|
||||
}
|
||||
else {
|
||||
if ((typeof _this.callbacks['0']).toLowerCase() === 'function') {
|
||||
// Call that
|
||||
_this.callbacks['0']();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this.request.send(this.send);
|
||||
};
|
||||
// Stop
|
||||
AJAX.prototype.stop = function () {
|
||||
this.request = null;
|
||||
};
|
||||
// Add post data
|
||||
AJAX.prototype.setSend = function (data) {
|
||||
// Storage array
|
||||
var store = new Array();
|
||||
// Iterate over the object and them in the array with an equals sign inbetween
|
||||
for (var item in data) {
|
||||
store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item]));
|
||||
}
|
||||
// Assign to send
|
||||
this.send = store.join('&');
|
||||
};
|
||||
// Set raw post
|
||||
AJAX.prototype.setRawSend = function (data) {
|
||||
this.send = data;
|
||||
};
|
||||
// Get response
|
||||
AJAX.prototype.response = function () {
|
||||
return this.request.responseText;
|
||||
};
|
||||
// Set charset
|
||||
AJAX.prototype.contentType = function (type, charset) {
|
||||
if (charset === void 0) { charset = null; }
|
||||
this.addHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8'));
|
||||
};
|
||||
// Add a header
|
||||
AJAX.prototype.addHeader = function (name, value) {
|
||||
// Attempt to remove a previous instance
|
||||
this.removeHeader(name);
|
||||
// Add the new header
|
||||
this.headers[name] = value;
|
||||
};
|
||||
// Remove a header
|
||||
AJAX.prototype.removeHeader = function (name) {
|
||||
if ((typeof this.headers[name]).toLowerCase() !== 'undefined') {
|
||||
delete this.headers[name];
|
||||
}
|
||||
};
|
||||
// Prepare request headers
|
||||
AJAX.prototype.prepareHeaders = function () {
|
||||
for (var header in this.headers) {
|
||||
this.request.setRequestHeader(header, this.headers[header]);
|
||||
}
|
||||
};
|
||||
// Adds a callback
|
||||
AJAX.prototype.addCallback = function (status, callback) {
|
||||
// Attempt to remove previous instances
|
||||
this.removeCallback(status);
|
||||
// Add the new callback
|
||||
this.callbacks[status] = callback;
|
||||
};
|
||||
// Delete a callback
|
||||
AJAX.prototype.removeCallback = function (status) {
|
||||
// Delete the callback if present
|
||||
if ((typeof this.callbacks[status]).toLowerCase() === 'function') {
|
||||
delete this.callbacks[status];
|
||||
}
|
||||
};
|
||||
// Sets the URL
|
||||
AJAX.prototype.setUrl = function (url) {
|
||||
this.url = url;
|
||||
};
|
||||
return AJAX;
|
||||
})();
|
107
public/error.css
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* i'll just throw this here for now since i haven't properly figured out something yet
|
||||
*/
|
||||
|
||||
html,
|
||||
body {
|
||||
min-height: 100%;
|
||||
width: 90;
|
||||
}
|
||||
|
||||
html {
|
||||
background: url('/images/satori-error.png') top right no-repeat #FFF;
|
||||
font-family: 'verdana', sans-serif;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 2em;
|
||||
}
|
||||
|
||||
#wrap {
|
||||
max-width: 34em;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h1 img {
|
||||
margin: 0 .5em -.75em 0;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 2em 0;
|
||||
line-height: 1.33em;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1.9em 0;
|
||||
background: #BBB;
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: .75em 0 0 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0 0 .8em 3.46em;
|
||||
line-height: 1.32em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: red;
|
||||
}
|
||||
|
||||
img+a:before {
|
||||
content: " ";
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 2.5em 0;
|
||||
}
|
||||
|
||||
li:nth-child(3) img {
|
||||
margin: -0.2em 0;
|
||||
}
|
||||
|
||||
li:nth-child(4) img {
|
||||
margin: -0.5em 0;
|
||||
}
|
||||
|
||||
table {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
table,
|
||||
tr,
|
||||
td {
|
||||
background: rgba(0, 0, 0, .2);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table img {
|
||||
border-radius: 32px;
|
||||
box-shadow: 0 4px 32px #888;
|
||||
}
|
Before Width: | Height: | Size: 87 B After Width: | Height: | Size: 87 B |
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/access-denied.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/aitemu-ban.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public/images/aitemu-none.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/images/default-banner.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
public/images/enable-javascript.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 643 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 408 B |
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 643 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 428 B After Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 506 B |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 647 B |
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 524 B |
Before Width: | Height: | Size: 663 B After Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 675 B After Width: | Height: | Size: 675 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 639 B After Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 526 B After Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 443 B After Width: | Height: | Size: 443 B |
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 514 B |
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 600 B |
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 528 B |
Before Width: | Height: | Size: 614 B After Width: | Height: | Size: 614 B |
Before Width: | Height: | Size: 521 B After Width: | Height: | Size: 521 B |
Before Width: | Height: | Size: 367 B After Width: | Height: | Size: 367 B |