From c98515883097467896a3f46b755c8cb892fe8961 Mon Sep 17 00:00:00 2001
From: Ludovic Pouzenc <lpouzenc@gmail.com>
Date: Tue, 29 Oct 2013 18:42:08 +0100
Subject: Import initial avec une arbrescence éclatée
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 code/admin/add.php               |  55 ++++++++++++
 code/admin/admin.css             |  15 ++++
 code/admin/admin.js              | 123 +++++++++++++++++++++++++++
 code/admin/ajax.php              |  63 ++++++++++++++
 code/admin/auth.php              |  53 ++++++++++++
 code/admin/editor-bind-code.html |  71 ++++++++++++++++
 code/admin/index.php             | 111 ++++++++++++++++++++++++
 code/admin/render.php            |  59 +++++++++++++
 code/admin/utils.php             | 179 +++++++++++++++++++++++++++++++++++++++
 code/index.php                   |   9 ++
 10 files changed, 738 insertions(+)
 create mode 100644 code/admin/add.php
 create mode 100644 code/admin/admin.css
 create mode 100644 code/admin/admin.js
 create mode 100644 code/admin/ajax.php
 create mode 100644 code/admin/auth.php
 create mode 100644 code/admin/editor-bind-code.html
 create mode 100644 code/admin/index.php
 create mode 100644 code/admin/render.php
 create mode 100644 code/admin/utils.php
 create mode 100644 code/index.php

(limited to 'code')

diff --git a/code/admin/add.php b/code/admin/add.php
new file mode 100644
index 0000000..c839e0a
--- /dev/null
+++ b/code/admin/add.php
@@ -0,0 +1,55 @@
+<?php
+	require_once('utils.php');
+	need_auth();
+
+	// Config loading
+	$site_conf = load_ini_site_conf("content/site_conf.ini"); 
+	if ( ! is_array($site_conf) ) trigger_error("Error parsing site_conf.ini", E_USER_ERROR);
+
+	// Localization Init
+	l10n_init($site_conf['site_admin_lang']);
+
+	$kind=sanitize($_GET, 'kind', '/[^a-z_]+/', 'page'); /* Could be : page, media */
+?>
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="treeview.css">
+<link rel="stylesheet" href="admin.css">
+<script type="text/javascript" src="admin.js"></script>
+<title><?=($kind=='media')?_('Add media'):_('Add page')?></title>
+</head>
+<body>
+<form>
+
+<fieldset>
+<legend><?=($kind=='media')?_('Media tree'):_('Page tree')?></legend>
+<div class="css-treeview">
+<?php
+	if ($kind=='media') {
+		$tree=find_all('../media', 'media');
+	} else {
+		$tree=find_all('./content', 'page');
+	}
+	php_array_to_tree($tree, 'select_fold');
+?>
+</div>
+</fieldset>
+
+<fieldset>
+<legend><?=($kind=='media')?_('Media folder'):_('Page folder')?></legend>
+<label for="fold_path"><?=_('Folder path')?></label>
+<input id="fold_path" name="fold_path" readonly="readonly" value="<?=_('(choose a folder in the tree)')?>"><br>
+
+<label for="fold_add_name"><?=_('New item name')?></label>
+<input id="fold_add_name" type="text" value="">
+
+<label for="fold_add_fold"><?=_('Actions')?></label>
+<input id="fold_add_fold" type="button" value="<?=_('Add folder')?>" onclick="go_add('<?=$kind?>','fold');">
+<input id="fold_add_item" type="button" value="<?=_('Add page')?>" onclick="go_add('<?=$kind?>','item');">
+</fieldset>
+
+</form>
+</body>
+</html>
diff --git a/code/admin/admin.css b/code/admin/admin.css
new file mode 100644
index 0000000..f01a35d
--- /dev/null
+++ b/code/admin/admin.css
@@ -0,0 +1,15 @@
+fieldset {
+	display:inline-block;
+	vertical-align: top;
+	margin: 0.5em;
+	width:45%;
+}
+fieldset>label {
+	display:inline-block;
+	text-align:right;
+	width: 12em;
+}
+.tree_add {
+	display:block;
+	float:right;
+}
diff --git a/code/admin/admin.js b/code/admin/admin.js
new file mode 100644
index 0000000..3d0125d
--- /dev/null
+++ b/code/admin/admin.js
@@ -0,0 +1,123 @@
+// Helpers
+function gEBId(id) { return document.getElementById(id) };
+
+function microAjaxJSON(query,callback) {
+	microAjax(query,function (res) {
+		var parsed_json;
+		try {
+			parsed_json = JSON.parse(res);
+		} catch(err) {
+			alert(res);
+			return;
+		}
+		callback(parsed_json);
+	});
+}
+
+// Admin simple actions
+function select_fold(path) {
+	gEBId("fold_path").value=path;
+}
+
+// Admin AJAX actions
+function load_page_props(path) {
+	gEBId("page_path").value=path;
+	var url = "ajax.php?action=load_page_props&path=" + encodeURIComponent(path);
+	microAjaxJSON(url, function (parsed_json) {
+		gEBId("page_title").value = parsed_json.page_title;
+		gEBId("page_description").value = parsed_json.page_description;
+		gEBId("page_keywords").value = parsed_json.page_keywords;
+	});
+}
+
+function load_media_props(path) {
+	gEBId("media_path").value=path;
+	var url = "ajax.php?action=load_media_props&path=" + encodeURIComponent(path);
+	microAjaxJSON(url, function (parsed_json) {
+		gEBId("media_title").value = parsed_json.media_title;
+		gEBId("media_description").value = parsed_json.media_description;
+		//gEBId("media_keywords").value = parsed_json.media_keywords;
+	});
+}
+
+function save_page_props() {
+	var path = gEBId("page_path").value;
+	var page_title = gEBId("page_title").value;
+	var page_description = gEBId("page_description").value;
+	var page_keywords = gEBId("page_keywords").value;
+
+	//TODO : check against regex
+
+	var url = "ajax.php?action=save_page_props"
+			+ "&path=" + encodeURIComponent(path)
+			+ "&page_title=" + encodeURIComponent(page_title)
+			+ "&page_description=" + encodeURIComponent(page_description)
+			+ "&page_keywords=" + encodeURIComponent(page_keywords);
+
+	microAjaxJSON(url, function (parsed_json) {
+		if ( parsed_json.result != "OK" ) {
+			alert("Error\nResult: " + parsed_json.result + "\nRequest: " + url);
+			return;
+		}
+		//TODO : says to user that the work is done
+	});
+}
+
+function save_media_props() {
+	var path = gEBId("media_path").value;
+	var title = gEBId("media_title").value;
+	var description = gEBId("media_description").value;
+	//var keywords = gEBId("media_keywords").value;
+
+	//TODO : check against regex
+
+	var url = "ajax.php?action=save_media_props"
+			+ "&path=" + encodeURIComponent(path)
+			+ "&title=" + encodeURIComponent(title)
+			+ "&description=" + encodeURIComponent(description);
+	//		+ "&keywords=" + encodeURIComponent(keywords);
+
+	microAjaxJSON(url, function (parsed_json) {
+		if ( parsed_json.result != "OK" ) {
+			alert("Error\nResult: " + parsed_json.result + "\nRequest: " + url);
+			return;
+		}
+		//TODO : says to user that the work is done
+	});
+}
+
+function save_site_props() {
+	//TODO
+}
+
+// Admin other actions (with page change or refresh)
+function go_add_form(kind) {
+	document.location = 'add.php?kind=' + encodeURIComponent(kind);
+}
+
+function go_add(kind,type) {
+	var path = gEBId("fold_path").value;
+	var name = gEBId("fold_add_name").value;
+	// TODO : check name and path against regex
+	var url = 'add.php?kind=' + encodeURIComponent(kind)
+		+ '&action=add_' + encodeURIComponent(type)
+		+ '&path=' + encodeURIComponent(path)
+		+ '&name=' + encodeURIComponent(name);
+	document.location = url;
+}
+
+function go_edit_page() {
+	var path = gEBId("page_path").value;
+	document.location = 'render.php?action=edit&page=' + encodeURIComponent(path);
+}
+
+function go_delete_page() {
+	var path = gEBId("page_path").value;
+	//TODO : confirmation, ajax query, if OK, confirm then refresh
+}
+
+function go_delete_media() {
+	var path = gEBId("page_path").value;
+	//TODO : confirmation, ajax query, if OK, confirm then refresh
+}
+
diff --git a/code/admin/ajax.php b/code/admin/ajax.php
new file mode 100644
index 0000000..0893843
--- /dev/null
+++ b/code/admin/ajax.php
@@ -0,0 +1,63 @@
+<?php
+	require_once('utils.php');
+	need_auth();
+
+	function load_page_props($path) {
+		return load_ini_page_props($path);
+	}
+
+	function load_media_props($path) {
+		return array(
+			'media_title' => 'test title', 
+			'media_description' => 'test descr',
+			'media_keywords' => 'test, keyword',
+		 );
+	}
+
+	function save_page_props($path) {
+		//TODO : Should validate props here also...
+		$props=load_page_props($path);
+
+		foreach ( array('page_title', 'page_description', 'page_keywords') as $k ) {
+			if ( array_key_exists($k,$_GET) ) $props[$k]=$_GET[$k];
+		}
+
+		$ini_path="content/$path/props.ini";
+		return write_ini_file($props, $ini_path, false);
+	}
+
+	function save_media_props($path) {
+		return FALSE;
+	}
+
+	// URL params clean-up
+	$action=sanitize($_GET, 'action', '/[^a-z_]+/', 'none'); /* Could be : load_page_props, load_media_props... */
+	$path=sanitize($_GET, 'path', '/[^a-z0-9\/]+/', ''); // Never put \. in this regex
+
+	switch($action) {
+		case 'load_page_props':
+			$res = load_page_props($path);
+			break;
+		case 'load_media_props':
+			$res = load_media_props($path);
+			break;
+		case 'save_page_props':
+			if ( save_page_props($path) ) {
+				$res=array('result' => 'OK');
+			} else {
+				$res=array('result' => 'FAILED');
+			}
+			break;
+		case 'save_media_props':
+			if ( save_media_props($path) ) {
+				$res=array('result' => 'OK');
+			} else {
+				$res=array('result' => 'FAILED');
+			}
+			break;
+		default:
+			$res = array('result' => 'ERROR', 'error'=>'invalid action');
+		break;
+	}
+	echo json_encode($res);
+?>
diff --git a/code/admin/auth.php b/code/admin/auth.php
new file mode 100644
index 0000000..f4afee0
--- /dev/null
+++ b/code/admin/auth.php
@@ -0,0 +1,53 @@
+<?php
+	// Edit secrets here
+	$configured_user = 'admin';
+	$configured_pass = 'admin';
+
+	// Auth validation
+	$auth_fail=FALSE;
+	if ( array_key_exists('u', $_POST) && array_key_exists('p', $_POST) ) {
+		if ( $_POST['u'] === $configured_user && $_POST['p'] === $configured_pass ) {
+			// Auth success
+			session_start();
+			$_SESSION['auth_user'] = TRUE;
+			// Auto-redirect to previous page
+			if ( array_key_exists('auth_return', $_SESSION) && (strlen($_SESSION['auth_return']) > 0) ) {
+				header('Location: ' . $_SESSION['auth_return']);
+			}
+			echo "Authenticated\n";
+			exit();
+		} else {
+			$auth_fail=TRUE;
+		}
+	}
+?>
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Authentification</title>
+</head>
+<body>
+<form method="post">
+	<fieldset style="float:left">
+		<legend>Authentification</legend>
+		<table>
+			<tr>
+				<td><label>User</label></td>
+				<td><input type="text" name="u" value="admin"></td>
+			</tr>
+			<tr>
+				<td><label>Pass</label></td>
+				<td><input type="password" name="p" value="admin"></td>
+			</tr>
+			<tr>
+				<td colspan="2" align="right">
+					<span><?php if ($auth_fail) echo "Login failed";?></span>
+					<input type="submit" value="Login">
+				</td>
+			</tr>
+		</table>
+	</fieldset>
+</form>
+</body>
+</html>
diff --git a/code/admin/editor-bind-code.html b/code/admin/editor-bind-code.html
new file mode 100644
index 0000000..d6a9825
--- /dev/null
+++ b/code/admin/editor-bind-code.html
@@ -0,0 +1,71 @@
+<!-- Editor deffered loading -->
+<link href="http://cdn.aloha-editor.org/latest/css/aloha.css" type="text/css" rel="stylesheet">
+<script type="text/javascript" src="http://cdn.aloha-editor.org/latest/lib/vendor/jquery-1.7.1.js"></script>
+<script type="text/javascript" src="http://cdn.aloha-editor.org/latest/lib/require.js"></script>
+<script>
+	var Aloha = window.Aloha || ( window.Aloha = {} );
+	
+	Aloha.settings = {
+		locale: 'fr',
+		plugins: {
+			format: {
+				config : [ 'b', 'i','sub','sup'],
+			  	editables : {
+					// no formatting allowed for title
+					'#title'	: [ ], 
+					// formatting for all editable DIVs
+					'div'		: [ 'b', 'i', 'del', 'sub', 'sup'  ], 
+					// content is a DIV and has class .article so it gets both buttons
+					'.article'	: [ 'b', 'i', 'p', 'title', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'removeFormat']
+			  	}
+			},
+			list: {
+			 	// all elements with no specific configuration get an UL, just for fun :)
+				config : [ 'ul' ],
+			  	editables : {
+					// Even if this is configured it is not set because OL and UL are not allowed in H1.
+					'#title'	: [ 'ol' ], 
+					// all divs get OL
+					'div'		: [ 'ol' ], 
+					// content is a DIV. It would get only OL but with class .article it also gets UL.
+					'.article'	: [ 'ul' ]
+			  	}
+			},
+			link: {
+				config : [ 'a' ],
+			  	editables : {
+					// No links in the title.
+					'#title'	: [  ]
+			  	}
+			}
+		},
+		sidebar: {
+			disabled: false
+		}
+	};
+</script>
+
+<script type="text/javascript" src="http://cdn.aloha-editor.org/latest/lib/aloha.js"
+			data-aloha-plugins="common/ui,
+								common/format,
+		                        common/table,
+		                        common/list,
+		                        common/link,
+		                        common/highlighteditables,
+		                        common/block,
+		                        common/undo,
+		                        common/image,
+		                        common/contenthandler,
+		                        common/paste,
+		                        common/commands,
+		                        common/abbr"></script>
+
+<script type="text/javascript">
+Aloha.ready(function() {
+	// mark the editable parts
+	$('#title').aloha();
+	$('#teaser').aloha();
+	$('#content').aloha();	
+});
+
+</script>
diff --git a/code/admin/index.php b/code/admin/index.php
new file mode 100644
index 0000000..ddb95b3
--- /dev/null
+++ b/code/admin/index.php
@@ -0,0 +1,111 @@
+<?php
+	require_once('utils.php');
+	need_auth();
+
+	// Config loading
+	$site_conf = load_ini_site_conf("content/site_conf.ini"); 
+	if ( ! is_array($site_conf) ) trigger_error("Error parsing site_conf.ini", E_USER_ERROR);
+
+	// Localization Init
+	l10n_init($site_conf['site_admin_lang']);
+?>
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<link rel="stylesheet" href="treeview.css">
+<link rel="stylesheet" href="admin.css">
+<script type="text/javascript" src="microajax.minified.js"></script>
+<script type="text/javascript" src="admin.js"></script>
+<title><?=_('Admin')?></title>
+</head>
+<body>
+<form>
+
+<fieldset>
+<legend><?=_('Page tree')?></legend>
+<input id="page_add" class="tree_add" type="button" value="<?=_('Add page')?>" onclick="go_add_form('page');">
+<div class="css-treeview">
+<?php
+	$page_tree=find_all('./content', 'page');
+	//echo '<pre>' . print_r($page_tree,true) . '</pre>' . "\n";
+	php_array_to_tree($page_tree, '', 'load_page_props', '', 'pagedir-0');
+?>
+</div>
+</fieldset>
+
+<fieldset>
+<legend><?=_('Selected page')?></legend>
+<label for="page_path"><?=_('Page path')?></label>
+<input id="page_path" name="page_path" readonly="readonly" value="<?=_('(choose a page in the tree)')?>"><br>
+
+<label for="page_edit"><?=_('Actions')?></label>
+<input id="page_edit" type="button" value="<?=_('Edit page')?>" onclick="go_edit_page();">
+<input id="page_delete" type="button" value="<?=_('Delete page')?>" onclick="go_delete_page();">
+
+<br><br>
+
+<label for="page_title"><?=_('Page title')?></label>
+<input id="page_title" name="page_title" value=""><br>
+
+<label for="page_description"><?=_('Page description')?></label>
+<input id="page_description" name="page_description" value=""><br>
+
+<label for="page_keywords"><?=_('Page keywords')?></label>
+<input id="page_keywords" name="page_keywords" value=""><br>
+
+<label for="page_submit"></label>
+<input id="page_submit" type="button" value="<?=_('Save properties')?>" onclick="save_page_props();"><br>
+</fieldset>
+
+<fieldset>
+<legend><?=_('Media tree')?></legend>
+<input id="media_add" class="tree_add" type="button" value="<?=_('Add media')?>" onclick="go_add_form('media');">
+<div class="css-treeview">
+<?php
+	$media_tree=find_all('../media', 'media');
+	//echo '<pre>' . print_r($media_tree,true) . '</pre>' . "\n";
+	php_array_to_tree($media_tree, '', 'load_media_props', '', 'mediadir-0');
+?>
+</div>
+</fieldset>
+
+<fieldset>
+<legend><?=_('Selected Media')?></legend>
+<label for="media_path"><?=_('Media path')?></label>
+<input id="media_path" name="media_path" readonly="readonly" value="<?=_('(choose a media in the tree)')?>"><br>
+
+<label for="media_edit"><?=_('Actions')?></label>
+<!--<input id="media_edit" type="button" value="Edit media">-->
+<input id="media_delete" type="button" value="<?=_('Delete media')?>" onclick="go_delete_media();">
+
+<br><br>
+
+<label for="media_title"><?=_('Media title')?></label>
+<input id="media_title" name="media_title" value=""><br>
+
+<label for="media_description"><?=_('Media description')?></label>
+<input id="media_description" name="media_description" value=""><br>
+<!--
+<label for="media_keywords">Media keywords</label>
+<input id="media_keywords" name="media_keywords" value=""><br>
+-->
+<label for="media_submit"></label>
+<input id="media_submit" type="button" value="<?=_('Save properties')?>" onclick="save_media_props();"><br>
+</fieldset>
+
+<fieldset>
+<legend><?=_('Site properties')?></legend>
+<label for="site_admin_lang"><?=_('Admin lang')?></label>
+<input id="site_admin_lang" name="site_admin_lang" value="<?=$site_conf['site_admin_lang']?>"><br>
+
+<label for="site_default_page"><?=_('Default page')?></label>
+<input id="site_default_page" name="site_default_page" value="<?=$site_conf['site_default_page']?>"><br>
+
+<label for="site_submit"></label>
+<input id="site_submit" type="button" value="<?=_('Save properties')?>" onclick="save_site_props();"><br>
+</fieldset>
+
+</form>
+</body>
+</html>
diff --git a/code/admin/render.php b/code/admin/render.php
new file mode 100644
index 0000000..9d4175e
--- /dev/null
+++ b/code/admin/render.php
@@ -0,0 +1,59 @@
+<?php
+	require_once('utils.php');
+	need_auth();
+
+	// Config loading
+	$site_conf = load_ini_site_conf("content/site_conf.ini"); 
+	if ( ! is_array($site_conf) ) trigger_error("Error parsing site_conf.ini", E_USER_ERROR);
+
+	// URL params clean-up
+	$action=sanitize($_GET, 'action', '/[^a-z_]+/', 'preview'); /* Could be : preview, edit, publish */
+	$page=sanitize($_GET, 'page', '/[^a-z0-9\/]+/', $site_conf['site_default_page']); // Never put \. in this regex
+
+	// Template vars init ($page, $page_path, $page_props, $page_tpl_url)
+	$page_path = "content/$page"; 
+	if ( ! is_dir($page_path) ) trigger_error("Error : page does not exists ($page)", E_USER_ERROR);
+	$page_props = load_ini_page_props($page);
+	if ( ! is_array($page_props) ) trigger_error("Error parsing page properties ($page/props.ini)", E_USER_ERROR);
+	$page_tpl_url = str_repeat('../', substr_count($page, '/')) . "admin/templates/" . $page_props['page_template'] . '/';
+
+	if ($action === 'publish') ob_start(); /* Buffering for redirecting to file instead of browser */
+
+	// Basic HTML5 skeleton + page props insertion + template call
+?>
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title><?=$page_props['page_title']?></title>
+<meta name="description" content="<?=$page_props['page_description']?>">
+<meta name="keywords" content="<?=$page_props['page_keywords']?>">
+<link rel="stylesheet" href="<?="$page_tpl_url" /*FIXME*/?>screen.css">
+</head>
+<body>
+<?php
+	require("templates/" . $page_props['page_template'] . '/layout-' . $page_props['page_layout'] . '.php');
+	if ( $action === 'edit') require('editor-bind-code.html');
+?>
+</body>
+</html>
+<?php
+	// If publishing, write resulting HTML page on a .html file
+	if ($action === 'publish') {
+		$out=ob_get_contents();
+		ob_end_clean();
+		$dest="../$page.html";
+		$destfold=dirname($dest);
+		if ( ! is_dir($destfold) ) {
+			if ( ! mkdir($destfold,0777,true) ) {
+				trigger_error("Error : Can't create '$destfold'", E_USER_ERROR);
+			}
+		}
+		$size=file_put_contents($dest, $out);
+		if ( $size === FALSE ) {
+			trigger_error("Error : Can't write " . strlen($out) . " bytes to '$dest'", E_USER_ERROR);
+		} else {
+			echo json_encode(array('result'=>'ok', 'size' => $size));
+		}
+	}
+?>
diff --git a/code/admin/utils.php b/code/admin/utils.php
new file mode 100644
index 0000000..f02146a
--- /dev/null
+++ b/code/admin/utils.php
@@ -0,0 +1,179 @@
+<?php
+	function sanitize($arg_array, $arg_key, $replace_chars_re, $default_value) {
+		//FIXME :  should check string type and strlen !
+		if ( ! array_key_exists($arg_key, $arg_array) ) return $default_value;
+		return preg_replace($replace_chars_re, '_', $arg_array[$arg_key]);
+	}
+
+	function sanitize_ini($ini_path, $array_entry_props) {
+		$array_ini = parse_ini_file($ini_path);
+		if ( is_array($array_ini) ) { 
+			// Sanitize any existing ini entries. Destroy unwanted ones.
+			foreach ( $array_ini as $k => $v ) {
+				if ( array_key_exists($k, $array_entry_props)
+					&& array_key_exists('replace_chars_re', $array_entry_props[$k])
+					&& array_key_exists('default_value', $array_entry_props[$k])
+				) {
+					$array_ini[$k] = sanitize($array_ini, $k,
+					       	$array_entry_props[$k]['replace_chars_re'],
+						$array_entry_props[$k]['default_value'] );
+				} else {
+					unset($array_ini[$k]);
+				}
+			}
+			// Set default value for all missing ini entries (if default value exists)
+			foreach ( $array_entry_props as $k => $v ) {
+				if ( !array_key_exists($k, $array_ini) && array_key_exists('default_value', $array_entry_props[$k]) ) {
+					$array_ini[$k] = $array_entry_props[$k]['default_value'];
+				}
+			}
+		}
+		return $array_ini;
+	}
+
+	function load_ini_site_conf($ini_path) {
+		$sanitize_site_conf = array(
+			'site_admin_lang' => array( 'replace_chars_re' => '/[^a-zA-Z\/\_-]+/', 'default_value' => 'C' ),
+			'site_default_page' => array( 'replace_chars_re' => '/[^a-z0-9\/]+/', 'default_value' => 'en/index' ),
+		);
+		return sanitize_ini($ini_path, $sanitize_site_conf);
+	}
+
+	function load_ini_page_props($page) {
+		$sanitize_page_props = array(
+			//FIXME : title regex : all but html special chars ?
+			'page_title'    => array( 'replace_chars_re' => '/[^\w !_,.-]+/', 'default_value' => '(missing title in props.ini)' ),
+			'page_template' => array( 'replace_chars_re' => '/[^a-z0-9]+/', 'default_value' => 'default' ),
+			'page_layout'   => array( 'replace_chars_re' => '/[^a-z0-9]+/', 'default_value' => 'article' ),
+			'page_description' => array( 'replace_chars_re' => '/[^\w !_,.-]+/', 'default_value' => '(missing description in props.ini)' ),
+			'page_keywords'    => array( 'replace_chars_re' => '/[^\w !_,.-]+/', 'default_value' => '(missing keywords in props.ini)' ),
+		);
+		$ini_path="content/$page/props.ini";
+		return sanitize_ini($ini_path, $sanitize_page_props);
+	}
+
+	function l10n_init($lang) {
+		setlocale(LC_MESSAGES, "$lang.utf8");
+		$base = bindtextdomain('editablesite', './locale');
+		$domain = textdomain('editablesite');
+		bind_textdomain_codeset('editablesite', 'UTF-8');
+		//echo "<pre>l10n file is '$base/$lang.utf8/LC_MESSAGES/$domain.mo'</pre>\n";
+	}
+
+	function need_auth() {
+		session_start();
+		if ( ! array_key_exists('auth_user', $_SESSION) || $_SESSION['auth_user'] !== TRUE ) {
+			$_SESSION['auth_return'] = $_SERVER['REQUEST_URI'];
+			header('Location: auth.php');
+			exit();
+		}
+	}
+
+	function is_ress($kind, $path) {
+		switch ($kind) {
+			case 'page':	return is_file($path.'/props.ini');
+			case 'media':	return substr($path, -4)=='.jpg' && is_file($path);
+			default :	return FALSE;
+		}
+	}
+
+	function strcmp_tree($a,$b) {
+		if (is_array($a) || is_array($b) ) return 0;
+		return strnatcasecmp($a,$b);
+	}
+
+	function find_all($path, $kind) {
+		$result=array();
+		if ( $handle = opendir($path) ) {
+			while (FALSE !== ($entry = readdir($handle))) {
+				if ( array_search($entry, array('.','..')) !== FALSE ) continue;
+				$childpath=$path.'/'.$entry;
+				if ( is_ress($kind, $childpath) ) $result[] = $entry;
+				else if ( is_dir($childpath) ) $result[$entry]=find_all($childpath,$kind);
+			}
+			closedir($handle);
+		}
+		uasort($result, 'strcmp_tree');
+		return $result;
+	}
+
+	function php_array_to_tree($page_tree, $node_cb, $leaf_cb='', $path='', $itemid="item-0") {
+		if ( ! is_array($page_tree) ) return;
+		echo "<ul>\n";
+		foreach ($page_tree as $k => $v) {
+			if ( is_numeric($k) ) {
+				// Leaf
+				if ( strlen($leaf_cb) > 0 ) {
+					echo '<li><a href="javascript:' . $leaf_cb . "('$path$v');" . '">' . $v . '</a></li>' . "\n";
+				} else {
+					echo "<li>$v</li>\n";
+				}
+			} else {
+				// Node
+				// Increment $itemid last component
+				// "1-1-2-1" will come "1-1-2-2"
+				$tmp = explode('-', $itemid);
+				array_push($tmp, array_pop($tmp) + 1);
+				$itemid = implode('-', $tmp);
+				$tmp=null;
+				/// Write the node
+				echo "<li>\n";
+				echo '<input type="checkbox" checked="checked" id="' . $itemid . '">' . "\n";
+				echo '<label for="' . $itemid . '">';
+				if ( strlen($node_cb) > 0 ) {
+					echo '<a href="javascript:' . $node_cb . "('$path$k');" . '">' . $k . '</a>';
+				} else {
+					echo $k;
+				}
+ 				echo "</label>\n";
+				// Recursively build the tree below the node
+				php_array_to_tree($v, $node_cb, $leaf_cb, $path.$k.'/', $itemid."-0");
+				echo "</li>\n";
+			}
+		}
+		echo "</ul>\n";
+	}
+
+	function safe_put_file($path, $content) {
+		//FIXME : if exists, then mktemp, put in it then rm and mv. Right preservation problems ?
+		if ($handle = fopen($path, 'w')) {
+			$res = fwrite($handle, $content);
+			fclose($handle);
+		}
+	}
+
+	function _write_ini_file_r(&$content, $assoc_arr, $has_sections)
+	{
+		foreach ($assoc_arr as $key => $val) {
+			if (is_array($val)) {
+				if($has_sections) {
+					$content .= "[$key]\n";
+					_write_ini_file_r($content, $val, false);
+				} else {
+					foreach($val as $iKey => $iVal) {
+						if (is_int($iKey))
+							$content .= $key ."[] = $iVal\n";
+						else
+							$content .= $key ."[$iKey] = $iVal\n";
+					}
+				}
+			} else {
+				if ( preg_match('/^\w+$/',$val)===1 )
+					$content .= "$key = $val\n";
+				else
+					$content .= "$key = \"" . str_replace('"', '\"', $val) . "\"\n";
+			}
+		}
+	}
+
+	function write_ini_file($assoc_arr, $path, $has_sections) {
+		$res=FALSE;
+		$content = '';
+		_write_ini_file_r($content, $assoc_arr, $has_sections);
+		if (is_string($content) && strlen($content) > 0) {
+			safe_put_file($path, $content);
+		}
+
+		return $res;
+	}
+
diff --git a/code/index.php b/code/index.php
new file mode 100644
index 0000000..b07f778
--- /dev/null
+++ b/code/index.php
@@ -0,0 +1,9 @@
+<?php
+	chdir('admin/');
+	require_once('utils.php');
+
+	// Config loading
+	$site_conf = load_ini_site_conf("content/site_conf.ini"); 
+	if ( ! is_array($site_conf) ) trigger_error("Error parsing site_conf.ini", E_USER_ERROR);
+
+	header('Location: ' . $site_conf['site_default_page'].'.html');
-- 
cgit v1.2.3