|
- <?php
- class Aggregator extends Modules {
- static function __install() {
- $config = Config::current();
- $config->set("last_aggregation", 0);
- $config->set("aggregate_every", 30);
- $config->set("disable_aggregation", false);
- $config->set("aggregates", array());
-
- Group::add_permission("add_aggregate", "Add Aggregate");
- Group::add_permission("edit_aggregate", "Edit Aggregate");
- Group::add_permission("delete_aggregate", "Delete Aggregate");
- }
-
- static function __uninstall() {
- $config = Config::current();
- $config->remove("last_aggregation");
- $config->remove("aggregate_every");
- $config->remove("disable_aggregation");
- $config->remove("aggregates");
-
- Group::remove_permission("add_aggregate");
- Group::remove_permission("edit_aggregate");
- Group::remove_permission("delete_aggregate");
- }
-
- public function main_index($main) {
- $config = Config::current();
- if ($config->disable_aggregation or time() - $config->last_aggregation < ($config->aggregate_every * 60))
- return;
-
- $aggregates = (array) $config->aggregates;
-
- if (empty($aggregates))
- return;
-
- foreach ($aggregates as $name => $feed) {
- $xml_contents = preg_replace(array("/<(\/?)dc:date>/", "/xmlns=/"),
- array("<\\1date>", "a="),
- get_remote($feed["url"]));
- $xml = simplexml_load_string($xml_contents, "SimpleXMLElement", LIBXML_NOCDATA);
-
- if ($xml === false)
- continue;
-
- # Flatten namespaces recursively
- $this->flatten($xml);
-
- $items = array();
-
- if (isset($xml->entry))
- foreach ($xml->entry as $entry)
- array_unshift($items, $entry);
- elseif (isset($xml->item))
- foreach ($xml->item as $item)
- array_unshift($items, $item);
- else
- foreach ($xml->channel->item as $item)
- array_unshift($items, $item);
-
- foreach ($items as $item) {
- $date = oneof(@$item->pubDate, @$item->date, @$item->updated, 0);
- $updated = strtotime($date);
-
- if ($updated > $feed["last_updated"]) {
- # Get creation date ('created' in Atom)
- $created = @$item->created ? strtotime($item->created) : 0;
- if ($created <= 0)
- $created = $updated;
-
- # Construct the post data from the user-defined XPath mapping:
- $data = array("aggregate" => $name);
- foreach ($feed["data"] as $attr => $field) {
- $field = (!empty($field) ? $this->parse_field($field, $item) : "");
- $data[$attr] = (is_string($field) ? $field : YAML::dump($field));
- }
-
- $clean = sanitize(oneof(@$data["title"], @$data["name"], ""));
-
- Post::add($data, $clean, null, $feed["feather"], $feed["author"],
- false,
- "public",
- datetime($created),
- datetime($updated));
-
- $aggregates[$name]["last_updated"] = $updated;
- }
- }
- }
-
- $config->set("aggregates", $aggregates);
- $config->set("last_aggregation", time());
- }
-
- public function admin_manage_aggregates($admin) {
- $aggregates = array();
-
- foreach ((array) Config::current()->aggregates as $name => $aggregate)
- $aggregates[] = array_merge(array("name" => $name), array("user" => new User($aggregate["author"])), $aggregate);
-
- $admin->display("manage_aggregates", array("aggregates" => new Paginator($aggregates, 25)));
- }
-
- public function manage_nav($navs) {
- if (!Visitor::current()->group->can("edit_aggregate", "delete_aggregate"))
- return $navs;
-
- $navs["manage_aggregates"] = array("title" => __("Aggregates", "aggregator"),
- "selected" => array("edit_aggregate", "delete_aggregate", "new_aggregate"));
-
- return $navs;
- }
-
- public function manage_nav_pages($pages) {
- array_push($pages, "manage_aggregates", "edit_aggregate", "delete_aggregate", "new_aggregate");
- return $pages;
- }
-
- public function manage_nav_show($possibilities) {
- $possibilities[] = Visitor::current()->group->can("edit_aggregate", "delete_aggregate");
- return $possibilities;
- }
-
- public function determine_action($action) {
- if ($action != "manage") return;
-
- if (Visitor::current()->group->can("edit_aggregate", "delete_aggregate"))
- return "manage_aggregates";
- }
-
- public function settings_nav($navs) {
- if (Visitor::current()->group->can("change_settings"))
- $navs["aggregation_settings"] = array("title" => __("Aggregation", "aggregator"));
-
- return $navs;
- }
-
- private function flatten(&$start) {
- foreach ($start as $key => $val) {
- if (count($val) and !is_string($val)) {
- foreach ($val->getNamespaces(true) as $namespace => $url) {
- if (empty($namespace))
- continue;
-
- foreach ($val->children($url) as $attr => $child) {
- $name = $namespace.":".$attr;
- $val->$name = $child;
- foreach ($child->attributes() as $attr => $value)
- $val->$name->addAttribute($attr, $value);
- }
- }
-
- $this->flatten($val);
- }
- }
- }
-
- static function image_from_content($html) {
- preg_match("/img src=('|\")([^ \\1]+)\\1/", $html, $image);
- return $image[2];
- }
-
- static function upload_image_from_content($html) {
- return upload_from_url(self::image_from_content($html));
- }
-
- public function parse_field($value, $item, $basic = true) {
- if (is_array($value)) {
- $parsed = array();
- foreach ($value as $key => $val)
- $parsed[$this->parse_field($key, $item, false)] = $this->parse_field($val, $item, false);
-
- return $parsed;
- } elseif (!is_string($value))
- return $value;
-
- if ($basic and preg_match("/^([a-z0-9:\/]+)$/", $value)) {
- $xpath = $item->xpath($value);
- return html_entity_decode($xpath[0], ENT_QUOTES, "utf-8");
- }
-
- if (preg_match("/feed\[(.+)\]\.attr\[([^\]]+)\]/", $value, $matches)) {
- $xpath = $item->xpath($matches[1]);
- $value = str_replace($matches[0],
- html_entity_decode($xpath[0]->attributes()->$matches[2],
- ENT_QUOTES,
- "utf-8"),
- $value);
- }
-
- if (preg_match("/feed\[(.+)\]/", $value, $matches)) {
- $xpath = $item->xpath($matches[1]);
- $value = str_replace($matches[0],
- html_entity_decode($xpath[0], ENT_QUOTES, "utf-8"),
- $value);
- }
-
- if (preg_match_all("/call:([^\(]+)\((.+)\)/", $value, $calls))
- foreach ($calls[0] as $index => $full) {
- $function = $calls[1][$index];
- $arguments = explode(" || ", $calls[2][$index]);
-
- $value = str_replace($full,
- call_user_func_array($function, $arguments),
- $value);
- }
-
- return $value;
- }
-
- public function admin_aggregation_settings($admin) {
- if (!Visitor::current()->group->can("change_settings"))
- show_403(__("Access Denied"), __("You do not have sufficient privileges to change settings."));
-
- if (empty($_POST))
- return $admin->display("aggregation_settings");
-
- if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
- show_403(__("Access Denied"), __("Invalid security key."));
-
- $config = Config::current();
- $set = array($config->set("aggregate_every", $_POST['aggregate_every']),
- $config->set("disable_aggregation", !empty($_POST['disable_aggregation'])));
-
- if (!in_array(false, $set))
- Flash::notice(__("Settings updated."), "/admin/?action=aggregation_settings");
- }
-
- public function admin_new_aggregate($admin) {
- $admin->context["users"] = User::find();
-
- if (!Visitor::current()->group->can("add_aggregate"))
- show_403(__("Access Denied"), __("You do not have sufficient privileges to add aggregates.", "aggregator"));
-
- if (empty($_POST))
- return $admin->display("new_aggregate");
-
- if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
- show_403(__("Access Denied"), __("Invalid security key."));
-
- if (empty($_POST['data']))
- return Flash::warning(__("Please enter the attributes for the Feather."));
-
- if (empty($_POST['name']))
- return Flash::warning(__("Please enter a name for the aggregate."));
-
- if (empty($_POST['url']))
- return Flash::warning(__("What are you, crazy?! I can't create an aggregate without a source!"));
-
- $config = Config::current();
-
- $aggregate = array("url" => $_POST['url'],
- "last_updated" => 0,
- "feather" => $_POST['feather'],
- "author" => $_POST['author'],
- "data" => YAML::load($_POST['data']));
-
- $config->aggregates[$_POST['name']] = $aggregate;
- $config->set("aggregates", $config->aggregates);
- $config->set("last_aggregation", 0); # to force a refresh
-
- Flash::notice(__("Aggregate created.", "aggregator"), "/admin/?action=manage_aggregates");
- }
-
- public function admin_edit_aggregate($admin) {
- if (empty($_GET['id']))
- error(__("No ID Specified"), __("An ID is required to delete an aggregate.", "aggregator"));
-
- if (!Visitor::current()->group->can("edit_aggregate"))
- show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this aggregate.", "aggregator"));
-
- $admin->context["users"] = User::find();
-
- $config = Config::current();
-
- $aggregate = $config->aggregates[$_GET['id']];
-
- if (empty($_POST))
- return $admin->display("edit_aggregate",
- array("users" => User::find(),
- "aggregate" => array("name" => $_GET['id'],
- "url" => $aggregate["url"],
- "feather" => $aggregate["feather"],
- "author" => $aggregate["author"],
- "data" => preg_replace("/---\n/",
- "",
- YAML::dump($aggregate["data"])))));
-
- if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
- show_403(__("Access Denied"), __("Invalid security key."));
-
- $aggregate = array("url" => $_POST['url'],
- "last_updated" => 0,
- "feather" => $_POST['feather'],
- "author" => $_POST['author'],
- "data" => YAML::load($_POST['data']));
-
- unset($config->aggregates[$_GET['id']]);
- $config->aggregates[$_POST['name']] = $aggregate;
-
- $config->set("aggregates", $config->aggregates);
- $config->set("last_aggregation", 0); // to force a refresh
-
- Flash::notice(__("Aggregate updated.", "aggregator"), "/admin/?action=manage_aggregates");
- }
-
- public function admin_delete_aggregate($admin) {
- if (empty($_GET['id']))
- error(__("No ID Specified"), __("An ID is required to delete an aggregate.", "aggregator"));
-
- if (!Visitor::current()->group->can("delete_aggregate"))
- show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this aggregate.", "aggregator"));
-
- $config = Config::current();
-
- $aggregate = $config->aggregates[$_GET['id']];
-
- $admin->context["aggregate"] = array("name" => $_GET['id'],
- "url" => $aggregate["url"]);
- $admin->display("delete_aggregate", array("aggregate" => array("name" => $_GET['id'],
- "url" => $aggregate["url"])));
- }
-
- public function admin_destroy_aggregate($admin) {
- if (empty($_POST['id']))
- error(__("No ID Specified"), __("An ID is required to delete an aggregate.", "aggregator"));
-
- if ($_POST['destroy'] == "bollocks")
- redirect("/admin/?action=manage_aggregates");
-
- if (!isset($_POST['hash']) or $_POST['hash'] != Config::current()->secure_hashkey)
- show_403(__("Access Denied"), __("Invalid security key."));
-
- if (!Visitor::current()->group->can("delete_aggregate"))
- show_403(__("Access Denied"), __("You do not have sufficient privileges to delete this aggregate.", "aggregator"));
-
- $name = $_POST['id'];
- if ($_POST["delete_posts"]) {
- $this->delete_posts($name);
- $notice = __("Aggregate and its posts deleted.", "aggregator");
- } else {
- $notice = __("Aggregate deleted.", "aggregator");
- }
-
- $config = Config::current();
- unset($config->aggregates[$name]);
- $config->set("aggregates", $config->aggregates);
- Flash::notice($notice, "/admin/?action=manage_aggregates");
- }
-
- function delete_posts($aggregate_name) {
- $sql = SQL::current();
- $attrs = $sql->select("post_attributes",
- "post_id",
- array("name" => "aggregate", "value" => $aggregate_name))->fetchAll();
- foreach( $attrs as $attr) {
- Post::delete($attr["post_id"]);
- }
-
- }
-
- public function help_aggregation_syntax() {
- $title = __("Post Values", "aggregator");
-
- $body = "<p>".__("Use <a href=\"http://yaml.org/\">YAML</a> to specify what post attribute holds what value of the feed entry.", "aggregator")."</p>";
-
- $body.= "<h2>".__("XPath", "aggregator")."</h2>";
- $body.= "<cite><strong>".__("Usage")."</strong>: <code>feed[xp/ath]</code></cite>\n";
- $body.= "<p>".__("You can use XPath to navigate the feed and find the correct attribute.", "aggregator")."</p>";
-
- $body.= "<h2>".__("Attributes", "aggregator")."</h2>";
- $body.= "<cite><strong>".__("Usage")."</strong>: <code>feed[xp/ath].attr[foo]</code></cite>\n";
- $body.= "<p>".__("To get the attribute of an element, use XPath to find it and the <code>.attr[]</code> syntax to grab an attribute.", "aggregator")."</p>";
-
- $body.= "<h2>".__("Functions", "aggregator")."</h2>";
- $body.= "<cite><strong>".__("Usage")."</strong>: <code>call:foo_function(feed[foo] || feed[arg2])</code></cite>\n";
- $body.= "<p>".__("To call a function and use its return value for the post's value, use <code>call:</code>. Separate arguments with <code> || </code>.", "aggregator")."</p>";
- $body.= "<p>".__("The Aggregator module provides a couple helper functions:", "aggregator")."</p>";
- $body.= "<cite><strong>".__("To upload an image from the content", "aggregator")."</strong>: <code>call:Aggregator::upload_image_from_content(feed[content])</code></cite>";
- $body.= "<cite><strong>".__("To get the URL of an image in the content", "aggregator")."</strong>: <code>call:Aggregator::image_from_content(feed[content])</code></cite>";
-
- $body.= "<h2>".__("Example", "aggregator")."</h2>";
- $body.= "<p>".__("From the Photo feather:", "aggregator")."</pre>";
- $body.= "<pre><code>filename: call:upload_from_url(feed[link].attr[href])\ncaption: feed[description] # or just \"description\"</code></pre>";
-
- return array($title, $body);
- }
- }
|