diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ce6d0cf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# Memex, a personal web memory
+
+## Overview
+
+This hopes one day to grow up to be an Open Source social bookmarking web application.
+
+## Installation
+
+* Get a copy or a link of the latest [Zend Framework][zf] into the `library/` directory.
+* Try using `scripts/load.sqlite.php` to create the database.
+
+[zf]: http://framework.zend.com/download/latest
+
+## Contributors
+
+* l.m.orchard - '.$text.'
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..7779ede
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,13 @@
+# TODO
+
+* Make *-dist versions of config files, delete originals
+* Implement an installer that can produce local configs
+* v1 API
+* AtomPub API
+* {Atom,RSS,JSON} feeds
+* AJAXify post save / edit / delete
+* Implement network based on RSS/Atom aggregation
+* [Message / work queues and deferred processing](queues)
+* Make it scale
+
+[queues]: http://decafbad.com/blog/2008/07/04/queue-everything-and-delight-everyone
diff --git a/application/bootstrap.php b/application/bootstrap.php
new file mode 100644
index 0000000..72c86e1
--- /dev/null
+++ b/application/bootstrap.php
@@ -0,0 +1,26 @@
+registerPlugin($init);
diff --git a/application/config/app.ini b/application/config/app.ini
new file mode 100644
index 0000000..65e119d
--- /dev/null
+++ b/application/config/app.ini
@@ -0,0 +1,26 @@
+[production]
+database.adapter = "PDO_SQLITE"
+database.params.dbname = APPLICATION_PATH "/../data/db/memex.db"
+database.profile = false
+
+log.writer = "Stream"
+log.priority = 2
+log.path = APPLICATION_PATH "/../logs/application.log"
+
+error.show_exceptions = false
+
+; TODO: Change this in install script?
+form.salt = this is the form salt
+
+[development : production]
+database.params.dbname = APPLICATION_PATH "/../data/db/memex-dev.db"
+database.profile = true
+
+log.writer = "Firebug"
+log.priority = 7
+
+error.show_exceptions = true
+
+[testing : production]
+database.params.dbname = APPLICATION_PATH "/../data/db/memex-test.db"
+database.profile = true
diff --git a/application/config/routes.ini b/application/config/routes.ini
new file mode 100644
index 0000000..238d4d9
--- /dev/null
+++ b/application/config/routes.ini
@@ -0,0 +1,8 @@
+[production]
+routes.foob.route = "foob/*"
+routes.foob.defaults.controller = "index"
+routes.foob.defaults.action = "index"
+routes.foob.defaults.poof = "wang"
+
+[development : production]
+[testing : production]
diff --git a/application/config/routes.php b/application/config/routes.php
new file mode 100644
index 0000000..a7d40ff
--- /dev/null
+++ b/application/config/routes.php
@@ -0,0 +1,178 @@
+ array(
+
+ 'post_profile_tags' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => '(.*)/(.*)',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'profile'
+ ),
+ 'map' => array(
+ 1 => 'screen_name',
+ 2 => 'tags'
+ ),
+ 'reverse' => '%s/%s',
+ ),
+ 'post_profile' => array(
+ 'type' => 'Zend_Controller_Router_Route',
+ 'route' => ':screen_name',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'profile'
+ )
+ ),
+
+ 'doc_index' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'docs/(.*)',
+ 'defaults' => array(
+ 'controller' => 'doc', 'action' => 'index'
+ ),
+ 'map' => array(
+ 1 => 'path'
+ ),
+ 'reverse' => 'docs/%s',
+ ),
+
+
+ 'post_save' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'save',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'save'
+ )
+ ),
+ 'post_view' => array(
+ 'type' => 'Zend_Controller_Router_Route',
+ 'route' => 'posts/:uuid',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'view'
+ )
+ ),
+ 'post_save_edit' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'posts/(.*);edit',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'save', 'subaction' => 'edit'
+ ),
+ 'map' => array(
+ 1 => 'uuid'
+ ),
+ 'reverse' => 'posts/%s;edit',
+ ),
+ 'post_save_copy' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'posts/(.*);copy',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'save', 'subaction' => 'copy'
+ ),
+ 'map' => array(
+ 1 => 'uuid'
+ ),
+ 'reverse' => 'posts/%s;copy',
+ ),
+ 'post_delete' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'posts/(.*);delete',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'delete'
+ ),
+ 'map' => array(
+ 1 => 'uuid'
+ ),
+ 'reverse' => 'posts/%s;delete',
+ ),
+ 'post_tag_recent' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'recent',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'tag'
+ )
+ ),
+ 'post_tag' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'tag/(.*)',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'tag'
+ ),
+ 'map' => array(
+ 1 => 'tags'
+ ),
+ 'reverse' => 'tag/%s',
+ ),
+ /*
+ 'post_profile_tags' => array(
+ 'type' => 'Zend_Controller_Router_Route_Regex',
+ 'route' => 'people/(.*)/(.*)',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'profile'
+ ),
+ 'map' => array(
+ 1 => 'screen_name',
+ 2 => 'tags'
+ ),
+ 'reverse' => 'people/%s/%s',
+ ),
+ 'post_profile' => array(
+ 'type' => 'Zend_Controller_Router_Route',
+ 'route' => 'people/:screen_name',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'profile'
+ )
+ ),
+ */
+
+ 'profile_index' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'people',
+ 'defaults' => array(
+ 'controller' => 'profile', 'action' => 'index'
+ )
+ ),
+
+ 'auth_home' => array(
+ 'route' => 'home',
+ 'defaults' => array(
+ 'controller' => 'auth', 'action' => 'home'
+ )
+ ),
+
+ 'auth_register' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'register',
+ 'defaults' => array(
+ 'controller' => 'auth', 'action' => 'register'
+ )
+ ),
+ 'auth_login' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'login',
+ 'defaults' => array(
+ 'controller' => 'auth', 'action' => 'login'
+ )
+ ),
+ 'auth_logout' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => 'logout',
+ 'defaults' => array(
+ 'controller' => 'auth', 'action' => 'logout'
+ )
+ ),
+ 'auth_openid' => array(
+ 'type' => "Zend_Controller_Router_Route_Static",
+ 'route' => 'openid',
+ 'defaults' => array(
+ 'controller' => 'auth', 'action' => 'openid'
+ )
+ ),
+
+ 'post_tag_recent' => array(
+ 'type' => 'Zend_Controller_Router_Route_Static',
+ 'route' => '',
+ 'defaults' => array(
+ 'controller' => 'post', 'action' => 'tag'
+ )
+ ),
+
+ )
+);
diff --git a/application/controllers/AuthController.php b/application/controllers/AuthController.php
new file mode 100644
index 0000000..f6f2ea5
--- /dev/null
+++ b/application/controllers/AuthController.php
@@ -0,0 +1,201 @@
+hasIdentity()) {
+ if (!in_array($this->getRequest()->getActionName(), array('logout', 'home'))) {
+ $this->_helper->redirector('home');
+ }
+ } else {
+ if (!in_array($this->getRequest()->getActionName(), array('index', 'register', 'login'))) {
+ $this->_helper->redirector('index');
+ }
+ }
+
+ $this->view->login_form = $this->_helper->getForm(
+ 'login',
+ array(
+ 'action' => $this->view->url(
+ array(
+ 'controller' => 'auth',
+ 'action' => 'login',
+ ),
+ 'auth_login',
+ true
+ ),
+ )
+ );
+ $this->view->registration_form = $this->_helper->getForm(
+ 'registration',
+ array(
+ 'action' => $this->view->url(
+ array(
+ 'controller' => 'auth',
+ 'action' => 'register',
+ ),
+ 'auth_register',
+ true
+ ),
+ )
+ );
+ }
+
+ /**
+ * Combination login / registration action.
+ */
+ public function indexAction()
+ {
+ }
+
+ /**
+ * Convenience action to redirect to logged in user's default profile.
+ */
+ public function homeAction()
+ {
+ $logins = $this->_helper->getModel('Logins');
+ $identity = Zend_Auth::getInstance()->getIdentity();
+ $profile = $logins->fetchDefaultProfileForLogin($identity->id);
+
+ return $this->_helper->redirector->gotoRoute(
+ array('screen_name' => $profile['screen_name']),
+ 'post_profile'
+ );
+ }
+
+ /**
+ * New user registration action.
+ */
+ function registerAction()
+ {
+ $request = $this->getRequest();
+ if (!$this->getRequest()->isPost()) {
+ return;
+ }
+
+ $form = $this->view->registration_form;
+ if (!$form->isValid($request->getPost())) {
+ return;
+ }
+
+ $logins = $this->_helper->getModel('Logins');
+ //try {
+ $new_login_id = $logins->registerWithProfile($form->getValues());
+ //} catch (Exception $e) {
+ // TODO: Better error message
+ // $form->setDescription('Registration failed, please try again.');
+ // return;
+ //}
+
+ $this->_helper->redirector('home');
+ }
+
+ /**
+ * User login action.
+ */
+ public function loginAction()
+ {
+ $request = $this->getRequest();
+ if (!$request->isPost()) {
+ return;
+ }
+
+ $form = $this->view->login_form;
+ if (!$form->isValid($request->getPost())) {
+ return;
+ }
+
+ // Get our authentication adapter and check credentials
+ $adapter = $this->getAuthAdapter($form->getValues());
+ $auth = Zend_Auth::getInstance();
+ $result = $auth->authenticate($adapter);
+ if (!$result->isValid()) {
+ $form->setDescription('Login name and password not valid');
+ return;
+ }
+
+ // Persist some identity details
+ $logins_model = $this->_helper->getModel('Logins');
+ $identity = $adapter->getResultRowObject(array(
+ 'id', 'login_name', 'email', 'created'
+ ));
+ $identity->default_profile =
+ $logins_model->fetchDefaultProfileForLogin($identity->id);
+ $auth->getStorage()->write($identity);
+
+ // We're authenticated! Redirect to the user page
+ $this->_helper->redirector('home');
+ }
+
+ /**
+ * User logout action.
+ */
+ public function logoutAction()
+ {
+ // Clear the identity and remove it from the view.
+ Zend_Auth::getInstance()->clearIdentity();
+ $this->view->assign(array(
+ 'auth_identity' => null,
+ 'auth_profile' => null
+ ));
+ }
+
+ function openidAction()
+ {
+ $status = "";
+ $auth = Zend_Auth::getInstance();
+ if ((isset($_POST['openid_action']) &&
+ $_POST['openid_action'] == "login" &&
+ !empty($_POST['openid_identifier'])) ||
+ isset($_GET['openid_mode']) ||
+ isset($_POST['openid_mode'])) {
+ $result = $auth->authenticate(
+ new Zend_Auth_Adapter_OpenId(@$_POST['openid_identifier']));
+ if ($result->isValid()) {
+ $status = "You are logged in as "
+ . $auth->getIdentity();
+ } else {
+ $auth->clearIdentity();
+ foreach ($result->getMessages() as $message) {
+ $status .= "$message\n";
+ }
+ }
+ } else if ($auth->hasIdentity()) {
+ if (isset($_POST['openid_action']) &&
+ $_POST['openid_action'] == "logout") {
+ $auth->clearIdentity();
+ } else {
+ $status = "You are logged in as ";
+ // . $auth->getIdentity();
+ }
+ }
+ $this->view->status = $status;
+
+ }
+
+ /**
+ * Build a Zend auth adapter given a username and password pair.
+ */
+ public function getAuthAdapter($values)
+ {
+ if (null === $this->_authAdapter) {
+ $this->_authAdapter = new Zend_Auth_Adapter_DbTable(
+ Zend_Db_Table_Abstract::getDefaultAdapter(),
+ 'logins',
+ 'login_name',
+ 'password',
+ '?' // AND (date_banned IS NULL)'
+ );
+ }
+ $this->_authAdapter->setIdentity($values['login_name']);
+ $this->_authAdapter->setCredential(md5($values['password']));
+ return $this->_authAdapter;
+ }
+
+}
diff --git a/application/controllers/DocController.php b/application/controllers/DocController.php
new file mode 100644
index 0000000..85ac30d
--- /dev/null
+++ b/application/controllers/DocController.php
@@ -0,0 +1,34 @@
+getRequest();
+
+ $doc_path = $request->getParam('path');
+ if (!$doc_path) $doc_path = 'README';
+
+ $root_docs = array( 'README', 'TODO' );
+
+ if (in_array($doc_path, $root_docs)) {
+ $path = dirname(APPLICATION_PATH) . '/' . $doc_path . '.md';
+ } else {
+ $path = dirname(APPLICATION_PATH) . '/docs/' . $doc_path . '.md';
+ }
+
+ if (!is_file($path))
+ throw new Zend_Exception('Not Found', 404);
+ if (realpath($path) != $path)
+ throw new Zend_Exception('Forbidden ' . realpath($path) . ' != ' . $path, 403);
+
+ $this->view->doc_path =
+ $doc_path;
+ $this->view->doc_content =
+ Markdown(file_get_contents($path));
+ }
+}
diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php
new file mode 100644
index 0000000..6994501
--- /dev/null
+++ b/application/controllers/ErrorController.php
@@ -0,0 +1,55 @@
+_helper->viewRenderer->setViewSuffix('phtml');
+
+ // Grab the error object from the request
+ $errors = $this->_getParam('error_handler');
+
+ // $errors will be an object set as a parameter of the request object,
+ // type is a property
+ switch ($errors->type) {
+ case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
+ case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
+
+ // 404 error -- controller or action not found
+ $this->getResponse()->setHttpResponseCode(404);
+ $this->view->message = 'Page not found';
+ break;
+ default:
+ // application error
+ $this->getResponse()->setHttpResponseCode(500);
+ $this->view->message = 'Application error';
+ break;
+ }
+
+ // pass the environment to the view script so we can conditionally
+ // display more/less information
+ $this->view->env = $this->getInvokeArg('env');
+
+ // pass the actual exception object to the view
+ $this->view->exception = $errors->exception;
+
+ // pass the request to the view
+ $this->view->request = $errors->request;
+ }
+}
diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php
new file mode 100644
index 0000000..6d5230f
--- /dev/null
+++ b/application/controllers/IndexController.php
@@ -0,0 +1,32 @@
+view->account = Zend_Auth::getInstance()->getIdentity();
+ }
+
+}
diff --git a/application/controllers/PostController.php b/application/controllers/PostController.php
new file mode 100644
index 0000000..cf18dfb
--- /dev/null
+++ b/application/controllers/PostController.php
@@ -0,0 +1,322 @@
+hasIdentity()) {
+ } else {
+ if (in_array($this->getRequest()->getActionName(), array('save', 'delete'))) {
+ $this->_helper->redirector->gotoRoute(array(), 'auth_login');
+ }
+ }
+ }
+
+ /**
+ * Profile home page, listing posts and etc
+ */
+ public function profileAction()
+ {
+ $request = $this->getRequest();
+
+ // Try to match the screen name to a profile, or bail with a 404.
+ $profiles_model = $this->_helper->getModel('Profiles');
+ $screen_name = $request->getParam('screen_name');
+ $profile = $profiles_model->fetchByScreenName($screen_name);
+ if (!$profile) {
+ throw new Zend_Exception("Profile '$screen_name' not found.", 404);
+ }
+ $this->view->profile = $profile;
+ $this->view->screen_name = $screen_name;
+
+ // Parse out any tags specified in the URL route.
+ $tags_model = $this->_helper->getModel('Tags');
+ $tags = $this->view->tags =
+ $tags_model->parseTags($request->getParam('tags'));
+
+ // Set up the count, page size, and page number parameters
+ // for paginator.
+ $posts_model = $this->_helper->getModel('Posts');
+ $posts_count = $this->view->posts_count =
+ $posts_model->countByProfileAndTags($profile['id'], $tags);
+ $page_size = 25;
+ $page_number = $request->getQuery('page');
+ if (!$page_number) $page_number = 1;
+
+ // Build the paginator for the view.
+ $paginator = new Zend_Paginator(
+ new Zend_Paginator_Adapter_Null($posts_count)
+ );
+ $this->view->paginator = $paginator
+ ->setCurrentPageNumber($page_number)
+ ->setItemCountPerPage($page_size);
+
+ // Fetch the posts using the route tags and pagination vars.
+ $posts_start = ($page_number - 1) * $page_size;
+ $posts = $posts_model->fetchByProfileAndTags(
+ $profile['id'], $tags, $posts_start, $page_size
+ );
+ $this->view->posts = $posts;
+ }
+
+ /**
+ * Tag view action
+ */
+ function tagAction()
+ {
+ $request = $this->getRequest();
+
+ // Parse out any tags specified in the URL route.
+ $tags_model = $this->_helper->getModel('Tags');
+ $tags = $this->view->tags =
+ $tags_model->parseTags($request->getParam('tags'));
+
+ // Set up the count, page size, and page number parameters
+ // for paginator.
+ $posts_model = $this->_helper->getModel('Posts');
+ $posts_count = $this->view->posts_count =
+ $posts_model->countByTags($tags);
+ $page_size = 25;
+ $page_number = $request->getQuery('page');
+ if (!$page_number) $page_number = 1;
+
+ // Build the paginator for the view.
+ $paginator = new Zend_Paginator(
+ new Zend_Paginator_Adapter_Null($posts_count)
+ );
+ $this->view->paginator = $paginator
+ ->setCurrentPageNumber($page_number)
+ ->setItemCountPerPage($page_size);
+
+ // Fetch the posts using the route tags and pagination vars.
+ $posts_start = ($page_number - 1) * $page_size;
+ $posts = $posts_model->fetchByTags(
+ $tags, $posts_start, $page_size
+ );
+ $this->view->posts = $posts;
+ }
+
+ /**
+ * Post view action.
+ */
+ function viewAction()
+ {
+ $identity = Zend_Auth::getInstance()->getIdentity();
+ $request = $this->getRequest();
+ $get_data = $request->getQuery();
+ $post_data = $request->getPost();
+
+ $uuid = $request->getParam('uuid');
+ if (isset($get_data['uuid'])) {
+ $uuid = $get_data['uuid'];
+ } elseif (isset($post_data['uuid'])) {
+ $uuid = $post_data['uuid'];
+ }
+
+ $posts_model = $this->_helper->getModel('Posts');
+
+ // If we have a URL, try looking up existing post data.
+ if ($uuid) {
+ $post = $posts_model->fetchOneByUUID($uuid);
+ }
+ $this->view->post = $post;
+
+ // Make sure the post exists, and belongs to the current profile
+ $profile_id = (!$identity) ? null : $identity->default_profile['id'];
+ if (empty($post)) {
+ throw new Zend_Exception("Post '$uuid' not found.", 404);
+ } elseif ($post['profile_id'] != $profile_id && $post['visibility'] > 0) {
+ // TODO: Need more work on the visibility / privacy thing.
+ throw new Zend_Exception("View of '$uuid' forbidden.", 403);
+ }
+
+ }
+
+ /**
+ * Post delete action.
+ */
+ function deleteAction()
+ {
+ $identity = Zend_Auth::getInstance()->getIdentity();
+ $request = $this->getRequest();
+ $get_data = $request->getQuery();
+ $post_data = $request->getPost();
+
+ $uuid = $request->getParam('uuid');
+ if (isset($get_data['uuid'])) {
+ $uuid = $get_data['uuid'];
+ } elseif (isset($post_data['uuid'])) {
+ $uuid = $post_data['uuid'];
+ }
+
+ if (!isset($post_data['cancel'])) {
+
+ $profile_id = $identity->default_profile['id'];
+ $posts_model = $this->_helper->getModel('Posts');
+
+ $form = $this->view->delete_form = $this->_helper->getForm(
+ 'postDelete', array('action' => $this->view->url())
+ );
+
+ // If we have a URL, try looking up existing post data.
+ if ($uuid) {
+ $post = $posts_model->fetchOneByUUID($uuid);
+ } elseif ($url) {
+ $post = $posts_model->fetchOneByUrlAndProfile($url, $profile_id);
+ }
+ $this->view->post = $post;
+
+ // Make sure the post exists, and belongs to the current profile
+ if (empty($post)) {
+ throw new Zend_Exception("Post '$uuid' not found.", 404);
+ } elseif ($post['profile_id'] != $profile_id) {
+ throw new Zend_Exception("Delete of '$uuid' forbidden.", 403);
+ }
+
+ // Allow pre-population from query string
+ if (!$this->getRequest()->isPost()) {
+ $get_data['uuid'] = $uuid;
+ $form->isValid($get_data);
+ return;
+ }
+
+ // Now, try validating the POST request.
+ $post_data['uuid'] = $uuid;
+ if (!$form->isValid($post_data)) {
+ return;
+ }
+
+ // Finally, perform the deletion.
+ $posts_model->deleteByUUID($uuid);
+ }
+
+ // Any other values for ?jump lead to the profile page.
+ return $this->_helper->redirector->gotoRoute(
+ array('screen_name' => $identity->default_profile['screen_name']),
+ 'post_profile'
+ );
+
+ }
+
+ /**
+ * Handle saving a new bookmark, with a variety of post-save redirection
+ * options.
+ */
+ function saveAction()
+ {
+ $identity = Zend_Auth::getInstance()->getIdentity();
+ $request = $this->getRequest();
+ $get_data = $request->getQuery();
+ $post_data = $request->getPost();
+
+ $have_url = false;
+
+ // Try getting the in-progress post's URL from query or form.
+ $url = null;
+ if (isset($get_data['url'])) {
+ $url = $get_data['url'];
+ } elseif (isset($post_data['url'])) {
+ $url = $post_data['url'];
+ }
+ if ($url) $have_url = true;
+
+ $uuid = $request->getParam('uuid');
+ if (isset($get_data['uuid'])) {
+ $uuid = $get_data['uuid'];
+ } elseif (isset($post_data['uuid'])) {
+ $uuid = $post_data['uuid'];
+ }
+
+ if (!isset($post_data['cancel'])) {
+
+ $profile_id = $identity->default_profile['id'];
+ $posts_model = $this->_helper->getModel('Posts');
+
+ // If we have a URL, try looking up existing post data.
+ $existing_post = null;
+ if ($uuid) {
+ $existing_post = $posts_model->fetchOneByUUID($uuid);
+ } elseif ($url) {
+ $existing_post =
+ $posts_model->fetchOneByUrlAndProfile($url, $profile_id);
+ }
+
+ if (empty($existing_post)) {
+ $existing_post = array();
+ } else {
+ $have_url = true;
+ if ($existing_post['profile_id'] != $profile_id) {
+ // If the logged in profile and the post profile ID don't
+ // match, then this is a cross-profile copy and the UUID
+ // should be nuked to force a copy instead of update.
+ unset($existing_post['uuid']);
+ }
+ }
+
+ $form = $this->view->post_form = $this->_helper->getForm(
+ 'post',
+ array(
+ 'action' => $this->view->url(),
+ 'have_url' => $have_url
+ )
+ );
+
+ // Allow pre-population from query string
+ if (!$this->getRequest()->isPost()) {
+ $new_post_data = array_merge($existing_post, $get_data);
+ $form->isValid($new_post_data);
+ return;
+ }
+
+ // Now, try validating the POST request.
+ $new_post_data = array_merge($existing_post, $post_data);
+ if (!$form->isValid($new_post_data)) {
+ return;
+ }
+
+ $new_post_data['profile_id'] = $profile_id;
+
+ // Finally, try saving the combination of existing and new input.
+ $saved_post = $posts_model->save($new_post_data);
+ }
+
+ // The ?jump parameter indicates one of several post-save redirect
+ // options.
+ $jump = $post_data['jump'];
+ if ($jump == 'doclose' || $jump == 'close') {
+
+ // jump=doclose or jump=close should close the window after
+ // posting.
+ return $this->_helper->redirector->gotoRoute(
+ array('controller'=>'post', 'action'=>'doclose'),
+ 'post_doclose'
+ );
+
+ } elseif (strpos($jump, '/') === 0) {
+
+ // jump=/... forwards the user to some path within the site
+ return $this->_helper->redirector->gotoUrl($jump, array(
+ 'prependBase' => true
+ ));
+
+ } elseif ($jump == 'yes' && $url) {
+
+ // If there's a URL and ?jump=yes, then hop on over to the original URL.
+ return $this->_helper->redirector->gotoUrl($url);
+
+ } else {
+
+ // Any other values for ?jump lead to the profile page.
+ return $this->_helper->redirector->gotoRoute(
+ array('screen_name' => $identity->default_profile['screen_name']),
+ 'post_profile'
+ );
+
+ }
+
+ }
+
+}
diff --git a/application/controllers/ProfileController.php b/application/controllers/ProfileController.php
new file mode 100644
index 0000000..e29ef61
--- /dev/null
+++ b/application/controllers/ProfileController.php
@@ -0,0 +1,23 @@
+_loader = new Zend_Loader_PluginLoader(array(
+ 'Memex_Form' => APPLICATION_PATH . '/forms',
+ ));
+ }
+
+ /**
+ * Load and return a form object
+ *
+ * @param string $form
+ * @param Zend_Config|array $config
+ * @return Zend_Form
+ */
+ public function getForm($form, $config = null)
+ {
+ if (!array_key_exists($form, $this->_forms)) {
+ $class = $this->_loader->load($form);
+ $this->_forms[$form] = new $class($config);
+ }
+ return $this->_forms[$form];
+ }
+
+ /**
+ * Proxy to getForm()
+ *
+ * @param string $form
+ * @param array|Zend_Config|null $config
+ * @return Zend_Form
+ */
+ public function direct($form, $config = null)
+ {
+ return $this->getForm($form, $config);
+ }
+}
diff --git a/application/controllers/helpers/GetModel.php b/application/controllers/helpers/GetModel.php
new file mode 100644
index 0000000..2f1f122
--- /dev/null
+++ b/application/controllers/helpers/GetModel.php
@@ -0,0 +1,48 @@
+_loader = new Zend_Loader_PluginLoader(array(
+ 'Memex_Model' => APPLICATION_PATH . '/models',
+ ));
+ }
+
+ /**
+ * Load a model class and return an object instance
+ *
+ * @param string $model
+ * @return object
+ */
+ public function getModel($model)
+ {
+ $class = $this->_loader->load($model);
+ return new $class;
+ }
+
+ /**
+ * Proxy to getModel()
+ *
+ * @param string $model
+ * @return object
+ */
+ public function direct($model)
+ {
+ return $this->getModel($model);
+ }
+}
diff --git a/application/forms/Login.php b/application/forms/Login.php
new file mode 100644
index 0000000..205968e
--- /dev/null
+++ b/application/forms/Login.php
@@ -0,0 +1,44 @@
+addElement('text', 'login_name', array(
+ 'filters' => array('StringTrim', 'StringToLower'),
+ 'validators' => array(
+ array('Alnum', true, array(false)),
+ array('StringLength', false, array(3, 64))
+ ),
+ 'required' => true,
+ 'label' => 'Login name:',
+ ));
+
+ $password = $this->addElement('password', 'password', array(
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('StringLength', false, array(3, 64))
+ ),
+ 'required' => true,
+ 'label' => 'Password:',
+ ));
+
+ $login = $this->addElement('submit', 'login', array(
+ 'required' => false,
+ 'ignore' => true,
+ 'label' => 'Login',
+ ));
+
+ // We want to display a 'failed authentication' message if necessary;
+ // we'll do that with the form 'description', so we need to add that
+ // decorator.
+ $this->setDecorators(array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')),
+ array('Description', array('placement' => 'prepend')),
+ 'Form'
+ ));
+ }
+}
diff --git a/application/forms/Post.php b/application/forms/Post.php
new file mode 100644
index 0000000..d90f386
--- /dev/null
+++ b/application/forms/Post.php
@@ -0,0 +1,118 @@
+addElementPrefixPath(
+ 'Memex_Validate', APPLICATION_PATH . '/models/Validate/',
+ 'validate'
+ )
+ ->addElementPrefixPath(
+ 'Memex_Filter', APPLICATION_PATH . '/models/Filter',
+ 'filter'
+ )
+
+ ->addElement('text', 'url', array(
+ 'label' => 'URL',
+ 'required' => true,
+ 'filters' => array('StringTrim', 'NormalizeUrl'),
+ 'validators' => array('Uri')
+ ));
+
+ if (!$this->getAttrib('have_url')) {
+
+ // If the form doesn't have a URL value yet, force into GET and
+ // omit the rest of the form.
+ $this->setMethod('get')
+ ->addElement('submit', 'save', array(
+ 'label' => 'Next'
+ ));
+
+ } else {
+
+ // Once a URL has been supplied, build the rest of the POST form.
+ $this->setMethod('post')
+ ->addElement('hash', 'csrf', array(
+ 'salt' => Zend_Registry::get('config')->form->salt,
+ 'decorators' => array(
+ array('ViewHelper')
+ )
+ ))
+ ->addElement('text', 'title', array(
+ 'label' => 'Title',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('StringLength', true, array(0, 255))
+ )
+ ))
+ ->addElement('textarea', 'notes', array(
+ 'attribs' => array(
+ 'rows' => '5',
+ 'cols' => '50'
+ ),
+ 'label' => 'Notes',
+ 'required' => false,
+ 'validators' => array(
+ array('StringLength', true, array(0, 1024))
+ )
+ ))
+ ->addElement('text', 'tags', array(
+ 'label' => 'Tags',
+ 'required' => false,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ )
+ ))
+ ->addElement('checkbox', 'private', array(
+ 'label' => 'Private'
+ ))
+ /* TODO: Variable visibility?
+ ->addElement('radio', 'visibility', array(
+ 'label' => 'Share',
+ 'required' => true,
+ 'attribs' => array(
+ 'value' => 1,
+ ),
+ 'multiOptions' => array(
+ '1' => 'public',
+ '2' => 'friends-only',
+ '0' => 'private',
+ )
+ ));
+ */
+
+ ->addElement('submit', 'save', array(
+ 'label' => 'save'
+ ))
+ ->addElement('submit', 'cancel', array(
+ 'label' => 'cancel'
+ ));
+
+ }
+
+ $this
+ ->addElement('hidden', 'uuid', array(
+ 'decorators' => array('ViewHelper')
+ ))
+ ->addElement('hidden', 'v', array(
+ 'decorators' => array('ViewHelper')
+ ))
+ ->addElement('hidden', 'noui', array(
+ 'decorators' => array('ViewHelper')
+ ))
+ ->addElement('hidden', 'jump', array(
+ 'decorators' => array('ViewHelper')
+ ))
+ ->addElement('hidden', 'src', array(
+ 'decorators' => array('ViewHelper')
+ ));
+
+ }
+}
diff --git a/application/forms/PostDelete.php b/application/forms/PostDelete.php
new file mode 100644
index 0000000..bf53e69
--- /dev/null
+++ b/application/forms/PostDelete.php
@@ -0,0 +1,35 @@
+addElementPrefixPath(
+ 'Memex_Validate', APPLICATION_PATH . '/models/Validate/',
+ 'validate'
+ )
+ ->addElementPrefixPath(
+ 'Memex_Filter', APPLICATION_PATH . '/models/Filter',
+ 'filter'
+ )
+ ->setMethod('post')
+ ->addElement('hash', 'csrf', array(
+ 'salt' => Zend_Registry::get('config')->form->salt,
+ 'decorators' => array(
+ array('ViewHelper')
+ )
+ ))
+ ->addElement('hidden', 'uuid', array(
+ 'decorators' => array('ViewHelper')
+ ))
+ ->addElement('submit', 'delete', array(
+ 'label' => 'delete'
+ ))
+ ->addElement('submit', 'cancel', array(
+ 'label' => 'cancel'
+ ));
+ }
+}
diff --git a/application/forms/Registration.php b/application/forms/Registration.php
new file mode 100644
index 0000000..794e882
--- /dev/null
+++ b/application/forms/Registration.php
@@ -0,0 +1,152 @@
+setMethod('post')
+ ->addElementPrefixPath(
+ 'Memex_Validate', APPLICATION_PATH . '/models/Validate/',
+ 'validate'
+ );
+
+ $this
+ ->addElement('text', 'login_name', array(
+ 'label' => 'Login name',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('StringLength', false, array(3, 64)),
+ array('Alnum', true, array(false)),
+ array('LoginNameAvailable', false, array($helper->getModel('Logins')))
+ )
+ ))
+ ->addElement('text', 'email', array(
+ 'label' => 'Email address',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('EmailAddress')
+ )
+ ))
+ ->addElement('password', 'password', array(
+ 'label' => 'Password',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('StringLength', false, array(3, 64)),
+ array('PasswordStrength')
+ )
+ ))
+ ->addElement('password', 'password_confirm', array(
+ 'label' => 'Password (confirm)',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('MatchField', false, array('password'))
+ )
+ ));
+
+ $this->addDisplayGroup(
+ array('login_name', 'email', 'password', 'password_confirm'),
+ 'login',
+ array('legend' => 'Login details')
+ );
+
+ $this
+ ->addElement('text', 'screen_name', array(
+ 'label' => 'Screen name',
+ 'required' => true,
+ 'filters' => array('StringTrim', 'StringToLower'),
+ 'validators' => array(
+ array('Alnum', false, array(false)),
+ array('StringLength', true, array(3, 64)),
+ array('ScreenNameAvailable', false, array($helper->getModel('Profiles')))
+ )
+ ))
+ ->addElement('text', 'full_name', array(
+ 'label' => 'Full name',
+ 'required' => true,
+ 'filters' => array('StringTrim'),
+ 'validators' => array(
+ array('StringLength', false, array(3, 128))
+ )
+ ))
+ ->addElement('textarea', 'bio', array(
+ 'attribs' => array(
+ 'rows' => '5',
+ 'cols' => '50'
+ ),
+ 'label' => 'Bio / About you',
+ 'required' => false,
+ 'filters' => array('StringTrim', 'StripTags'),
+ 'validators' => array(
+ array('StringLength', false, array(0, 1024))
+ )
+ ));
+
+ $this->addDisplayGroup(
+ array('screen_name', 'full_name', 'bio'),
+ 'account',
+ array('legend' => 'Profile details')
+ );
+
+ $this
+ ->addElement('captcha', 'captcha', array(
+ 'label' => 'Please enter the 5 letters displayed below:',
+ 'required' => true,
+ 'captcha' => array(
+ 'captcha' => 'Figlet', 'wordLen' => 5, 'timeout' => 300
+ )
+ ))
+ ->addElement('submit', 'submit', array(
+ 'label' => 'Register'
+ ));
+
+ $this->addDisplayGroup(
+ array('captcha', 'submit'),
+ 'finish',
+ array('legend' => 'Register')
+ );
+
+ /* TODO: Work with this to make ul/li form, or give in and try styling dl/dt/dd
+
+ $this->setElementDecorators(array(
+ 'ViewHelper',
+ 'Errors',
+ 'Description',
+ array(array('data'=>'HtmlTag'),array('tag'=>'span')),
+ array('Label',array()),
+ array(array('row'=>'HtmlTag'),array('tag'=>'li'))
+ ));
+
+ $this->setDisplayGroupDecorators(array(
+ 'FormElements',
+ array(array('data'=>'HtmlTag'),array('tag'=>'ul')),
+ 'FieldSet',
+ array(array('row'=>'HtmlTag'),array('tag'=>'li'))
+ ));
+
+ $this->setDecorators(array(
+ 'FormElements',
+ array(array('data'=>'HtmlTag'),array('tag'=>'ul')),
+ 'Form'
+ ));
+
+ */
+
+ $this->setDecorators(array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')),
+ array('Description', array('placement' => 'prepend')),
+ 'Form'
+ ));
+
+ parent::init();
+ }
+
+}
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml
new file mode 100644
index 0000000..886e105
--- /dev/null
+++ b/application/layouts/scripts/layout.phtml
@@ -0,0 +1,89 @@
+
+getRequest();
+ $action_name = $request->getActionName();
+ $controller_name = $request->getControllerName();
+
+ if (null === $this->auth_profile) {
+ } else {
+ $profile_home_url = $this->url(
+ array('screen_name' => $this->auth_profile['screen_name']),
+ 'post_profile'
+ );
+ }
+?>
+
+
+
+
", $text); + } + return $text; + } + + function mdwp_strip_p($t) { return preg_replace('{?p>}i', '', $t); } + + function mdwp_hide_tags($text) { + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); + } + function mdwp_show_tags($text) { + global $mdwp_hidden_tags, $mdwp_placeholders; + return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); + } +} + + +### bBlog Plugin Info ### + +function identify_modifier_markdown() { + return array( + 'name' => 'markdown', + 'type' => 'modifier', + 'nicename' => 'Markdown', + 'description' => 'A text-to-HTML conversion tool for web writers', + 'authors' => 'Michel Fortin and John Gruber', + 'licence' => 'BSD-like', + 'version' => MARKDOWN_VERSION, + 'help' => 'Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber. More...' + ); +} + + +### Smarty Modifier Interface ### + +function smarty_modifier_markdown($text) { + return Markdown($text); +} + + +### Textile Compatibility Mode ### + +# Rename this file to "classTextile.php" and it can replace Textile everywhere. + +if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) { + # Try to include PHP SmartyPants. Should be in the same directory. + @include_once 'smartypants.php'; + # Fake Textile class. It calls Markdown instead. + class Textile { + function TextileThis($text, $lite='', $encode='') { + if ($lite == '' && $encode == '') $text = Markdown($text); + if (function_exists('SmartyPants')) $text = SmartyPants($text); + return $text; + } + # Fake restricted version: restrictions are not supported for now. + function TextileRestricted($text, $lite='', $noimage='') { + return $this->TextileThis($text, $lite); + } + # Workaround to ensure compatibility with TextPattern 4.0.3. + function blockLite($text) { return $text; } + } +} + + + +# +# Markdown Parser Class +# + +class Markdown_Parser { + + # Regex to match balanced [brackets]. + # Needed to insert a maximum bracked depth while converting to PHP. + var $nested_brackets_depth = 6; + var $nested_brackets_re; + + var $nested_url_parenthesis_depth = 4; + var $nested_url_parenthesis_re; + + # Table of hash values for escaped characters: + var $escape_chars = '\`*_{}[]()>#+-.!'; + var $escape_chars_re; + + # Change to ">" for HTML output. + var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; + var $tab_width = MARKDOWN_TAB_WIDTH; + + # Change to `true` to disallow markup or entities. + var $no_markup = false; + var $no_entities = false; + + # Predefined urls and titles for reference links and images. + var $predef_urls = array(); + var $predef_titles = array(); + + + function Markdown_Parser() { + # + # Constructor function. Initialize appropriate member variables. + # + $this->_initDetab(); + $this->prepareItalicsAndBold(); + + $this->nested_brackets_re = + str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). + str_repeat('\])*', $this->nested_brackets_depth); + + $this->nested_url_parenthesis_re = + str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). + str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); + + $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; + + # Sort document, block, and span gamut in ascendent priority order. + asort($this->document_gamut); + asort($this->block_gamut); + asort($this->span_gamut); + } + + + # Internal hashes used during transformation. + var $urls = array(); + var $titles = array(); + var $html_hashes = array(); + + # Status flag to avoid invalid nesting. + var $in_anchor = false; + + + function setup() { + # + # Called before the transformation process starts to setup parser + # states. + # + # Clear global hashes. + $this->urls = $this->predef_urls; + $this->titles = $this->predef_titles; + $this->html_hashes = array(); + + $in_anchor = false; + } + + function teardown() { + # + # Called after the transformation process to clear any variable + # which may be taking up memory unnecessarly. + # + $this->urls = array(); + $this->titles = array(); + $this->html_hashes = array(); + } + + + function transform($text) { + # + # Main function. Performs some preprocessing on the input text + # and pass it through the document gamut. + # + $this->setup(); + + # Remove UTF-8 BOM and marker character in input, if present. + $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); + + # Standardize line endings: + # DOS to Unix and Mac to Unix + $text = preg_replace('{\r\n?}', "\n", $text); + + # Make sure $text ends with a couple of newlines: + $text .= "\n\n"; + + # Convert all tabs to spaces. + $text = $this->detab($text); + + # Turn block-level HTML blocks into hash entries + $text = $this->hashHTMLBlocks($text); + + # Strip any lines consisting only of spaces and tabs. + # This makes subsequent regexen easier to write, because we can + # match consecutive blank lines with /\n+/ instead of something + # contorted like /[ ]*\n+/ . + $text = preg_replace('/^[ ]+$/m', '', $text); + + # Run document gamut methods. + foreach ($this->document_gamut as $method => $priority) { + $text = $this->$method($text); + } + + $this->teardown(); + + return $text . "\n"; + } + + var $document_gamut = array( + # Strip link definitions, store in hashes. + "stripLinkDefinitions" => 20, + + "runBasicBlockGamut" => 30, + ); + + + function stripLinkDefinitions($text) { + # + # Strips link definitions from text, stores the URLs and titles in + # hash references. + # + $less_than_tab = $this->tab_width - 1; + + # Link defs are in the form: ^[id]: url "optional title" + $text = preg_replace_callback('{ + ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + (\S+?)>? # url = $2 + [ ]* + \n? # maybe one newline + [ ]* + (?: + (?<=\s) # lookbehind for whitespace + ["(] + (.*?) # title = $3 + [")] + [ ]* + )? # title is optional + (?:\n+|\Z) + }xm', + array(&$this, '_stripLinkDefinitions_callback'), + $text); + return $text; + } + function _stripLinkDefinitions_callback($matches) { + $link_id = strtolower($matches[1]); + $this->urls[$link_id] = $matches[2]; + $this->titles[$link_id] =& $matches[3]; + return ''; # String that will replace the block + } + + + function hashHTMLBlocks($text) { + if ($this->no_markup) return $text; + + $less_than_tab = $this->tab_width - 1; + + # Hashify HTML blocks: + # We only want to do this for block-level HTML tags, such as headers, + # lists, and tables. That's because we still want to wrap
s around + # "paragraphs" that are wrapped in non-block-level tags, such as anchors, + # phrase emphasis, and spans. The list of tags we're looking for is + # hard-coded: + # + # * List "a" is made of tags which can be both inline or block-level. + # These will be treated block-level when the start tag is alone on + # its line, otherwise they're not matched here and will be taken as + # inline later. + # * List "b" is made of tags which are always block-level; + # + $block_tags_a_re = 'ins|del'; + $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. + 'script|noscript|form|fieldset|iframe|math'; + + # Regular expression for the content of a block tag. + $nested_tags_level = 4; + $attr = ' + (?> # optional tag attributes + \s # starts with whitespace + (?> + [^>"/]+ # text outside quotes + | + /+(?!>) # slash not followed by ">" + | + "[^"]*" # text inside double quotes (tolerate ">") + | + \'[^\']*\' # text inside single quotes (tolerate ">") + )* + )? + '; + $content = + str_repeat(' + (?> + [^<]+ # content without tag + | + <\2 # nested opening tag + '.$attr.' # attributes + (?> + /> + | + >', $nested_tags_level). # end of opening tag + '.*?'. # last level nested tag content + str_repeat(' + \2\s*> # closing nested tag + ) + | + <(?!/\2\s*> # other tags with a different name + ) + )*', + $nested_tags_level); + $content2 = str_replace('\2', '\3', $content); + + # First, look for nested blocks, e.g.: + #
` blocks.
+ #
+ $text = preg_replace_callback('{
+ (?:\n\n|\A\n?)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?>
+ [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ }xm',
+ array(&$this, '_doCodeBlocks_callback'), $text);
+
+ return $text;
+ }
+ function _doCodeBlocks_callback($matches) {
+ $codeblock = $matches[1];
+
+ $codeblock = $this->outdent($codeblock);
+ $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
+
+ # trim leading newlines and trailing newlines
+ $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
+
+ $codeblock = "$codeblock\n
";
+ return "\n\n".$this->hashBlock($codeblock)."\n\n";
+ }
+
+
+ function makeCodeSpan($code) {
+ #
+ # Create a code span markup for $code. Called from handleSpanToken.
+ #
+ $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
+ return $this->hashPart("$code
");
+ }
+
+
+ var $em_relist = array(
+ '' => '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(? '(?:(? '(?<=\S)(? '(?<=\S)(?em_relist as $em => $em_re) {
+ foreach ($this->strong_relist as $strong => $strong_re) {
+ # Construct list of allowed token expressions.
+ $token_relist = array();
+ if (isset($this->em_strong_relist["$em$strong"])) {
+ $token_relist[] = $this->em_strong_relist["$em$strong"];
+ }
+ $token_relist[] = $em_re;
+ $token_relist[] = $strong_re;
+
+ # Construct master expression from list.
+ $token_re = '{('. implode('|', $token_relist) .')}';
+ $this->em_strong_prepared_relist["$em$strong"] = $token_re;
+ }
+ }
+ }
+
+ function doItalicsAndBold($text) {
+ $token_stack = array('');
+ $text_stack = array('');
+ $em = '';
+ $strong = '';
+ $tree_char_em = false;
+
+ while (1) {
+ #
+ # Get prepared regular expression for seraching emphasis tokens
+ # in current context.
+ #
+ $token_re = $this->em_strong_prepared_relist["$em$strong"];
+
+ #
+ # Each loop iteration seach for the next emphasis token.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
+ $text_stack[0] .= $parts[0];
+ $token =& $parts[1];
+ $text =& $parts[2];
+
+ if (empty($token)) {
+ # Reached end of text span: empty stack without emitting.
+ # any more emphasis.
+ while ($token_stack[0]) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ break;
+ }
+
+ $token_len = strlen($token);
+ if ($tree_char_em) {
+ # Reached closing marker while inside a three-char emphasis.
+ if ($token_len == 3) {
+ # Three-char closing marker, close em and strong.
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ $strong = '';
+ } else {
+ # Other closing marker: close one em or strong and
+ # change current token state to match the other
+ $token_stack[0] = str_repeat($token{0}, 3-$token_len);
+ $tag = $token_len == 2 ? "strong" : "em";
+ $span = $text_stack[0];
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span$tag>";
+ $text_stack[0] = $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ $tree_char_em = false;
+ } else if ($token_len == 3) {
+ if ($em) {
+ # Reached closing marker for both em and strong.
+ # Closing strong marker:
+ for ($i = 0; $i < 2; ++$i) {
+ $shifted_token = array_shift($token_stack);
+ $tag = strlen($shifted_token) == 2 ? "strong" : "em";
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span$tag>";
+ $text_stack[0] .= $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ } else {
+ # Reached opening three-char emphasis marker. Push on token
+ # stack; will be handled by the special condition above.
+ $em = $token{0};
+ $strong = "$em$em";
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $tree_char_em = true;
+ }
+ } else if ($token_len == 2) {
+ if ($strong) {
+ # Unwind any dangling emphasis marker:
+ if (strlen($token_stack[0]) == 1) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ # Closing strong marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span";
+ $text_stack[0] .= $this->hashPart($span);
+ $strong = '';
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $strong = $token;
+ }
+ } else {
+ # Here $token_len == 1
+ if ($em) {
+ if (strlen($token_stack[0]) == 1) {
+ # Closing emphasis marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "$span";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ } else {
+ $text_stack[0] .= $token;
+ }
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $em = $token;
+ }
+ }
+ }
+ return $text_stack[0];
+ }
+
+
+ function doBlockQuotes($text) {
+ $text = preg_replace_callback('/
+ ( # Wrap whole match in $1
+ (?>
+ ^[ ]*>[ ]? # ">" at the start of a line
+ .+\n # rest of the first line
+ (.+\n)* # subsequent consecutive lines
+ \n* # blanks
+ )+
+ )
+ /xm',
+ array(&$this, '_doBlockQuotes_callback'), $text);
+
+ return $text;
+ }
+ function _doBlockQuotes_callback($matches) {
+ $bq = $matches[1];
+ # trim one level of quoting - trim whitespace-only lines
+ $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
+ $bq = $this->runBlockGamut($bq); # recurse
+
+ $bq = preg_replace('/^/m', " ", $bq);
+ # These leading spaces cause problem with content,
+ # so we need to fix that:
+ $bq = preg_replace_callback('{(\s*.+?
)}sx',
+ array(&$this, '_DoBlockQuotes_callback2'), $bq);
+
+ return "\n". $this->hashBlock("\n$bq\n
")."\n\n";
+ }
+ function _doBlockQuotes_callback2($matches) {
+ $pre = $matches[1];
+ $pre = preg_replace('/^ /m', '', $pre);
+ return $pre;
+ }
+
+
+ function formParagraphs($text) {
+ #
+ # Params:
+ # $text - string to process with html tags
+ #
+ # Strip leading and trailing lines:
+ $text = preg_replace('/\A\n+|\n+\z/', '', $text);
+
+ $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+ #
+ # Wrap
tags and unhashify HTML blocks
+ #
+ foreach ($grafs as $key => $value) {
+ if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
+ # Is a paragraph.
+ $value = $this->runSpanGamut($value);
+ $value = preg_replace('/^([ ]*)/', "
", $value);
+ $value .= "
";
+ $grafs[$key] = $this->unhash($value);
+ }
+ else {
+ # Is a block.
+ # Modify elements of @grafs in-place...
+ $graf = $value;
+ $block = $this->html_hashes[$graf];
+ $graf = $block;
+// if (preg_match('{
+// \A
+// ( # $1 = tag
+// ]*
+// \b
+// markdown\s*=\s* ([\'"]) # $2 = attr quote char
+// 1
+// \2
+// [^>]*
+// >
+// )
+// ( # $3 = contents
+// .*
+// )
+// () # $4 = closing tag
+// \z
+// }xs', $block, $matches))
+// {
+// list(, $div_open, , $div_content, $div_close) = $matches;
+//
+// # We can't call Markdown(), because that resets the hash;
+// # that initialization code should be pulled into its own sub, though.
+// $div_content = $this->hashHTMLBlocks($div_content);
+//
+// # Run document gamut methods on the content.
+// foreach ($this->document_gamut as $method => $priority) {
+// $div_content = $this->$method($div_content);
+// }
+//
+// $div_open = preg_replace(
+// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
+//
+// $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
+// }
+ $grafs[$key] = $graf;
+ }
+ }
+
+ return implode("\n\n", $grafs);
+ }
+
+
+ function encodeAttribute($text) {
+ #
+ # Encode text for a double-quoted HTML attribute. This function
+ # is *not* suitable for attributes enclosed in single quotes.
+ #
+ $text = $this->encodeAmpsAndAngles($text);
+ $text = str_replace('"', '"', $text);
+ return $text;
+ }
+
+
+ function encodeAmpsAndAngles($text) {
+ #
+ # Smart processing for ampersands and angle brackets that need to
+ # be encoded. Valid character entities are left alone unless the
+ # no-entities mode is set.
+ #
+ if ($this->no_entities) {
+ $text = str_replace('&', '&', $text);
+ } else {
+ # Ampersand-encoding based entirely on Nat Irons's Amputator
+ # MT plugin:
+ $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
+ '&', $text);;
+ }
+ # Encode remaining <'s
+ $text = str_replace('<', '<', $text);
+
+ return $text;
+ }
+
+
+ function doAutoLinks($text) {
+ $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
+ array(&$this, '_doAutoLinks_url_callback'), $text);
+
+ # Email addresses:
+ $text = preg_replace_callback('{
+ <
+ (?:mailto:)?
+ (
+ [-.\w\x80-\xFF]+
+ \@
+ [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
+ )
+ >
+ }xi',
+ array(&$this, '_doAutoLinks_email_callback'), $text);
+
+ return $text;
+ }
+ function _doAutoLinks_url_callback($matches) {
+ $url = $this->encodeAttribute($matches[1]);
+ $link = "$url";
+ return $this->hashPart($link);
+ }
+ function _doAutoLinks_email_callback($matches) {
+ $address = $matches[1];
+ $link = $this->encodeEmailAddress($address);
+ return $this->hashPart($link);
+ }
+
+
+ function encodeEmailAddress($addr) {
+ #
+ # Input: an email address, e.g. "foo@example.com"
+ #
+ # Output: the email address as a mailto link, with each character
+ # of the address encoded as either a decimal or hex entity, in
+ # the hopes of foiling most address harvesting spam bots. E.g.:
+ #
+ #
+ #
+ # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
+ # With some optimizations by Milian Wolff.
+ #
+ $addr = "mailto:" . $addr;
+ $chars = preg_split('/(? $char) {
+ $ord = ord($char);
+ # Ignore non-ascii chars.
+ if ($ord < 128) {
+ $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
+ # roughly 10% raw, 45% hex, 45% dec
+ # '@' *must* be encoded. I insist.
+ if ($r > 90 && $char != '@') /* do nothing */;
+ else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
+ else $chars[$key] = ''.$ord.';';
+ }
+ }
+
+ $addr = implode('', $chars);
+ $text = implode('', array_slice($chars, 7)); # text without `mailto:`
+ $addr = "$text";
+
+ return $addr;
+ }
+
+
+ function parseSpan($str) {
+ #
+ # Take the string $str and parse it into tokens, hashing embeded HTML,
+ # escaped characters and handling code spans.
+ #
+ $output = '';
+
+ $span_re = '{
+ (
+ \\\\'.$this->escape_chars_re.'
+ |
+ (?no_markup ? '' : '
+ |
+ # comment
+ |
+ <\?.*?\?> | <%.*?%> # processing instruction
+ |
+ <[/!$]?[-a-zA-Z0-9:]+ # regular tags
+ (?>
+ \s
+ (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
+ )?
+ >
+ ').'
+ )
+ }xs';
+
+ while (1) {
+ #
+ # Each loop iteration seach for either the next tag, the next
+ # openning code span marker, or the next escaped character.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ # Create token from text preceding tag.
+ if ($parts[0] != "") {
+ $output .= $parts[0];
+ }
+
+ # Check if we reach the end.
+ if (isset($parts[1])) {
+ $output .= $this->handleSpanToken($parts[1], $parts[2]);
+ $str = $parts[2];
+ }
+ else {
+ break;
+ }
+ }
+
+ return $output;
+ }
+
+
+ function handleSpanToken($token, &$str) {
+ #
+ # Handle $token provided by parseSpan by determining its nature and
+ # returning the corresponding value that should replace it.
+ #
+ switch ($token{0}) {
+ case "\\":
+ return $this->hashPart("". ord($token{1}). ";");
+ case "`":
+ # Search for end marker in remaining text.
+ if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
+ $str, $matches))
+ {
+ $str = $matches[2];
+ $codespan = $this->makeCodeSpan($matches[1]);
+ return $this->hashPart($codespan);
+ }
+ return $token; // return as text since no ending marker found.
+ default:
+ return $this->hashPart($token);
+ }
+ }
+
+
+ function outdent($text) {
+ #
+ # Remove one level of line-leading tabs or spaces
+ #
+ return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
+ }
+
+
+ # String length function for detab. `_initDetab` will create a function to
+ # hanlde UTF-8 if the default function does not exist.
+ var $utf8_strlen = 'mb_strlen';
+
+ function detab($text) {
+ #
+ # Replace tabs with the appropriate amount of space.
+ #
+ # For each line we separate the line in blocks delemited by
+ # tab characters. Then we reconstruct every line by adding the
+ # appropriate number of space between each blocks.
+
+ $text = preg_replace_callback('/^.*\t.*$/m',
+ array(&$this, '_detab_callback'), $text);
+
+ return $text;
+ }
+ function _detab_callback($matches) {
+ $line = $matches[0];
+ $strlen = $this->utf8_strlen; # strlen function for UTF-8.
+
+ # Split in blocks.
+ $blocks = explode("\t", $line);
+ # Add each blocks to the line.
+ $line = $blocks[0];
+ unset($blocks[0]); # Do not add first block twice.
+ foreach ($blocks as $block) {
+ # Calculate amount of space, insert spaces, insert block.
+ $amount = $this->tab_width -
+ $strlen($line, 'UTF-8') % $this->tab_width;
+ $line .= str_repeat(" ", $amount) . $block;
+ }
+ return $line;
+ }
+ function _initDetab() {
+ #
+ # Check for the availability of the function in the `utf8_strlen` property
+ # (initially `mb_strlen`). If the function is not available, create a
+ # function that will loosely count the number of UTF-8 characters with a
+ # regular expression.
+ #
+ if (function_exists($this->utf8_strlen)) return;
+ $this->utf8_strlen = create_function('$text', 'return preg_match_all(
+ "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
+ $text, $m);');
+ }
+
+
+ function unhash($text) {
+ #
+ # Swap back in all the tags hashed by _HashHTMLBlocks.
+ #
+ return preg_replace_callback('/(.)\x1A[0-9]+\1/',
+ array(&$this, '_unhash_callback'), $text);
+ }
+ function _unhash_callback($matches) {
+ return $this->html_hashes[$matches[0]];
+ }
+
+}
+
+/*
+
+PHP Markdown
+============
+
+Description
+-----------
+
+This is a PHP translation of the original Markdown formatter written in
+Perl by John Gruber.
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like and as well).
+
+For more information about Markdown's syntax, see:
+
+
+
+
+Bugs
+----
+
+To file bug reports please send email to:
+
+
+
+Please include with your report: (1) the example input; (2) the output you
+expected; (3) the output Markdown actually produced.
+
+
+Version History
+---------------
+
+See the readme file for detailed release notes for this version.
+
+
+Copyright and License
+---------------------
+
+PHP Markdown
+Copyright (c) 2004-2008 Michel Fortin
+
+All rights reserved.
+
+Based on Markdown
+Copyright (c) 2003-2006 John Gruber
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+*/
+?>
\ No newline at end of file
diff --git a/application/views/helpers/BaseUrl.php b/application/views/helpers/BaseUrl.php
new file mode 100644
index 0000000..4f5509f
--- /dev/null
+++ b/application/views/helpers/BaseUrl.php
@@ -0,0 +1,21 @@
+getBaseUrl();
+
+ // Remove trailing slashes
+ $file = ($file !== null) ? ltrim($file, '/\\') : null;
+
+ // Build return
+ $return = rtrim($baseUrl, '/\\') . (($file !== null) ? ('/' . $file) : '');
+
+ return $return;
+ }
+}
diff --git a/application/views/helpers/QueryUrl.php b/application/views/helpers/QueryUrl.php
new file mode 100644
index 0000000..e7f623d
--- /dev/null
+++ b/application/views/helpers/QueryUrl.php
@@ -0,0 +1,24 @@
+getRouter();
+ return $router->assemble($urlOptions, $name, $reset, $encode) .
+ '?' . http_build_query(array_merge($_GET, $params));
+ }
+}
diff --git a/application/views/scripts/auth/index.phtml b/application/views/scripts/auth/index.phtml
new file mode 100644
index 0000000..f923c07
--- /dev/null
+++ b/application/views/scripts/auth/index.phtml
@@ -0,0 +1,5 @@
+Login
+= $this->login_form ?>
+
+New User? Please register!
+= $this->registration_form ?>
diff --git a/application/views/scripts/auth/login.phtml b/application/views/scripts/auth/login.phtml
new file mode 100644
index 0000000..125920c
--- /dev/null
+++ b/application/views/scripts/auth/login.phtml
@@ -0,0 +1,5 @@
+placeholder('crumbs')->captureStart() ?>
+ / login
+placeholder('crumbs')->captureEnd() ?>
+
+= $this->login_form ?>
diff --git a/application/views/scripts/auth/logout.phtml b/application/views/scripts/auth/logout.phtml
new file mode 100644
index 0000000..6e66716
--- /dev/null
+++ b/application/views/scripts/auth/logout.phtml
@@ -0,0 +1,5 @@
+Logged out
+
+Log in again?
+
+= $this->login_form ?>
diff --git a/application/views/scripts/auth/openid.phtml b/application/views/scripts/auth/openid.phtml
new file mode 100644
index 0000000..1272441
--- /dev/null
+++ b/application/views/scripts/auth/openid.phtml
@@ -0,0 +1,7 @@
+status);?>
+
diff --git a/application/views/scripts/auth/register.phtml b/application/views/scripts/auth/register.phtml
new file mode 100644
index 0000000..67e9416
--- /dev/null
+++ b/application/views/scripts/auth/register.phtml
@@ -0,0 +1,5 @@
+placeholder('crumbs')->captureStart() ?>
+ / register
+placeholder('crumbs')->captureEnd() ?>
+
+= $this->registration_form ?>
diff --git a/application/views/scripts/doc/index.phtml b/application/views/scripts/doc/index.phtml
new file mode 100644
index 0000000..cffd24e
--- /dev/null
+++ b/application/views/scripts/doc/index.phtml
@@ -0,0 +1,5 @@
+placeholder('crumbs')->captureStart() ?>
+ / docs / = $this->doc_path ?>
+placeholder('crumbs')->captureEnd() ?>
+
+= $this->doc_content ?>
diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml
new file mode 100644
index 0000000..c742c28
--- /dev/null
+++ b/application/views/scripts/error/error.phtml
@@ -0,0 +1,15 @@
+An error occurred
+= $this->message ?>
+
+ if (Memex_Plugin_Initialize::getConfig()->error->show_exceptions): ?>
+ Exception information:
+
+ Message: = $this->exception->getMessage() ?>
+
+
+ Stack trace:
+ = $this->exception->getTraceAsString() ?>
+
+ Request Parameters:
+ var_dump($this->request->getParams()) ?>
+ endif ?>
diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml
new file mode 100644
index 0000000..a360aff
--- /dev/null
+++ b/application/views/scripts/index/index.phtml
@@ -0,0 +1,14 @@
+
+ Hello, from the Zend Framework MVC!
+ I am the index controllers's view script.
+
+
+= $this->validated ?>
+
+= $this->form ?>
+
+login_id) : ?>
+Logged in as escape($this->login_name);?>.
+Logout
+
diff --git a/application/views/scripts/pagination_control.phtml b/application/views/scripts/pagination_control.phtml
new file mode 100644
index 0000000..6f40064
--- /dev/null
+++ b/application/views/scripts/pagination_control.phtml
@@ -0,0 +1,44 @@
+queryUrl(array('page' => $this->first));
+ $url_previous = $this->queryUrl(array('page' => $this->previous));
+ $url_next = $this->queryUrl(array('page' => $this->next));
+ $url_last = $this->queryUrl(array('page' => $this->last));
+?>
+
+
+
+
+ -
+ previous)): ?>
+ previous
+
+ previous
+
+
+
+ pagesInRange as $page): ?>
+ -
+ current): ?>
+ = $page; ?>
+
+ = $page; ?>
+
+
+
+
+ -
+ next)): ?>
+ next
+
+ next
+
+
+
+
+
+ = $this->firstItemNumber; ?> -
+ = $this->lastItemNumber; ?>
+ of = $this->totalItemCount; ?>
+
+
+
diff --git a/application/views/scripts/pagination_mini_control.phtml b/application/views/scripts/pagination_mini_control.phtml
new file mode 100644
index 0000000..6072569
--- /dev/null
+++ b/application/views/scripts/pagination_mini_control.phtml
@@ -0,0 +1,29 @@
+queryUrl(array('page' => $this->first));
+ $url_previous = $this->queryUrl(array('page' => $this->previous));
+ $url_next = $this->queryUrl(array('page' => $this->next));
+ $url_last = $this->queryUrl(array('page' => $this->last));
+?>
+
+
+ -
+ previous)): ?>
+ previous
+
+ previous
+
+
+
+ -
+ next)): ?>
+ next
+
+ next
+
+
+
+ -
+ page = $this->current ?> of = $this->last ?>
+
+
+
diff --git a/application/views/scripts/post.phtml b/application/views/scripts/post.phtml
new file mode 100644
index 0000000..6001320
--- /dev/null
+++ b/application/views/scripts/post.phtml
@@ -0,0 +1,39 @@
+auth_profile &&
+ $this->post['profile_id'] == $this->auth_profile['id']);
+
+ $profile_home_url = $this->url(
+ array('screen_name' => $this->post['screen_name']),
+ 'post_profile'
+ );
+?>
+
+
+ = $this->escape($this->post['title']) ?>
+
+ post['notes'])): ?>
+ = $this->escape($this->post['notes']) ?>
+
+
+
+
+ = var_export($this->post->toArray()) ?>
+
+
+
diff --git a/application/views/scripts/post/delete.phtml b/application/views/scripts/post/delete.phtml
new file mode 100644
index 0000000..ffdb13e
--- /dev/null
+++ b/application/views/scripts/post/delete.phtml
@@ -0,0 +1,10 @@
+Delete this?
+
+
+ = $this->partial('post.phtml', array(
+ 'auth_profile' => $this->auth_profile,
+ 'post' => $this->post
+ )); ?>
+
+
+= $this->delete_form ?>
diff --git a/application/views/scripts/post/index.phtml b/application/views/scripts/post/index.phtml
new file mode 100644
index 0000000..5372dcb
--- /dev/null
+++ b/application/views/scripts/post/index.phtml
@@ -0,0 +1,2 @@
+
+= $this->post_form ?>
diff --git a/application/views/scripts/post/profile.phtml b/application/views/scripts/post/profile.phtml
new file mode 100644
index 0000000..7307403
--- /dev/null
+++ b/application/views/scripts/post/profile.phtml
@@ -0,0 +1,66 @@
+url(
+ array('screen_name' => $this->screen_name),
+ 'post_profile'
+ );
+?>
+
+placeholder('crumbs')->captureStart() ?>
+ / people / = $this->escape($this->screen_name) ?>
+ tags): ?>
+ / = $this->escape(join(' + ', $this->tags)) ?>
+
+placeholder('crumbs')->captureEnd() ?>
+
+placeholder('infobar')->captureStart() ?>
+ auth_profile && $this->screen_name == $this->auth_profile['screen_name']) ?
+ 'your items' : $this->screen_name . "'s items";
+ ?>
+ tags): ?>
+ All = $whose_items ?> (= $this->posts_count ?>)
+
+ = $whose_items ?> tagged tags as $tag): ?>
+ = $this->escape($tag) ?>
+ (= $this->posts_count ?>)
+
+placeholder('infobar')->captureEnd() ?>
+
+posts)): ?>
+
+
+
+
+
+ = $this->paginationControl(
+ $this->paginator, 'Sliding', 'pagination_mini_control.phtml'
+ ); ?>
+
+
+ posts as $post): ?>
+ = $this->partial('post.phtml', array(
+ 'profile' => $this->profile,
+ 'auth_profile' => $this->auth_profile,
+ 'post' => $post
+ )); ?>
+
+
+
+ = $this->paginationControl(
+ $this->paginator, 'Sliding', 'pagination_control.phtml'
+ ); ?>
+
+
diff --git a/application/views/scripts/post/save.phtml b/application/views/scripts/post/save.phtml
new file mode 100644
index 0000000..4906fe3
--- /dev/null
+++ b/application/views/scripts/post/save.phtml
@@ -0,0 +1,12 @@
+url(
+ array('screen_name' => $this->auth_profile['screen_name']),
+ 'post_profile'
+ );
+?>
+
+placeholder('crumbs')->captureStart() ?>
+ / = $this->escape($this->auth_profile['screen_name']) ?>
+placeholder('crumbs')->captureEnd() ?>
+
+= $this->post_form ?>
diff --git a/application/views/scripts/post/tag.phtml b/application/views/scripts/post/tag.phtml
new file mode 100644
index 0000000..b0abc3a
--- /dev/null
+++ b/application/views/scripts/post/tag.phtml
@@ -0,0 +1,56 @@
+placeholder('crumbs')->captureStart() ?>
+ tags)): ?>
+ / recent
+
+ / tag / = join(' + ', $this->tags) ?>
+
+placeholder('crumbs')->captureEnd() ?>
+
+placeholder('infobar')->captureStart() ?>
+ tags)): ?>
+ Recent items (= $this->posts_count ?>)
+
+ Recent items tagged tags as $tag): ?>
+ = $this->escape($tag) ?>
+ (= $this->posts_count ?>)
+
+placeholder('infobar')->captureEnd() ?>
+
+posts)): ?>
+
+
+
+
+
+ = $this->paginationControl(
+ $this->paginator, 'Sliding', 'pagination_mini_control.phtml'
+ ); ?>
+
+
+ posts as $post): ?>
+ = $this->partial('post.phtml', array(
+ 'profile' => $this->profile,
+ 'auth_profile' => $this->auth_profile,
+ 'post' => $post
+ )); ?>
+
+
+
+ = $this->paginationControl(
+ $this->paginator, 'Sliding', 'pagination_control.phtml'
+ ); ?>
+
+
diff --git a/application/views/scripts/post/view.phtml b/application/views/scripts/post/view.phtml
new file mode 100644
index 0000000..c85d568
--- /dev/null
+++ b/application/views/scripts/post/view.phtml
@@ -0,0 +1,6 @@
+
+ = $this->partial('post.phtml', array(
+ 'auth_profile' => $this->auth_profile,
+ 'post' => $this->post
+ )); ?>
+
diff --git a/application/views/scripts/profile/index.phtml b/application/views/scripts/profile/index.phtml
new file mode 100644
index 0000000..4a670ab
--- /dev/null
+++ b/application/views/scripts/profile/index.phtml
@@ -0,0 +1 @@
+People home
diff --git a/data/.exists b/data/.exists
new file mode 100644
index 0000000..e69de29
diff --git a/docs/.exists b/docs/.exists
new file mode 100644
index 0000000..e69de29
diff --git a/library/.exists b/library/.exists
new file mode 100644
index 0000000..e69de29
diff --git a/logs/.exists b/logs/.exists
new file mode 100644
index 0000000..e69de29
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..e31220e
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,47 @@
+# Let's make sure that we have setup the timezone for this application,
+# In some php.ini files, this value is not set. This will ensure it exists
+# for every reqeust of this application.
+php_value date.timezone "UTC"
+
+# Let's also make sure that we can use the php short tag, "". The
+# reason this is enabled is so that we can use these short tags in our
+# php based view scripts. This allows for a shorter and cleaner view
+# script, After all PHP IS A TEMPLATING LANGUAGE :)
+php_value short_open_tag "1"
+
+# Set the error_reporting to E_ALL|E_STRICT
+# Since .htaccess only take an integer (and cannot render the PHP
+# contstants, we need to find out what the integer actuall is)
+#
+# > php -r "echo E_ALL|E_STRICT;"
+# 8191
+php_value error_reporting "8191"
+
+# The following display_*error directives instruct PHP how to display
+# errors that might come up. In a production environment, it might
+# be good to set these values inside the actual VHOST definiation.
+# NOTE: these display error ini's should most likely be OFF
+# in your production environment
+php_value display_startup_errors "1"
+php_value display_errors "1"
+
+# NOTE: by setting the above php ini values, we are ensuring that
+# regardless of the servers php.ini values, we can be assured that
+# our application will have these set values set on every request.
+
+
+
+
+# The rules below basically say that if the file exists in the tree, just
+# serve it; otherwise, go to index.php. This is more future-proof for your
+# site, because if you start adding more and more content types, you don't
+# need to alter the .htaccess to accomodate them.
+# This is an important concept for the Front Controller Pattern which the
+# ZF MVC makes use of.
+RewriteEngine On
+RewriteCond %{REQUEST_FILENAME} -s [OR]
+RewriteCond %{REQUEST_FILENAME} -l [OR]
+RewriteCond %{REQUEST_FILENAME} -d
+RewriteRule ^.*$ - [NC,L]
+RewriteRule ^.*$ /index.php [NC,L]
+
diff --git a/public/css/main.css b/public/css/main.css
new file mode 100644
index 0000000..e9e4a4e
--- /dev/null
+++ b/public/css/main.css
@@ -0,0 +1,317 @@
+/**
+ *
+ */
+html, body, form {
+ margin: 0; padding: 0;
+}
+body {
+ font-family: arial, sans-serif;
+ background-color: #fff;
+}
+
+a:link {
+ text-decoration: none;
+ color: #00f;
+}
+a:visited {
+ text-decoration: none;
+ color: #639;
+}
+a:hover {
+ text-decoration: underline;
+}
+a:active {
+ text-decoration: underline;
+}
+
+#header {
+ font-size: 75%;
+ margin: 0.5em 1.5em 0.5em 1.5em;
+ padding: 0 0 0 0;
+ height: 80px;
+ position: relative;
+}
+#header .logo {
+ float: left;
+}
+#header .logo span:before {
+ font-size: 75px;
+ content: '\2756';
+}
+#header .logo span {
+ padding: 0 15px 0 10px;
+}
+#header .crumbs {
+ font-size: 200%;
+ padding: 0.25em 0 0 0;
+}
+#header .crumbs .title {
+ display: inline;
+ font-weight: bold;
+}
+#header .main {
+ margin: 0 0 0 0;
+ padding: 0.75em 0 0 0;
+ position: absolute;
+ left: 85px;
+ bottom: 1.25em;
+}
+#header .main .nav {
+ margin: 0 0 0 0;
+ padding: 0 0 0 0;
+ list-style: none;
+}
+#header .main .nav li {
+ display: inline;
+ border-left: 1px solid #000;
+ margin-left: 0.25em;
+ padding-left: 0.5em;
+}
+#header .main .nav li.first {
+ font-weight: bold;
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+#header .main .title {
+ margin: 0 0 0.5em 0;
+ padding: 0 0 0 0;
+}
+#header .sub {
+ position: absolute;
+ right: 0;
+ bottom: 1.25em;
+ margin: 1.25em 0 0 0;
+ padding: 0 0 0 0;
+}
+#header .sub .nav {
+ margin: 0 0 0 0;
+ padding: 0 0 0 0;
+}
+#header .sub .nav li {
+ display: inline;
+ border-left: 1px solid #000;
+ margin-left: 0.25em;
+ padding-left: 0.5em;
+}
+#header .sub .nav li.first {
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+#infobar {
+ font-size: 75%;
+ margin: 0.5em 1.5em 1.5em 1.5em;
+ padding: 0.5em 0.8em;
+ background-color: #eee;
+ border: 1px solid #ccc;
+ border-left: none;
+ border-right: none;
+}
+#content {
+ font-size: 75%;
+ margin: 0em 1.5em 0em 1.5em;
+}
+#footer {
+ position: relative;
+ font-size: 75%;
+ margin: 0em 1.5em 1.5em 1.5em;
+ padding: 0.5em 0 0 0;
+ background-color: #eee;
+}
+#footer .nav {
+ background-color: #fff;
+ margin: 0 0 0 0;
+ padding: 0.5em 0 0 0;
+ border-top: 1px solid #ccc;
+ list-style: none;
+}
+#footer .nav:before {
+ content: "\2756";
+}
+#footer .nav li {
+ display: inline;
+ border-left: 1px solid #000;
+ margin-left: 0.25em;
+ padding-left: 0.5em;
+}
+#footer .nav li.first {
+ font-weight: bold;
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+#footer .license {
+ position: absolute;
+ top: 1em;
+ right: 0;
+}
+#footer .license img {
+ border: 0;
+}
+
+.posts {
+ clear: both;
+ margin: 0em 0em 1.5em 0em;
+ padding: 0 0 0 0;
+ list-style: none;
+}
+.posts .post {
+ clear: both;
+ padding: 0.25em 0 0.75em 0;
+}
+.posts .post .title {
+ margin: 0; padding: 0;
+ font-weight: normal;
+ font-size: 132%;
+}
+.posts .post .notes {
+ margin: 0.125em 0.0em 0.125em 0.0em;
+ padding: 0 0 0 0;
+}
+.posts .post .meta {
+ color: #999;
+}
+.posts .post .meta a {
+ color: #99F;
+}
+.posts .post .tags:before {
+ content: ' to ';
+}
+.posts .post .tags {
+ display: inline;
+ margin: 0; padding: 0;
+}
+.posts .post .tags .tag {
+ display: inline;
+ list-style: none;
+ padding: 0 0 0 0;
+ margin: 0 0.25ex 0 0.25ex;
+ margin: 0 0 0 0;
+}
+.posts .post .author:before {
+ content: ' by ';
+}
+.posts .post .author {
+}
+.posts .post .date:before {
+ content: ' ... on ';
+}
+.posts .post .date {
+}
+.posts .post .commands:before {
+ content: ' ... ';
+}
+.posts .post .commands {
+ margin: 0 0 0 0;
+ padding: 0 0 0 0;
+ list-style: none;
+ display: inline;
+}
+.posts .post .commands li {
+ display: inline;
+ /*
+ border-left: 1px solid #000;
+ */
+ margin-left: 0;
+ padding-left: 0;
+}
+.posts .post .commands li:before {
+ content: ' / ';
+}
+.posts .post .commands li.first {
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+.posts .post .commands li.first:before {
+ content: '';
+}
+
+.pagination {
+ clear: both;
+ margin: 0.5em 0em 0em 0em;
+ padding: 0.5em 0 0.5em 0;
+ color: #666;
+ text-align: center;
+ background-color: #eee;
+ border-top: 1px solid #ccc;
+}
+.pagination .pages {
+ /* background-color: #fff; */
+ margin: 0 0 0em 0;
+ padding: 1.0em 0 0.75em 0;
+ list-style: none;
+}
+.pagination .pages li {
+ display: inline;
+}
+.pagination .pages li.page {
+ display: inline;
+ margin: 0 0.125em 0 0.125em;
+}
+.pagination .pages li.page a, .pagination .pages li.page span.current {
+ border: 1px solid #ccc;
+ padding: 0 0.5em 0 0.5em;
+}
+.pagination .pages li.page span.current {
+ background: #ccc;
+ font-weight: bold;
+ color: #333;
+}
+.pagination .pages li:after.page {
+ content: ' | ';
+}
+.pagination .pages li.previous {
+ padding-right: 0.5em;
+}
+.pagination .pages li.previous:before {
+ content: '\00AB';
+}
+.pagination .pages li.next {
+ padding-left: 0.5em;
+}
+.pagination .pages li.next:after {
+ content: '\00BB';
+}
+.pagination .page_position {
+ /* background: #fff; */
+}
+.pagination_mini {
+ margin: 0 0em 1.5em 0em;
+ padding: 0 0 0 0;
+ list-style: none;
+ color: #999;
+}
+.pagination_mini li {
+ display: inline;
+}
+.pagination_mini .next:before {
+}
+.pagination_mini .next:after {
+ content: '\00BB';
+}
+.pagination_mini .next {
+}
+.pagination_mini .next .disabled {
+}
+.pagination_mini .previous:before {
+ content: '\00AB';
+}
+.pagination_mini .previous:after {
+ content: ' | ';
+}
+.pagination_mini .previous {
+}
+.pagination_mini .previous .disabled {
+}
+.pagination_mini .page {
+ padding-left: 1.5em;
+}
+
+.ctrl_doc .doc_content {
+ font-size: 120%;
+ padding: 0 85px 0 85px;
+ line-height: 1.75em;
+}
+
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..1b079ce
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,18 @@
+'
+ . 'An exception occured while bootstrapping the application.';
+ if (defined('APPLICATION_ENVIRONMENT') && APPLICATION_ENVIRONMENT != 'production') {
+ echo '
' . $exception->getMessage() . '
'
+ . 'Stack Trace:'
+ . '' . $exception->getTraceAsString() . '
';
+ }
+ echo '