Commit: c9b98bb923a4cba867edc2994bd6a491acd9d9f6
Author: Jon Adams | Date: 2009-10-31 03:00:47 -0700
diff --git a/config/bootstrap.php b/config/bootstrap.php
new file mode 100644
index 0000000..dad85c4
--- /dev/null
+++ b/config/bootstrap.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+namespace lithium;
+
+use \lithium\core\Environment;
+use \lithium\core\Libraries;
+
+/**
+ * This is the path to the class libraries used by your application, and must contain a copy of the
+ * Lithium core. By default, this directory is named 'libraries', and resides in the same
+ * directory as your application. If you use the same libraries in multiple applications, you can
+ * set this to a shared path on your server.
+ */
+define('LITHIUM_LIBRARY_PATH', dirname(dirname(__DIR__)) . '/lithium/libraries');
+
+/**
+ * This is the path to your application's directory. It contains all the sub-folders for your
+ * application's classes and files. You don't need to change this unless your webroot folder is
+ * stored outside of your app folder.
+ */
+define('LITHIUM_APP_PATH', dirname(__DIR__));
+
+/**
+ * Locate and load Lithium core library files. Throws a fatal error if the core can't be found.
+ * If your Lithium core directory is named something other than 'lithium', change the string below.
+ */
+if (!include LITHIUM_LIBRARY_PATH . '/lithium/core/Libraries.php') {
+ $message = "Lithium core could not be found. Check the value of LITHIUM_LIBRARY_PATH in ";
+ $message .= "config/bootstrap.php. It should point to the directory containing your ";
+ $message .= "/libraries directory.";
+ trigger_error($message, E_USER_ERROR);
+}
+
+/**
+ * Add the Lithium core library. This sets default paths and initializes the autoloader. You
+ * generally should not need to override any settings.
+ */
+Libraries::add('lithium');
+
+/**
+ * Optimize default request cycle by loading common classes. If you're implementing custom
+ * request/response or dispatch classes, you can safely remove these. Actually, you can safely
+ * remove them anyway, they're just there to give slightly you better out-of-the-box performance.
+ */
+require LITHIUM_LIBRARY_PATH . '/lithium/core/Object.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/core/StaticObject.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/Collection.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/collection/Filters.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/Inflector.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/Set.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/util/String.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/core/Environment.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/http/Base.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/http/Media.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/http/Request.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/http/Response.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/http/Route.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Controller.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Dispatcher.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Request.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/action/Response.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/View.php';
+require LITHIUM_LIBRARY_PATH . '/lithium/template/view/Renderer.php';
+
+/**
+ * Add the application. You can pass a `'path'` key here if this bootstrap file is outside of
+ * your main application, but generally you should not need to change any settings.
+ */
+Libraries::add('app');
+
+/**
+ * Add some plugins
+ */
+// Libraries::add('plugin', 'li3_docs');
+
+/**
+ * This configures your session storage. The Cookie storage adapter must be connected first, since
+ * it intercepts any writes where the `'expires'` key is set in the options array. When creating a
+ * new application, it is suggested that you change the value of `'key'` below.
+ */
+
+/**
+* Session configuration
+*/
+// use \lithium\storage\Session;
+//
+// Session::config(array(
+// 'cookie' => array(
+// 'adapter' => 'Cookie',
+// 'name' => 'AppCookieName',
+// 'expires' => '+5 days',
+// 'domain' => '',
+// 'path' => '/',
+// 'filters' => array(
+// // 'Encryption' => array('key' => '0409448a5206980ab15682c3281c1a3b1fb10c55')
+// )
+// ),
+// 'default' => array('adapter' => 'Php', 'filters' => array())
+// ));
+
+/**
+ * To enable admin or plugin routing, uncomment the following lines, and see `app/config/routes.php`
+ * to enable the admin routing namespace.
+ */
+// use \lithium\action\Dispatcher;
+//
+// Dispatcher::config(array('rules' => array(
+// 'admin' => array('action' => 'admin_{:action}'),
+// 'plugin' => array('controller' => '{:plugin}.{:controller}')
+// )));
+
+/*
+ * Inflector configuration example. If your application has custom singular or plural rules, or
+ * extra non-ASCII characters to transliterate, you can configure that by uncommenting the lines
+ * below.
+ */
+// use lithium\util\Inflector;
+//
+// Inflector::rules("plural", array(
+// '/(s)tatus$/i' => '\1\2tatuses',
+// '/^(ox)$/i' => '\1\2en',
+// '/([m|l])ouse$/i' => '\1ice'
+// ));
+//
+// Inflector::rules("uninflectedPlural", array('.*[nrlm]ese', '.*deer', '.*ois', '.*pox'));
+//
+// Inflector::rules("irregularPlural", array('atlas' => 'atlases', 'brother' => 'brothers'));
+//
+// Inflector::rules("singular", array(
+// '/(s)tatuses$/i' => '\1\2tatus',
+// '/(matr)ices$/i' =>'\1ix','/(vert|ind)ices$/i'
+// ));
+
+/**
+ * Globalization (g11n) catalog configuration. The catalog allows for obtaining and
+ * writing globalized data. Each configuration can be adjusted through the following settings:
+ *
+ * - `'adapter' The name of a supported adapter. The builtin adapters are _memory_ (a
+ * simple adapter good for runtime data and testing), _gettext_, _cldr_ (for
+ * interfacing with Unicode's common locale data repository) and _code_ (used mainly for
+ * extracting message templates from source code).
+ *
+ * - `'path'` All adapters with the exception of the _memory_ adapter require a directory
+ * which holds the data.
+ *
+ * - `'scope'` If you plan on using scoping i.e. for accessing plugin data separately you
+ * need to specify a scope for each configuration, except for those using the _memory_ or
+ * _gettext_ adapter which handle this internally.
+ */
+// use lithium\g11n\Catalog;
+//
+// Catalog::config(array(
+// 'runtime' => array('adapter' => 'Memory'),
+// 'app' => array('adapter' => 'Gettext', 'path' => LITHIUM_APP_PATH . '/resources/po'),
+// 'lithium' => array('adapter' => 'Gettext', 'path' => LITHIUM_LIBRARY_PATH . '/lithium/resources/po')
+// ));
+
+/**
+ * Globalization runtime data. You can add globalized data during runtime utilizing a
+ * configuration set up to use the _memory_ adapter.
+ */
+// $data = array('en' => function($n) { return $n != 1 ? 1 : 0; });
+// Catalog::write('message.plural', $data, array('name' => 'runtime'));
+
+/**
+ * Enabling globalization integration. Classes in the framework are designed with
+ * globalization in mind. To enable globalization for these classes we just need to pass
+ * the needed data into them.
+ */
+// use lithium\util\Validator;
+// use lithium\util\Inflector;
+//
+// Validator::add('postalCode',
+// Catalog::read('validation.postalCode', array('en_US'))
+// );
+// Inflector::rules('transliterations',
+// Catalog::read('inflection.transliteration', array('en'))
+// );
+
+/**
+ * Your custom code goes here.
+ */
+
+?>
diff --git a/config/connections.php b/config/connections.php
new file mode 100644
index 0000000..eae8daf
--- /dev/null
+++ b/config/connections.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use \lithium\data\Connections;
+
+Connections::add('couch', 'http', array(
+ 'adapter' => 'Couch',
+ 'host' => '127.0.0.1',
+ 'port' => 5984,
+));
+?>
diff --git a/config/routes.php b/config/routes.php
new file mode 100644
index 0000000..f34abcc
--- /dev/null
+++ b/config/routes.php
@@ -0,0 +1,11 @@
+<?php
+
+use \lithium\http\Router;
+
+Router::connect('/', array('controller' => 'anologue', 'action' => 'index'));
+Router::connect('/new', array('controller' => 'anologue', 'action' => 'add'));
+Router::connect('/say/{:id}', array('controller' => 'anologue', 'action' => 'say'));
+Router::connect('/json/{:id}', array('controller' => 'anologue', 'action' => 'json'));
+Router::connect('/{:id}', array('controller' => 'anologue', 'action' => 'view'));
+
+?>
diff --git a/config/switchboard.php b/config/switchboard.php
new file mode 100644
index 0000000..856f610
--- /dev/null
+++ b/config/switchboard.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * Welcome to the switchboard. This file contains a series of method filters that allow you to
+ * intercept different parts of Lithium's request cycle as they happen. You can apply filters to
+ * any object method that has a `@filter` flag in its API documentation.
+ *
+ * When applying a filter, you need the name of the method you want to call, along with a *closure*,
+ * that defines what you want the filter to do. All filters take the same 3 parameters: `$self`,
+ * `$params`, and `$chain`.
+ *
+ * - `$self`: If the filter is applied on an object instance, then `$self` will be that instance. If
+ * applied to a static class, then `$self` will be a string containing the fully-namespaced class
+ * name.
+ *
+ * - `$params`: Contains an associative array of the parameters that are passed into the method. You
+ * can modify or inspect these parameters before allowing the method to continue.
+ *
+ * - `$chain`: Finally, `$chain` contains the list of filters in line to be executed. At the bottom
+ * of `$chain` is the method itself. This is why most filters contain a line that looks like
+ * `return $chain->next($self, $params, $chain);`. This passes control to the next filter in the
+ * chain, and finally, to the method itself. This allows you to interact with the return value as
+ * well as the parameters.
+ */
+
+use \lithium\http\Router;
+use \lithium\core\Environment;
+use \lithium\action\Dispatcher;
+
+/**
+ * Loads application routes before the request is dispatched. Change this to `include_once` if
+ * more than one request cycle is executed per HTTP request.
+ *
+ * @see lithium\http\Router
+ */
+Dispatcher::applyFilter('run', function($self, $params, $chain) {
+ include __DIR__ . '/routes.php';
+ return $chain->next($self, $params, $chain);
+});
+
+/**
+ * Intercepts the `Dispatcher` as it finds a controller object, and passes the `'request'` parameter
+ * to the `Environment` class to detect which environment the application is running in.
+ *
+ * @see lithium\action\Request
+ * @see lithium\core\Environment
+ */
+Dispatcher::applyFilter('_callable', function($self, $params, $chain) {
+ Environment::set($params['request']);
+ return $chain->next($self, $params, $chain);
+});
+
+?>
diff --git a/controllers/AnologueController.php b/controllers/AnologueController.php
new file mode 100644
index 0000000..ad8f98b
--- /dev/null
+++ b/controllers/AnologueController.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace app\controllers;
+
+use \app\models\Anologue;
+
+class AnologueController extends \lithium\action\Controller {
+
+ public function index() {
+ $this->render();
+ }
+
+ public function say() {
+ $status = 'error';
+ if (!empty($this->request->params['id'])) {
+ $data = $this->request->data;
+ if (!empty($data)) {
+ $result = Anologue::addMessage($this->request->params['id'], $data);
+ if ($result->ok) {
+ $status = 'success';
+ } else {
+ $status = 'fail';
+ }
+ }
+ }
+ $this->render(array('json' => $this->_jsend($status)));
+ }
+
+ public function view() {
+ if (!empty($this->request->params['id'])) {
+ $anologue = Anologue::findById($this->request->params['id']);
+ $this->set(compact('anologue'));
+ $this->render();
+ }
+ }
+
+ public function add() {
+ $anologue = Anologue::create();
+ $this->redirect(array('controller' => 'anologue', 'action' => 'view', 'id' => $anologue->_id));
+ }
+
+ public function json() {
+ $status = 'error';
+ $anologue = false;
+ if (!empty($this->request->params['id'])) {
+ $status = 'fail';
+ $anologue = Anologue::findById($this->request->params['id']);
+ if ($anologue) {
+ $status = "success";
+ }
+ }
+ $data = new \stdClass();
+ $data->anologue = $anologue;
+ $this->render(array('json' => $this->_jsend($status, $data)));
+ }
+
+ protected function _jsend($status = "fail", $data = null) {
+ $jsend = new \stdClass();
+ $jsend->status = $status;
+ $jsend->data = $data;
+ return $jsend;
+ }
+}
+
+?>
diff --git a/extensions/data/source/http/adapter/Couch.php b/extensions/data/source/http/adapter/Couch.php
new file mode 100644
index 0000000..bc96510
--- /dev/null
+++ b/extensions/data/source/http/adapter/Couch.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace app\extensions\data\source\http\adapter;
+
+class Couch extends \lithium\data\source\Http {
+
+ protected function _prepare($data = array()) {
+ return parent::_prepare(json_encode($data));
+ }
+
+ protected function _send($path = null) {
+ $this->request->headers('Content-Type', 'application/json');
+ $data = parent::_send($path);
+ if (get_magic_quotes_gpc() == true) {
+ $data = stripslashes($data);
+ }
+ return json_decode($data);
+ }
+}
+?>
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..666c4df
--- /dev/null
+++ b/index.php
@@ -0,0 +1,11 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+require 'webroot/index.php';
+
+?>
\ No newline at end of file
diff --git a/models/Anologue.php b/models/Anologue.php
new file mode 100644
index 0000000..f387ec8
--- /dev/null
+++ b/models/Anologue.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace app\models;
+
+use \lithium\data\Connections;
+
+/**
+ * Data model for anologue store in couchdb.
+ */
+class Anologue extends \lithium\core\StaticObject {
+
+ public static $alias = 'anologue';
+
+ protected static $_schema = array(
+ 'anologue' => array(
+ 'messages' => null
+ ),
+ 'message' => array(
+ 'author' => 'anonymous',
+ 'email' => null,
+ 'timestamp' => null,
+ 'text' => null
+ )
+ );
+
+ protected static $_meta = array(
+ 'source' => 'anologue'
+ );
+
+ public static function findById($id) {
+ $uri = static::$_meta['source'] . '/' . $id;
+ $result = Connections::get('couch')->get($uri);
+ $result->messages = static::decodeMessages($result->messages);
+ return $result;
+ }
+
+ public static function create() {
+ $data = static::$_schema['anologue'];
+ $result = Connections::get('couch')->post(static::$_meta['source'], $data);
+ if (!empty($result->ok)) {
+ $result->_id = $result->id;
+ $result->_rev = $result->rev;
+ unset($result->ok);
+ unset($result->id);
+ unset($result->rev);
+ $result->messages = static::decodeMessages($result->messages);
+ return $result;
+ }
+ return null;
+ }
+
+ public static function addMessage($id = null, $message = array()) {
+ if (!empty($id) && is_string($id)) {
+ $anologue = static::findById($id);
+ } else {
+ $message = $id;
+ $id = null;
+ $anologue = static::create();
+ }
+ $message = $message + array('timestamp' => time()) + static::$_schema['message'];
+ $anologue->messages[] = $message;
+ $anologue->messages = static::encodeMessages($anologue->messages);
+ $result = Connections::get('couch')->post(static::$_meta['source'], $anologue);
+ return $result;
+ }
+
+ public static function encodeMessages($messages = array()) {
+ if (!empty($messages)) {
+ foreach ($messages as $key => $message) {
+ $message->text = rawurlencode($message->text);
+ $messages[$key] = $message;
+ }
+ }
+ return $messages;
+ }
+
+ public static function decodeMessages($messages = array()) {
+ if (!empty($messages)) {
+ foreach ($messages as $key => $message) {
+ $message->text = rawurldecode($message->text);
+ $messages[$key] = $message;
+ }
+ }
+ return $messages;
+ }
+}
+
+?>
diff --git a/tests/cases/controllers/empty b/tests/cases/controllers/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/extensions/adapters/empty b/tests/cases/extensions/adapters/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/extensions/behaviors/empty b/tests/cases/extensions/behaviors/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/extensions/commands/empty b/tests/cases/extensions/commands/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/extensions/components/empty b/tests/cases/extensions/components/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/extensions/data/source/empty b/tests/cases/extensions/data/source/empty
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/cases/extensions/data/source/empty
@@ -0,0 +1 @@
+
diff --git a/tests/cases/extensions/helpers/empty b/tests/cases/extensions/helpers/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/cases/models/empty b/tests/cases/models/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/fixtures/empty b/tests/fixtures/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tests/groups/empty b/tests/groups/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tmp/cache/empty b/tmp/cache/empty
new file mode 100644
index 0000000..e69de29
diff --git a/tmp/logs/empty b/tmp/logs/empty
new file mode 100755
index 0000000..e69de29
diff --git a/views/anologue/index.html.php b/views/anologue/index.html.php
new file mode 100644
index 0000000..f019096
--- /dev/null
+++ b/views/anologue/index.html.php
@@ -0,0 +1,12 @@
+<h1 class="title">anologue</h1>
+<div id="anologue-new"><a href="/new">start an anologue »</a></div>
+<p>it's dead simple. i need to have a conversation with one or more people.</p>
+<p>there's <strong>email</strong>, but i get pretty sick of the delays, the copied text that quickly gets out of control. i get sick of signuatures. and i get so much crap in my inbox as-is, it can be cumbersome to try and isolate those emails and then isolate the important parts of them.</p>
+<p>i don't like <strong>instant messaging</strong>. there are so many clients out there, and, sure there are tools to help them come together, but there are some people that will never us im (and i don't blame them.) so that doesn't work.</p>
+<p><strong>twitter</strong> is right out.</p>
+<p>then, there's one of my favorites: <strong>irc</strong>. but, let's face it, it's mostly for nerds.</p>
+<h2 class="sub">anonymous (or not) dialogue</h2>
+<p>it's like im, meets irc, meets <a href="http://pastium.org">your favorite paste app</a>, meets instant coffee.</p>
+<p>no accounts. no installations. no shit? <a href="/new">just dive in to a chat</a>, invite whoever you want by giving them a link, and chat away. if you can type, you can use anologue. and, judging by the average commenter on youtube, i'd say that includes all but those without electricity. for the more technically saavy, you may appreciate the implementation of markdown and use of gravatar.</p>
+<h2 class="sub">flotsam</h2>
+<p>this is open source. let's make this better, together. built with php 5.3, using the lithium framework, couchdb, jquery and perhaps a few other scripts that all come together for the gooey goodness that you're about to experience. this one's for you: internets.</p>
diff --git a/views/anologue/view.html.php b/views/anologue/view.html.php
new file mode 100644
index 0000000..8c19ead
--- /dev/null
+++ b/views/anologue/view.html.php
@@ -0,0 +1,57 @@
+<form id="anologue-form">
+
+<div class="anologue-settings">
+ <div>
+ <label for="anologue-author">Name</label>
+ <input type="text" name="anologue-author" id="anologue-author" value="anonymous" />
+ </div>
+ <div>
+ <label for="anologue-email">Email</label>
+ <input type="text" name="anologue-email" id="anologue-email" value="" />
+ </div>
+ <div>
+ <label for="anologue-scroll">disable auto-scroll </label><input name="anologue-scroll" id="anologue-scroll" type="checkbox" value="disable" />
+ </div>
+</div>
+
+<h1 class="smaller-title"><a href="/">anologue</a></h1>
+<h3 class="hash"><?php echo $this->html->link($anologue->_id, array('action' => 'view', 'id' => $anologue->_id), array('title' => 'Copy this url and give it to others')); ?></h3>
+
+<ul id="anologue" class="anologue">
+<?php if (!empty($anologue->messages)) { ?>
+ <?php foreach ($anologue->messages as $key => $message) { ?>
+ <li class="message" id="message-<?=md5($message->timestamp . $message->author);?>">
+ <ul class="data">
+ <li class="time"><?=@date('G:i:s', $message->timestamp);?></li>
+ <li class="author">
+ <img class="gravatar" src="http://gravatar.com/avatar/<?=@md5($message->email);?>?s=16&d=http://li3.rad-dev.org/img/icons/silk/shading.png" border="0" />
+ « <?=$message->author;?> »
+ </li>
+ <li class="text"><div class="markdown"><?=$message->text;?></div></li>
+ </ul>
+ </li>
+ <?php } ?>
+<?php } ?>
+</ul>
+
+<div class="anologue-speak">
+ <span class="label">Enter your message to the right and press [enter]. <a href="http://daringfireball.net/projects/markdown/basics" target="_markdown">MarkDown</a> supported.<br />
+ <a href="http://li3.rad-dev.org" target="_li3"><img src="http://imgur.com/6eddU.gif" alt="powered by lithium" border="0" /></a>
+ </span>
+ <div class="text">
+ <textarea name="anologue-text" id="anologue-text"></textarea>
+ </div>
+</div>
+
+</form>
+
+<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
+<script type="text/javascript" src="/js/md5.jquery.js"></script>
+<script type="text/javascript" src="/js/showdown.js"></script>
+<script type="text/javascript" src="/js/anologue.js"></script>
+<script type="text/javascript" charset="utf-8">
+ $(document).ready(function() {
+ anologue.setup({line: <?php echo count($anologue->messages); ?>, anologue: <?php echo json_encode($anologue); ?>});
+ });
+</script>
+
diff --git a/views/layouts/default.html.php b/views/layouts/default.html.php
new file mode 100644
index 0000000..cf2e3a9
--- /dev/null
+++ b/views/layouts/default.html.php
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+<head>
+ <?=@$this->html->charset(); ?>
+ <title>anologue</title>
+ <?=@$this->html->style('anologue'); ?>
+ <?=@$this->scripts(); ?>
+ <?=@$this->html->link('Icon', null, array('type' => 'icon')); ?>
+</head>
+<body>
+ <div id="container">
+ <div id="header"></div>
+ <div id="content">
+ <?=@$this->content; ?>
+ </div>
+ <div id="footer"></div>
+ </div>
+</body>
+</html>
diff --git a/webroot/.htaccess b/webroot/.htaccess
new file mode 100644
index 0000000..21796cf
--- /dev/null
+++ b/webroot/.htaccess
@@ -0,0 +1,7 @@
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !favicon.ico$
+ RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
+</IfModule>
\ No newline at end of file
diff --git a/webroot/css/anologue.css b/webroot/css/anologue.css
new file mode 100644
index 0000000..195884e
--- /dev/null
+++ b/webroot/css/anologue.css
@@ -0,0 +1,42 @@
+* { margin:0; padding:0; }
+h1.title { font-size:4em; opacity:.05; }
+h1.smaller-title { opacity:.05; }
+ h1 a { display:block; color:black; }
+ h1 a:hover { color:black; }
+h1.smaller-title:hover { opacity:.15; }
+h2.sub { font-weight:normal; opacity:.5; margin:1em 0 .25em 0; }
+h3.hash { opacity:.5; }
+ h3.hash a:hover { color:#00737F; }
+a { color:#00737F; text-decoration:none; }
+a:hover { color:#00E5FF; }
+html, body, input, password, textarea { background:white; color:black; font-family:Helvetica, Arial, sans-serif; }
+body { padding:1em; }
+p { padding:.5em 0; }
+
+#anologue-new a { display:block; font-weight:bold; padding:1em; position:fixed; top:0; right:0; font-size:2em; }
+
+ .anologue-settings { position:fixed; z-index:50; display:block; top:0; right:0; text-align:right; padding:.5em; background-color:rgba(0,0,0,.15); font-size:.65em; }
+ .anologue-settings label { opacity:.35; }
+ .anologue-settings input { font-size:.85em; padding:.5em; margin:.25em 0 0 0; border:0; }
+
+ .anologue { position:relative; z-index:10; width:100%; display:block; clear:both; margin:1em 0 4em 0; }
+ .anologue .message { display:block; background:#fafafa; border-bottom:1px solid #f0f0f0; vertical-align:top; font-size:.65em; }
+ .anologue .message:hover { background:white; }
+ .anologue .data { position:relative; }
+ .anologue .data li { display:block; padding:.5em; position:absolute; left:0; top:0; }
+ .anologue li.time { opacity:.05; white-space:nowrap; text-align:center; width:4em; }
+ .anologue .message:hover li.time { opacity:.25; }
+ .anologue li.author { font-weight:bold; white-space:nowrap; text-align:right; width:14em; left:5em; padding:1em .5em; }
+ .anologue li.author img { opacity:.5; float:right; margin:0 0 0 .5em; }
+ .anologue .message:hover li.author img { opacity:1; }
+ .anologue li.text { position:relative; padding:.5em .5em .5em 20.5em; min-height:1em; }
+ .anologue .text .markdown { }
+
+ .anologue-speak { position:fixed; z-index:50; bottom:0; left:0; display:block; padding:.5em 2%; width:96%; clear:both; background-color:rgba(0,0,0,.75); color:white; }
+ .anologue-speak span.label { display:block; position:absolute; z-index:100; top:.5em; left:.5em; font-size:.65em; padding:.5em; width:20.5em; color:rgba(255,255,255,.75); }
+ .anologue-speak .label img { padding:.25em 0 0 0; opacity:.5; }
+ .anologue-speak .label img:hover { opacity:1; }
+ .anologue-speak .text { padding:0 0 0 19.5em; font-size:.65em; }
+ .anologue-speak textarea { display:block; width:96%; border:0; padding:.5em 1em; height:4em; font-size:1em; color:white; background:rgba(255,255,255,.25); }
+
+
diff --git a/webroot/favicon.ico b/webroot/favicon.ico
new file mode 100644
index 0000000..6924956
Binary files /dev/null and b/webroot/favicon.ico differ
diff --git a/webroot/img/empty b/webroot/img/empty
new file mode 100644
index 0000000..e69de29
diff --git a/webroot/img/shading.png b/webroot/img/shading.png
new file mode 100644
index 0000000..09275f9
Binary files /dev/null and b/webroot/img/shading.png differ
diff --git a/webroot/index.php b/webroot/index.php
new file mode 100644
index 0000000..6735be4
--- /dev/null
+++ b/webroot/index.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+/**
+ * Welcome to Lithium! This front-controller file is the gateway to your application. It is
+ * responsible for intercepting requests, and handing them off to the Dispatcher for processing.
+ *
+ * If you're sharing a single Lithium core install or other libraries among multiple
+ * applications, you may need to manually set things like LITHIUM_LIBRARY_PATH. You can do that in
+ * app/config/bootstrap.php, which is loaded below:
+ */
+require dirname(__DIR__) . '/config/bootstrap.php';
+
+/**
+ * Dispatch a new request with the default settings.
+ */
+echo lithium\action\Dispatcher::run();
+
+?>
\ No newline at end of file
diff --git a/webroot/js/anologue.js b/webroot/js/anologue.js
new file mode 100644
index 0000000..24f86f5
--- /dev/null
+++ b/webroot/js/anologue.js
@@ -0,0 +1,91 @@
+var anologue = {
+ _config: {
+ line: 0,
+ anologue: {},
+ converter: {}
+ },
+
+ setup: function(config) {
+ this._config = config;
+ this._config.converter = new Showdown.converter();
+ $("#anologue-form").submit(function() {
+ anologue.say();
+ return false;
+ });
+ $("#anologue-text").keypress(function(e) {
+ if (e.which == 13) {
+ anologue.say();
+ return false;
+ }
+ });
+ this.markdown();
+ this.listener();
+ $('html, body').animate({
+ scrollTop: $('.message:last-child').offset().top
+ }, 'slow', 'swing');
+ $("#anologue-text").focus();
+ },
+
+ listener: function() {
+ $.getJSON('/json/' + this._config.anologue._id, {}, function(response) {
+ if (response.status != "success") {
+ return anologue.alert(response.status);
+ }
+ if (response.data.anologue != anologue._config.anologue) {
+ anologue._config.anologue = response.data.anologue;
+ if (response.data.anologue.messages != null) {
+ for (var i = anologue._config.line; i < response.data.anologue.messages.length; i++) {
+ anologue.push(response.data.anologue.messages[i]);
+ anologue._config.line++;
+ }
+ anologue.markdown();
+ }
+ }
+ setTimeout(function() { anologue.listener(); }, 1000);
+ });
+ },
+
+ say: function() {
+ var data = {
+ author: $('#anologue-author').val(),
+ email: $('#anologue-email').val(),
+ text: $('#anologue-text').val()
+ }
+ $("#anologue-text").val("");
+ $.post("/say/" + this._config.anologue._id, data, function(response) {
+ // nada
+ });
+ },
+
+ push: function(message) {
+ var timeroo = new Date();
+ var timestamp = timeroo.getHours() + ':' + timeroo.getMinutes() + ':' + timeroo.getSeconds();
+ var id = 'message-' + $.md5(message.timestamp + message.author);
+ var html = '<li class="message" id="' + id + '" style="display:none;"><ul class="data"><li class="time">' + timestamp + '</li><li class="author"><img class="gravatar" src="http://gravatar.com/avatar/' + $.md5(message.email) + '?s=16&d=http://li3.rad-dev.org/img/icons/silk/shading.png" border="0" /> « ' + message.author + ' » </li><li class="text"><div class="markdown">' + message.text + '</div></li></ul></li>';
+ $("#anologue").append(html)
+ $('#'+id).animate({
+ opacity: 'show',
+ height: 'show',
+ }, 'slow');
+ var scrollDisabled = $('#anologue-scroll:checked').val();
+ if (!scrollDisabled) {
+ $('html, body').animate({
+ scrollTop: $('#'+id).offset().top
+ }, 'normal');
+ }
+ },
+
+ alert: function(message) {
+ alert(message);
+ return null;
+ },
+
+ markdown: function() {
+ $('.markdown').each(function() {
+ if (!$(this).hasClass('marked')) {
+ var text = anologue._config.converter.makeHtml($(this).text());
+ $(this).replaceWith(text).addClass('marked');
+ }
+ });
+ }
+}
diff --git a/webroot/js/md5.jquery.js b/webroot/js/md5.jquery.js
new file mode 100644
index 0000000..5031daa
--- /dev/null
+++ b/webroot/js/md5.jquery.js
@@ -0,0 +1,230 @@
+
+ /**
+ * jQuery MD5 hash algorithm function
+ *
+ * <code>
+ * Calculate the md5 hash of a String
+ * String $.md5 ( String str )
+ * </code>
+ *
+ * Calculates the MD5 hash of str using the ยป RSA Data Security, Inc. MD5 Message-Digest Algorithm, and returns that hash.
+ * MD5 (Message-Digest algorithm 5) is a widely-used cryptographic hash function with a 128-bit hash value. MD5 has been employed in a wide variety of security applications, and is also commonly used to check the integrity of data. The generated hash is also non-reversable. Data cannot be retrieved from the message digest, the digest uniquely identifies the data.
+ * MD5 was developed by Professor Ronald L. Rivest in 1994. Its 128 bit (16 byte) message digest makes it a faster implementation than SHA-1.
+ * This script is used to process a variable length message into a fixed-length output of 128 bits using the MD5 algorithm. It is fully compatible with UTF-8 encoding. It is very useful when u want to transfer encrypted passwords over the internet. If you plan using UTF-8 encoding in your project don't forget to set the page encoding to UTF-8 (Content-Type meta tag).
+ * This function orginally get from the WebToolkit and rewrite for using as the jQuery plugin.
+ *
+ * Example
+ * Code
+ * <code>
+ * $.md5("I'm Persian.");
+ * </code>
+ * Result
+ * <code>
+ * "b8c901d0f02223f9761016cfff9d68df"
+ * </code>
+ *
+ * @alias Muhammad Hussein Fattahizadeh < muhammad [AT] semnanweb [DOT] com >
+ * @link http://www.semnanweb.com/jquery-plugin/md5.html
+ * @see http://www.webtoolkit.info/
+ * @license http://www.gnu.org/licenses/gpl.html [GNU General Public License]
+ * @param {jQuery} {md5:function(string))
+ * @return string
+ */
+
+ (function($){
+
+ var rotateLeft = function(lValue, iShiftBits) {
+ return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
+ }
+
+ var addUnsigned = function(lX, lY) {
+ var lX4, lY4, lX8, lY8, lResult;
+ lX8 = (lX & 0x80000000);
+ lY8 = (lY & 0x80000000);
+ lX4 = (lX & 0x40000000);
+ lY4 = (lY & 0x40000000);
+ lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
+ if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
+ if (lX4 | lY4) {
+ if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
+ else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
+ } else {
+ return (lResult ^ lX8 ^ lY8);
+ }
+ }
+
+ var F = function(x, y, z) {
+ return (x & y) | ((~ x) & z);
+ }
+
+ var G = function(x, y, z) {
+ return (x & z) | (y & (~ z));
+ }
+
+ var H = function(x, y, z) {
+ return (x ^ y ^ z);
+ }
+
+ var I = function(x, y, z) {
+ return (y ^ (x | (~ z)));
+ }
+
+ var FF = function(a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var GG = function(a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var HH = function(a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var II = function(a, b, c, d, x, s, ac) {
+ a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
+ return addUnsigned(rotateLeft(a, s), b);
+ };
+
+ var convertToWordArray = function(string) {
+ var lWordCount;
+ var lMessageLength = string.length;
+ var lNumberOfWordsTempOne = lMessageLength + 8;
+ var lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;
+ var lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;
+ var lWordArray = Array(lNumberOfWords - 1);
+ var lBytePosition = 0;
+ var lByteCount = 0;
+ while (lByteCount < lMessageLength) {
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
+ lByteCount++;
+ }
+ lWordCount = (lByteCount - (lByteCount % 4)) / 4;
+ lBytePosition = (lByteCount % 4) * 8;
+ lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
+ lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
+ lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
+ return lWordArray;
+ };
+
+ var wordToHex = function(lValue) {
+ var WordToHexValue = "", WordToHexValueTemp = "", lByte, lCount;
+ for (lCount = 0; lCount <= 3; lCount++) {
+ lByte = (lValue >>> (lCount * 8)) & 255;
+ WordToHexValueTemp = "0" + lByte.toString(16);
+ WordToHexValue = WordToHexValue + WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);
+ }
+ return WordToHexValue;
+ };
+
+ var uTF8Encode = function(string) {
+ string = string.replace(/\x0d\x0a/g, "\x0a");
+ var output = "";
+ for (var n = 0; n < string.length; n++) {
+ var c = string.charCodeAt(n);
+ if (c < 128) {
+ output += String.fromCharCode(c);
+ } else if ((c > 127) && (c < 2048)) {
+ output += String.fromCharCode((c >> 6) | 192);
+ output += String.fromCharCode((c & 63) | 128);
+ } else {
+ output += String.fromCharCode((c >> 12) | 224);
+ output += String.fromCharCode(((c >> 6) & 63) | 128);
+ output += String.fromCharCode((c & 63) | 128);
+ }
+ }
+ return output;
+ };
+
+ $.extend({
+ md5: function(string) {
+ var x = Array();
+ var k, AA, BB, CC, DD, a, b, c, d;
+ var S11=7, S12=12, S13=17, S14=22;
+ var S21=5, S22=9 , S23=14, S24=20;
+ var S31=4, S32=11, S33=16, S34=23;
+ var S41=6, S42=10, S43=15, S44=21;
+ string = uTF8Encode(string);
+ x = convertToWordArray(string);
+ a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
+ for (k = 0; k < x.length; k += 16) {
+ AA = a; BB = b; CC = c; DD = d;
+ a = FF(a, b, c, d, x[k+0], S11, 0xD76AA478);
+ d = FF(d, a, b, c, x[k+1], S12, 0xE8C7B756);
+ c = FF(c, d, a, b, x[k+2], S13, 0x242070DB);
+ b = FF(b, c, d, a, x[k+3], S14, 0xC1BDCEEE);
+ a = FF(a, b, c, d, x[k+4], S11, 0xF57C0FAF);
+ d = FF(d, a, b, c, x[k+5], S12, 0x4787C62A);
+ c = FF(c, d, a, b, x[k+6], S13, 0xA8304613);
+ b = FF(b, c, d, a, x[k+7], S14, 0xFD469501);
+ a = FF(a, b, c, d, x[k+8], S11, 0x698098D8);
+ d = FF(d, a, b, c, x[k+9], S12, 0x8B44F7AF);
+ c = FF(c, d, a, b, x[k+10], S13, 0xFFFF5BB1);
+ b = FF(b, c, d, a, x[k+11], S14, 0x895CD7BE);
+ a = FF(a, b, c, d, x[k+12], S11, 0x6B901122);
+ d = FF(d, a, b, c, x[k+13], S12, 0xFD987193);
+ c = FF(c, d, a, b, x[k+14], S13, 0xA679438E);
+ b = FF(b, c, d, a, x[k+15], S14, 0x49B40821);
+ a = GG(a, b, c, d, x[k+1], S21, 0xF61E2562);
+ d = GG(d, a, b, c, x[k+6], S22, 0xC040B340);
+ c = GG(c, d, a, b, x[k+11], S23, 0x265E5A51);
+ b = GG(b, c, d, a, x[k+0], S24, 0xE9B6C7AA);
+ a = GG(a, b, c, d, x[k+5], S21, 0xD62F105D);
+ d = GG(d, a, b, c, x[k+10], S22, 0x2441453);
+ c = GG(c, d, a, b, x[k+15], S23, 0xD8A1E681);
+ b = GG(b, c, d, a, x[k+4], S24, 0xE7D3FBC8);
+ a = GG(a, b, c, d, x[k+9], S21, 0x21E1CDE6);
+ d = GG(d, a, b, c, x[k+14], S22, 0xC33707D6);
+ c = GG(c, d, a, b, x[k+3], S23, 0xF4D50D87);
+ b = GG(b, c, d, a, x[k+8], S24, 0x455A14ED);
+ a = GG(a, b, c, d, x[k+13], S21, 0xA9E3E905);
+ d = GG(d, a, b, c, x[k+2], S22, 0xFCEFA3F8);
+ c = GG(c, d, a, b, x[k+7], S23, 0x676F02D9);
+ b = GG(b, c, d, a, x[k+12], S24, 0x8D2A4C8A);
+ a = HH(a, b, c, d, x[k+5], S31, 0xFFFA3942);
+ d = HH(d, a, b, c, x[k+8], S32, 0x8771F681);
+ c = HH(c, d, a, b, x[k+11], S33, 0x6D9D6122);
+ b = HH(b, c, d, a, x[k+14], S34, 0xFDE5380C);
+ a = HH(a, b, c, d, x[k+1], S31, 0xA4BEEA44);
+ d = HH(d, a, b, c, x[k+4], S32, 0x4BDECFA9);
+ c = HH(c, d, a, b, x[k+7], S33, 0xF6BB4B60);
+ b = HH(b, c, d, a, x[k+10], S34, 0xBEBFBC70);
+ a = HH(a, b, c, d, x[k+13], S31, 0x289B7EC6);
+ d = HH(d, a, b, c, x[k+0], S32, 0xEAA127FA);
+ c = HH(c, d, a, b, x[k+3], S33, 0xD4EF3085);
+ b = HH(b, c, d, a, x[k+6], S34, 0x4881D05);
+ a = HH(a, b, c, d, x[k+9], S31, 0xD9D4D039);
+ d = HH(d, a, b, c, x[k+12], S32, 0xE6DB99E5);
+ c = HH(c, d, a, b, x[k+15], S33, 0x1FA27CF8);
+ b = HH(b, c, d, a, x[k+2], S34, 0xC4AC5665);
+ a = II(a, b, c, d, x[k+0], S41, 0xF4292244);
+ d = II(d, a, b, c, x[k+7], S42, 0x432AFF97);
+ c = II(c, d, a, b, x[k+14], S43, 0xAB9423A7);
+ b = II(b, c, d, a, x[k+5], S44, 0xFC93A039);
+ a = II(a, b, c, d, x[k+12], S41, 0x655B59C3);
+ d = II(d, a, b, c, x[k+3], S42, 0x8F0CCC92);
+ c = II(c, d, a, b, x[k+10], S43, 0xFFEFF47D);
+ b = II(b, c, d, a, x[k+1], S44, 0x85845DD1);
+ a = II(a, b, c, d, x[k+8], S41, 0x6FA87E4F);
+ d = II(d, a, b, c, x[k+15], S42, 0xFE2CE6E0);
+ c = II(c, d, a, b, x[k+6], S43, 0xA3014314);
+ b = II(b, c, d, a, x[k+13], S44, 0x4E0811A1);
+ a = II(a, b, c, d, x[k+4], S41, 0xF7537E82);
+ d = II(d, a, b, c, x[k+11], S42, 0xBD3AF235);
+ c = II(c, d, a, b, x[k+2], S43, 0x2AD7D2BB);
+ b = II(b, c, d, a, x[k+9], S44, 0xEB86D391);
+ a = addUnsigned(a, AA);
+ b = addUnsigned(b, BB);
+ c = addUnsigned(c, CC);
+ d = addUnsigned(d, DD);
+ }
+ var tempValue = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
+ return tempValue.toLowerCase();
+ }
+ });
+ })(jQuery);
diff --git a/webroot/js/showdown.js b/webroot/js/showdown.js
new file mode 100644
index 0000000..86b9bc1
--- /dev/null
+++ b/webroot/js/showdown.js
@@ -0,0 +1,419 @@
+/*
+ A A L Source code at:
+ T C A <http://www.attacklab.net/>
+ T K B
+*/
+
+var Showdown={};
+Showdown.converter=function(){
+var _1;
+var _2;
+var _3;
+var _4=0;
+this.makeHtml=function(_5){
+_1=new Array();
+_2=new Array();
+_3=new Array();
+_5=_5.replace(/~/g,"~T");
+_5=_5.replace(/\$/g,"~D");
+_5=_5.replace(/\r\n/g,"\n");
+_5=_5.replace(/\r/g,"\n");
+_5="\n\n"+_5+"\n\n";
+_5=_6(_5);
+_5=_5.replace(/^[ \t]+$/mg,"");
+_5=_7(_5);
+_5=_8(_5);
+_5=_9(_5);
+_5=_a(_5);
+_5=_5.replace(/~D/g,"$$");
+_5=_5.replace(/~T/g,"~");
+return _5;
+};
+var _8=function(_b){
+var _b=_b.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(_c,m1,m2,m3,m4){
+m1=m1.toLowerCase();
+_1[m1]=_11(m2);
+if(m3){
+return m3+m4;
+}else{
+if(m4){
+_2[m1]=m4.replace(/"/g,""");
+}
+}
+return "";
+});
+return _b;
+};
+var _7=function(_12){
+_12=_12.replace(/\n/g,"\n\n");
+var _13="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";
+var _14="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";
+_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,_15);
+_12=_12.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,_15);
+_12=_12.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,_15);
+_12=_12.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,_15);
+_12=_12.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,_15);
+_12=_12.replace(/\n\n/g,"\n");
+return _12;
+};
+var _15=function(_16,m1){
+var _18=m1;
+_18=_18.replace(/\n\n/g,"\n");
+_18=_18.replace(/^\n/,"");
+_18=_18.replace(/\n+$/g,"");
+_18="\n\n~K"+(_3.push(_18)-1)+"K\n\n";
+return _18;
+};
+var _9=function(_19){
+_19=_1a(_19);
+var key=_1c("<hr />");
+_19=_19.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
+_19=_19.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
+_19=_19.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
+_19=_1d(_19);
+_19=_1e(_19);
+_19=_1f(_19);
+_19=_7(_19);
+_19=_20(_19);
+return _19;
+};
+var _21=function(_22){
+_22=_23(_22);
+_22=_24(_22);
+_22=_25(_22);
+_22=_26(_22);
+_22=_27(_22);
+_22=_28(_22);
+_22=_11(_22);
+_22=_29(_22);
+_22=_22.replace(/ +\n/g," <br />\n");
+return _22;
+};
+var _24=function(_2a){
+var _2b=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
+_2a=_2a.replace(_2b,function(_2c){
+var tag=_2c.replace(/(.)<\/?code>(?=.)/g,"$1`");
+tag=_2e(tag,"\\`*_");
+return tag;
+});
+return _2a;
+};
+var _27=function(_2f){
+_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_30);
+_2f=_2f.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_30);
+_2f=_2f.replace(/(\[([^\[\]]+)\])()()()()()/g,_30);
+return _2f;
+};
+var _30=function(_31,m1,m2,m3,m4,m5,m6,m7){
+if(m7==undefined){
+m7="";
+}
+var _39=m1;
+var _3a=m2;
+var _3b=m3.toLowerCase();
+var url=m4;
+var _3d=m7;
+if(url==""){
+if(_3b==""){
+_3b=_3a.toLowerCase().replace(/ ?\n/g," ");
+}
+url="#"+_3b;
+if(_1[_3b]!=undefined){
+url=_1[_3b];
+if(_2[_3b]!=undefined){
+_3d=_2[_3b];
+}
+}else{
+if(_39.search(/\(\s*\)$/m)>-1){
+url="";
+}else{
+return _39;
+}
+}
+}
+url=_2e(url,"*_");
+var _3e="<a href=\""+url+"\"";
+if(_3d!=""){
+_3d=_3d.replace(/"/g,""");
+_3d=_2e(_3d,"*_");
+_3e+=" title=\""+_3d+"\"";
+}
+_3e+=">"+_3a+"</a>";
+return _3e;
+};
+var _26=function(_3f){
+_3f=_3f.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,_40);
+_3f=_3f.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,_40);
+return _3f;
+};
+var _40=function(_41,m1,m2,m3,m4,m5,m6,m7){
+var _49=m1;
+var _4a=m2;
+var _4b=m3.toLowerCase();
+var url=m4;
+var _4d=m7;
+if(!_4d){
+_4d="";
+}
+if(url==""){
+if(_4b==""){
+_4b=_4a.toLowerCase().replace(/ ?\n/g," ");
+}
+url="#"+_4b;
+if(_1[_4b]!=undefined){
+url=_1[_4b];
+if(_2[_4b]!=undefined){
+_4d=_2[_4b];
+}
+}else{
+return _49;
+}
+}
+_4a=_4a.replace(/"/g,""");
+url=_2e(url,"*_");
+var _4e="<img src=\""+url+"\" alt=\""+_4a+"\"";
+_4d=_4d.replace(/"/g,""");
+_4d=_2e(_4d,"*_");
+_4e+=" title=\""+_4d+"\"";
+_4e+=" />";
+return _4e;
+};
+var _1a=function(_4f){
+_4f=_4f.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(_50,m1){
+return _1c("<h1>"+_21(m1)+"</h1>");
+});
+_4f=_4f.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(_52,m1){
+return _1c("<h2>"+_21(m1)+"</h2>");
+});
+_4f=_4f.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(_54,m1,m2){
+var _57=m1.length;
+return _1c("<h"+_57+">"+_21(m2)+"</h"+_57+">");
+});
+return _4f;
+};
+var _58;
+var _1d=function(_59){
+_59+="~0";
+var _5a=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+if(_4){
+_59=_59.replace(_5a,function(_5b,m1,m2){
+var _5e=m1;
+var _5f=(m2.search(/[*+-]/g)>-1)?"ul":"ol";
+_5e=_5e.replace(/\n{2,}/g,"\n\n\n");
+var _60=_58(_5e);
+_60=_60.replace(/\s+$/,"");
+_60="<"+_5f+">"+_60+"</"+_5f+">\n";
+return _60;
+});
+}else{
+_5a=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+_59=_59.replace(_5a,function(_61,m1,m2,m3){
+var _65=m1;
+var _66=m2;
+var _67=(m3.search(/[*+-]/g)>-1)?"ul":"ol";
+var _66=_66.replace(/\n{2,}/g,"\n\n\n");
+var _68=_58(_66);
+_68=_65+"<"+_67+">\n"+_68+"</"+_67+">\n";
+return _68;
+});
+}
+_59=_59.replace(/~0/,"");
+return _59;
+};
+_58=function(_69){
+_4++;
+_69=_69.replace(/\n{2,}$/,"\n");
+_69+="~0";
+_69=_69.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(_6a,m1,m2,m3,m4){
+var _6f=m4;
+var _70=m1;
+var _71=m2;
+if(_70||(_6f.search(/\n{2,}/)>-1)){
+_6f=_9(_72(_6f));
+}else{
+_6f=_1d(_72(_6f));
+_6f=_6f.replace(/\n$/,"");
+_6f=_21(_6f);
+}
+return "<li>"+_6f+"</li>\n";
+});
+_69=_69.replace(/~0/g,"");
+_4--;
+return _69;
+};
+var _1e=function(_73){
+_73+="~0";
+_73=_73.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(_74,m1,m2){
+var _77=m1;
+var _78=m2;
+_77=_79(_72(_77));
+_77=_6(_77);
+_77=_77.replace(/^\n+/g,"");
+_77=_77.replace(/\n+$/g,"");
+_77="<pre><code>"+_77+"\n</code></pre>";
+return _1c(_77)+_78;
+});
+_73=_73.replace(/~0/,"");
+return _73;
+};
+var _1c=function(_7a){
+_7a=_7a.replace(/(^\n+|\n+$)/g,"");
+return "\n\n~K"+(_3.push(_7a)-1)+"K\n\n";
+};
+var _23=function(_7b){
+_7b=_7b.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(_7c,m1,m2,m3,m4){
+var c=m3;
+c=c.replace(/^([ \t]*)/g,"");
+c=c.replace(/[ \t]*$/g,"");
+c=_79(c);
+return m1+"<code>"+c+"</code>";
+});
+return _7b;
+};
+var _79=function(_82){
+_82=_82.replace(/&/g,"&");
+_82=_82.replace(/</g,"<");
+_82=_82.replace(/>/g,">");
+_82=_2e(_82,"*_{}[]\\",false);
+return _82;
+};
+var _29=function(_83){
+_83=_83.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,"<strong>$2</strong>");
+_83=_83.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");
+return _83;
+};
+var _1f=function(_84){
+_84=_84.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(_85,m1){
+var bq=m1;
+bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");
+bq=bq.replace(/~0/g,"");
+bq=bq.replace(/^[ \t]+$/gm,"");
+bq=_9(bq);
+bq=bq.replace(/(^|\n)/g,"$1 ");
+bq=bq.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(_88,m1){
+var pre=m1;
+pre=pre.replace(/^ /mg,"~0");
+pre=pre.replace(/~0/g,"");
+return pre;
+});
+return _1c("<blockquote>\n"+bq+"\n</blockquote>");
+});
+return _84;
+};
+var _20=function(_8b){
+_8b=_8b.replace(/^\n+/g,"");
+_8b=_8b.replace(/\n+$/g,"");
+var _8c=_8b.split(/\n{2,}/g);
+var _8d=new Array();
+var end=_8c.length;
+for(var i=0;i<end;i++){
+var str=_8c[i];
+if(str.search(/~K(\d+)K/g)>=0){
+_8d.push(str);
+}else{
+if(str.search(/\S/)>=0){
+str=_21(str);
+str=str.replace(/^([ \t]*)/g,"<p>");
+str+="</p>";
+_8d.push(str);
+}
+}
+}
+end=_8d.length;
+for(var i=0;i<end;i++){
+while(_8d[i].search(/~K(\d+)K/)>=0){
+var _91=_3[RegExp.$1];
+_91=_91.replace(/\$/g,"$$$$");
+_8d[i]=_8d[i].replace(/~K\d+K/,_91);
+}
+}
+return _8d.join("\n\n");
+};
+var _11=function(_92){
+_92=_92.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&");
+_92=_92.replace(/<(?![a-z\/?\$!])/gi,"<");
+return _92;
+};
+var _25=function(_93){
+_93=_93.replace(/\\(\\)/g,_94);
+_93=_93.replace(/\\([`*_{}\[\]()>#+-.!])/g,_94);
+return _93;
+};
+var _28=function(_95){
+_95=_95.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
+_95=_95.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(_96,m1){
+return _98(_a(m1));
+});
+return _95;
+};
+var _98=function(_99){
+function char2hex(ch){
+var _9b="0123456789ABCDEF";
+var dec=ch.charCodeAt(0);
+return (_9b.charAt(dec>>4)+_9b.charAt(dec&15));
+}
+var _9d=[function(ch){
+return "&#"+ch.charCodeAt(0)+";";
+},function(ch){
+return "&#x"+char2hex(ch)+";";
+},function(ch){
+return ch;
+}];
+_99="mailto:"+_99;
+_99=_99.replace(/./g,function(ch){
+if(ch=="@"){
+ch=_9d[Math.floor(Math.random()*2)](ch);
+}else{
+if(ch!=":"){
+var r=Math.random();
+ch=(r>0.9?_9d[2](ch):r>0.45?_9d[1](ch):_9d[0](ch));
+}
+}
+return ch;
+});
+_99="<a href=\""+_99+"\">"+_99+"</a>";
+_99=_99.replace(/">.+:/g,"\">");
+return _99;
+};
+var _a=function(_a3){
+_a3=_a3.replace(/~E(\d+)E/g,function(_a4,m1){
+var _a6=parseInt(m1);
+return String.fromCharCode(_a6);
+});
+return _a3;
+};
+var _72=function(_a7){
+_a7=_a7.replace(/^(\t|[ ]{1,4})/gm,"~0");
+_a7=_a7.replace(/~0/g,"");
+return _a7;
+};
+var _6=function(_a8){
+_a8=_a8.replace(/\t(?=\t)/g," ");
+_a8=_a8.replace(/\t/g,"~A~B");
+_a8=_a8.replace(/~B(.+?)~A/g,function(_a9,m1,m2){
+var _ac=m1;
+var _ad=4-_ac.length%4;
+for(var i=0;i<_ad;i++){
+_ac+=" ";
+}
+return _ac;
+});
+_a8=_a8.replace(/~A/g," ");
+_a8=_a8.replace(/~B/g,"");
+return _a8;
+};
+var _2e=function(_af,_b0,_b1){
+var _b2="(["+_b0.replace(/([\[\]\\])/g,"\\$1")+"])";
+if(_b1){
+_b2="\\\\"+_b2;
+}
+var _b3=new RegExp(_b2,"g");
+_af=_af.replace(_b3,_94);
+return _af;
+};
+var _94=function(_b4,m1){
+var _b6=m1.charCodeAt(0);
+return "~E"+_b6+"E";
+};
+};
+
diff --git a/webroot/test.php b/webroot/test.php
new file mode 100644
index 0000000..ae29d26
--- /dev/null
+++ b/webroot/test.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Lithium: the most rad php framework
+ *
+ * @copyright Copyright 2009, Union of Rad, Inc. (http://union-of-rad.org)
+ * @license http://opensource.org/licenses/bsd-license.php The BSD License
+ */
+
+use \lithium\core\Libraries;
+use \lithium\test\Group;
+use \lithium\test\Dispatcher;
+use \lithium\util\Inflector;
+use \lithium\util\reflection\Inspector;
+
+$startBenchmark = microtime(true);
+
+error_reporting(E_ALL | E_STRICT | E_DEPRECATED);
+
+require dirname(__DIR__) . '/config/bootstrap.php';
+$core = dirname(dirname(__DIR__)) . '/libraries/lithium';
+
+$testRun = Dispatcher::run(null, $_GET);
+$stats = Dispatcher::process($testRun['results']);
+
+?>
+<!doctype html>
+<html>
+ <head>
+ <title>Lithium Unit Test Dashboard</title>
+ <link rel="stylesheet" href="css/debug.css" />
+ </head>
+ <body class="test-dashboard">
+ <h1>Lithium Unit Test Dashboard</h1>
+
+ <div style="float: left; padding: 10px 0 20px 20px; width: 20%;">
+ <h2><a href="?group=\">Tests</a></h2>
+ <?php echo Dispatcher::menu('html'); ?>
+ </div>
+
+ <div style="float:left; padding: 10px; width: 75%">
+ <h2>Stats for <?php echo $testRun['title']; ?></h2>
+
+ <h3>Test results</h3>
+
+ <span class="filters">
+ <?php
+ $filters = Libraries::locate('testFilters');
+ $base = $_SERVER['REQUEST_URI'];
+
+ foreach ($filters as $i => $class) {
+ $url = $base . "&filters[]={$class}";
+ $name = join('', array_slice(explode("\\", $class), -1));
+ $key = Inflector::underscore($name);
+
+ echo "<a class=\"{$key}\" href=\"{$url}\">{$name}</a>";
+
+ if ($i < count($filters) - 1) {
+ echo ' | ';
+ }
+ }
+ ?>
+ </span>
+
+ <?php
+ $passes = count($stats['passes']);
+ $fails = count($stats['fails']);
+ $errors = count($stats['errors']);
+ $exceptions = count($stats['exceptions']);
+ $success = ($passes === $stats['asserts'] && $errors === 0);
+
+ echo '<div class="test-result test-result-' . ($success ? 'success' : 'fail') . '"';
+ echo ">{$passes} / {$stats['asserts']} passes, {$fails} ";
+ echo ((intval($stats['fails']) == 1) ? 'fail' : 'fails') . " and {$exceptions} ";
+ echo ((intval($exceptions) == 1) ? 'exceptions' : 'exceptions');
+ echo '</div>';
+
+ foreach ((array)$stats['errors'] as $error) {
+ switch ($error['result']) {
+ case 'fail':
+ $error += array('class' => 'unknown', 'method' => 'unknown');
+ echo '<div class="test-assert test-assert-failed">';
+ echo "Assertion '{$error['assertion']}' failed in ";
+ echo "{$error['class']}::{$error['method']}() on line ";
+ echo "{$error['line']}: ";
+ echo "<span class=\"content\">{$error['message']}</span>";
+ break;
+ case 'exception':
+ echo '<div class="test-exception">';
+ echo "Exception thrown in {$error['class']}::{$error['method']}() ";
+ echo "on line {$error['line']}: ";
+ echo "<span class=\"content\">{$error['message']}</span>";
+ if (isset($error['trace']) && !empty($error['trace'])) {
+ echo "Trace:<span class=\"trace\">{$error['trace']}</span>";
+ }
+ break;
+ }
+ echo '</div>';
+ }
+
+ foreach ((array)$testRun['filters'] as $class => $data) {
+ echo $class::output('html', $data);
+ }
+
+ $tests = Group::all(array('transform' => true));
+ $exclude = '/\w+Test$|webroot|index$|^app\\\\config|^\w+\\\\views\/|\./';
+ $options = compact('exclude') + array('recursive' => true);
+ $classes = array_diff(Libraries::find('lithium', $options), $tests);
+ sort($classes);
+ ?>
+ <h3>Classes with no test case (<?php echo count($classes); ?>)</h3>
+ <ul class="classes">
+ <?php
+ foreach ($classes as $class) {
+ echo "<li>{$class}</li>";
+ }
+ ?>
+ </ul>
+
+ <h3>Included files (<?php echo count(get_included_files()); ?>)</h3>
+ <ul class="files">
+ <?php
+ $base = dirname(dirname($core));
+ $files = str_replace($base, '', get_included_files());
+ sort($files);
+
+ foreach ($files as $file) {
+ echo "<li>{$file}</li>";
+ }
+ ?>
+ </ul>
+ </div>
+ <div style="clear:both"></div>
+ </body>
+</html>
\ No newline at end of file