Pursuing work on web interface. BaikalAdmin is now a Framework, and is based on Flake

This commit is contained in:
Jérôme Schneider 2012-03-24 00:58:09 +01:00
parent a2346185fe
commit 75a971dac2
53 changed files with 2278 additions and 299 deletions

View file

@ -1,33 +0,0 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
* All rights reserved
*
* http://baikal.codr.fr
*
* This script is part of the Baïkal Server project. The Baïkal
* Server project is free software; you can redistribute it
* and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
define("BAIKAL_FRAMEWORK_ROOT", dirname(__FILE__) . "/");
define("BAIKAL_FRAMEWORK_LIBDIR", BAIKAL_FRAMEWORK_ROOT . "lib/");
define("BAIKAL_FRAMEWORK_RESDIR", BAIKAL_FRAMEWORK_ROOT . "res/");
require_once(BAIKAL_FRAMEWORK_LIBDIR . "BaikalTemplate.php");
require_once(BAIKAL_FRAMEWORK_LIBDIR . "BaikalAdmin.php");
require_once(BAIKAL_FRAMEWORK_LIBDIR . "BaikalTools.php");

View file

@ -1,120 +0,0 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
* All rights reserved
*
* http://baikal.codr.fr
*
* This script is part of the Baïkal Server project. The Baïkal
* Server project is free software; you can redistribute it
* and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
require_once(BAIKAL_PATH_FRAMEWORKS . "Tabulator/Tabulator.html.php");
class BaikalAdmin {
static function assertEnabled() {
if(!defined("BAIKAL_ADMIN_ENABLED") || BAIKAL_ADMIN_ENABLED !== TRUE) {
die("<h1>Ba&iuml;kal Admin is disabled.</h1>To enable it, set BAIKAL_ADMIN_ENABLED to TRUE in <b>Specific/config.php</b>");
}
$bLocked = TRUE;
$sEnableFile = BAIKAL_PATH_SPECIFIC . "ENABLE_ADMIN";
if(file_exists($sEnableFile)) {
clearstatcache();
$iTime = intval(filemtime($sEnableFile));
if((time() - $iTime) < 3600) {
// file has been created more than an hour ago
// delete and declare locked
$bLocked = FALSE;
} else {
if(!@unlink($sEnableFile)) {
die("<h1>Ba&iuml;kal Admin is locked.</h1>To unlock it, delete and re-create an empty file named ENABLE_ADMIN in <b>Specific/config.php</b>");
}
}
}
if($bLocked) {
die("<h1>Ba&iuml;kal Admin is locked.</h1>To unlock it, create an empty file named ENABLE_ADMIN in <b>Specific/</b>");
} else {
// update filemtime
@touch($sEnableFile);
}
}
static function assertAuthentified() {
if(!self::isAuthentified()) {
header(utf8_decode('WWW-Authenticate: Basic realm="Baïkal admin"'));
header('HTTP/1.0 401 Unauthorized');
die("Please authenticate.");
}
return TRUE;
}
static function isAuthentified() {
if(array_key_exists("PHP_AUTH_USER", $_SERVER)) {
$sUser = $_SERVER["PHP_AUTH_USER"];
} else {
$sUser = FALSE;
}
if(array_key_exists("PHP_AUTH_PW", $_SERVER)) {
$sPass = $_SERVER["PHP_AUTH_PW"];
} else {
$sPass = FALSE;
}
$sPassHash = self::hashAdminPassword($sPass);
if($sUser === "admin" && $sPassHash === BAIKAL_ADMIN_PASSWORDHASH) {
return TRUE;
}
return FALSE;
}
static function hashAdminPassword($sPassword) {
return md5('admin:' . BAIKAL_AUTH_REALM . ':' . $sPassword);
}
static function wrapWithInterface($sHtml) {
$oTemplate = new BaikalTemplate(BAIKAL_FRAMEWORK_RESDIR . "template.html");
return $oTemplate->parse(array(
"pagetitle" => "Users",
"pagecontent" => $sHtml
));
}
static function displayUsers() {
$aUsers = BaikalTools::getUsers();
$oTabulator = new TabulatorHtml("table table-bordered");
$oTabulator->addColumn(self::makeColumn("id", "Id", "numeric"));
$oTabulator->addColumn(self::makeColumn("username", "Username"));
return $oTabulator->render($aUsers);
}
static function &makeColumn($sName, $sHeader = "", $sType = "text") {
$oColumn = new TabulatorColumnHtml($sName, $sHeader, $sType);
return $oColumn;
}
}

View file

@ -1,59 +0,0 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
* All rights reserved
*
* http://baikal.codr.fr
*
* This script is part of the Baïkal Server project. The Baïkal
* Server project is free software; you can redistribute it
* and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
class BaikalTemplate {
private $sAbsPath = "";
private $sHtml = "";
public function __construct($sAbsPath) {
$this->sAbsPath = $sAbsPath;
$this->sHtml = self::getTemplateFile(
$this->sAbsPath
);
}
public function parse($aMarkers = array()) {
return self::parseTemplateCodePhp(
$this->sHtml,
$aMarkers
);
}
protected static function getTemplateFile($sAbsPath) {
return file_get_contents($sAbsPath);
}
protected static function parseTemplateCodePhp($sCode, $aMarkers) {
extract($aMarkers);
ob_start();
echo eval('?>' . $sCode . '<?');
$sHtml = ob_get_contents();
ob_end_clean();
return $sHtml;
}
}

View file

@ -1,71 +0,0 @@
<?php
/***************************************************************
* Copyright notice
*
* (c) 2012 Jérôme Schneider <mail@jeromeschneider.fr>
* All rights reserved
*
* http://baikal.codr.fr
*
* This script is part of the Baïkal Server project. The Baïkal
* Server project is free software; you can redistribute it
* and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
class BaikalTools {
public static function &db() {
return $GLOBALS["pdo"];
}
public static function getUsers() {
$aUsers = array();
# Fetching user
$stmt = BaikalTools::db()->prepare("SELECT * FROM users");
$stmt->execute();
while(($user = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_FIRST)) !== FALSE) {
$aUsers[] = $user;
}
reset($aUsers);
return $aUsers;
}
static function bashPrompt($prompt) {
print $prompt;
@flush();
@ob_flush();
$confirmation = @trim(fgets(STDIN));
return $confirmation;
}
static function bashPromptSilent($prompt = "Enter Password:") {
$command = "/usr/bin/env bash -c 'echo OK'";
if(rtrim(shell_exec($command)) !== 'OK') {
trigger_error("Can't invoke bash");
return;
}
$command = "/usr/bin/env bash -c 'read -s -p \""
. addslashes($prompt)
. "\" mypassword && echo \$mypassword'";
$password = rtrim(shell_exec($command));
echo "\n";
return $password;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace BaikalAdmin\Controler\User;
class Listing extends \Flake\Core\Controler {
function execute() {
}
function render() {
$aRes = array();
$oUsers = \BaikalAdmin\Model\User::getBaseRequester()->execute();
$oView = new \BaikalAdmin\View\User\Listing();
$oView->setData("users", $oUsers);
return $oView->render();
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace BaikalAdmin\Core;
class Auth {
static function assertEnabled() {
if(!defined("BAIKAL_ADMIN_ENABLED") || BAIKAL_ADMIN_ENABLED !== TRUE) {
die("<h1>Ba&iuml;kal Admin is disabled.</h1>To enable it, set BAIKAL_ADMIN_ENABLED to TRUE in <b>Specific/config.php</b>");
}
$bLocked = TRUE;
$sEnableFile = BAIKAL_PATH_SPECIFIC . "ENABLE_ADMIN";
if(file_exists($sEnableFile)) {
clearstatcache();
$iTime = intval(filemtime($sEnableFile));
if((time() - $iTime) < 3600) {
// file has been created more than an hour ago
// delete and declare locked
$bLocked = FALSE;
} else {
if(!@unlink($sEnableFile)) {
die("<h1>Ba&iuml;kal Admin is locked.</h1>To unlock it, delete and re-create an empty file named ENABLE_ADMIN in <b>Specific/config.php</b>");
}
}
}
if($bLocked) {
die("<h1>Ba&iuml;kal Admin is locked.</h1>To unlock it, create an empty file named ENABLE_ADMIN in <b>Specific/</b>");
} else {
// update filemtime
@touch($sEnableFile);
}
}
static function assertAuthentified() {
if(!self::isAuthentified()) {
header(utf8_decode('WWW-Authenticate: Basic realm="Baïkal admin"'));
header('HTTP/1.0 401 Unauthorized');
die("Please authenticate.");
}
return TRUE;
}
static function isAuthentified() {
if(array_key_exists("PHP_AUTH_USER", $_SERVER)) {
$sUser = $_SERVER["PHP_AUTH_USER"];
} else {
$sUser = FALSE;
}
if(array_key_exists("PHP_AUTH_PW", $_SERVER)) {
$sPass = $_SERVER["PHP_AUTH_PW"];
} else {
$sPass = FALSE;
}
$sPassHash = self::hashAdminPassword($sPass);
if($sUser === "admin" && $sPassHash === BAIKAL_ADMIN_PASSWORDHASH) {
return TRUE;
}
return FALSE;
}
static function hashAdminPassword($sPassword) {
return md5('admin:' . BAIKAL_AUTH_REALM . ':' . $sPassword);
}
}

View file

@ -0,0 +1,4 @@
<?php
require_once(dirname(__FILE__) . '/ClassLoader.php');
\BaikalAdmin\Core\ClassLoader::register();

View file

@ -0,0 +1,42 @@
<?php
declare(ENCODING = 'utf-8');
namespace BaikalAdmin\Core;
class ClassLoader {
public static function register() {
return spl_autoload_register(array(get_called_class(), 'loadClass'));
}
public static function loadClass($sFullClassName) {
$aParts = explode("\\", $sFullClassName);
if(count($aParts) === 1) {
return;
}
if($aParts[0] !== "BaikalAdmin") {
return;
}
// ejecting the Radical
$sRadical = array_shift($aParts);
if($sRadical === "BaikalAdmin") {
$sRootPath = BAIKALADMIN_PATH_ROOT;
}
$sClassName = array_pop($aParts);
$sBasePath = $sRootPath . implode("/", $aParts) . "/";
$sClassPath = $sBasePath . $sClassName . ".php";
if(file_exists($sClassPath) && is_readable($sClassPath)) {
require_once($sClassPath);
} else {
echo '<h1>PHP Autoload Error. Cannot find ' . $sFullClassName . '</h1>';
echo "<pre>" . print_r(debug_backtrace(), TRUE) . "</pre>";
die();
}
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace BaikalAdmin\Model;
class User extends \Flake\Core\Model\Db {
const DATATABLE = 'users';
const PRIMARYKEY = 'username';
}

View file

@ -2,24 +2,28 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Baïkal Admin</title> <title>{pagetitle}</title>
<base href="{baseurl}"></base>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
<!-- Le styles --> <!-- Le styles -->
<link href="/res/TwitterBootstrap/css/bootstrap.css" rel="stylesheet"> <link href="/res/core/TwitterBootstrap/css/bootstrap.css" rel="stylesheet">
<link href="/res/core/BaikalAdmin/Templates/Page/style.css" rel="stylesheet">
<style> <style>
body { body {
padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
} }
</style> </style>
<link href="/res/TwitterBootstrap/css/bootstrap-responsive.css" rel="stylesheet"> <link href="/res/core/TwitterBootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<link href="/res/core/TwitterBootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements --> <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--> <![endif]-->
{head}
</head> </head>
<body> <body>
@ -45,17 +49,14 @@
</div> </div>
<div class="container"> <div class="container">
{Payload}
<h1><?= $pagetitle ?></h1>
<p>Use this document as a way to quick start any new project.<br> All you get is this message and a barebones HTML document.</p>
<?= $pagecontent ?>
</div> <!-- /container --> </div> <!-- /container -->
<!-- Le javascript <!-- Le javascript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="/res/TwitterBootstrap/js/jquery-1.7.1.min.js"></script> <script src="/res/core/TwitterBootstrap/js/jquery-1.7.1.min.js"></script>
<script src="/res/TwitterBootstrap/js/bootstrap.min.js"></script> <script src="/res/core/TwitterBootstrap/js/bootstrap.min.js"></script>
{javascript}
</body> </body>
</html> </html>

View file

@ -0,0 +1,28 @@
/* Jumbotrons
-------------------------------------------------- */
.jumbotron {
position: relative;
}
.jumbotron h1 {
margin-bottom: 9px;
font-size: 81px;
font-weight: bold;
letter-spacing: -1px;
line-height: 1;
}
.jumbotron p {
margin-bottom: 18px;
font-weight: 300;
}
.jumbotron .btn-large {
font-size: 20px;
font-weight: normal;
padding: 14px 24px;
margin-right: 10px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.jumbotron .btn-large small {
font-size: 14px;
}

View file

@ -0,0 +1,17 @@
<header class="jumbotron subhead" id="overview">
<h1>Users</h1>
<p class="lead">List, browse, create, update and delete Baïkal user accounts.</p>
</header>
<table class="table table-bordered table-striped">
<tr>
<th>id</th>
<th>username</th>
</tr>
<? foreach($users as $user) { ?>
<tr>
<td><?= $user->get("id"); ?></td>
<td><?= $user->get("username"); ?></td>
</tr>
<? } ?>
</table>

View file

@ -0,0 +1,10 @@
<?php
namespace BaikalAdmin\Route\User;
class Listing {
public function execute(\Flake\Core\Render\Container &$oRenderContainer) {
$oRenderContainer->zone("Payload")->addBlock(new \BaikalAdmin\Controler\User\Listing());
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace BaikalAdmin\View\User;
class Listing extends \Flake\Core\View {
public function render() {
$oTemplate = new \Flake\Core\Template(BAIKALADMIN_PATH_TEMPLATES . "User/Listing.html", TRUE);
return $oTemplate->parse(array(
"users" => $this->get("users"),
));
}
}

View file

@ -0,0 +1,19 @@
<?php
define("BAIKALADMIN_PATH_ROOT", BAIKAL_PATH_FRAMEWORKS . "BaikalAdmin/");
define("BAIKALADMIN_PATH_TEMPLATES", BAIKALADMIN_PATH_ROOT . "Resources/Templates/");
define("FLAKE_DB_FILEPATH", BAIKAL_SQLITE_FILE);
define("FLAKE_TIMEZONE", BAIKAL_TIMEZONE);
define("FLAKE_PATH_FRAMEWORKS", BAIKAL_PATH_FRAMEWORKS);
define("FLAKE_PATH_ROOT", BAIKAL_PATH_FRAMEWORKS . "Flake/");
define("FLAKE_PATH_WWWROOT", BAIKAL_PATH_WWWROOT);
define("FLAKE_SAFEHASH_SALT", "une-clef-super-secrete");
define("FLAKE_LOCALE", "fr_FR.UTF-8");
# TODO: CHANGE THIS
define("FLAKE_BASEURL", "http://baikal.jeromeschneider.fr/");
$GLOBALS["ROUTES"] = array(
"default" => "\BaikalAdmin\Route\User\Listing",
);

View file

@ -26,13 +26,41 @@
ini_set("display_errors", 1); ini_set("display_errors", 1);
error_reporting(E_ALL); error_reporting(E_ALL);
define("BAIKAL_CONTEXT", TRUE); define("BAIKAL_CONTEXT", TRUE);
define("BAIKAL_CONTEXT_ADMIN", TRUE); define("BAIKAL_CONTEXT_ADMIN", TRUE);
define("PATH_ENTRYDIR", dirname(__FILE__) . "/");
require_once(dirname(dirname(dirname(__FILE__))) . "/Bootstrap.php"); # ../../, symlink-safe # Bootstrap Baikal Core
require_once(BAIKAL_PATH_FRAMEWORKS . "Baikal/Includes.php"); require_once(dirname(dirname(dirname(dirname(PATH_ENTRYDIR)))) . "/Core/Bootstrap.php"); # ../../../../, symlink-safe
# Include BaikalAdmin config files
require_once(PATH_ENTRYDIR . "config.php");
# Bootstrap BaikalAdmin
require_once(PATH_ENTRYDIR . "Core/Bootstrap.php");
# Bootstrap Flake
require_once(FLAKE_PATH_ROOT . "Core/Bootstrap.php");
# Evaluate assertions
\BaikalAdmin\Core\Auth::assertEnabled();
\BaikalAdmin\Core\Auth::assertAuthentified();
# Create and setup a page object
$oPage = new \Flake\Controler\Page(BAIKALADMIN_PATH_TEMPLATES . "Page/index.html");
$oPage->injectHTTPHeaders();
$oPage->setTitle(FLAKE_BASEURL);
$oPage->setBaseUrl(FLAKE_BASEURL);
# Route the request
\Flake\Util\Router::route($oPage);
# Render the page
echo $oPage->render();
/*require_once(BAIKAL_PATH_FRAMEWORKS . "Baikal/Includes.php");
BaikalAdmin::assertEnabled(); BaikalAdmin::assertEnabled();
BaikalAdmin::assertAuthentified(); BaikalAdmin::assertAuthentified();
BaikalAdmin::handleRequest();*/
echo BaikalAdmin::wrapWithInterface(BaikalAdmin::displayUsers());

View file

@ -0,0 +1,130 @@
<?php
namespace Flake\Controler;
class Cli extends \Flake\Core\Render\Container {
function render() {
$this->sys_init();
$this->init();
$this->echoFlush($this->notice("process started @" . strftime("%d/%m/%Y %H:%M:%S")));
$this->execute();
$this->echoFlush($this->notice("process ended @" . strftime("%d/%m/%Y %H:%M:%S")) . "\n\n");
}
function execute() {
reset($this->aSequence);
while(list($sKey,) = each($this->aSequence)) {
$this->aSequence[$sKey]["block"]->execute();
}
}
/**************************************************************************/
var $sLog = "";
function sys_init() {
$this->rawLine("Command line: " . (implode(" ", $_SERVER["argv"])));
$this->initArgs();
}
function init() {
}
function initArgs() {
$sShortOpts = "";
$sShortOpts .= "h"; // help; pas de valeur
$sShortOpts .= "w:"; // author; valeur obligatoire
$aLongOpts = array(
"help", // help; pas de valeur
"helloworld", // author; pas de valeur
);
$this->aArgs = getopt($sShortOpts, $aLongOpts);
}
function getScriptPath() {
return realpath($_SERVER['argv'][0]);
}
function getSyntax() {
return $this->getScriptPath();
}
function syntaxError() {
$sStr = $this->rawLine("Syntax error.\nUsage: " . $this->getSyntax());
die("\n\n" . $sStr . "\n\n");
}
function log($sStr) {
$this->sLog .= $sStr;
}
function header($sMsg) {
$sStr = "\n" . str_repeat("#", 80);
$sStr .= "\n" . "#" . str_repeat(" ", 78) . "#";
$sStr .= "\n" . "#" . str_pad(strtoupper($sMsg), 78, " ", STR_PAD_BOTH) . "#";
$sStr .= "\n" . "#" . str_repeat(" ", 78) . "#";
$sStr .= "\n" . str_repeat("#", 80);
$sStr .= "\n";
$this->log($sStr);
return $sStr;
}
function subHeader($sMsg) {
$sStr = "\n\n# " . str_pad(strtoupper($sMsg) . " ", 78, "-", STR_PAD_RIGHT) . "\n";
$this->log($sStr);
return $sStr;
}
function subHeader2($sMsg) {
$sStr = "\n# # " . str_pad($sMsg . " ", 76, "-", STR_PAD_RIGHT) . "\n";
$this->log($sStr);
return $sStr;
}
function textLine($sMsg) {
$sStr = ". " . $sMsg . "\n";
$this->log($sStr);
return $sStr;
}
function rawLine($sMsg) {
$sStr = $sMsg . "\n";
$this->log($sStr);
return $sStr;
}
function notice($sMsg) {
$sStr = "\n" . str_pad($sMsg, 80, ".", STR_PAD_BOTH) . "\n";
$this->log($sStr);
return $sStr;
}
function getLog() {
return $this->sLog;
}
function file_writeBin($sPath, $sData, $bUTF8 = TRUE) {
$rFile = fopen($sPath, "wb");
if($bUTF8 === TRUE) {
fputs($rFile, "\xEF\xBB\xBF" . $sData);
} else {
fputs($rFile, $sData);
}
fclose($rFile);
}
function echoFlush($sString = "") {
echo $sString;
ob_flush();
flush();
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Flake\Controler;
class HtmlBlock extends \Flake\Core\Controler {
function __construct($sHtml) {
$this->sHtml = $sHtml;
}
function execute() {
}
function render() {
return $this->sHtml;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Flake\Controler;
class HtmlBlockTemplated extends \Flake\Core\Controler {
function __construct($sTemplatePath, $aMarkers = array()) {
$this->sTemplatePath = $sTemplatePath;
$this->aMarkers = $aMarkers;
}
function render() {
$oTemplate = new \Flake\Core\Template($this->sTemplatePath);
$sHtml = $oTemplate->parse(
$this->aMarkers
);
return $sHtml;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Flake\Controler;
class Kickstart extends \Flake\Core\Controler {
public function execute() {
# "--helloworld"
if(array_key_exists("helloworld", self::cli()->aArgs)) {
$this->action_helloworld();
}
}
public static function &cli() {
return $GLOBALS["oCli"];
}
public function __call($sName, $aArguments) {
if(substr($sName, 0, 4) === "cli_") {
$sCallName = substr($sName, 4);
if(method_exists(self::cli(), $sCallName)) {
return call_user_func_array(array(self::cli(), $sCallName), $aArguments);
}
}
die("Undefined method " . $sName);
}
public function render() {
}
public function action_helloworld() {
$this->cli_echoFlush($this->cli_header("Hello, World !"));
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Flake\Controler;
class Page extends \Flake\Core\Render\Container {
var $sTitle = "";
var $sMetaKeywords = "";
var $sMetaDescription = "";
var $sTemplatePath = "";
function __construct($sTemplatePath) {
$this->sTemplatePath = $sTemplatePath;
}
function setTitle($sTitle) {
$this->sTitle = $sTitle;
}
function setMetaKeywords($sKeywords) {
$this->sMetaKeywords = $sKeywords;
}
function setMetaDescription($sDescription) {
$this->sMetaDescription = $sDescription;
}
function getTitle() {
return $this->sTitle;
}
function getMetaKeywords() {
$sString = str_replace(array("le", "la", "les", "de", "des", "un", "une"), " ", $this->sMetaKeywords);
$sString = \Flake\Util\Tools::stringToUrlToken($sString);
return implode(", ", explode("-", $sString));
}
function getMetaDescription() {
return $this->sMetaDescription;
}
function setBaseUrl($sBaseUrl) {
$this->sBaseUrl = $sBaseUrl;
}
function getBaseUrl() {
return $this->sBaseUrl;
}
function renderBlocks() {
$aHtml = array();
reset($this->aSequence);
while(list($sKey,) = each($this->aSequence)) {
$this->aSequence[$sKey]["rendu"] = $this->aSequence[$sKey]["block"]->render();
}
$aHtml = array();
reset($this->aBlocks);
while(list($sZone,) = each($this->aBlocks)) {
$aHtml[$sZone] = implode("", $this->aBlocks[$sZone]);
}
reset($aHtml);
return $aHtml;
}
function injectHTTPHeaders() {
header("Content-Type: text/html; charset=utf-8");
}
function render() {
$this->execute();
$aRenderedBlocks = $this->renderBlocks();
$aRenderedBlocks["pagetitle"] = $this->getTitle();
$aRenderedBlocks["pagemetakeywords"] = $this->getMetaKeywords();
$aRenderedBlocks["pagemetadescription"] = $this->getMetaDescription();
$aRenderedBlocks["baseurl"] = $this->getBaseUrl();
$oTemplate = new \Flake\Core\Template($this->sTemplatePath);
$sHtml = $oTemplate->parse(
$aRenderedBlocks
);
return $sHtml;
}
function execute() {
reset($this->aSequence);
while(list($sKey,) = each($this->aSequence)) {
$this->aSequence[$sKey]["block"]->execute();
}
}
function addCss($sCssAbsPath) {
$sCompiledPath = PATH_buildcss;
$sFileName = basename($sCssAbsPath);
$sCompiledFilePath = $sCompiledPath . \Flake\Util\Tools::shortMD5($sFileName) . "_" . $sFileName;
if(substr(strtolower($sCompiledFilePath), -4) !== ".css") {
$sCompiledFilePath .= ".css";
}
if(!file_exists($sCompiledPath)) {
@mkdir($sCompiledPath);
if(!file_exists($sCompiledPath)) {
die("Page: Cannot create " . $sCompiledPath);
}
}
\Frameworks\LessPHP\Delegate::compileCss($sCssAbsPath, $sCompiledFilePath);
$sCssUrl = \Flake\Util\Tools::serverToRelativeWebPath($sCompiledFilePath);
$sHtml = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" . $sCssUrl . "\" media=\"all\"/>";
$this->zone("head")->addBlock(new \Flake\Controler\HtmlBlock($sHtml));
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Flake\Core;
class Auth extends \Flake\Core\FLObject {
public static function isAdmin() {
$sUrl = \Flake\Util\Tools::getCurrentUrl();
$aParts = explode("/", $sUrl);
return in_array("edit", $aParts);
}
}

View file

@ -0,0 +1,37 @@
<?php
// les notices PHP ne sont pas affichées
ini_set("display_errors", 1);
ini_set("error_reporting", E_ALL & ~E_NOTICE);
if(!function_exists("appendSlash")) {
function appendSlash($sPath) {
if($sPath{strlen($sPath) - 1} !== "/") {
$sPath .= "/";
}
return $sPath;
}
}
if(!function_exists("debug")) {
function debug($mVar, $sHeader=0) {
\Flake\Util\Tools::debug($mVar, $sHeader);
}
}
require_once(FLAKE_PATH_ROOT . 'Core/ClassLoader.php');
\Flake\Core\ClassLoader::register();
if(!\Flake\Util\Tools::isCliPhp()) {
session_start();
\Flake\Util\Tools::decode_GET();
}
setlocale(LC_ALL, FLAKE_LOCALE);
date_default_timezone_set(FLAKE_TIMEZONE);
$GLOBALS["DB"] = new \Flake\Core\Database\Sqlite();
$GLOBALS["DB"]->init(FLAKE_DB_FILEPATH);
$GLOBALS["TEMPLATESTACK"] = array();

View file

@ -0,0 +1,46 @@
<?php
declare(ENCODING = 'utf-8');
namespace Flake\Core;
class ClassLoader {
public static function register() {
return spl_autoload_register(array(get_called_class(), 'loadClass'));
}
public static function loadClass($sFullClassName) {
$aParts = explode("\\", $sFullClassName);
if(count($aParts) === 1) {
return;
}
if($aParts[0] !== "Flake" && $aParts[0] !== "Specific" && $aParts[0] !== "Frameworks") {
return;
}
// ejecting the Radical
$sRadical = array_shift($aParts);
if($sRadical === "Flake") {
$sRootPath = FLAKE_PATH_ROOT;
} elseif($sRadical === "Specific") {
$sRootPath = FLAKE_PATH_SPECIFIC; # When prefix does not point another namespaced framework, we use "Specific"
} elseif($sRadical === "Frameworks") {
$sRootPath = FLAKE_PATH_FRAMEWORKS;
}
$sClassName = array_pop($aParts);
$sBasePath = $sRootPath . implode("/", $aParts) . "/";
$sClassPath = $sBasePath . $sClassName . ".php";
if(file_exists($sClassPath) && is_readable($sClassPath)) {
require_once($sClassPath);
} else {
echo '<h1>PHP Autoload Error. Cannot find ' . $sFullClassName . '</h1>';
echo "<pre>" . print_r(debug_backtrace(), TRUE) . "</pre>";
die();
}
}
}

View file

@ -0,0 +1,179 @@
<?php
namespace Flake\Core;
class Collection extends \Flake\Core\FLObject implements \Iterator {
protected $aCollection = array();
protected $aMeta = array();
public function current() {
return current($this->aCollection);
}
public function key() {
return key($this->aCollection);
}
public function next() {
return next($this->aCollection);
}
public function rewind() {
$this->reset();
}
public function valid() {
$key = key($this->aCollection);
return ($key !== NULL && $key !== FALSE);
}
public function &each() {
list($key, $val) = each($this->aCollection);
return $val;
}
public function reset() {
reset($this->aCollection);
}
public function prev() {
return prev($this->aCollection);
}
/*
* Retourne le nombre d'élémént
*
* @return int nombre l'éléments
*/
public function count() {
return count($this->aCollection);
}
/*
* Retourne le tableau des clés
*
* @return array tableau des clés
*/
public function keys() {
return array_keys($this->aCollection);
}
/*
* Vérifie si la collection est vide
*
* @return bool TRUE si la collection est vide
*/
public function isEmpty() {
return $this->count() === 0;
}
public function isAtFirst() {
return $this->key() === array_shift($this->keys());
}
public function isAtLast() {
return $this->key() === array_pop($this->keys());
}
/*
* Insère un élément dans la collection
*
* @param $mMixed valeur à insérer dans la collection
* @return void
*/
public function push(&$mMixed) {
array_push($this->aCollection, $mMixed);
}
/*
* Vide la collection
*
* @return void
*/
public function flush() {
unset($this->aCollection);
$this->aCollection = array();
}
/*
* Retourne le premier élément de la collection
*
* @return mixed premier élément de la collection
*/
public function &first() {
if(!$this->isEmpty()) {
$aKeys = $this->keys();
return $this->aCollection[array_shift($aKeys)];
}
return null;
}
public function &last() {
if(!$this->isEmpty()) {
$aKeys = $this->keys();
return $this->aCollection[array_pop($aKeys)];
}
return null;
}
/*
* Converti la collection en tableau
*
* @return array collection sous forme de tableau
*/
public function toArray() {
return $this->aCollection;
}
/*
* Méthode magique __call
*
* @param $sName string nom de la méthode
* @param $aArguments array argument passé initialement
* @return mixed valeur de la collection correspond
*/
public function &__call($sName, $aArguments) {
if(
strlen($sName) > 7 &&
$sName{0} === "s" &&
$sName{1} === "e" &&
$sName{2} === "t" &&
$sName{3} === "M" &&
$sName{4} === "e" &&
$sName{5} === "t" &&
$sName{6} === "a"
) {
$sKey = strtolower(substr($sName, 7, 1)) . substr($sName, 8);
$mValue =& $aArguments[0];
if(is_null($mValue)) {
if(array_key_exists($sKey, $this->aMeta)) {
unset($this->aMeta[$sKey]);
}
} else {
$this->aMeta[$sKey] =& $mValue;
}
} elseif(
strlen($sName) > 7 &&
$sName{0} === "g" &&
$sName{1} === "e" &&
$sName{2} === "t" &&
$sName{3} === "M" &&
$sName{4} === "e" &&
$sName{5} === "t" &&
$sName{6} === "a"
) {
$sKey = strtolower(substr($sName, 7, 1)) . substr($sName, 8);
if(array_key_exists($sKey, $this->aMeta)) {
return $this->aMeta[$sKey];
} else {
return null;
}
} else {
throw new \Exception("Method " . $sName . "() not found on " . self::getClass());
}
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Flake\Core;
class CollectionTyped extends \Flake\Core\Collection {
private $sTypeClassOrProtocol;
/*
* Constructeur de la classe
*
* @param $sTypeClassOrProtocol string type des éléments de la collection
* @return void
*/
public function __construct($sTypeClassOrProtocol) {
$this->sTypeClassOrProtocol = $sTypeClassOrProtocol;
$this->setMetaType($this->sTypeClassOrProtocol);
}
/*
* Insère un élément dans la collection
*
* @param $mMixed mixed valeur à insérer dans la collection
* @return void
*/
public function push(&$mMixed) {
if(!\Flake\Util\Tools::is_a($mMixed, $this->sTypeClassOrProtocol)) {
throw new \Exception("\Flake\Core\CollectionTyped<" . $this->sTypeClassOrProtocol . ">: Given object is not correctly typed.");
}
parent::push($mMixed);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Flake\Core;
abstract class Controler extends \Flake\Core\FLObject {
protected $aParams = array();
public function __construct($aParams = array()) {
$this->aParams = $aParams;
}
abstract function execute();
abstract function render();
public static function buildRouteWithParams() {
$aParams = func_get_args();
$sControler = "\\" . get_called_class();
array_unshift($aParams, $sControler);
return call_user_func_array("\Flake\Util\Router::buildRouteForControlerWithParams", $aParams);
}
}

View file

@ -0,0 +1,161 @@
<?php
namespace Flake\Core;
abstract class Database extends \Flake\Core\FLObject {
/* common stuff */
function messageAndDie($sMessage) {
$sError = "<h2>" . get_class($this) . ": " . $sMessage . "</h2>";
die($sError);
}
function exec_INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
return $this->query($this->INSERTquery($table,$fields_values,$no_quote_fields));
}
function INSERTquery($table,$fields_values,$no_quote_fields=FALSE) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
if (is_array($fields_values) && count($fields_values)) {
// quote and escape values
$fields_values = $this->fullQuoteArray($fields_values,$table,$no_quote_fields);
// Build query:
$query = 'INSERT INTO '.$table.'
(
'.implode(',
',array_keys($fields_values)).'
) VALUES (
'.implode(',
',$fields_values).'
)';
// Return query:
if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
return $query;
}
}
function exec_UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
return $this->query($this->UPDATEquery($table,$where,$fields_values,$no_quote_fields));
}
function UPDATEquery($table,$where,$fields_values,$no_quote_fields=FALSE) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function (contrary to values in the arrays which may be insecure).
if (is_string($where)) {
if (is_array($fields_values) && count($fields_values)) {
// quote and escape values
$nArr = $this->fullQuoteArray($fields_values,$table,$no_quote_fields);
$fields = array();
foreach ($nArr as $k => $v) {
$fields[] = $k.'='.$v;
}
// Build query:
$query = 'UPDATE '.$table.'
SET
'.implode(',
',$fields).
(strlen($where)>0 ? '
WHERE
'.$where : '');
// Return query:
if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
return $query;
}
} else {
die('<strong>Fatal Error:</strong> "Where" clause argument for UPDATE query was not a string in $this->UPDATEquery() !');
}
}
function exec_DELETEquery($table,$where) {
return $this->query($this->DELETEquery($table,$where));
}
function DELETEquery($table,$where) {
if (is_string($where)) {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function
$query = 'DELETE FROM '.$table.
(strlen($where)>0 ? '
WHERE
'.$where : '');
if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
return $query;
} else {
die('<strong>Fatal Error:</strong> "Where" clause argument for DELETE query was not a string in $this->DELETEquery() !');
}
}
function exec_SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
return $this->query($this->SELECTquery($select_fields,$from_table,$where_clause,$groupBy,$orderBy,$limit));
}
function SELECTquery($select_fields,$from_table,$where_clause,$groupBy='',$orderBy='',$limit='') {
// Table and fieldnames should be "SQL-injection-safe" when supplied to this function
// Build basic query:
$query = 'SELECT '.$select_fields.'
FROM '.$from_table.
(strlen($where_clause)>0 ? '
WHERE
'.$where_clause : '');
// Group by:
if (strlen($groupBy)>0) {
$query.= '
GROUP BY '.$groupBy;
}
// Order by:
if (strlen($orderBy)>0) {
$query.= '
ORDER BY '.$orderBy;
}
// Group by:
if (strlen($limit)>0) {
$query.= '
LIMIT '.$limit;
}
// Return query:
if ($this->debugOutput || $this->store_lastBuiltQuery) $this->debug_lastBuiltQuery = $query;
return $query;
}
function fullQuoteStr($str, $table) {
return '\''.$this->quoteStr($str, $table).'\'';
}
function fullQuoteArray($arr, $table, $noQuote=FALSE) {
if (is_string($noQuote)) {
$noQuote = explode(',',$noQuote);
} elseif (!is_array($noQuote)) { // sanity check
$noQuote = FALSE;
}
foreach($arr as $k => $v) {
if ($noQuote===FALSE || !in_array($k,$noQuote)) {
$arr[$k] = $this->fullQuoteStr($v, $table);
}
}
return $arr;
}
/* fonctions abstraites */
abstract function query($sSql);
abstract function fetch($rSql);
abstract function sql_insert_id();
abstract function quoteStr($str, $table);
}

View file

@ -0,0 +1,54 @@
<?php
namespace Flake\Core;
class Mysql extends \Flake\Core\Database {
var $link = FALSE; // current DB link
var $debugOutput = FALSE;
var $store_lastBuiltQuery = TRUE;
var $debug_lastBuiltQuery = "";
/* fonctions abstraites */
function init($sHost = DB_host, $sLogin = DB_login, $sPassword = DB_password, $sDatabase = DB_database) {
if(is_resource($this->link)) {
$this->messageAndDie("DB already initialized");
}
$this->link = mysql_connect(
$sHost,
$sLogin,
$sPassword
) OR $this->messageAndDie("invalid DB credentials.");
mysql_select_db($sDatabase, $this->link) OR $this->messageAndDie("could not select DB");
// on initialise la connection UTF-8 aux données
mysql_query("set character_set_database='utf8'", $this->link);
mysql_query("set character_set_client='utf8'", $this->link);
mysql_query("set character_set_connection='utf8'", $this->link);
mysql_query("set character_set_results='utf8'", $this->link);
mysql_query("set character_set_server='utf8'", $this->link);
}
function query($sSql) {
return mysql_query($sSql, $this->link);
}
function fetch($rSql) {
if(is_resource($rSql)) {
return mysql_fetch_assoc($rSql);
}
return FALSE;
}
function sql_insert_id() {
return mysql_insert_id($this->link);
}
function quoteStr($str, $table) {
return mysql_real_escape_string($str, $this->link);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Flake\Core\Database;
class Sqlite extends \Flake\Core\Database {
var $oDb = FALSE; // current DB link
var $debugOutput = FALSE;
var $store_lastBuiltQuery = TRUE;
var $debug_lastBuiltQuery = "";
var $sDbPath = "";
function init($sDbPath) {
if(is_object($this->oDb)) {
$this->messageAndDie("DB already initialized");
}
$this->sDbPath = $sDbPath;
$this->oDb = new \SQLite3($this->sDbPath);
}
function query($sSql) {
return $this->oDb->query($sSql);
}
function fetch($rSql) {
if(is_object($rSql)) {
return $rSql->fetchArray(SQLITE3_ASSOC);
}
return FALSE;
}
function sql_insert_id() {
$rSql = $this->query("SELECT last_insert_rowid() as uid");
if(($aRes = $this->fetch($rSql)) !== FALSE) {
return intval($aRes["uid"]);
}
return FALSE;
}
function quoteStr($str, $table=FALSE) {
if(function_exists("sqlite_escape_string")) {
return sqlite_escape_string($str);
}
return addslashes($str);
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Flake\Core;
class FLObject {
public function __toString() {
return print_r($this, TRUE);
}
public static function getClass() {
return get_called_class();
}
public function isA($sClassOrProtocolName) {
return \Flake\Util\Tools::is_a($this, $sClassOrProtocolName);
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Flake\Core;
abstract class Model extends \Flake\Core\FLObject {
protected $aData = array();
public function getData() {
reset($this->aData);
return $this->aData;
}
public function get($sWhat) {
if(array_key_exists($sWhat, $this->aData)) {
return $this->aData[$sWhat];
}
return FALSE;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Flake\Core\Model;
abstract class Db extends \Flake\Core\Model {
public function __construct($sPrimary) {
if(($this->aData = $this->getRecordByPrimary($sPrimary)) === FALSE) {
throw new \Exception("\Flake\Core\Model '" . $sPrimary . "' not found");
}
}
public static function &getBaseRequester() {
$oRequester = new \Flake\Core\Requester(self::getClass());
$oRequester->setDataTable(self::getDataTable());
return $oRequester;
}
public static function &getByRequest(\FS\Core\Requester $oRequester) {
// renvoie une collection de la classe du modèle courant (this)
return $oRequester->execute();
}
public static function getDataTable() {
$sClass = self::getClass();
return $sClass::DATATABLE;
}
public static function getPrimaryKey() {
$sClass = self::getClass();
return $sClass::PRIMARYKEY;
}
protected function getRecordByPrimary($sPrimary) {
$rSql = $GLOBALS["DB"]->exec_SELECTquery(
"*",
self::getDataTable(),
self::getPrimaryKey() . "='" . $GLOBALS["DB"]->quoteStr($sPrimary) . "'"
);
if(($aRs = $GLOBALS["DB"]->fetch($rSql)) !== FALSE) {
reset($aRs);
return $aRs;
}
return FALSE;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace Flake\Core\Model;
abstract class NoDb extends \Flake\Core\Model {
public function __construct($aData = array()) {
$this->aData = $aData;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Flake\Core\Render;
class Container extends \Flake\Core\FLObject {
var $aSequence = array();
var $aBlocks = array();
var $aRendu = array();
var $aZones = array();
function addBlock(&$oBlock, $sZone = "_DEFAULT_") {
$aTemp = array(
"block" => &$oBlock,
"rendu" => "",
);
$this->aSequence[] =& $aTemp;
$this->aBlocks[$sZone][] =& $aTemp["rendu"];
}
function &zone($sZone) {
if(!array_key_exists($sZone, $this->aZones)) {
$this->aZones[$sZone] = new \Flake\Core\Render\Zone($this, $sZone);
}
return $this->aZones[$sZone];
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Flake\Core\Render;
class Zone extends \Flake\Core\FLObject {
function __construct(&$oZonableObject, $sZone) {
$this->oZonableObject =& $oZonableObject;
$this->sZone = $sZone;
}
function addBlock(&$oBlock) {
$this->oZonableObject->addBlock(
$oBlock,
$this->sZone
);
}
}

View file

@ -0,0 +1,201 @@
<?php
namespace Flake\Core;
class Requester extends \Flake\Core\FLObject {
protected $sDataTable = "";
protected $aClauses = array();
protected $sModelClass = "";
protected $sOrderField = "";
protected $sOrderDirection = "ASC";
protected $iLimitStart = FALSE;
protected $iLimitNumber = FALSE;
protected $bHasBeenExecuted = FALSE;
public function __construct($sModelClass) {
$this->sModelClass = $sModelClass;
}
public function setDataTable($sDataTable) {
$this->sDataTable = $sDataTable;
return $this;
}
public function addClause($sField, $sValue) {
$this->addClauseEquals($sField, $sValue);
return $this;
}
public function addClauseEquals($sField, $sValue) {
$sWrap = "{field}='{value}'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseNotEquals($sField, $sValue) {
$sWrap = "{field}!='{value}'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseLike($sField, $sValue) {
$sWrap = "{field} LIKE '%{value}%'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseLikeBeginning($sField, $sValue) {
$sWrap = "{field} LIKE '{value}%'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseLikeEnd($sField, $sValue) {
$sWrap = "{field} LIKE '%{value}'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseNotLike($sField, $sValue) {
$sWrap = "{field} NOT LIKE '%{value}%'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseNotLikeBeginning($sField, $sValue) {
$sWrap = "{field} NOT LIKE '{value}%'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseNotLikeEnd($sField, $sValue) {
$sWrap = "{field} NOT LIKE '%{value}'";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseIn($sField, $sValue) {
$sWrap = "{field} IN ({value})";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function addClauseNotIn($sField, $sValue) {
$sWrap = "{field} NOT IN ({value})";
$this->addClauseWrapped($sField, $sValue, $sWrap);
return $this;
}
public function orderBy($sOrderField, $sOrderDirection = "ASC") {
$this->sOrderField = $sOrderField;
$this->sOrderDirection = $sOrderDirection;
return $this;
}
public function limit($iStart, $iNumber = FALSE) {
if($iNumber !== FALSE) {
return $this->setLimitStart($iStart)->setLimitNumber($iLimitNumber);
}
return $this->setLimitStart($iStart);
}
public function setLimitStart($iLimitStart) {
$this->iLimitStart = $iLimitStart;
return $this;
}
public function setLimitNumber($iLimitNumber) {
$this->iLimitNumber = $iLimitNumber;
return $this;
}
protected function addClauseWrapped($sField, $sValue, $sWrap) {
$sValue = $this->escapeSqlValue($sValue);
$sClause = str_replace(
array(
"{field}",
"{value}",
),
array(
$sField,
$sValue
),
$sWrap
);
$this->addClauseLiteral($sClause);
return $this;
}
public function addClauseLiteral($sClause) {
$this->aClauses[] = $sClause;
return $this;
}
protected function escapeSqlValue($sValue) {
return $GLOBALS["DB"]->quoteStr(
$sValue,
$this->sDataTable
);
}
protected function &reify($aData) {
$sTemp = $this->sModelClass;
return new $sTemp($aData[$sTemp::getPrimaryKey()]);
}
public function hasBeenExecuted() {
return $this->bHasBeenExecuted;
}
public function getQuery() {
if(empty($this->aClauses)) {
$sWhere = "1=1";
} else {
$sWhere = implode(" AND ", $this->aClauses);
}
if(trim($this->sOrderField) !== "") {
$sOrderBy = $this->sOrderField . " " . $this->sOrderDirection;
}
$sLimit = "";
if($this->iLimitStart !== FALSE) {
if($this->iLimitNumber !== FALSE) {
$sLimit = $this->iLimitStart . ", " . $this->iLimitNumber;
} else {
$sLimit = $this->iLimitStart;
}
} elseif($this->iLimitNumber !== FALSE) {
$sLimit = "0, " . $this->iLimitNumber;
}
return $GLOBALS["DB"]->SELECTquery(
"*",
$this->sDataTable,
$sWhere,
"",
$sOrderBy,
$sLimit
);
}
public function execute() {
$oCollection = new \Flake\Core\CollectionTyped($this->sModelClass);
$sSql = $this->getQuery();
$rSql = $GLOBALS["DB"]->query($sSql);
while(($aRs = $GLOBALS["DB"]->fetch($rSql)) !== FALSE) {
$oCollection->push(
$this->reify($aRs)
);
}
$this->bHasBeenExecuted = TRUE;
return $oCollection;
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Flake\Core;
class Template extends \Flake\Core\FLObject {
private $sAbsPath = "";
private $bPhp = FALSE;
private $sHtml = "";
public function __construct($sAbsPath, $bPhp = FALSE) {
$this->sAbsPath = $sAbsPath;
$this->bPhp = $bPhp;
$this->sHtml = $this->getTemplateFile(
$this->sAbsPath
);
}
private function getTemplateFile($sAbsPath) {
return file_get_contents($sAbsPath);
}
function parse($aMarkers = array()) {
if($this->bPhp) {
return \Flake\Util\Tools::parseTemplateCodePhp(
$this->sHtml,
$aMarkers
);
} else {
return \Flake\Util\Tools::parseTemplateCode(
$this->sHtml,
$aMarkers
);
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Flake\Core;
class View extends \Flake\Core\FLObject {
protected $aData;
public function __construct() {
$this->aData = array();
}
public function setData($sName, $mData) {
$this->aData[$sName] = $mData;
}
public function getData() {
return $this->aData;
}
public function get($sWhat) {
if(array_key_exists($sWhat, $this->aData)) {
return $this->aData[$sWhat];
}
return FALSE;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Flake\Route\Kickstart;
class Main {
public function execute(\Flake\Core\Render\Container &$oRenderContainer) {
$oRenderContainer->zone("Payload")->addBlock(new \Flake\Controler\Kickstart());
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace Flake\Util;
class Router extends \Flake\Core\FLObject {
public static function getCurrentRoute() {
$sUrl = trim($GLOBALS["_SERVER"]["REDIRECT_URL"]);
if($sUrl{0} === "/") {
$sUrl = substr($sUrl, 1);
}
if($sUrl{strlen($sUrl) - 1} === "/") {
$sUrl = substr($sUrl, 0, -1);
}
$aParts = explode("/", $sUrl);
$sRoute = $aParts[0];
if(trim($sUrl) === "") {
$sRoute = "default";
} else {
if(!array_key_exists($sRoute, $GLOBALS["ROUTES"])) {
$sRoute = "default";
}
}
return $sRoute;
}
public static function getControlerForRoute($sRoute) {
return $GLOBALS["ROUTES"][$sRoute];
}
public static function getRouteForControler($sControler) {
if($sControler{0} !== "\\") {
$sControler = "\\" . $sControler;
}
reset($GLOBALS["ROUTES"]);
while(list($sRoute,) = each($GLOBALS["ROUTES"])) {
if(str_replace("\\Route", "\\Controler", $GLOBALS["ROUTES"][$sRoute]) === $sControler) {
return $sRoute;
}
}
return "";
}
public static function route(\Flake\Core\Render\Container &$oRenderContainer) {
$sControler = self::getControlerForRoute(
self::getCurrentRoute()
);
$sControler::execute($oRenderContainer);
}
public static function buildRouteForControlerWithParams() {
$aParams = func_get_args();
$sControler = array_shift($aParams);
$sRouteForControler = self::getRouteForControler($sControler);
return "/" . $sRouteForControler . "/" . implode("/", $aParams);
}
/* public static function buildRouteWithParams() {
$aParams = func_get_args();
list(, $aCall) = debug_backtrace(FALSE);
$sClass = $aCall["class"];
$sClass = str_replace("\\Route", "", $sClass);
$sRouteForControler = self::getRouteForControler($sClass);
return "/" . $sRouteForControler . "/" . implode("/", $aParams);
}*/
}

View file

@ -0,0 +1,587 @@
<?php
namespace Flake\Util;
class Tools extends \Flake\Core\FLObject {
static function getCurrentUrl() {
return $_SERVER["REQUEST_URI"];
}
static function getUrlTokens() {
$sUrl = self::getCurrentUrl();
if($sUrl{0} === "/") {
$sUrl = substr($sUrl, 1);
}
if(trim($sUrl) !== "") {
return explode("/", $sUrl);
}
return array();
}
public static function deCamelCase($sString, $sGlue=" ") {
$sSep = md5(rand());
$sRes = preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $sSep . '$0', $sString));
if($sGlue !== "" && preg_match('/^[[:upper:]].*/', $sRes)) {
$sRes = $sSep . $sRes;
}
return str_replace($sSep, $sGlue, $sRes);
}
static function getAction() {
return \Flake\Util\Tools::GET("action");
}
static function absolutizeURL($sUrl) {
$aUrl = parse_url($sUrl);
if($aUrl["scheme"] !== "http" && $aUrl["scheme"] !== "https") {
if($sUrl{0} === "/") {
$sUrl = substr($sUrl, 1);
}
return FLAKE_BASEURL . $sUrl;
}
return $sUrl;
}
public static function serverToAbsoluteWebPath($sAbsoluteServerPath) {
if(substr($sAbsoluteServerPath, 0, strlen(FLAKE_PATH_WWWROOT)) === FLAKE_PATH_WWWROOT) {
return FLAKE_BASEURL . substr($sAbsoluteServerPath, strlen(FLAKE_PATH_WWWROOT));
}
return $sAbsoluteServerPath;
}
public static function serverToRelativeWebPath($sAbsPath) {
return "/" . str_replace(FLAKE_PATH_WWWROOT, "", $sAbsPath);
}
static function view_array($array_in) {
if (is_array($array_in)) {
$result='<table border="1" cellpadding="1" cellspacing="0" bgcolor="white">';
if (!count($array_in)) {$result.= '<tr><td><font face="Verdana,Arial" size="1"><b>'.htmlspecialchars("EMPTY!").'</b></font></td></tr>';}
while (list($key,$val)=each($array_in)) {
$result.= '<tr><td valign="top"><font face="Verdana,Arial" size="1">'.htmlspecialchars((string)$key).'</font></td><td>';
if (is_array($array_in[$key])) {
$result.= \Flake\Util\Tools::view_array($array_in[$key]);
} else
$result.= '<font face="Verdana,Arial" size="1" color="red">'.nl2br(htmlspecialchars((string)$val)).'<br /></font>';
$result.= '</td></tr>';
}
$result.= '</table>';
} else {
$result = '<table border="1" cellpadding="1" cellspacing="0" bgcolor="white">
<tr>
<td><font face="Verdana,Arial" size="1" color="red">'.nl2br(htmlspecialchars((string)$array_in)).'<br /></font></td>
</tr>
</table>'; // Output it as a string.
}
return $result;
}
static function debug($var="",$brOrHeader=0) {
if($brOrHeader === 0) {
$trail = debug_backtrace();
$trail = array_reverse($trail);
array_pop($trail); // la ligne d'appel à debug
array_pop($trail); // la ligne d'appel à debug
$aLastNode = array_pop($trail); // l'appel qui nous intéresse
$brOrHeader = $aLastNode['class'].$aLastNode['type'].$aLastNode['function'];
}
if ($brOrHeader) {
echo '<table class="typo3-debug" border="0" cellpadding="0" cellspacing="0" bgcolor="white" style="border:0px; margin-top:3px; margin-bottom:3px;"><tr><td style="background-color:#bbbbbb; font-family: verdana,arial; font-weight: bold; font-size: 10px;">'.htmlspecialchars((string)$brOrHeader).'</td></tr><tr><td>';
}
if (is_array($var)) {
echo \Flake\Util\Tools::view_array($var);
} elseif (is_object($var)) {
echo '<b>|Object:<pre>';
print_r($var);
echo '</pre>|</b>';
} elseif ((string)$var!='') {
echo '<b>|'.htmlspecialchars((string)$var).'|</b>';
} else {
echo '<b>| debug |</b>';
}
if ($brOrHeader) {
echo '</td></tr></table>';
}
}
static function debug_trail() {
$trail = debug_backtrace();
$trail = array_reverse($trail);
array_pop($trail);
$path = array();
foreach($trail as $dat) {
$path[] = $dat['class'].$dat['type'].$dat['function'];
}
return implode(' // ',$path);
}
static function POST($sVar = FALSE) {
if($sVar !== FALSE) {
$aData = \Flake\Util\Tools::POST();
if(array_key_exists($sVar, $aData)) {
return $aData[$sVar];
}
return "";
}
return is_array($GLOBALS["_POST"]) ? $GLOBALS["_POST"] : array();
}
static function GET($sVar = FALSE) {
if($sVar !== FALSE) {
$aData = \Flake\Util\Tools::GET();
if(array_key_exists($sVar, $aData)) {
return $aData[$sVar];
}
return "";
}
return is_array($GLOBALS["_GET"]) ? $GLOBALS["_GET"] : array();
}
static function GP($sVar = FALSE) {
if($sVar !== FALSE) {
$aData = \Flake\Util\Tools::GP();
if(array_key_exists($sVar, $aData)) {
return $aData[$sVar];
}
return "";
}
return array_merge(
\Flake\Util\Tools::GET(),
\Flake\Util\Tools::POST()
);
}
static function makeLink($sAction, $aAdditionalParams = FALSE) {
if($aAdditionalParams === FALSE) {
// aucun paramètre additionnel
if(trim($sAction) === "home") {
return FLAKE_BASEURL;
} else {
return FLAKE_BASEURL . "?action=" . rawurlencode($sAction);
}
} else {
$aTemp = array();
while(list($sKey,) = each($aAdditionalParams)) {
if($sKey{0} === "u" && $sKey{1} === "_") {
// il s'agit d'un message textuel; on l'encode en base 64
$aTemp[] = rawurlencode($sKey) . "=" . rawurlencode(base64_encode($aAdditionalParams[$sKey]));
} else {
$aTemp[] = rawurlencode($sKey) . "=" . rawurlencode($aAdditionalParams[$sKey]);
}
}
$sAdditionalParams = implode("&", $aTemp);
if(trim($sAction) === "home") {
return FLAKE_BASEURL . "?" . $sAdditionalParams;
} else {
return FLAKE_BASEURL . "?action=" . $sAction . "&" . $sAdditionalParams;
}
}
}
function safelock($sString) {
return substr(md5(FLAKE_SAFEHASH_SALT . ":" . $sString), 0, 5);
}
function redirect($sUrl) {
header("Location: " . $sUrl);
exit(0);
}
function refreshPage() {
header("Location: " . \Flake\Util\Tools::getCurrentUrl());
exit(0);
}
function decode_GET() {
$aGet = \Flake\Util\Tools::GET();
$aKeys = array_keys($aGet);
while(list(,$sKey) = each($aKeys)) {
if($sKey{0} === "u" && $sKey{1} === "_") {
$aGet[$sKey] = base64_decode($aGet[$sKey]);
}
}
$GLOBALS["_GET"] = $aGet;
reset($GLOBALS["_GET"]);
}
function validEmail($sEmail) {
return (filter_var($sEmail, FILTER_VALIDATE_EMAIL) !== FALSE);
}
function filterFormInput($sInput) {
return strip_tags($sInput);
}
function getHumanDate($iStamp) {
return ucwords(strftime("%A, %d %B %Y", $iStamp));
}
function getHumanTime($iStamp) {
return strftime("%Hh%M", $iStamp);
}
public static function trimExplode($string, $delim=",", $removeEmptyValues = false, $limit = 0) {
$explodedValues = explode($delim, $string);
$result = array_map('trim', $explodedValues);
if ($removeEmptyValues) {
$temp = array();
foreach($result as $value) {
if ($value !== '') {
$temp[] = $value;
}
}
$result = $temp;
}
if ($limit != 0) {
if ($limit < 0) {
$result = array_slice($result, 0, $limit);
} elseif (count($result) > $limit) {
$lastElements = array_slice($result, $limit - 1);
$result = array_slice($result, 0, $limit - 1);
$result[] = implode($delim, $lastElements);
}
}
return $result;
}
/**
* Taken from TYPO3
* Returns true if the first part of $str matches the string $partStr
* Usage: 59
*
* @param string Full string to check
* @param string Reference string which must be found as the "first part" of the full string
* @return boolean True if $partStr was found to be equal to the first part of $str
*/
public static function isFirstPartOfStr($str,$partStr) {
// Returns true, if the first part of a $str equals $partStr and $partStr is not ''
$psLen = strlen($partStr);
if ($psLen) {
return substr($str,0,$psLen)==(string)$partStr;
} else return false;
}
/**
* Binary-reads a file
*
* @param string $sPath: absolute server path to file
* @return string file contents
*/
public static function file_readBin($sPath) {
$sData = "";
$rFile = fopen($sPath, "rb");
while(!feof($rFile)) {
$sData .= fread($rFile, 1024);
}
fclose($rFile);
return $sData;
}
/**
* Binary-writes a file
*
* @param string $sPath: absolute server path to file
* @param string $sData: file contents
* @param boolean $bUTF8: add UTF8-BOM or not ?
* @return void
*/
function file_writeBin($sPath, $sData) {
$rFile=fopen($sPath, "wb");
fputs($rFile, $sData);
fclose($rFile);
}
public static function sendHtmlMail($sToAddress, $sSubject, $sBody, $sFromName, $sFromAddress, $sReplyToName, $sReplyToAddress) {
$sMessage = <<<TEST
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Email</title>
</head>
<body>
{$sBody}
</body>
</html>
TEST;
$sHeaders = "From: " . $sFromName . "<" . $sFromAddress . ">" . "\r\n";
$sHeaders .= "Reply-To: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
$sHeaders .= "Bcc: " . $sReplyToName . "<" . $sReplyToAddress . ">" . "\r\n";
$sHeaders .= "Content-Type: text/html" . "\r\n";
mail($sToAddress, $sSubject, $sMessage, $sHeaders);
}
public static function shortMD5($sValue) {
return strtolower(substr(md5($sValue), 0, 5));
}
public static function overrideFirstWithSecond($sFirst, $sSecond) {
if(trim($sSecond) !== "") {
return $sSecond;
}
return "" . $sFirst;
}
public function parseTemplateCodePhp($sCode, $aMarkers) {
extract($aMarkers);
ob_start();
echo eval('?>' . $sCode . '<?');
$sHtml = ob_get_contents();
ob_end_clean();
return $sHtml;
}
public static function stackMarkers($aMarkers) {
array_push($GLOBALS["TEMPLATESTACK"], $aMarkers);
}
public static function unstackMarkers() {
array_pop($GLOBALS["TEMPLATESTACK"]);
}
public static function &getMarkers() {
if(count($GLOBALS["TEMPLATESTACK"]) === 0) {
return FALSE;
}
return $GLOBALS["TEMPLATESTACK"][count($GLOBALS["TEMPLATESTACK"]) - 1];
}
public static function parseTemplateCode($sCode, $aMarkers) {
self::stackMarkers($aMarkers);
$sPattern = '/{([^\{\}\n]*)}/';
$sCode = preg_replace_callback(
$sPattern,
"self::processMarkersCallBackClearNotUsed",
$sCode,
-1 // no limit
);
self::unstackMarkers();
return $sCode;
}
public static function processMarkersCallBackClearNotUsed($aMatch) {
return self::resolveForTemplate($aMatch[1], self::getMarkers());
}
public static function resolveForTemplate($sSearch, $aMarkers) {
$aSearchParts = explode(".", $sSearch);
$sSearchPart = array_shift($aSearchParts);
if(\Flake\Util\Tools::is_a($aMarkers, "\Flake\Core\Model")) {
$aMarkers = $aMarkers->getData();
}
if(!array_key_exists($sSearchPart, $aMarkers)) {
return "";
}
if(count($aSearchParts) > 0) {
return self::resolveForTemplate(implode(".", $aSearchParts), $aMarkers[$sSearchPart]);
}
return $aMarkers[$sSearchPart];
}
public static function is_a($object, $class) {
if(is_object($object)) return $object instanceof $class;
if(is_string($object)){
if(is_object($class)) $class=get_class($class);
if(class_exists($class)) return is_subclass_of($object, $class) || $object==$class;
if(interface_exists($class)) {
$reflect = new \ReflectionClass($object);
return $reflect->implementsInterface($class);
}
}
return false;
}
public static function HTTPStatus($iCode, $sMessage) {
header("HTTP/1.1 404 Not Found");
header("Status: 404 Not Found");
die("<h1>HTTP Status " . $iCode . " : " . $sMessage . "</h1>");
}
public static function number2Rank($a) {
$a = intval($a);
if ($a === 1) {
return "premier";
} elseif($a === 2) {
return "second";
}
$sNumber = self::number2Human($a);
$sLastLetter = substr($sNumber, -1, 1);
if($sLastLetter === "e") {
$sNumber = substr($sNumber, 0, -1);
} elseif($sLastLetter === "q") {
$sNumber = $sNumber . "u";
} elseif($sLastLetter === "f") {
$sNumber = substr($sNumber, 0, -1) . "v";
}
return $sNumber . "ième";
}
public static function number2Human($a) {
$temp = explode('.',$a);
if (isset($temp[1]) && $temp[1]!='') {
return self::number2Human($temp[0]).' virgule '.self::number2Human($temp[1]) ;
}
if ($a<0) return 'moins '.self::number2Human(-$a);
if ($a<17) {
switch ($a) {
case 0: return 'zero';
case 1: return 'un';
case 2: return 'deux';
case 3: return 'trois';
case 4: return 'quatre';
case 5: return 'cinq';
case 6: return 'six';
case 7: return 'sept';
case 8: return 'huit';
case 9: return 'neuf';
case 10: return 'dix';
case 11: return 'onze';
case 12: return 'douze';
case 13: return 'treize';
case 14: return 'quatorze';
case 15: return 'quinze';
case 16: return 'seize';
}
} else if ($a<20) {
return 'dix-' . self::number2Human($a-10);
} else if ($a<100) {
if ($a%10==0) {
switch($a) {
case 20: return 'vingt';
case 30: return 'trente';
case 40: return 'quarante';
case 50: return 'cinquante';
case 60: return 'soixante';
case 70: return 'soixante-dix';
case 80: return 'quatre-vingt';
case 90: return 'quatre-vingt-dix';
}
} elseif(substr($a, -1) == 1) {
if( ((int)($a/10)*10)<70 ) {
return self::number2Human((int)($a/10)*10).'-et-un';
} elseif ($a==71) {
return 'soixante-et-onze';
} elseif ($a==81) {
return 'quatre-vingt-un';
} elseif ($a==91) {
return 'quatre-vingt-onze';
}
} elseif ($a<70) {
return self::number2Human($a-$a%10).'-'.self::number2Human($a%10);
} elseif ($a<80) {
return self::number2Human(60).'-'.self::number2Human($a%20);
} else {
return self::number2Human(80).'-'.self::number2Human($a%20);
}
} else if ($a==100) {
return 'cent';
} else if ($a<200) {
return self::number2Human(100).' '.self::number2Human($a%100);
} else if ($a<1000) {
return self::number2Human((int)($a/100)).' '.self::number2Human(100).' '.self::number2Human($a%100);
} else if ($a==1000) {
return 'mille';
} else if ($a<2000) {
return self::number2Human(1000).' '.self::number2Human($a%1000).' ';
} else if ($a<1000000) {
return self::number2Human((int)($a/1000)).' '.self::number2Human(1000).' '.self::number2Human($a%1000);
}
}
public static function stringToUrlToken($sString) {
# Taken from TYPO3 extension realurl
$space = "-";
$sString = strtr($sString, ' -+_\'', $space . $space . $space . $space . $space); // convert spaces
$sString = iconv('UTF-8', 'ASCII//TRANSLIT', $sString);
$sString = strtolower($sString);
$sString = preg_replace('/[^a-zA-Z0-9\\' . $space . ']/', '', $sString);
$sString = preg_replace('/\\' . $space . '{2,}/', $space, $sString); // Convert multiple 'spaces' to a single one
$sString = trim($sString, $space);
return $sString;
}
public static function isCliPhp() {
return strtolower(php_sapi_name()) === "cli";
}
public static function getIP() {
$alt_ip = $_SERVER['REMOTE_ADDR'];
if(isset($_SERVER['HTTP_CLIENT_IP'])) {
$alt_ip = $_SERVER['HTTP_CLIENT_IP'];
} else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
// make sure we dont pick up an internal IP defined by RFC1918
foreach($matches[0] AS $ip) {
if (!preg_match('#^(10|172\.16|192\.168)\.#', $ip)) {
$alt_ip = $ip;
break;
}
}
} else if (isset($_SERVER['HTTP_FROM'])) {
$alt_ip = $_SERVER['HTTP_FROM'];
}
return $alt_ip;
}
public static function getUserAgent() {
return $_SERVER['HTTP_USER_AGENT'];
}
}

View file

@ -0,0 +1 @@
../../Frameworks/BaikalAdmin/Resources

View file

@ -0,0 +1 @@
# Resources must be symlinked relatively to this folder

0
INSTALL.md Normal file → Executable file
View file

0
LICENSE.txt Normal file → Executable file
View file

0
README.md Normal file → Executable file
View file

View file

@ -1 +0,0 @@
../Core/WWWRoot/admin

1
html/admin/index.php Symbolic link
View file

@ -0,0 +1 @@
../../Core/Frameworks/BaikalAdmin/index.php

View file

@ -1 +0,0 @@
../Core/WWWRoot/res

1
html/res/core Symbolic link
View file

@ -0,0 +1 @@
../../Core/Resources/Web