header { background: #999; margin: -2em -2em 1em -2em; border-radius: .5em; overflow: hidden; border-bottom: .2em solid #666; text-align: center; } #global > header h1 { font-weight: normal; font-size: 400%; margin: -.2em; } #global > header h1 a { color: #666; text-decoration: none; display: block; } #global > header h1 a:hover { color: #fff; transition: .5s; } #global > header h2 { font-weight: normal; } #global > footer { border-top: .2em solid #666; padding: .5em; font-size: 0.9em; color: #ccc; margin: 1em -2em -2em -2em; border-radius: .5em; background: #999; } #global > footer a { color: #fff; } #global > footer form { float: right; } #global > footer form p { display: inline; } input, textarea { padding: .2em; } fieldset input[type=text], fieldset textarea { width: 99%; } fieldset dt { margin: .5em 0; } fieldset dd { margin-left: 1em; } a.rss { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOBAMAAADtZjDiAAAABGdBTUEAAK/INwWK6QAAADBQTFRF200A2kwA20wA10sA22Mi54Mg8ogf5Hgi+Jo49tq9445J8L+U6Ykz559o3E0A////BRDeewAAAAR0Uk5Ti+zi8Y/BLYAAAAB4SURBVAjXY2BQFBISFFRgYPG9VZPT1sDwZP3/nys6+hhM1t6e/xtIi/uuOTt/BpC//uedmz/yGMRX7/+d9TuPQeZM1Pyr+/MYTNPSzk+9v47BfP+06D/37zGY//8W/ev+PgbT/duirqzyY1BNSwsvcXFiYHoHAgwAS6I2i3LYZakAAAAASUVORK5CYII=") no-repeat left center; padding-left: 20px; } .powered { font-size: .9em; } fieldset { border: .1em solid #999; border-radius: .5em; padding: .5em 1em; margin: 1em; } #content header { text-align: center; margin: 1em 0; } #content header a { color: inherit; text-decoration: inherit; } #content article .content { max-width: 60em; margin: 1em auto; } #content footer { text-align: center; font-size: .9em; } .admin { color: red !important; background: yellow; padding: .2em .5em; display: inline-block; border-radius: .5em; } .tips, .error { background: #ddd; border-radius: .5em; padding: 1em; text-align: center; } dl.tips dt { font-weight: bold; } dl.tips dd { margin: 0.5em; } .error { color: red; font-size: 200%; } .content p, .content ul, .content ol, .content pre, .content blockquote, .content table, .content h1, .content h2, .content h3, .content h4, .content h5, .content h6 { margin: .5em 0; line-height: 120%; } .content ul, .content ol { margin-left: 2em; } .content sub, .content sup { line-height: 0; font-size: .8em; } .content table, .content tr, .content th, .content td { border: 1px solid #999; border-collapse: collapse; padding: .2em; background: #fff; } .content table th { background: #ddd; } .content .footnotes, .content pre, .content blockquote { background: #ddd; border-radius: .5em; padding: .5em; } .content .footnotes { font-size: .9em; margin-top: 1.5em; } .content hr { border: 1px solid #999; } @media screen and (max-width: 800px), handheld { #global > footer { text-align: center; } #global > footer form { float: none; } #global > footer form p { display: block; margin: .5em; } } CSS_EOF; // Serve the default stylesheet if (!empty($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] == 'style.css') { header('Content-Type: text/css'); header('Date: ' . date('D, j M Y G:i:s ') . 'GMT'); header('Content-Type: text/css'); header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600*24*365) . ' GMT'); header('Cache-Control: max-age=31536000, must-revalidate'); header('Pragma: cache'); echo $default_css; exit; } class picoBlog { public $file = 'data.json'; public $bypage = 20; public $reverse_order = true; public $title = 'My picoBlog'; public $desc = 'Another useless blog'; public $locale = 'en_AU'; public $date_format = '%A %e %B %Y at %k:%M'; public $url = ''; public $root_url = ''; public $user_login = 'admin'; public $user_password = 'abcd'; public $markup = 'bbcode'; public $skriv = false; protected $posts = []; const VERSION = '2.0.0'; public function __construct() { $this->file = __DIR__ . '/' . $this->file; $this->skriv = file_exists(__DIR__ . '/SkrivLite.php'); $this->getUrl(); } public function login($login, $password) { if ($login != $this->user_login || $password != $this->user_password) return false; @session_start(); $_SESSION['logged'] = true; return true; } public function logout() { @session_start(); $_SESSION = []; } public function isLogged() { @session_start(); if (!empty($_SESSION['logged'])) return true; if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_USER'] == $this->user_login && $_SERVER['PHP_AUTH_PW'] == $this->user_password) { return true; } return false; } public function getUrl($id = null, $title = null) { if (empty($this->url)) { $proto = empty($_SERVER['HTTPS']) ? 'http' : 'https'; $host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; $this->url = $proto . '://' . $host . $_SERVER['REQUEST_URI']; $this->url = preg_replace('/[\?&].*$/', '', $this->url); $this->root_url = preg_replace('!/.*?$!', '/', $this->url); } $url = $this->url; if ($id) { $url .= '?' . $this->getUri($id, $title); } return $url; } public function getUri($id = null, $title = null) { return base_convert($id, 10, 36) . '-' . (empty($title) ? 'post' : trim(preg_replace('/[^\w\d_.-]+/u', '-', $title), '-')); } public function formatText($text, $markup = null) { if (is_null($markup)) { $markup = $this->markup; } if ($markup == 'skriv') { if ($this->skriv === true) { require_once __DIR__ . '/SkrivLite.php'; $this->skriv = new \KD2\SkrivLite; } $text = $this->skriv->render($text); } else { $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); // Links on URLs $text = preg_replace('!(?<=\s|^)((ftp|http|https)://([^\s]+))!um', '[url]$1[/url]', $text); // BBCode $text = preg_replace('#(?', $text); $text = preg_replace('#(?\\1', $text); $text = preg_replace('#(?\\1', $text); if ($markup != 'bbcode_comments') { $text = preg_replace_callback('#(?'; }, $text); $text = preg_replace_callback('#(?' . $match[2] . ''; }, $text); } $text = nl2br($text); } return $text; } public function loadData() { if (file_exists($this->file)) { $content = file_get_contents($this->file); $content = json_decode($content, true); if (is_null($content)) throw new Exception('JSON decoding error code: ' . json_last_error()); $this->posts = $content['posts']; unset($content); $this->sortPosts(); return true; } return false; } public function sortPosts($force_reverse = null) { if ($this->reverse_order || $force_reverse) krsort($this->posts); else ksort($this->posts); } public function writeData() { $data = [ 'posts' => $this->posts, ]; return @file_put_contents($this->file, json_encode($data, JSON_PRETTY_PRINT)); } public function getPost($id) { return array_key_exists($id, $this->posts) ? $this->posts[$id] : false; } public function listPosts($begin=0) { return array_slice($this->posts, $begin, $this->bypage, true); } public function editPost($id, $title, $text) { $this->posts[(int)$id] = ['t' => trim($title), 'c' => trim($text)]; } public function deletePost($id) { unset($this->posts[(int)$id]); } public function getPagination() { if (count($this->posts) <= $this->bypage) return false; $pages = ceil(count($this->posts) / $this->bypage); return $pages; } } class picoConf { public $file = 'userconfig.php'; public function __construct() { $this->file = dirname(__FILE__) . '/' . $this->file; } public function write($config) { $out = '$value) { $value = strtr($value, ['$' => '\\$', '"' => '\\"']); $out .= '$pb->'.$key.' = "'.$value."\";\n"; } $out.= '?>'; if (!@file_put_contents($this->file, $out)) return false; return true; } } $pb = new picoBlog; // Loading user config if (file_exists(dirname(__FILE__) . '/userconfig.php')) { require_once dirname(__FILE__) . '/userconfig.php'; } // Migrate post data from PHP array (version 1.0.x) to JSON (1.1.x) if (file_exists(__DIR__ . '/datas.php')) { require __DIR__ . '/datas.php'; $json = [ 'posts' => [], ]; // Convert to UTF-8 if needed foreach ($datas as $id=>$post) { // Clever trick, see http://php.net/manual/en/reference.pcre.pattern.modifiers.php // "UTF-8 validity of the pattern is checked since PHP 4.3.5" (when using /u modifier) if (!preg_match('!!u', $post)) { $post = utf8_encode($post); } $json['posts'][(int)$id] = ['t' => null, 'c' => $post]; } file_put_contents($pb->file, json_encode($json, JSON_PRETTY_PRINT)); unset($datas, $json); echo "

Your blog has been upgraded (data migrated to JSON format)!

"; if (!@rename(__DIR__ . '/datas.php', __DIR__ . '/datas_legacy_1.0.x.php')) { die("

Please rename or delete the file datas.php now.

"); } else { die("

The original posts data store has been renamed to datas_legacy_1.0.x.php.

" . "

Please reload this page to continue.

"); } } // Loading data or create dummy post if (!$pb->loadData()) { $pb->editPost(time(), "Welcome to picoBlog!", "Welcome to your [url=http://dev.kd2.org/picoblog/]picoBlog[/url].\n\n". "Edit this post to see a bit how this thing works. Oh, I forgot to tell you, the default ". "login is [b]admin[/b] and the password is [b]abcd[/b]. I suggest you ". "to login and change them right now in the configuration."); if (!$pb->writeData()) die("Can't write to ".$pb->file); header('Location: '.$pb->url.'?new'); exit; } // Post REST API! if ($_SERVER['REQUEST_METHOD'] == 'PUT') { if (!$pb->isLogged()) { header('HTTP/1.1 401 Unauthorized', true, 401); die("Wrong login/password or no login/password supplied.\n"); } $content = file_get_contents('php://input'); if ($content == '') { die("No content was supplied. Please supply a body to your request.\n"); } $id = time(); $pb->editPost($id, null, $content); $pb->writeData(); $url = $pb->getUrl($id); header('Location: ' . $url, true, 302); die("Post saved to " . $url . "\n"); } // For translating things setlocale(LC_TIME, $pb->locale); ///////////// PAGES /////////////////////////////////////////////////////////////////////////////// // Stop here if this file has been included, it means we just want to use // picoBlog as a blogging backend but we will provide out own frontend $files = get_included_files(); if (count($files) > 1 && $files[0] != __FILE__) { return; } function __post($key) { return array_key_exists($key, $_POST) ? $_POST[$key] : null; } function escape($str) { return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); } function escapeXML($str) { return htmlspecialchars($str, ENT_XML1, 'UTF-8'); } function html_head($title = null, $body_id = null) { global $pb; if (file_exists(__DIR__ . '/userstyle.css')) $css = $pb->root_url . 'userstyle.css'; else $css = $pb->url . '?style.css'; echo ' '.escape($title), '

'.escape($pb->title).'

'.$pb->desc.'

'; } function html_foot() { global $pb; echo '
'; exit; } function html_post($id, $post) { global $pb; echo '
'; $date = strftime($pb->date_format, $id); $title = (trim($post['t']) === '') ? $date : $post['t']; echo '

' . escape($title) . '

'; if ($date != $title) echo '
'.strftime($pb->date_format, $id).'
'; echo '
'.$pb->formatText($post['c']).'
'; } // Login process if (isset($_GET['login'])) { if ($pb->login($_POST['login'], $_POST['password'])) { header('Location: '.$pb->url); exit; } html_head(); echo '

Wrong login or password. Try again!

'; html_foot(); } // Logout elseif (isset($_GET['logout'])) { $pb->logout(); html_head(); echo '

You have been disconnected.

'; html_foot(); } // User configuration elseif (isset($_GET['config']) AND $pb->isLogged()) { if (__post('save')) { $config = [ 'title' => __post('title'), 'desc' => __post('desc'), 'locale' => preg_match('/^[a-z]{2}_[A-Z]{2}$/', __post('locale')) ? __post('locale') : $pb->locale, 'date_format' => __post('date_format'), 'bypage' => (int) __post('bypage'), 'reverse_order' => (bool) __post('reverse_order'), 'user_login' => __post('user_login'), 'user_password' => __post('user_password'), 'markup' => __post('markup'), ]; $pc = new picoConf; if (!$pc->write($config)) { die("Can't write to ".$pc->file); } header('Location: '.$pb->url); exit; } html_head('Configuration', 'config'); echo '

Configuration - picoBlog '.$pb::VERSION.'

Why don\'t you check for a new version?

Do you know that you can change the style of your picoBlog ?
Just create a new file called userstyle.css in your picoBlog directory and edit it
Bored of your own style ? Just remove or rename the file.
Blog informations
Blog title
Blog description (HTML allowed)
Language informations
Locale (eg. en_GB or fr_FR)
Date format (strftime format)
Blog preferences
Number of entries by page
Order of entries

Posts markup

'.(!$pb->skriv ? '(requires SkrivLite.php, see documentation)' : '').'
Login informations
Login
Password

'; html_foot(); } // Deleting a post elseif (isset($_GET['delete']) AND $pb->isLogged()) { $pb->deletePost($_GET['delete']); if (!$pb->writeData()) { die("Can't write to ".$pb->file); } header('Location: '.$pb->url); exit; } // Editing a post elseif (isset($_GET['edit']) && $pb->isLogged()) { if (!empty($_POST['text'])) { if (empty($_GET['edit'])) $id = time(); else $id = (int) $_GET['edit']; if (__post('date')) { $new_date = strtotime(__post('date')); if ((int)$new_date != (int)$_GET['edit'] && !empty($new_date)) { $pb->deletePost($_GET['edit']); $id = $new_date; } } $pb->editPost($id, __post('title'), __post('text')); if (!$pb->writeData()) { die("Can't write to ".$pb->file); } $url = $pb->getUrl($id, __post('title')); echo '

Entry saved. Redirecting…

'; exit; } html_head($pb->title); if (empty($_GET['edit'])) { echo '

New post

'; $post = [ 't' => 'A new post', 'c' => 'Describe your post here. BBcode is [b]allowed[/b]. URLs are automatically converted.', ]; $date = date('Y-m-d H:i:s'); } else { echo '

Edit #'.(int)$_GET['edit'].'

'; $post = $pb->getPost($_GET['edit']); $date = date('Y-m-d H:i:s', (int)$_GET['edit']); } echo '
Title:
Content:
'; if ($pb->markup == 'skriv') echo 'You can use SkrivML to format your text.'; else echo 'You can use BBcode to format your text. ' .'The following tags are allowed: [b], [i], [s], [u], [quote], [url], [code] and [img]'; echo '

'; html_foot(); } // RSS feed elseif (isset($_GET['rss'])) { $pb->reverse_order = true; $pb->sortPosts(true); $list = $pb->listPosts(0); $last_update = array_keys($list); $last_update = date(DATE_RSS, $last_update[0]); header('Content-Type: text/xml'); echo ' '.escapeXML($pb->title).' '.escapeXML($pb->desc).' '.$pb->url.' '.substr($pb->locale, 0, 2).' '.$last_update.' daily 1 '.$last_update.' '; foreach ($list as $id=>$post) { echo ' '; } echo ' '; foreach ($list as $id=>$post) { echo ' '.escapeXML($post['t'] ?: '#' . $id).' '.$pb->getUrl($id, $post['t']).' '.date(DATE_RSS, $id).' '.substr($pb->locale, 0, 2).' '.escapeXML($pb->formatText($post['c'])).' '; } echo ' '; exit; } // Permalink to a post elseif (!empty($_SERVER['QUERY_STRING']) && preg_match('/^([\d\w]+)-.*/', $_SERVER['QUERY_STRING'], $match)) { $id = (int) base_convert($match[1], 36, 10); $post = $pb->getPost($id); $uri = $pb->getUri($id, $post['t']); // Protect against wrong URIs and redirect to the right one if ($uri != $match[0]) { header('Location: ' . $pb->getUrl($id, $post['t'])); exit; } html_head($post['t']); if (!$post) echo '

Can\'t find this post.

'; else { html_post($id, $post); } html_foot(); } // Entries by page else { $page = 1; if (!empty($_SERVER['QUERY_STRING']) && preg_match('/^p([0-9]+)$/', $_SERVER['QUERY_STRING'], $match)) { $page = (int) $match[1]; } $begin = ($page - 1) * $pb->bypage; $list = $pb->listPosts($begin); html_head($pb->title); if ($pb->isLogged()) echo '

New post

'; if (empty($list)) echo '

No item.

'; else { foreach ($list as $id=>$post) { html_post($id, $post); } } $pages = $pb->getPagination(); if (!empty($pages)) { echo ' '; } html_foot(); }