[LucidCMS] PDO - OOP - Ajax - Secure - Modern - Fast - Advanced

Status
Not open for further replies.

MayoMayn

BestDev
Oct 18, 2016
1,423
683
SRQwjJB.png

(Thanks to @DeezeXX for creating the logo)
Old demo:
All working pages etc except client.

LucidCMS is the fastest, smoothest, most secure and advanced Habbo CMS that this community has ever seen.
This is a project that I've spent more than 450 hours and 4 months on.
Will it be free? I'm not sure, but if not, there's a minimal donation to receive a download.
I'd like to quote down every single feature that this CMS has, but literally I don't bother to do so.

Components used:
  • composer
  • php-di
  • acclimate
  • container-interop
  • jquery
  • ajax
  • timeago
  • emojione
  • htmlpurifier
  • tinymce
  • bbcode
  • bootstrap

Features
  • Synced mysql and PHP for timezones.
  • Allowing/disallowing the use of VPN/Proxy and country access.
  • Minimal honeypot system using robots.txt
  • Advanced Forum System.
  • Profile Pages
  • Values Page
  • Login Streaks with badges
  • Referral System with badges
  • Preventing users from having 2 online on the same machine.
  • New currency called "Activity Points" which are given upon daily reward depending on your login streak.
  • Advanced Login Attempts table.
  • Advanced Security
  • Full PDO and Ajax
  • Advanced housekeeping
  • Auto Ban System for users that have refunded a purchase without it being allowed
  • Advanced automatic shopping system
  • Several social media connected to an account for faster login using a single button.
  • Case opening using Activity Points and shop currency.
  • Facebook emojis.
  • If a user navigate to the hotel using a referral link, the code will stay as a cookie, so the user gets their referral.
  • And more will follow..

Code Snippets
index.php
PHP:
<?php
/**
 * Specify global settings
 */
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('session.gc_maxlifetime', 3600);
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);
session_save_path(__DIR__ . '/app/storage/sessions');

/**
 * Autoload composer
 */
require_once __DIR__ . '/app/autoload.php';

/**
 * Use namespaces for framworks
 */
use Acclimate\Container\ContainerAcclimator;
use Acclimate\Container\CompositeContainer;

/**
 * Set new container and acclimator
 */
$acclimator = new ContainerAcclimator();
$container     = new CompositeContainer();

/**
 * New DI
 */
$builder = new \DI\ContainerBuilder();

/**
 * Wrap DI with Container
 */
$builder->wrapContainer($container);

/**
 * Build DIC
 */
$injector = $builder->build();

/**
 * Acclimate Container to DIC
 */
$container->addContainer($acclimator->acclimate($injector));

/**
 * Build Controller
 */
$controller = $container->get('Controller');

/**
 * Wrap Controller with Container and Injector
 */
$controller->wrapContainers($injector, $container);

/**
 * Prepare classes to be globally injected.
 * @param string
 * @var string
 */
$controller->prepareClasses([
    'Database'             => 'class.database.php',
    'PageController'     => 'class.page.php',
    'ErrorController'    => 'class.error.php',
    'AuthController'    => 'class.auth.php',
    'ContentController'    => 'class.content.php',
    'RegisterController' => 'class.register.php',
    'HomeController'    => 'class.home.php',
    'UserController'    => 'class.user.php',
    'Config'             => 'class.config.php',
    'Storage'            => 'class.storage.php',
    'Core'                => 'class.core.php',
    'FindRetros'        => 'class.findretros.php',
    'SessionController'  => 'class.session.php'
]);

/**
 * Inject Classes
 */
$controller->injectClasses();

/**
 * Call Controllers
 */
$content    = $container->get('ContentController');
$error      = $container->get('ErrorController');
$page         = $container->get('PageController');
$auth         = $container->get('AuthController');
$user         = $container->get('UserController');
$home         = $container->get('HomeController');
$session    = $container->get('SessionController');
$config     = $container->get('Config');
$uid         = $session->get('user.id');

/**
 * Start session
 */
//    session_set_save_handler($session, true);
/*if(!$session->isValid(5)) {
    $session->forget();
}*/

/**
 * Handle current page
 */
$page->handler();

/**
 * param => variable
 *
 * @return void
 */
$page->setParams([
    'hotelurl'           => $config->hotel('url'),
    'hotelname'          => $config->hotel('name'),
    'username'        => $session->get('user.username'),
    'title'            => $page->current['title'],
    'imaging'        => $config->hotel('figure_imaging'),
    'homebox'       => $home->showbox(),
    'figure'        => $user->getInfo($uid, 'look'),
    'fbappid'        => $config->facebook('app_id'),
    'diamonds'      => $user->getInfo($uid, 'diamonds'),
    'points'        => $user->getInfo($uid, 'vip_points'),
    'coins'         => $user->getInfo($uid, 'credits'),
    'duckets'       => $user->getInfo($uid, 'activity_points'),
    'date'            => date('Y-m-d'),
    'hotellogo'        => $config->hotel('logo'),
    'errorcontent'  => $error->error(),
    'referral'        => (isset($_COOKIE['referral']) ? $_COOKIE['referral'] : ""),
    'page'            => $page->url,
    'isreferralset' => (!isset($_COOKIE['referral']) ? '<a style="cursor:pointer;" id="referralLink" class="medium">Got referred by a user? Click here &raquo;</a>' : ""),
    'csrftoken'     => $auth->csrf_token(),
    'loginattempts' => $auth->viewLoginAttempts()
]);

/**
 * Call functions on specific pages
 * page => [ class => function ]
 *
 * @return void
 */
$page->setFunctions([
    'index' => [
        'AuthController' => 'login'
    ],
    'logout' => [
        'AuthController' => 'logout'
    ],
    'register' => [
        'RegisterController' => 'signup'
    ],
    'home' => [
        'HomeController' => 'openbox'
    ]
]);

/**
 * Output content on page
 */
$content->output();

class.controller.php
PKlykEh.png

class.auth.php
RfcDIyK.png

MoYvAY1.png

GM1yJL1.png

gE2M4eh.png

class.storage.php
jAXHbI5.png

class.database.php
or2UXYt.png

xKufcEa.png

class.page.php
NUlP9tt.png

5FEhz4g.png

igB6IJk.png

class.content.php
WgWYTgE.png

f9229486-9216-4f28-8143-26c8550654d4

class.session.php
eWlKySY.png

msmlUVF.png

oShf2aL.png

JhYr5v8.png

class.config.php
0CWqJMZ.png

Screenies
User Interface
VPN / Proxy Restriction
MRl2UKW.png

Index
szqDU35.png

Register
8XwFIM2.png

4jUD4kQ.png

Home
d4BNOO4.png

Profile
CaFsfg4.png

Values
5e7ybAs.png

Forum
LaHAggU.png

Ia9QJaP.png

R0ZZIE6.png

Shop


di46atB.png

R9o6Bbb.png

Client
CGEfYlO.png

6gAVhEH.png

Housekeeping
Settings Management

Badge Management
Compatible with the latest Plus Emulator

What needs to be fixed?
Sessions
Currently there are problems with the sessions, when using NGINX with HHVM on Linux.
Instead of updating the same session, it instead creates a whole new session every time its updated.

Forum
Currently the tinymce doesn't interact the proper way it should with bbcode plugin.
Quoting another comment, fucks up because of tinymce, so I might have to swich to CKEditor instead.

What needs to be added?
Back-end
  • Back-end is mostly finished, now I've just got to recode all the pages, so it works for the new barebone system.
  • Housekeeping needs to be finish coded.
  • Easy language control
  • 2 MUS commands for Plus Emulator, so the user doesn't have to reload their client to receive badges or items.
  • Easy installation, no need for editing SWF external texts or edit of any files, all can be done in the Housekeeping Settings Management.

All constructive criticism is appreciated.
If you any ideas for a feature that could be added, please comment.

Donation is appreciated!
This will assure a release and future updates of this CMS, as I've spent many hours and a lot of server costs to code this.

Credits to @Synt4x for the template, @Damiens for the forum CSS, and @Sledmore for the values front-end.
 
Last edited:

Sledmore

Chaturbate Livestreamer
Staff member
FindRetros Moderator
Jul 24, 2010
5,194
3,901
Unsure who approved, still need it unapproved, or all good?

Nonetheless, looks good. Good job.
 

Core

Member
Nov 10, 2016
356
138
Not a fan of the front-end but love the back-end - so clean. Finally a PHP developer in this community who structures MySQL queries properly xD!
 

Etrion

?
Dec 22, 2016
108
32
I srsly think the back-end is fucking amazing, really well done @Sentinel
Only thing that is a turn off atm is the front-end, like @Core said, but just gotta love it, you sir will make it even better, GJ!<3_<3
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
I srsly think the back-end is fucking amazing, really well done @Sentinel
Only thing that is a turn off atm is the front-end, like @Core said, but just gotta love it, you sir will make it even better, GJ!<3_<3
Thanks for the input!
I just liked a new interface, as I got pretty tired of the old one, so decided to use the one by @Synt4x, with some changes.
Once this is finished and released somehow, I might code a template system, and a new template aswell, if people still would enjoy the old vanilla.
 
Last edited:

MayoMayn

BestDev
Oct 18, 2016
1,423
683
#UPDATE
Had to recode the whole session class handler due to some annoying errors, but I finally got it figured out.
This is how the class looks right now:
PHP:
use Acclimate\Container\CompositeContainer as Container;
class SessionController {

    private $key, $iv, $ip;

    public function __construct($cookie = []) {
        $this->key = md5($_SERVER['HTTP_HOST']);

        $this->start();
    }

    public function setContainer(Container $container) {
        $this->ip = $container->get('Core')->getIP();
    }

    public function start() {
        if(session_status() == PHP_SESSION_NONE) {
            if(session_start()) {
                return (mt_rand(0, 4) === 0) ? $this->refresh() : true; // 1/5
            }
        }

        return false;
    }

    public function forget() {
        if(session_status() == PHP_SESSION_NONE) {
            return false;
        }

        $_SESSION = [];

        return session_destroy();
    }

    public function refresh() {
        return session_regenerate_id(true);
    }

    public function isExpired($ttl = 30) {
        $activity = (isset($_SESSION['last_activity'])
            ? $_SESSION['last_activity']
            : false);

        if($activity !== false && time() - $activity > $ttl * 60) {
            return true;
        }

        $_SESSION['last_activity'] = time();
        return false;
    }

    public function isFingerprint() {
        $hash = md5($_SERVER['HTTP_USER_AGENT'] . $this->ip);
        if(isset($_SESSION['fingerprint'])) {
            return $_SESSION['fingerprint'] === $hash;
        }

        $_SESSION['fingerprint'] = $hash;
        return true;
    }

    public function isValid($ttl = 30) {
        return !$this->isExpired($ttl) && $this->isFingerprint();
    }

    public function get($name) {
        $parsed = explode('.', $name);
        $result = $_SESSION;

        while($parsed) {
            $next = array_shift($parsed);
            if(isset($result[$next])) {
                $result = $result[$next];
            } else {
                return null;
            }
        }

        //$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $this->key, $result, MCRYPT_MODE_CBC, $this->iv);
        return $this->openssl_decrypt($result);
    }

    public function put($name, $value) {
        $parsed = explode('.', $name);
        $session =& $_SESSION;

        while(count($parsed) > 1) {
            $next = array_shift($parsed);
            if(!isset($session[$next]) || !is_array($session[$next])) {
                $session[$next] = [];
            }
            $session =& $session[$next];
        }

        $session[array_shift($parsed)] = $this->openssl_encrypt($value);
    }

    public function exists($name) {
        return is_null($this->get($name)) ? false : true;
    }

    public function openssl_encrypt($data) {
        // Generate an initialization vector
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
        // Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
        $encrypted = openssl_encrypt($data, 'aes-256-cbc', $this->key, 0, $iv);
        // The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
        return base64_encode($encrypted . '::' . $iv);
    }

    public function openssl_decrypt($data) {
        // To decrypt, split the encrypted data from our IV - our unique separator used was "::"
        list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
        return openssl_decrypt($encrypted_data, 'aes-256-cbc', $this->key, 0, $iv);
    }

}
Then I found a CSRF class, and recoded it to work aswell.
PHP:
use Acclimate\Container\CompositeContainer as Container;
class NoCSRF  {

    private static $origin;

    public function setContainer(Container $container) {
        $ip = $container->get('Core')->getIP();
        self::$origin = sha1($ip . $_SERVER['HTTP_USER_AGENT']);
    }
       
    // $timespan token expiration time = 10 minutes
    public function check($csrf, $timespan = 10*60) { 
        // Check if CSRF token exists
        if(!isset($_SESSION['csrf'])) {
            die('Missing CSRF session token.');
        }
        // Get valid token from session
        $hash = $_SESSION['csrf'];
        // Origin checks
        if(self::$origin != substr(base64_decode($hash), 10, 40)) {
            die('Form origin does not match token origin.');
        }
        // Check if session token matches form token
        if($csrf !== $hash) {
            die('Invalid CSRF token.');
        }
        // Check for token expiration
        if($timespan != null && is_int($timespan) && intval(substr(base64_decode($hash), 0, 10)) + $timespan < time()) {
            // Unset old CSRF token
            unset($_SESSION['csrf']);
        }
        return true;
    }
   
    /**
     * CSRF token generation method. After generating the token, put it inside a hidden form field named $key.
     *
     * @param String $key The session key where the token will be stored. (Will also be the name of the hidden field name)
     * @return String The generated, base64 encoded token.
     */
    public function generate() {
        // check if token has been validated
        if(!isset($_SESSION['csrf'])) {
            // token generation (basically base64_encode any random complex string, time() is used for token expiration) 
            $token = base64_encode(time() . self::$origin . $this->randomString(32));
            // store the token in session
            $_SESSION['csrf'] = $token;
        }
        // return the token
        return $_SESSION['csrf'];
    }
    /**
     * Generates a random string of given $length.
     *
     * @param Integer $length The string length.
     * @return String The randomly generated string.
     */
    protected function randomString($length) {
        $seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijqlmnopqrtsuvwxyz0123456789';
        $max = strlen($seed) - 1;
        $string = '';
        for($i = 0; $i < $length; ++$i) {
            $string .= $seed{intval(mt_rand(0.0, $max))};
        }
        return $string;
    }

}
 
Last edited:

MayoMayn

BestDev
Oct 18, 2016
1,423
683
#UPDATES
Register has been recoded and working, still an error to fix, nothing important. Random whitespaces appear in the username validation, I worked around it, but better to find the source issue, which is really fucking weird, didn't change shit in the javascript after the old barebone, but seems to be working fine after using $.trim in the $.ajax success: function (r) response.

Going to start recoding the Facebook Auth tomorrow.

Any ideas for what I could add as features? Had a thought about coding some basic javascript games with prizes, or some lottery shit.
 
Last edited:

Zoxq

Member
Dec 13, 2013
62
14
Can't wait for this to get released! When do you think you will release it? / What is the minimum donation amount for a release?
 
Status
Not open for further replies.

Users who are viewing this thread

Top