<?php
/**
* Class: Admin Controller
* The logic behind the Admin area.
*/
class AdminController {
# Boolean: $displayed
# Has anything been displayed?
public $displayed = false;
# Array: $context
# Contains the context for various admin pages, to be passed to the Twig templates.
public $context = array();
# String: $selected_bookmarklet
# Holds the name of the Feather to be selected when they open the bookmarklet.
public $selected_bookmarklet;
# String: $base
# The base path for this controller.
public $base = "admin";
# Boolean: $feed
# Is the current page a feed?
public $feed = false;
/**
* Function: __construct
* Prepares Twig.
*/
private function __construct() {
$this->twig = new Twig_Loader((file_exists(THEME_DIR."/admin") ? THEME_DIR."/admin" : MAIN_DIR."/admin/layout"),
(is_writable(INCLUDES_DIR."/caches") and !DEBUG) ?
INCLUDES_DIR."/caches" :
null);
}
/**
* Function: parse
* Determines the action.
*/
public function parse($route) {
$visitor = Visitor::current();
# Protect non-responder functions.
if (in_array($route->action, array("__construct", "parse", "subnav_context", "display", "current")))
show_404();
if (empty($route->action) or $route->action == "write") {
# "Write > Post", if they can add posts or drafts.
if (($visitor->group->can("add_post") or $visitor->group->can("add_draft")) and
!empty(Config::current()->enabled_feathers))
return $route->action = "write_post";
# "Write > Page", if they can add pages.
if ($visitor->group->can("add_page"))
return $route->action = "write_page";
}
if (empty($route->action) or $route->action == "manage") {
# "Manage > Posts", if they can manage any posts.
if (Post::any_editable() or Post::any_deletable())
return $route->action = "manage_posts";
# "Manage > Pages", if they can manage pages.
if ($visitor->group->can("edit_page") or $visitor->group->can("delete_page"))
return $route->action = "manage_pages";
# "Manage > Users", if they can manage users.
if ($visitor->group->can("edit_user") or $visitor->group->can("delete_user"))
return $route->action = "manage_users";
# "Manage > Groups", if they can manage groups.
if ($visitor->group->can("edit_group") or $visitor->group->can("delete_group"))
return $route->action = "manage_groups";
}
if (empty($route->action) or $route->action == "settings") {
# "General Settings", if they can configure the installation.
if ($visitor->group->can("change_settings"))
return $route->action = "general_settings";
}
if (empty($route->action) or $route->action == "extend") {
# "Modules", if they can configure the installation.
if ($visitor->group->can("toggle_extensions"))
return $route->action = "modules";
}
Trigger::current()->filter($route->action, "admin_determine_action");
if (!isset($route->action))
show_403(__("Access Denied"), __("You do not have sufficient privileges to view this area."));
}
/**
* Function: write
* Post writing.
*/
public function write_post() {
$visitor = Visitor::current();
if (!$visitor->group->can("add_post", "add_draft"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create posts."));
$config = Config::current();
if (empty($config->enabled_feathers))
error(__("No Feathers"), __("Please install a feather or two in order to add a post."));
Trigger::current()->filter($options, array("write_post_options", "post_options"));
fallback($_GET['feather'], $config->enabled_feathers[0]);
$this->display("write_post",
array("groups" => Group::find(array("order" => "id ASC")),
"options" => $options,
"feathers" => Feathers::$instances,
"feather" => Feathers::$instances[$_GET['feather']]));
}
/**
* Function: bookmarklet
* Post writing, from the bookmarklet.
*/
public function bookmarklet() {
$visitor = Visitor::current();
if (!$visitor->group->can("add_post", "add_draft"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create posts."));
$config = Config::current();
if (empty($config->enabled_feathers))
error(__("No Feathers"), __("Please install a feather or two in order to add a post."));
if (!isset($this->selected_bookmarklet))
fallback($feather, $config->enabled_feathers[0]);
else
$feather = $this->selected_bookmarklet;
fallback($_GET['url']);
fallback($_GET['title']);
fallback($_GET['selection']);
$this->display("bookmarklet",
array("done" => isset($_GET['done']),
"feathers" => Feathers::$instances,
"selected_feather" => Feathers::$instances[$feather],
"args" => array("url" => stripslashes($_GET['url']),
"page_url" => stripslashes($_GET['url']),
"title" => stripslashes($_GET['title']),
"page_title" => stripslashes($_GET['title']),
"selection" => stripslashes($_GET['selection']))));
}
/**
* Function: add_post
* Adds a post when the form is submitted.
*/
public function add_post() {
$visitor = Visitor::current();
if (!$visitor->group->can("add_post", "add_draft"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create posts."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$post = Feathers::$instances[$_POST['feather']]->submit();
if (!$post->redirect)
$post->redirect = "/admin/?action=write_post";
if (!isset($_POST['bookmarklet']))
Flash::notice(__("Post created!"), $post->redirect);
else
redirect($post->redirect);
}
/**
* Function: edit_post
* Post editing.
*/
public function edit_post() {
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to edit a post."));
$post = new Post($_GET['id'], array("drafts" => true, "filter" => false));
if (!$post->editable())
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit this post."));
Trigger::current()->filter($options, array("edit_post_options", "post_options"), $post);
$this->display("edit_post",
array("post" => $post,
"groups" => Group::find(array("order" => "id ASC")),
"options" => $options,
"feather" => Feathers::$instances[$post->feather]));
}
/**
* Function: update_post
* Updates a post when the form is submitted.
*/
public function update_post() {
$post = new Post($_POST['id'], array("drafts" => true));
if ($post->no_results)
Flash::warning(__("Post not found."), "/admin/?action=manage_posts");
if (!$post->editable())
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit this post."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
Feathers::$instances[$post->feather]->update($post);
if (!isset($_POST['ajax']))
Flash::notice(_f("Post updated. <a href=\"%s\">View Post →</a>",
array($post->url())),
"/admin/?action=manage_posts");
else
exit((string) $_POST['id']);
}
/**
* Function: delete_post
* Post deletion (confirm page).
*/
public function delete_post() {
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to delete a post."));
$post = new Post($_GET['id'], array("drafts" => true));
if (!$post->deletable())
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this post."));
$this->display("delete_post", array("post" => $post));
}
/**
* Function: destroy_post
* Destroys a post (the real deal).
*/
public function destroy_post() {
if (empty($_POST['id']))
error(__("No ID Specified"), __("An ID is required to delete a post."));
if ($_POST['destroy'] == "bollocks")
redirect("/admin/?action=manage_posts");
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$post = new Post($_POST['id'], array("drafts" => true));
if (!$post->deletable())
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this post."));
Post::delete($_POST['id']);
Flash::notice(__("Post deleted."), "/admin/?action=manage_posts");
}
/**
* Function: manage_posts
* Post managing.
*/
public function manage_posts() {
if (!Post::any_editable() and !Post::any_deletable())
show_403(__("Access Denied"), __("You do not have sufficient privileges to manage any posts."));
fallback($_GET['query'], "");
list($where, $params) = keywords($_GET['query'], "post_attributes.value LIKE :query OR url LIKE :query", "post_attributes");
if (!empty($_GET['month']))
$where["created_at like"] = $_GET['month']."-%";
$visitor = Visitor::current();
if (!$visitor->group->can("view_draft", "edit_draft", "edit_post", "delete_draft", "delete_post"))
$where["user_id"] = $visitor->id;
$results = Post::find(array("placeholders" => true,
"drafts" => true,
"where" => $where,
"params" => $params));
$ids = array();
foreach ($results[0] as $result)
$ids[] = $result["id"];
if (!empty($ids))
$posts = new Paginator(Post::find(array("placeholders" => true,
"drafts" => true,
"where" => array("id" => $ids))),
25);
else
$posts = new Paginator(array());
foreach ($posts->paginated as &$post) {
if (preg_match_all("/\{([0-9]+)\}/", $post->status, $matches)) {
$groups = array();
$groupClasses = array();
foreach ($matches[1] as $id) {
$group = new Group($id);
$groups[] = "<span class=\"group_prefix\">Group:</span> ".$group->name;
$groupClasses[] = "group-".$id;
}
$post->status_name = join(", ", $groups);
$post->status_class = join(" ", $groupClasses);
} else {
$post->status_name = camelize($post->status, true);
$post->status_class = $post->status;
}
}
$this->display("manage_posts", array("posts" => $posts));
}
/**
* Function: write_page
* Page creation.
*/
public function write_page() {
if (!Visitor::current()->group->can("add_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create pages."));
$this->display("write_page", array("pages" => Page::find()));
}
/**
* Function: add_page
* Adds a page when the form is submitted.
*/
public function add_page() {
if (!Visitor::current()->group->can("add_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create pages."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
if (empty($_POST['title']) and empty($_POST['slug']))
error(__("Error"), __("Title and slug cannot be blank."));
$page = Page::add($_POST['title'],
$_POST['body'],
null,
$_POST['parent_id'],
!empty($_POST['show_in_list']),
0,
(!empty($_POST['slug']) ? $_POST['slug'] : sanitize($_POST['title'])));
Flash::notice(__("Page created!"), $page->url());
}
/**
* Function: edit_page
* Page editing.
*/
public function edit_page() {
if (!Visitor::current()->group->can("edit_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit this page."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to edit a page."));
$this->display("edit_page",
array("page" => new Page($_GET['id'], array("filter" => false)),
"pages" => Page::find(array("where" => array("id not" => $_GET['id'])))));
}
/**
* Function: update_page
* Updates a page when the form is submitted.
*/
public function update_page() {
if (!Visitor::current()->group->can("edit_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit pages."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
if (empty($_POST['title']) and empty($_POST['slug']))
error(__("Error"), __("Title and slug cannot be blank."));
$page = new Page($_POST['id']);
if ($page->no_results)
Flash::warning(__("Page not found."), "/admin/?action=manage_pages");
$page->update($_POST['title'], $_POST['body'], null, $_POST['parent_id'], !empty($_POST['show_in_list']), $page->list_order, null, $_POST['slug']);
if (!isset($_POST['ajax']))
Flash::notice(_f("Page updated. <a href=\"%s\">View Page →</a>",
array($page->url())),
"/admin/?action=manage_pages");
}
/**
* Function: reorder_pages
* Reorders pages.
*/
public function reorder_pages() {
foreach ($_POST['list_order'] as $id => $order) {
$page = new Page($id);
$page->update($page->title, $page->body, null, $page->parent_id, $page->show_in_list, $order, null, $page->url);
}
Flash::notice(__("Pages reordered."), "/admin/?action=manage_pages");
}
/**
* Function: delete_page
* Page deletion (confirm page).
*/
public function delete_page() {
if (!Visitor::current()->group->can("delete_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete pages."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to delete a page."));
$this->display("delete_page", array("page" => new Page($_GET['id'])));
}
/**
* Function: destroy_page
* Destroys a page.
*/
public function destroy_page() {
if (!Visitor::current()->group->can("delete_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete pages."));
if (empty($_POST['id']))
error(__("No ID Specified"), __("An ID is required to delete a post."));
if ($_POST['destroy'] == "bollocks")
redirect("/admin/?action=manage_pages");
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$page = new Page($_POST['id']);
if (!$page->no_results)
foreach ($page->children as $child)
if (isset($_POST['destroy_children']))
Page::delete($child->id, true);
else
$child->update($child->title, $child->body, 0, $child->show_in_list, $child->list_order, $child->url);
Page::delete($_POST['id']);
Flash::notice(__("Page deleted."), "/admin/?action=manage_pages");
}
/**
* Function: manage_pages
* Page managing.
*/
public function manage_pages() {
$visitor = Visitor::current();
if (!$visitor->group->can("edit_page") and !$visitor->group->can("delete_page"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to manage pages."));
fallback($_GET['query'], "");
list($where, $params) = keywords($_GET['query'], "title LIKE :query OR body LIKE :query", "pages");
$this->display("manage_pages",
array("pages" => new Paginator(Page::find(array("placeholders" => true,
"where" => $where,
"params" => $params)), 25)));
}
/**
* Function: new_user
* User creation.
*/
public function new_user() {
if (!Visitor::current()->group->can("add_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to add users."));
$config = Config::current();
$this->display("new_user",
array("default_group" => new Group($config->default_group),
"groups" => Group::find(array("where" => array("id not" => array($config->guest_group,
$config->default_group)),
"order" => "id DESC"))));
}
/**
* Function: add_user
* Add a user when the form is submitted.
*/
public function add_user() {
if (!Visitor::current()->group->can("add_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to add users."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
if (empty($_POST['login']))
error(__("Error"), __("Please enter a username for your account."));
$check = new User(array("login" => $_POST['login']));
if (!$check->no_results)
error(__("Error"), __("That username is already in use."));
if (empty($_POST['password1']) or empty($_POST['password2']))
error(__("Error"), __("Password cannot be blank."));
elseif ($_POST['password1'] != $_POST['password2'])
error(__("Error"), __("Passwords do not match."));
if (empty($_POST['email']))
error(__("Error"), __("E-mail address cannot be blank."));
elseif (!preg_match("/^[_A-z0-9-]+((\.|\+)[_A-z0-9-]+)*@[A-z0-9-]+(\.[A-z0-9-]+)*(\.[A-z]{2,4})$/", $_POST['email']))
error(__("Error"), __("Invalid e-mail address."));
User::add($_POST['login'],
$_POST['password1'],
$_POST['email'],
$_POST['full_name'],
$_POST['website'],
$_POST['group']);
Flash::notice(__("User added."), "/admin/?action=manage_users");
}
/**
* Function: edit_user
* User editing.
*/
public function edit_user() {
if (!Visitor::current()->group->can("edit_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit this user."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to edit a user."));
$this->display("edit_user",
array("user" => new User($_GET['id']),
"groups" => Group::find(array("order" => "id ASC",
"where" => array("id not" => Config::current()->guest_group)))));
}
/**
* Function: update_user
* Updates a user when the form is submitted.
*/
public function update_user() {
if (empty($_POST['id']))
error(__("No ID Specified"), __("An ID is required to edit a user."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$visitor = Visitor::current();
if (!$visitor->group->can("edit_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit users."));
$check_name = new User(null, array("where" => array("login" => $_POST['login'],
"id not" => $_POST['id'])));
if (!$check_name->no_results)
Flash::notice(_f("Login “%s” is already in use.", array($_POST['login'])),
"/admin/?action=edit_user&id=".$_POST['id']);
$user = new User($_POST['id']);
if ($user->no_results)
Flash::warning(__("User not found."), "/admin/?action=manage_user");
$password = (!empty($_POST['new_password1']) and $_POST['new_password1'] == $_POST['new_password2']) ?
md5($_POST['new_password1']) :
$user->password ;
$user->update($_POST['login'], $password, $_POST['email'], $_POST['full_name'], $_POST['website'], $_POST['group']);
if ($_POST['id'] == $visitor->id)
$_SESSION['password'] = $password;
Flash::notice(__("User updated."), "/admin/?action=manage_users");
}
/**
* Function: delete_user
* User deletion.
*/
public function delete_user() {
if (!Visitor::current()->group->can("delete_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete users."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to delete a user."));
$this->display("delete_user",
array("user" => new User($_GET['id']),
"users" => User::find(array("where" => array("id not" => $_GET['id'])))));
}
/**
* Function: destroy_user
* Destroys a user.
*/
public function destroy_user() {
if (!Visitor::current()->group->can("delete_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete users."));
if (empty($_POST['id']))
error(__("No ID Specified"), __("An ID is required to delete a user."));
if ($_POST['destroy'] == "bollocks")
redirect("/admin/?action=manage_users");
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$sql = SQL::current();
$user = new User($_POST['id']);
if (isset($_POST['posts'])) {
if ($_POST['posts'] == "delete")
foreach ($user->post as $post)
Post::delete($post->id);
elseif ($_POST['posts'] == "move")
$sql->update("posts",
array("user_id" => $user->id),
array("user_id" => $_POST['move_posts']));
}
if (isset($_POST['pages'])) {
if ($_POST['pages'] == "delete")
foreach ($user->page as $page)
Page::delete($page->id);
elseif ($_POST['pages'] == "move")
$sql->update("pages",
array("user_id" => $user->id),
array("user_id" => $_POST['move_pages']));
}
User::delete($_POST['id']);
Flash::notice(__("User deleted."), "/admin/?action=manage_users");
}
/**
* Function: manage_users
* User managing.
*/
public function manage_users() {
$visitor = Visitor::current();
if (!$visitor->group->can("edit_user") and !$visitor->group->can("delete_user") and !$visitor->group->can("add_user"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to manage users."));
fallback($_GET['query'], "");
list($where, $params) = keywords($_GET['query'], "login LIKE :query OR full_name LIKE :query OR email LIKE :query OR website LIKE :query", "users");
$this->display("manage_users",
array("users" => new Paginator(User::find(array("placeholders" => true,
"where" => $where,
"params" => $params)),
25)));
}
/**
* Function: new_group
* Group creation.
*/
public function new_group() {
if (!Visitor::current()->group->can("add_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create groups."));
$this->display("new_group",
array("permissions" => SQL::current()->select("permissions", "*", array("group_id" => 0))->fetchAll()));
}
/**
* Function: add_group
* Adds a group when the form is submitted.
*/
public function add_group() {
if (!Visitor::current()->group->can("add_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to create groups."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
Group::add($_POST['name'], array_keys($_POST['permissions']));
Flash::notice(__("Group added."), "/admin/?action=manage_groups");
}
/**
* Function: edit_group
* Group editing.
*/
public function edit_group() {
if (!Visitor::current()->group->can("edit_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit groups."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to edit a group."));
$this->display("edit_group",
array("group" => new Group($_GET['id']),
"permissions" => SQL::current()->select("permissions", "*", array("group_id" => 0))->fetchAll()));
}
/**
* Function: update_group
* Updates a group when the form is submitted.
*/
public function update_group() {
if (!Visitor::current()->group->can("edit_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to edit groups."));
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$permissions = array_keys($_POST['permissions']);
$check_name = new Group(null, array("where" => array("name" => $_POST['name'],
"id not" => $_POST['id'])));
if (!$check_name->no_results)
Flash::notice(_f("Group name “%s” is already in use.", array($_POST['name'])),
"/admin/?action=edit_group&id=".$_POST['id']);
$group = new Group($_POST['id']);
if ($group->no_results)
Flash::warning(__("Group not found."), "/admin/?action=manage_groups");
$group->update($_POST['name'], $permissions);
Flash::notice(__("Group updated."), "/admin/?action=manage_groups");
}
/**
* Function: delete_group
* Group deletion (confirm page).
*/
public function delete_group() {
if (!Visitor::current()->group->can("delete_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete groups."));
if (empty($_GET['id']))
error(__("No ID Specified"), __("An ID is required to delete a group."));
$this->display("delete_group",
array("group" => new Group($_GET['id']),
"groups" => Group::find(array("where" => array("id not" => $_GET['id']),
"order" => "id ASC"))));
}
/**
* Function: destroy_group
* Destroys a group.
*/
public function destroy_group() {
if (!Visitor::current()->group->can("delete_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to delete groups."));
if (!isset($_POST['id']))
error(__("No ID Specified"), __("An ID is required to delete a group."));
if ($_POST['destroy'] == "bollocks")
redirect("/admin/?action=manage_groups");
if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
show_403(__("Access Denied"), __("Invalid security key."));
$group = new Group($_POST['id']);
foreach ($group->users() as $user)
$user->update($user->login, $user->password, $user->email, $user->full_name, $user->website, $_POST['move_group']);
$config = Config::current();
if (!empty($_POST['default_group']))
$config->set("default_group", $_POST['default_group']);
if (!empty($_POST['guest_group']))
$config->set("guest_group", $_POST['guest_group']);
Group::delete($_POST['id']);
Flash::notice(__("Group deleted."), "/admin/?action=manage_groups");
}
/**
* Function: manage_groups
* Group managing.
*/
public function manage_groups() {
$visitor = Visitor::current();
if (!$visitor->group->can("edit_group") and !$visitor->group->can("delete_group") and !$visitor->group->can("add_group"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to manage groups."));
if (!empty($_GET['search'])) {
$user = new User(array("login" => $_GET['search']));
if (!$user->no_results)
$groups = new Paginator(array($user->group), 10);
else
$groups = new Paginator(array(), 10);
} else
$groups = new Paginator(Group::find(array("placeholders" => true, "order" => "id ASC")), 10);
$this->display("manage_groups",
array("groups" => $groups));
}
/**
* Function: export
* Export posts, pages, etc.
*/
public function export() {
if (!Visitor::current()->group->can("add_post"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to export content."));
if (empty($_POST))
return $this->display("export");
$config = Config::current();
$trigger = Trigger::current();
$route = Route::current();
$exports = array();
if (isset($_POST['posts'])) {
list($where, $params) = keywords($_POST['filter_posts'], "post_attributes.value LIKE :query OR url LIKE :query", "post_attributes");
if (!empty($_GET['month']))
$where["created_at like"] = $_GET['month']."-%";
$visitor = Visitor::current();
if (!$visitor->group->can("view_draft", "edit_draft", "edit_post", "delete_draft", "delete_post"))
$where["user_id"] = $visitor->id;
$results = Post::find(array("placeholders" => true,
"drafts" => true,
"where" => $where,
"params" => $params));
$ids = array();
foreach ($results[0] as $result)
$ids[] = $result["id"];
if (!empty($ids))
$posts = Post::find(array("drafts" => true,
"where" => array("id" => $ids),
"order" => "id ASC"),
array("filter" => false));
else
$posts = new Paginator(array());
$latest_timestamp = 0;
foreach ($posts as $post)
if (strtotime($post->created_at) > $latest_timestamp)
$latest_timestamp = strtotime($post->created_at);
$id = substr(strstr($config->url, "//"), 2);
$id = str_replace("#", "/", $id);
$id = preg_replace("/(".preg_quote(parse_url($config->url, PHP_URL_HOST)).")/", "\\1,".date("Y", $latest_timestamp).":", $id, 1);
$posts_atom = '<?xml version="1.0" encoding="utf-8"?>'."\r";
$posts_atom.= '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:chyrp="http://chyrp.net/export/1.0/">'."\r";
$posts_atom.= ' <title>'.fix($config->name).' Posts</title>'."\r";
$posts_atom.= ' <subtitle>'.fix($config->description).'</subtitle>'."\r";
$posts_atom.= ' <id>tag:'.parse_url($config->url, PHP_URL_HOST).','.date("Y", $latest_timestamp).':Chyrp</id>'."\r";
$posts_atom.= ' <updated>'.date("c", $latest_timestamp).'</updated>'."\r";
$posts_atom.= ' <link href="'.$config->url.'" rel="self" type="application/atom+xml" />'."\r";
$posts_atom.= ' <generator uri="http://chyrp.net/" version="'.CHYRP_VERSION.'">Chyrp</generator>'."\r";
foreach ($posts as $post) {
$title = fix($post->title(), false);
fallback($title, ucfirst($post->feather)." Post #".$post->id);
$updated = ($post->updated) ? $post->updated_at : $post->created_at ;
$tagged = substr(strstr(url("id/".$post->id), "//"), 2);
$tagged = str_replace("#", "/", $tagged);
$tagged = preg_replace("/(".preg_quote(parse_url($post->url(), PHP_URL_HOST)).")/", "\\1,".when("Y-m-d", $updated).":", $tagged, 1);
$url = $post->url();
$posts_atom.= ' <entry xml:base="'.fix($url).'">'."\r";
$posts_atom.= ' <title type="html">'.$title.'</title>'."\r";
$posts_atom.= ' <id>tag:'.$tagged.'</id>'."\r";
$posts_atom.= ' <updated>'.when("c", $updated).'</updated>'."\r";
$posts_atom.= ' <published>'.when("c", $post->created_at).'</published>'."\r";
$posts_atom.= ' <link href="'.fix($trigger->filter($url, "post_export_url", $post)).'" />'."\r";
$posts_atom.= ' <author chyrp:user_id="'.$post->user_id.'">'."\r";
$posts_atom.= ' <name>'.fix(oneof($post->user->full_name, $post->user->login)).'</name>'."\r";
if (!empty($post->user->website))
$posts_atom.= ' <uri>'.fix($post->user->website).'</uri>'."\r";
$posts_atom.= ' <chyrp:login>'.fix($post->user->login).'</chyrp:login>'."\r";
$posts_atom.= ' </author>'."\r";
$posts_atom.= ' <content>'."\r";
foreach ($post->attributes as $key => $val)
$posts_atom.= ' <'.$key.'>'.fix($val).'</'.$key.'>'."\r";
$posts_atom.= ' </content>'."\r";
foreach (array("feather", "clean", "url", "pinned", "status") as $attr)
$posts_atom.= ' <chyrp:'.$attr.'>'.fix($post->$attr).'</chyrp:'.$attr.'>'."\r";
$trigger->filter($posts_atom, "posts_export", $post);
$posts_atom.= ' </entry>'."\r";
}
$posts_atom.= '</feed>'."\r";
$exports["posts.atom"] = $posts_atom;
}
if (isset($_POST['pages'])) {
list($where, $params) = keywords($_POST['filter_pages'], "title LIKE :query OR body LIKE :query", "pages");
$pages = Page::find(array("where" => $where, "params" => $params, "order" => "id ASC"),
array("filter" => false));
$latest_timestamp = 0;
foreach ($pages as $page)
if (strtotime($page->created_at) > $latest_timestamp)
$latest_timestamp = strtotime($page->created_at);
$pages_atom = '<?xml version="1.0" encoding="utf-8"?>'."\r";
$pages_atom.= '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:chyrp="http://chyrp.net/export/1.0/">'."\r";
$pages_atom.= ' <title>'.fix($config->name).' Pages</title>'."\r";
$pages_atom.= ' <subtitle>'.fix($config->description).'</subtitle>'."\r";
$pages_atom.= ' <id>tag:'.parse_url($config->url, PHP_URL_HOST).','.date("Y", $latest_timestamp).':Chyrp</id>'."\r";
$pages_atom.= ' <updated>'.date("c", $latest_timestamp).'</updated>'."\r";
$pages_atom.= ' <link href="'.$config->url.'" rel="self" type="application/atom+xml" />'."\r";
$pages_atom.= ' <generator uri="http://chyrp.net/" version="'.CHYRP_VERSION.'">Chyrp</generator>'."\r";
foreach ($pages as $page) {
$updated = ($page->updated) ? $page->updated_at : $page->created_at ;
$tagged = substr(strstr($page->url(), "//"), 2);
$tagged = str_replace("#", "/", $tagged);
$tagged = preg_replace("/(".preg_quote(parse_url($page->url(), PHP_URL_HOST)).")/", "\\1,".when("Y-m-d", $updated).":", $tagged, 1);
$url = $page->url();
$pages_atom.= ' <entry xml:base="'.fix($url).'" chyrp:parent_id="'.$page->parent_id.'">'."\r";
$pages_atom.= ' <title type="html">'.fix($page->title).'</title>'."\r";
$pages_atom.= ' <id>tag:'.$tagged.'</id>'."\r";
$pages_atom.= ' <updated>'.when("c", $updated).'</updated>'."\r";
$pages_atom.= ' <published>'.when("c", $page->created_at).'</published>'."\r";
$pages_atom.= ' <link href="'.fix($trigger->filter($url, "page_export_url", $page)).'" />'."\r";
$pages_atom.= ' <author chyrp:user_id="'.fix($page->user_id).'">'."\r";
$pages_atom.= ' <name>'.fix(oneof($page->user->full_name, $page->user->login)).'</name>'."\r";
if (!empty($page->user->website))
$pages_atom.= ' <uri>'.fix($page->user->website).'</uri>'."\r";
$pages_atom.= ' <chyrp:login>'.fix($page->user->login).'</chyrp:login>'."\r";
$pages_atom.= ' </author>'."\r";
$pages_atom.= ' <content type="html">'.fix($page->body).'</content>'."\r";
foreach (array("show_in_list", "list_order", "clean", "url") as $attr)
$pages_atom.= ' <chyrp:'.$attr.'>'.fix($page->$attr).'</chyrp:'.$attr.'>'."\r";
$trigger->filter($pages_atom, "pages_export", $page);
$pages_atom.= ' </entry>'."\r";
}
$pages_atom.= '</feed>'."\r";
$exports["pages.atom"] = $pages_atom;
}
if (isset($_POST['groups'])) {
list($where, $params) = keywords($_POST['filter_groups'], "name LIKE :query", "groups");
$groups = Group::find(array("where" => $where, "params" => $params, "order" => "id ASC"));
$groups_yaml = array("groups" => array(),
"permissions" => array());
foreach (SQL::current()->select("permissions", "*", array("group_id" => 0))->fetchAll() as $permission)
$groups_yaml["permissions"][$permission["id"]] = $permission["name"];
foreach ($groups as $index => $group)
$groups_yaml["groups"][$group->name] = $group->permissions;
$exports["groups.yaml"] = YAML::dump($groups_yaml);
}
if (isset($_POST['users'])) {
list($where, $params) = keywords($_POST['filter_users'], "login LIKE :query OR full_name LIKE :query OR email LIKE :query OR website LIKE :query", "users");
$users = User::find(array("where" => $where, "params" => $params, "order" => "id ASC"));
$users_yaml = array();
foreach ($users as $user) {
$users_yaml[$user->login] = array();
foreach ($user as $name => $attr)
if ($name != "no_results" and $name != "group_id" and $name != "id" and $name != "login")
$users_yaml[$user->login][$name] = $attr;
elseif ($name == "group_id")
$users_yaml[$user->login]["group"] = $user->group->name;
}
$exports["users.yaml"] = YAML::dump($users_yaml);
}
$trigger->filter($exports, "export");
require INCLUDES_DIR."/lib/zip.php";
$zip = new ZipFile();
foreach ($exports as $filename => $content)
$zip->addFile($content, $filename);
$zip_contents = $zip->file();
$filename = sanitize(camelize($config->name), false, true)."_Export_".date("Y-m-d");
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$filename.".zip\"");
header("Content-length: ".strlen($zip_contents)."\n\n");
echo $zip_contents;
}
/**
* Function: import
* Importing content from other systems.
*/
public function import() {
if (!Visitor::current()->group->can("add_post"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to import content."));
$this->display("import");
}
/**
* Function: import_chyrp
* Chyrp importing.
*/
public function import_chyrp() {
if (empty($_POST))
redirect("/admin/?action=import");
if (!Visitor::current()->group->can("add_post"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to import content."));
if (isset($_FILES['posts_file']) and $_FILES['posts_file']['error'] == 0)
if (!$posts = simplexml_load_file($_FILES['posts_file']['tmp_name']) or $posts->generator != "Chyrp")
Flash::warning(__("Chyrp Posts export file is invalid."), "/admin/?action=import");
if (isset($_FILES['pages_file']) and $_FILES['pages_file']['error'] == 0)
if (!$pages = simplexml_load_file($_FILES['pages_file']['tmp_name']) or $pages->generator != "Chyrp")
Flash::warning(__("Chyrp Pages export file is invalid."), "/admin/?action=import");
if (ini_get("memory_limit") < 20)
ini_set("memory_limit", "20M");
$trigger = Trigger::current();
$visitor = Visitor::current();
$sql = SQL::current();
function media_url_scan(&$value) {
$config = Config::current();
$regexp_url = preg_quote($_POST['media_url'], "/");
if (preg_match_all("/{$regexp_url}([^\.\!,\?;\"\'<>\(\)\[\]\{\}\s\t ]+)\.([a-zA-Z0-9]+)/", $value, $media))
foreach ($media[0] as $matched_url) {
$filename = upload_from_url($matched_url);
$value = str_replace($matched_url, $config->url.$config->uploads_path.$filename, $value);
}
}
if (isset($_FILES['groups_file']) and $_FILES['groups_file']['error'] == 0) {
$import = YAML::load($_FILES['groups_file']['tmp_name']);
foreach ($import["groups"] as $name => $permissions)
if (!$sql->count("groups", array("name" => $name)))
$trigger->call("import_chyrp_group", Group::add($name, (array) $permissions));
foreach ($import["permissions"] as $id => $name)
if (!$sql->count("permissions", array("id" => $id)))
$sql->insert("permissions", array("id" => $id, "name" => $name));
}
if (isset($_FILES['users_file']) and $_FILES['users_file']['error'] == 0) {
$users = YAML::load($_FILES['users_file']['tmp_name']);
foreach ($users as $login => $user) {
$group_id = $sql->select("groups", "id", array("name" => $user["group"]), "id DESC")->fetchColumn();
$group = ($group_id) ? $group_id : $config->default_group ;
if (!$sql->count("users", array("login" => $login)))
$user = User::add($login,
$user["password"],
$user["email"],
$user["full_name"],
$user["website"],
$group,
$user["joined_at"]);
$trigger->call("import_chyrp_user", $user);
}
}
if (isset($_FILES['posts_file']) and $_FILES['posts_file']['error'] == 0)
foreach ($posts->entry as $entry) {
$chyrp = $entry->children("http://chyrp.net/export/1.0/");
$login = $entry->author->children("http://chyrp.net/export/1.0/")->login;
$user_id = $sql->select("users", "id", array("login" => $login), "id DESC")->fetchColumn();
$data = xml2arr($entry->content);
if (!empty($_POST['media_url']))
array_walk_recursive($data, "media_url_scan");
$post = Post::add($data,
$chyrp->clean,
Post::check_url($chyrp->url),
$chyrp->feather,
($user_id ? $user_id : $visitor->id),
(bool) (int) $chyrp->pinned,
$chyrp->status,
datetime($entry->published),
($entry->updated == $entry->published) ?
"0000-00-00 00:00:00" :
datetime($entry->updated),
"",
false);
$trigger->call("import_chyrp_post", $entry, $post);
}
if (isset($_FILES['pages_file']) and $_FILES['pages_file']['error'] == 0)
foreach ($pages->entry as $entry) {
$chyrp = $entry->children("http://chyrp.net/export/1.0/");
$attr = $entry->attributes("http://chyrp.net/export/1.0/");
$login = $entry->author->children("http://chyrp.net/export/1.0/")->login;
$user_id = $sql->select("users", "id", array("login" => $login), "id DESC")->fetchColumn();
$page = Page::add($entry->title,
$entry->content,
($user_id ? $user_id : $visitor->id),
$attr->parent_id,
(bool) (int) $chyrp->show_in_list,
$chyrp->list_order,
$chyrp->clean,
Page::check_url($chyrp->url),
datetime($entry->published),
($entry->updated == $entry->published) ? "0000-00-00 00:00:00" : datetime($entry->updated));
$trigger->call("import_chyrp_page", $entry, $page);
}
Flash::notice(__("Chyrp content successfully imported!"), "/admin/?action=import");
}
/**
* Function: import_wordpress
* WordPress importing.
*/
public function import_wordpress() {
if (empty($_POST))
redirect("/admin/?action=import");
if (!Visitor::current()->group->can("add_post"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to import content."));
$config = Config::current();
if (!in_array("text", $config->enabled_feathers))
error(__("Missing Feather"), __("Importing from WordPress requires the Text feather to be installed and enabled."));
if (ini_get("memory_limit") < 20)
ini_set("memory_limit", "20M");
$trigger = Trigger::current();
$stupid_xml = file_get_contents($_FILES['xml_file']['tmp_name']);
$sane_xml = preg_replace(array("/<wp:comment_content>/", "/<\/wp:comment_content>/"),
array("<wp:comment_content><![CDATA[", "]]></wp:comment_content>"),
$stupid_xml);
$sane_xml = str_replace(array("<![CDATA[<![CDATA[", "]]>]]>"),
array("<![CDATA[", "]]>"),
$sane_xml);
if (!substr_count($sane_xml, "xmlns:export"))
$sane_xml = preg_replace("/xmlns:content=\"([^\"]+)\"(\s+)/m",
"xmlns:content=\"\\1\"\\2xmlns:excerpt=\"http://wordpress.org/excerpt/1.0/\"\\2",
$sane_xml);
$fix_amps_count = 1;
while ($fix_amps_count)
$sane_xml = preg_replace("/<wp:meta_value>(.+)&(?!amp;)(.+)<\/wp:meta_value>/m",
"<wp:meta_value>\\1&\\2</wp:meta_value>",
$sane_xml, -1, $fix_amps_count);
$xml = simplexml_load_string($sane_xml, "SimpleXMLElement", LIBXML_NOCDATA);
if (!$xml or !substr_count($xml->channel->generator, "wordpress.org"))
Flash::warning(__("File does not seem to be a valid WordPress export file."),
"/admin/?action=import");
foreach ($xml->channel->item as $item) {
$wordpress = $item->children("http://wordpress.org/export/1.0/");
$content = $item->children("http://purl.org/rss/1.0/modules/content/");
if ($wordpress->status == "attachment" or $item->title == "zz_placeholder")
continue;
$regexp_url = preg_quote($_POST['media_url'], "/");
if (!empty($_POST['media_url']) and
preg_match_all("/{$regexp_url}([^\.\!,\?;\"\'<>\(\)\[\]\{\}\s\t ]+)\.([a-zA-Z0-9]+)/",
$content->encoded,
$media))
foreach ($media[0] as $matched_url) {
$filename = upload_from_url($matched_url);
$content->encoded = str_replace($matched_url, $config->url.$config->uploads_path.$filename, $content->encoded);
}
$clean = (isset($wordpress->post_name)) ? $wordpress->post_name : sanitize($item->title) ;
if (empty($wordpress->post_type) or $wordpress->post_type == "post") {
$status_translate = array("publish" => "public",
"draft" => "draft",
"private" => "private",
"static" => "public",
"object" => "public",
"inherit" => "public",
"future" => "draft",
"pending" => "draft");
$data = array("title" => trim($item->title), "body" => trim($content->encoded));
$post = Post::add($data,
$clean,
Post::check_url($clean),
"text",
null,
false,
$status_translate[(string) $wordpress->status],
($wordpress->post_date == "0000-00-00 00:00:00") ? datetime() : $wordpress->post_date,
null,
"",
false);
$trigger->call("import_wordpress_post", $item, $post);
} elseif ($wordpress->post_type == "page") {
$page = Page::add(trim($item->title),
trim($content->encoded),
null,
0,
true,
0,
$clean,
Page::check_url($clean),
($wordpress->post_date == "0000-00-00 00:00:00") ? datetime() : $wordpress->post_date);
$trigger->call("import_wordpress_page", $item, $page);
}
}
Flash::notice(__("WordPress content successfully imported!"), "/admin/?action=import");
}
/**
* Function: import_tumblr
* Tumblr importing.
*/
public function import_tumblr() {
if (empty($_POST))
redirect("/admin/?action=import");
if (!Visitor::current()->group->can("add_post"))
show_403(__("Access Denied"), __("You do not have sufficient privileges to import content."));
$config = Config::current();
if (!in_array("text", $config->enabled_feathers) or
!in_array("video", $config->enabled_feathers) or
!in_array("audio", $config->enabled_feathers) or
!in_array("chat", $config->enabled_feathers) or
!in_array("photo", $config->enabled_feathers) or
!in_array("quote", $config->enabled_feathers) or
!in_array("link", $config->enabled_feathers))
error(__("Missing Feather"), __("Importing from Tumblr requires the Text, Video, Audio, Chat, Photo, Quote, and Link feathers to be installed and enabled."));
if (ini_get("memory_limit") < 20)
ini_set("memory_limit", "20M");
if (!parse_url($_POST['tumblr_url'], PHP_URL_SCHEME))
$_POST['tumblr_url'] = "http://".$_POST['tumblr_url'];
set_time_limit(3600);
$url = rtrim($_POST['tumblr_url'], "/")."/api/read?num=50";
$api = preg_replace("/<(\/?)([a-z]+)\-([a-z]+)/", "<\\1\\2_\\3", get_remote($url));
$api = preg_replace("/ ([a-z]+)\-([a-z]+)=/", " \\1_\\2=", $api);
$xml = simplexml_load_string($api);
if (!isset($xml->tumblelog))
Flash::warning(__("The URL you specified does not seem to be a valid Tumblr site."),
"/admin/?action=import");
$already_in = $posts = array();
foreach ($xml->posts->post as $post) {
$posts[] = $post;
$already_in[] = $post->attributes()->id;
}
while ($xml->posts->attributes()->total > count($posts)) {
set_time_limit(3600);
$api = preg_replace("/<(\/?)([a-z]+)\-([a-z]+)/", "<\\1\\2_\\3", get_remote($url."&start=".count($posts)));
$api = preg_replace("/ ([a-z]+)\-([a-z]+)=/", " \\1_\\2=", $api);
$xml = simplexml_load_string($api, "SimpleXMLElement", LIBXML_NOCDATA);
foreach ($xml->posts->post as $post)
if (!in_array($post->attributes()->id, $already_in)) {
$posts[] = $post;
$already_in[] = $post->attributes()->id;
}
}
function reverse($a, $b) {
if (empty($a) or empty($b)) return 0;
return (strtotime($a->attributes()->date) < strtotime($b->attributes()->date)) ? -1 : 1 ;
}
set_time_limit(3600);
usort($posts, "reverse");
foreach ($posts as $key => $post) {
set_time_limit(3600);
if ($post->attributes()->type == "audio")
continue; # Can't import Audio posts since Tumblr has the files locked in to Amazon.
$translate_types = array("regular" => "text", "conversation" => "chat");
$clean = "";
switch($post->attributes()->type) {
case "regular":
$title = fallback($post->regular_title);
$values = array("title" => $title,
"body" => $post->regular_body);
$clean = sanitize($title);
break;
case "video":
$values = array("embed" => $post->video_player,
"caption" => fallback($post->video_caption));
break;
case "conversation":
$title = fallback($post->conversation_title);
$lines = array();
foreach ($post->conversation_line as $line)
$lines[] = $line->attributes()->label." ".$line;
$values = array("title" => $title,
"dialogue" => implode("\n", $lines));
$clean = sanitize($title);
break;
case "photo":