diff options
author | Dries Buytaert <dries@buytaert.net> | 2004-01-07 19:52:10 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2004-01-07 19:52:10 +0000 |
commit | 94f6e94ffdb72e0ac78c5d06ec95a9e6f9f6f47f (patch) | |
tree | 9456e655f26c86076bd97f4cdaae4c2cc76684fe | |
parent | 9352d6abf8cc4cbf78e6a92e6cfb18b1d9fc750f (diff) | |
download | brdo-94f6e94ffdb72e0ac78c5d06ec95a9e6f9f6f47f.tar.gz brdo-94f6e94ffdb72e0ac78c5d06ec95a9e6f9f6f47f.tar.bz2 |
- Many excellent news aggregator improvements by Kjartan:
+ Added drupal_http_request().
+ Replaced rssfeeds with OPML feed subscription list.
+ Added support for pubDate.
+ Added support for conditional gets using ETag and Last-Modified.
-rw-r--r-- | CHANGELOG | 3 | ||||
-rw-r--r-- | database/database.mssql | 6 | ||||
-rw-r--r-- | database/database.mysql | 4 | ||||
-rw-r--r-- | database/database.pgsql | 4 | ||||
-rw-r--r-- | includes/common.inc | 123 | ||||
-rw-r--r-- | modules/aggregator.module | 393 | ||||
-rw-r--r-- | modules/aggregator/aggregator.module | 393 | ||||
-rw-r--r-- | modules/drupal.module | 2 | ||||
-rw-r--r-- | modules/drupal/drupal.module | 2 | ||||
-rw-r--r-- | modules/filter.module | 2 | ||||
-rw-r--r-- | modules/filter/filter.module | 2 | ||||
-rw-r--r-- | modules/path.module | 37 | ||||
-rw-r--r-- | modules/path/path.module | 37 | ||||
-rw-r--r-- | update.php | 11 |
14 files changed, 591 insertions, 428 deletions
@@ -4,6 +4,9 @@ Drupal x.x.x, xxxx-xx-xx - added support for the MetaWeblog API and MovableType extensions. - added a file API: enables better document management. - improved the watchdog and search module to log search keys. +- news aggregator: + * added support for conditional GET. + * added OPML feed subscription list. - comment module: * made it possible to disable the "comment viewing controls". - performance: diff --git a/database/database.mssql b/database/database.mssql index a3a7d9257..079c6b0f3 100644 --- a/database/database.mssql +++ b/database/database.mssql @@ -104,10 +104,12 @@ CREATE TABLE [dbo].[feed] ( [title] [varchar] (255) NOT NULL , [url] [varchar] (255) NOT NULL , [refresh] [int] NOT NULL , - [timestamp] [int] NULL , + [checked] [int] MOT NULL , [attributes] [varchar] (255) NULL , [link] [varchar] (255) NULL , - [description] [varchar] (8000) NULL + [description] [varchar] (8000) NULL , + [etag] [varchar] (255) NULL , + [modified] [int] NOT NULL ) ON [PRIMARY] GO diff --git a/database/database.mysql b/database/database.mysql index 12cc04519..8a2762fcd 100644 --- a/database/database.mysql +++ b/database/database.mysql @@ -156,10 +156,12 @@ CREATE TABLE feed ( title varchar(255) NOT NULL default '', url varchar(255) NOT NULL default '', refresh int(10) NOT NULL default '0', - timestamp int(10) NOT NULL default '0', + checked int(10) NOT NULL default '0', attributes varchar(255) NOT NULL default '', link varchar(255) NOT NULL default '', description longtext NOT NULL, + etag varchar(255) NOT NULL default '', + modified int(10) NOT NULL default '0', PRIMARY KEY (fid), UNIQUE KEY link (url), UNIQUE KEY title (title) diff --git a/database/database.pgsql b/database/database.pgsql index 3c9f83897..7885e1947 100644 --- a/database/database.pgsql +++ b/database/database.pgsql @@ -154,10 +154,12 @@ CREATE TABLE feed ( title varchar(255) NOT NULL default '', url varchar(255) NOT NULL default '', refresh integer NOT NULL default '0', - timestamp integer NOT NULL default '0', + checked integer NOT NULL default '0', attributes varchar(255) NOT NULL default '', link varchar(255) NOT NULL default '', description text NOT NULL default '', + etag varchar(255) NOT NULL default '', + modified integer NOT NULL default '0', PRIMARY KEY (fid), UNIQUE (title), UNIQUE (url) diff --git a/includes/common.inc b/includes/common.inc index 203ede5fc..cad24179a 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -187,6 +187,127 @@ function drupal_not_found() { print theme("page", '<h1>'. t('Page not found') .'</h1>'); } } + +/** + * Flexible and powerful HTTP client implementation. Allows to GET, POST, PUT + * or any other HTTP requests. Handles redirects. + * + * @param $url A string containing a fully qualified URI. + * @param $headers An array containing a HTTP header => value pair. + * @param $method A string defining the HTTP request to use. + * @param $data A string containing data to include in the request. + * @param $retry An integer representing how many times to retry the request + * in case of a redirect. + * @return An object containing the HTTP request headers, response code, + * headers, data, and redirect status. + */ +function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) { + // Parse the URL, and make sure we can handle the schema + $uri = parse_url($url); + switch ($uri['scheme']) { + case 'http': + $fp = @fsockopen($uri['host'], ($uri['port'] ? $uri['port'] : 80), $errno, $errstr, 15); + break; + case 'https': + // Note: only works for PHP 4.3 compiled with openssl + $fp = @fsockopen("ssl://$uri[host]", ($uri['port'] ? $uri['port'] : 443), $errno, $errstr, 20); + break; + default: + $result->error = "invalid schema $uri[scheme]"; + return $result; + } + + // Make sure the socket opened properly + if (!$fp) { + $result->error = trim("$errno $errstr"); + return $result; + } + + // Construct the path to act on + $path = $uri['path'] ? $uri['path'] : '/'; + if ($uri['query']) { + $path .= "?$uri[query]"; + } + + // Create http request + $defaults = array( + 'host' => "Host: $uri[host]", + 'user-agent' => 'User-Agent: Drupal (+http://www.drupal.org/)', + 'content-length' => 'Content-Length: '. strlen($data) + ); + + foreach ($headers as $header => $value) { + $defaults[$header] = "$header: $value"; + } + + $request = "$method $path HTTP/1.0\r\n"; + $request .= implode("\r\n", $defaults); + $request .= "\r\n\r\n"; + if ($data) { + $request .= "$data\r\n"; + } + $result->request = $request; + + fwrite($fp, $request); + + // Fetch response. + while (!feof($fp) && $data = fgets($fp)) { + $response[] = $data; + } + fclose($fp); + + // Parse response. + list($protocol, $code, $text) = explode(' ', trim(array_shift($response)), 3); + $result->headers = array(); + $result->data = ''; + + // Parse headers. + while ($line = trim(array_shift($response))) { + if ($line == '') { + break; + } + list($header, $value) = explode(':', $line, 2); + $result->headers[$header] = trim($value); + } + + $result->data = implode('', $response); + + $responses = array( + 100 => 'Continue', 101 => 'Switching Protocols', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported' + ); + // RFC 2616 states that all unknown HTTP codes must be treated the same as + // the base code in their class: + if (!isset($responses[$code])) { + $code = floor($code / 100) * 100; + } + + switch ($code) { + case 200: // OK + case 304: // Not modified + break; + case 301: // Moved permanently + case 302: // Moved temporarily + case 307: // Moved temporarily + $location = $result->headers['Location']; + + if ($retry) { + $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry); + $result->redirect_code = $result->code; + } + $result->redirect_url = $location; + + break; + default: + $result->error = $text; + } + + $result->code = $code; + return $result; +} /* @} */ function error_handler($errno, $message, $filename, $line, $variables) { @@ -349,7 +470,7 @@ function drupal_specialchars($input, $quotes = ENT_NOQUOTES) { * allowed. See RFC 2822 for details. * * @param $mail A string containing an email address. - * @return + * @return */ function valid_email_address($mail) { $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+'; diff --git a/modules/aggregator.module b/modules/aggregator.module index 1983718bc..91752bf8a 100644 --- a/modules/aggregator.module +++ b/modules/aggregator.module @@ -1,13 +1,10 @@ <?php -// $Id$ - - -function aggregator_help($section = "admin/help#aggregator") { - $output = ""; +/* $Id$ */ +function aggregator_help($section) { switch ($section) { case 'admin/help#aggregator': - $output .= "<p>Thousands of web sites, especially news sites and weblogs, syndicate their most recent site content for others to display. The syndicated content always includes titles, also known as headlines, for the newest published stories. Each headline acts as a direct link to the stories on the remote site. Along with the headline, most sites typically provide either the first few paragraphs of the story or a short summary. Many individuals use client-based news aggregators on their personal computer to aggregate content, such as %amphetadesk</p>"; + $output = "<p>Thousands of web sites, especially news sites and weblogs, syndicate their most recent site content for others to display. The syndicated content always includes titles, also known as headlines, for the newest published stories. Each headline acts as a direct link to the stories on the remote site. Along with the headline, most sites typically provide either the first few paragraphs of the story or a short summary. Many individuals use client-based news aggregators on their personal computer to aggregate content, such as %amphetadesk</p>"; $output .= "<p>Drupal also has a news aggregator built in as a standard feature. With it, you can subscribe to feeds from other sites and display their content for your site users. Simply enable the aggregator module in site administration and enter the feeds that you choose.</p>"; $output .= "<h3>What do I need to subscribe to a feed?</h3>"; $output .= "<p>The standard method of syndication is using the XML-based %rss (RSS). To syndicate a site's content, obtain the full URL of the RSS page providing syndication. Common file tags for RSS pages are .rss, .xml and .rdf. Example: %slashdot-rss.</p>"; @@ -50,84 +47,77 @@ function aggregator_help($section = "admin/help#aggregator") { $output .= "</ul>"; $output .= "<h3>RSS feed blocks</h3>"; $output .= "<p>In addition to providing subscribed content through the news aggregator, Drupal automatically creates a block for each subscribed feed and every bundle created. Beside each headline in each block, Drupal includes an icon which acts a blog it link. Enable any or all of the blocks using block management.</p>"; - $output = t($output, array("%amphetadesk" => "<a href=\"http://www.disobey.com/amphetadesk/\">AmphetaDesk</a>", "%rss" => "<a href=\"http://groups.yahoo.com/group/rss-dev/files/specification.html\">Rich Site Summary</a>", "%slashdot-rss" => "<a href=\"http://slashdot.org/slashdot.rdf\">http://slashdot.org/slashdot.rdf</a>", "%syndic8" => "<a href=\"http://www.syndic8.com/\">Syndic8</a>", "%rss-what" => "<a href=\"http://www.xml.com/pub/a/2002/12/18/dive-into-xml.html\">What is RSS</a>", "%rss-evolution" => "<a href=\"http://www.webreference.com/authoring/languages/xml/rss/1/\">The Evolution of RSS</a>", "%admin-news" => l(t("RSS/RDF"), "admin/node/syndication/news"), "%new-feed" => l(t("new feed"), "admin/node/syndication/news/add/feed"), "%update-items" => l(t("update items"), "admin/node/syndication/news"))); - break; + $output .= "<h3>Subscription list</h3>"; + $output .= "<p>Drupal automatically generates an OPML feed file that is available by selecting the XML icon on the News Sources page.</p>"; + $output .= "<h3>Technical details</h3>"; + $output .= "<p>When fetching feeds Drupal supports conditional GETs, this reduces the bandwidth usage for feeds that have not been updated since the last check.</p>"; + $output .= "<p>If a feed is permanently moved to a new location Drupal will automatically update the feed URL to the new address.</p>"; + return t($output, array("%amphetadesk" => "<a href=\"http://www.disobey.com/amphetadesk/\">AmphetaDesk</a>", "%rss" => "<a href=\"http://groups.yahoo.com/group/rss-dev/files/specification.html\">Rich Site Summary</a>", "%slashdot-rss" => "<a href=\"http://slashdot.org/slashdot.rdf\">http://slashdot.org/slashdot.rdf</a>", "%syndic8" => "<a href=\"http://www.syndic8.com/\">Syndic8</a>", "%rss-what" => "<a href=\"http://www.xml.com/pub/a/2002/12/18/dive-into-xml.html\">What is RSS</a>", "%rss-evolution" => "<a href=\"http://www.webreference.com/authoring/languages/xml/rss/1/\">The Evolution of RSS</a>", "%admin-news" => l(t("RSS/RDF"), "admin/node/syndication/news"), "%new-feed" => l(t("new feed"), "admin/node/syndication/news/add/feed"), "%update-items" => l(t("update items"), "admin/node/syndication/news"))); case 'admin/system/modules#description': - $output = t("Used to aggregate syndicated content (RSS and RDF)."); - break; + return t("Used to aggregate syndicated content (RSS and RDF)."); case 'admin/system/modules/aggregator': - $output = t("Drupal's news aggregator controls how many RSS/RDF items from a single source are displayed in a \"Block\", and on the page that goes with that block."); - break; + return t("Drupal's news aggregator controls how many RSS/RDF items from a single source are displayed in a \"Block\", and on the page that goes with that block."); case 'admin/node/syndication/news': - $output = t("Several web sites, especially news related sites, syndicate parts of their site's content for other web sites to display. Usually, the syndicated content includes the latest headlines with a direct link to that story on the remote site. Some syndicated content also includes a description of the headline. The standard method of syndication is using the XML based Rich Site Summary (RSS). To get a feed to work you <strong>must</strong> run \"cron.php\". To display the feed in a block you must turn on the %block. <br /><ul><li>To delete a feed choose \"edit feed\"</li><li>To clear all of the entries from a feed choose \"Remove items\"</li><li>To check whether a feed is working, and to get new items <strong>now</strong> click on \"update items\"</li></ul><ul><li>To delete a bundle choose \"edit bundle\".</li></ul>", array("%block" => l(t("feed's block"), "admin/system/block"))); - break; + return t("Several web sites, especially news related sites, syndicate parts of their site's content for other web sites to display. Usually, the syndicated content includes the latest headlines with a direct link to that story on the remote site. Some syndicated content also includes a description of the headline. The standard method of syndication is using the XML based Rich Site Summary (RSS). To get a feed to work you <strong>must</strong> run \"cron.php\". To display the feed in a block you must turn on the %block. <br /><ul><li>To delete a feed choose \"edit feed\"</li><li>To clear all of the entries from a feed choose \"Remove items\"</li><li>To check whether a feed is working, and to get new items <strong>now</strong> click on \"update items\"</li></ul><ul><li>To delete a bundle choose \"edit bundle\".</li></ul>", array("%block" => l(t("feed's block"), "admin/system/block"))); case 'admin/node/syndication/news/add/feed': - $output = t("Add a site that has an RSS/RDF feed. The URL is the full path to the RSS feed file. For the feed to update automatically you must run \"cron.php\". The \"Attributes\" are used to bundle this feed with other feeds (See %bundle), and to tag articles from this feed.<br />Note: If you already have a feed with the URL you are planning to use, the system will not accept another feed with the same URL.", array("%bundle" => l(t("add new bundle"), "admin/node/syndication/news/add/bundle"))); - break; + return t("Add a site that has an RSS/RDF feed. The URL is the full path to the RSS feed file. For the feed to update automatically you must run \"cron.php\". The \"Attributes\" are used to bundle this feed with other feeds (See %bundle), and to tag articles from this feed.<br />Note: If you already have a feed with the URL you are planning to use, the system will not accept another feed with the same URL.", array("%bundle" => l(t("add new bundle"), "admin/node/syndication/news/add/bundle"))); case 'admin/node/syndication/news/add/bundle': - $output = t("Bundles provide a generalized way of creating composite feeds. They allow you, for example, to combine various sport-related feeds into one bundle called <i>Sport</i>. If an article from a feed has been \"tag\"-ged (See %tag too look at and change tags.) with a matching \"Attribute\" then it will be added to the bundle.", array("%tag" => l(t("tag news item"), "admin/node/syndication/news/tag"))); - break; + return t("Bundles provide a generalized way of creating composite feeds. They allow you, for example, to combine various sport-related feeds into one bundle called <i>Sport</i>. If an article from a feed has been \"tag\"-ged (See %tag too look at and change tags.) with a matching \"Attribute\" then it will be added to the bundle.", array("%tag" => l(t("tag news item"), "admin/node/syndication/news/tag"))); case 'admin/node/syndication/news/tag': - $output = t("This allows you to see and change an news item's \"tag\". All articles are originally tagged with the \"Attributes\" of their feed."); - break; + return t("This allows you to see and change an news item's \"tag\". All articles are originally tagged with the \"Attributes\" of their feed."); } - - return $output; } function aggregator_help_page() { - print theme("page", aggregator_help()); + print theme('page', aggregator_help('admin/help#aggregator')); } function aggregator_settings() { $number = array(5 => 5, 10 => 10, 15 => 15, 20 => 20, 25 => 25, 30 => 30, 35 => 35, 40 => 40, 45 => 45, 50 => 50, 55 => 55, 60 => 60, 65 => 65, 70 => 70, 75 => 75, 80 => 80, 85 => 85, 90 => 90, 95 => 95, 100 => 100); - $output .= form_select(t("Items per block"), "aggregator_block_limit", variable_get("aggregator_block_limit", 15), $number, t("The maximum number of news items displayed in one block.")); - $output .= form_select(t("Items per page"), "aggregator_page_limit", variable_get("aggregator_page_limit", 75), $number, t("The maximum number of news items displayed on one page.")); + + $output = form_select(t('Items per block'), 'aggregator_block_limit', variable_get('aggregator_block_limit', 15), $number, t('The maximum number of news items displayed in one block.')); + $output .= form_select(t('Items per page'), 'aggregator_page_limit', variable_get('aggregator_page_limit', 75), $number, t('The maximum number of news items displayed on one page.')); return $output; } function aggregator_perm() { - return array("administer news feeds", "access news feeds"); + return array('administer news feeds', 'access news feeds'); } function aggregator_link($type) { - - $links = array(); - - if ($type == "page" && user_access("access news feeds")) { - $links[] = l(t("news feeds"), "aggregator", array("title" => t("Read the latest news from syndicated web sites."))); - } - - if ($type == "system") { - if (user_access("administer news feeds")) { - menu("admin/node/syndication", t("syndication"), NULL, 5); - menu("admin/node/syndication/news", t("RSS/RDF"), "aggregator_admin"); - menu("admin/node/syndication/news/add/feed", t("new feed"), "aggregator_admin", 2); - menu("admin/node/syndication/news/add/bundle", t("new bundle"), "aggregator_admin", 3); - menu("admin/node/syndication/news/tag", t("tag items"), "aggregator_admin", 4); - menu("admin/node/syndication/news/help", t("help"), "aggregator_help_page", 9); + if ($type == 'page' && user_access('access news feeds')) { + return array(l(t('news feeds'), 'aggregator', array('title' => t('Read the latest news from syndicated web sites.')))); + } + + if ($type == 'system') { + if (user_access('administer news feeds')) { + menu('admin/node/syndication', t('syndication'), NULL, 5); + menu('admin/node/syndication/news', t('RSS/RDF'), 'aggregator_admin'); + menu('admin/node/syndication/news/add/feed', t('new feed'), 'aggregator_admin', 2); + menu('admin/node/syndication/news/add/bundle', t('new bundle'), 'aggregator_admin', 3); + menu('admin/node/syndication/news/tag', t('tag items'), 'aggregator_admin', 4); + menu('admin/node/syndication/news/help', t('help'), 'aggregator_help_page', 9); } - if (user_access("access news feeds")) { - menu("aggregator", t('news aggregator'), 'aggregator_page', 5); - menu("aggregator/feeds", t('news by source'), 'aggregator_page'); - menu("aggregator/bundles", t('news by topic'), 'aggregator_page'); - menu("aggregator/sources", t('news sources'), 'aggregator_page'); + + if (user_access('access news feeds')) { + menu('aggregator', t('news aggregator'), 'aggregator_page', 5); + menu('aggregator/feeds', t('news by source'), 'aggregator_page'); + menu('aggregator/bundles', t('news by topic'), 'aggregator_page'); + menu('aggregator/sources', t('news sources'), 'aggregator_page'); } } - - return $links; } function aggregator_cron() { - $result = db_query("SELECT * FROM {feed} WHERE timestamp + refresh < ". time()); + $result = db_query("SELECT * FROM {feed} WHERE checked + refresh < %d", time()); while ($feed = db_fetch_array($result)) { aggregator_refresh($feed); } } function aggregator_update() { - $result = db_query("SELECT * FROM {feed} "); + $result = db_query("SELECT * FROM {feed}"); while ($feed = db_fetch_array($result)) { aggregator_refresh($feed); } @@ -147,12 +137,13 @@ function theme_aggregator_format_item($item, $feed = 0) { } function aggregator_bundle_block($attributes) { - if ($attributes) { $keys = explode(",", $attributes); - foreach ($keys as $key) $where[] = "attributes LIKE '%". trim($key) ."%'"; + foreach ($keys as $key) { + $where[] = "attributes LIKE '%". check_query(trim($key)) ."%'"; + } - $result = db_query_range("SELECT * FROM {item} WHERE ". implode(" OR ", $where) ." ORDER BY iid DESC", 0, variable_get("aggregator_block_limit", 15)); + $result = db_query_range("SELECT * FROM {item} WHERE ". implode(" OR ", $where) ." ORDER BY timestamp DESC", 0, variable_get("aggregator_block_limit", 15)); } $items = array(); @@ -164,7 +155,7 @@ function aggregator_bundle_block($attributes) { } function aggregator_feed_block($feed) { - $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY iid DESC ", $feed->fid, 0, variable_get("aggregator_block_limit", 15)); + $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY timestamp DESC ", $feed->fid, 0, variable_get("aggregator_block_limit", 15)); $items = array(); while ($item = db_fetch_object($result)) { @@ -175,18 +166,18 @@ function aggregator_feed_block($feed) { } function aggregator_block($op, $delta) { - if (user_access("access news feeds")) { + if (user_access('access news feeds')) { if ($op == "list") { $result = db_query("SELECT * FROM {bundle} ORDER BY title"); while ($bundle = db_fetch_object($result)) { $block["bundle:$bundle->bid"]["info"] = "$bundle->title bundle"; } - + $result = db_query("SELECT * FROM {feed} ORDER BY fid"); while ($feed = db_fetch_object($result)) { $block["feed:$feed->fid"]["info"] = "$feed->title feed"; } - + return $block; } else { @@ -197,21 +188,19 @@ function aggregator_block($op, $delta) { $block["subject"] = $feed->title; $block["content"] .= aggregator_feed_block($feed) ."<div class=\"more-link\">". l(t("more"), "aggregator/feed/$feed->fid", array("title" => t("View this feed's recent news."))) ."</div>"; break; - case "bundle": $bundle = db_fetch_object(db_query("SELECT * FROM {bundle} WHERE bid = %d", $id)); $block["subject"] = $bundle->title; $block["content"] .= aggregator_bundle_block($bundle->attributes) ."<div class=\"more-link\">". l(t("more"), "aggregator/bundle/$bundle->bid", array("title" => t("View this bundle's recent news."))) ."</div>"; break; } - + return $block; } } } function aggregator_get_bundles($attributes = 0) { - $block = array(); $result = db_query("SELECT * FROM {bundle} ORDER BY title"); @@ -225,7 +214,6 @@ function aggregator_get_bundles($attributes = 0) { } function aggregator_get_feeds($attributes = 0) { - $block = array(); $result = db_query("SELECT * FROM {feed} ORDER BY fid"); @@ -264,7 +252,7 @@ function aggregator_element_start($parser, $name, $attributes) { function aggregator_element_end($parser, $name) { global $element; - switch ($name) { + switch ($name) { case "IMAGE": case "TEXTINPUT": case "ITEM": @@ -294,129 +282,145 @@ function aggregator_element_data($parser, $data) { } function aggregator_refresh($feed) { + // Generate conditional GET headers. + $headers = array(); + if ($feed['etag']) { + $headers['If-None-Match'] = $feed['etag']; + } + if ($feed['modified']) { + $headers['If-Modified-Since'] = gmdate("D, d M Y H:i:s", $feed['modified']) ." GMT"; + } + + // Request feed. + $result = drupal_http_request($feed['url'], $headers); + + // Process HTTP reponse code. + switch ($result->code) { + case 304: + db_query("UPDATE {feed} SET checked = %d WHERE fid = %d", time(), $feed['fid']); + return t("no new syndicated content from '%site'.", array('%site' => $feed['title'])); + case 301: + $feed['url'] = $result->redirect_url; + watchdog('special', "aggregator: updated URL for feed '$feed[title]' to $feed[url]"); + case 200: + case 302: + case 307: + // Filter the input data: + if (!valid_input_data($result->data)) { + return t("failed to parse RSS feed '%site': suspicious input data.", array("%site" => $feed["title"])); + } - // unset the global variables before we use them: - unset($GLOBALS["channel"], $GLOBALS["element"], $GLOBALS["item"], $GLOBALS["items"], $GLOBALS["tag"]); + $channel = aggregator_parse_feed($result->data, $feed); - // after we unset the variables, we can global them again: - global $items, $channel; + if ($result->headers['Last-Modified']) { + $modified = strtotime($result->headers['Last-Modified']); + } - /* - ** Check whether the feed is properly configured: - */ + db_query("UPDATE {feed} SET url = '%s', checked = %d, link = '%s', description = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel["LINK"], $channel["DESCRIPTION"], $result->headers['ETag'], $modified, $feed["fid"]); - if (!ereg("^http://|ftp://", $feed["url"])) { - return t("failed to parse RSS feed '%site': incorrect or missing URL.", array("%site" => $feed["title"])); + return t("syndicated content from '%site'.", array("%site" => $feed["title"])); + default: + return t("failed to parse RSS feed '%site': %error.", array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error)); } +} - /* - ** Grab the news items: - */ - - if ($fp = @fopen($feed["url"], "r")) { - // fetch data: - while (!feof($fp)) { - $data .= fgets($fp, 128); - } - fclose($fp); - - // filter the input data: - if (!valid_input_data($data)) { - return t("failed to parse RSS feed '%site': suspicious input data.", array("%site" => $feed["title"])); - } - - // parse the data: - $xml_parser = drupal_xml_parser_create($data); - xml_set_element_handler($xml_parser, "aggregator_element_start", "aggregator_element_end"); - xml_set_character_data_handler($xml_parser, "aggregator_element_data"); +function aggregator_parse_feed(&$data, $feed) { + global $items, $channel; - if (!xml_parse($xml_parser, $data, 1)) { - return t("failed to parse RSS feed '%site': %error at line %line.", array("%site" => $feed["title"], "%error" => xml_error_string(xml_get_error_code($xml_parser)), "%line" => xml_get_current_line_number($xml_parser))); - } - xml_parser_free($xml_parser); + // Unset the global variables before we use them: + unset($GLOBALS["element"], $GLOBALS["item"], $GLOBALS["tag"]); + $items = array(); + $channel = array(); - // initialize the translation table: - $tt = array_flip(get_html_translation_table(HTML_ENTITIES)); - $tt["'"] = "'"; + // parse the data: + $xml_parser = drupal_xml_parser_create($data); + xml_set_element_handler($xml_parser, "aggregator_element_start", "aggregator_element_end"); + xml_set_character_data_handler($xml_parser, "aggregator_element_data"); - db_query("UPDATE {feed} SET timestamp = %d, link = '%s', description = '%s' WHERE fid = %d", time(), $channel["LINK"], $channel["DESCRIPTION"], $feed["fid"]); + if (!xml_parse($xml_parser, $data, 1)) { + return t("failed to parse RSS feed '%site': %error at line %line.", array("%site" => $feed["title"], "%error" => xml_error_string(xml_get_error_code($xml_parser)), "%line" => xml_get_current_line_number($xml_parser))); + } + xml_parser_free($xml_parser); - /* - ** We reverse the array such that we store the first item last, - ** and the last item first. In the database, the newest item - ** should be at the top. - */ + // initialize the translation table: + $tt = array_flip(get_html_translation_table(HTML_ENTITIES)); + $tt["'"] = "'"; - $items = array_reverse($items); + /* + ** We reverse the array such that we store the first item last, + ** and the last item first. In the database, the newest item + ** should be at the top. + */ - foreach ($items as $item) { - unset($title, $link, $author, $description); + $items = array_reverse($items); - // Prepare the item: - foreach ($item as $key => $value) { - $item[$key] = filter_default(strtr(trim($value), $tt)); - } + foreach ($items as $item) { + unset($title, $link, $author, $description); - if ($item["TITLE"]) { - $title = $item["TITLE"]; - } - else { - /* - ** Use up to 40 characters of the description, ending at - ** word boundary, but don't split potential entities. - */ - $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", substr($item["DESCRIPTION"], 0, 40)); - } - - if ($item["LINK"]) { - $link = $item["LINK"]; - } - elseif ($item["GUID"] && (strncmp($item["GUID"], "http://", 7) == 0)) { - $link = $item["GUID"]; - } - else { - $link = $feed["link"]; - } + // Prepare the item: + foreach ($item as $key => $value) { + $item[$key] = filter_default(strtr(trim($value), $tt)); + } + if ($item["TITLE"]) { + $title = $item["TITLE"]; + } + else { /* - ** Save this item. Try to avoid duplicate entries as much as - ** possible. If we find a duplicate entry, we resolve it and - ** pass along it's ID such that we can update it if needed. - */ + ** Use up to 40 characters of the description, ending at + ** word boundary, but don't split potential entities. + */ + $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", substr($item["DESCRIPTION"], 0, 40)); + } - if ($link && $link != $feed["link"] && $link != $feed["url"]) { - $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND link = '%s'", $feed["fid"], $link)); - } - else { - $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND title = '%s'", $feed["fid"], $title)); - } + if ($item["LINK"]) { + $link = $item["LINK"]; + } + elseif ($item["GUID"] && (strncmp($item["GUID"], "http://", 7) == 0)) { + $link = $item["GUID"]; + } + else { + $link = $feed["link"]; + } - aggregator_save_item(array(iid => $entry->iid, fid => $feed["fid"], title => $title, link => $link, author => $item["AUTHOR"], description => $item["DESCRIPTION"], attributes => $feed["attributes"])); + if (!$timestamp = strtotime($item['PUBDATE'])) { + $timestamp = time(); } /* - ** Remove all the old, expired items: + ** Save this item. Try to avoid duplicate entries as much as + ** possible. If we find a duplicate entry, we resolve it and + ** pass along it's ID such that we can update it if needed. */ - unset($items); + if ($link && $link != $feed["link"] && $link != $feed["url"]) { + $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND link = '%s'", $feed["fid"], $link)); + } + else { + $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND title = '%s'", $feed["fid"], $title)); + } - $result = db_query("SELECT iid FROM {item} WHERE fid = %d ORDER BY timestamp", $feed["fid"]); + aggregator_save_item(array('iid' => $entry->iid, 'fid' => $feed["fid"], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item["AUTHOR"], 'description' => $item["DESCRIPTION"], 'attributes' => $feed["attributes"])); + } + /* + ** Remove all the old, expired items: + */ - while ($item = db_fetch_object($result)) { - $items[] = "iid = '$item->iid'"; - } + unset($items); - if (sizeof($items) > 50) { - db_query("DELETE FROM {item} WHERE ". implode(" OR ", array_slice($items, 0, - 50))); - } + $result = db_query("SELECT iid FROM {item} WHERE fid = %d ORDER BY timestamp", $feed["fid"]); - cache_clear_all(); + while ($item = db_fetch_object($result)) { + $items[] = "iid = '$item->iid'"; } - else { - return t("failed to parse RSS feed '%site': no data.", array("%site" => $feed["tite"])); + + if (sizeof($items) > 50) { + db_query("DELETE FROM {item} WHERE ". implode(" OR ", array_slice($items, 0, - 50))); } - return t("syndicated content from '%site'.", array("%site" => $feed["title"])); + cache_clear_all(); + + return $channel; } function aggregator_save_item($edit) { @@ -427,13 +431,12 @@ function aggregator_save_item($edit) { db_query("DELETE FROM {item} WHERE iid = %d", $edit["iid"]); } else if ($edit["title"] && $edit["link"]) { - db_query("INSERT INTO {item} (fid, title, link, author, description, attributes, timestamp) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $edit["fid"], $edit["title"], $edit["link"], $edit["author"], $edit["description"], $edit["attributes"], time()); + db_query("INSERT INTO {item} (fid, title, link, author, description, attributes, timestamp) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $edit["fid"], $edit["title"], $edit["link"], $edit["author"], $edit["description"], $edit["attributes"], $edit["timestamp"]); } } function aggregator_form_bundle($edit = array()) { - - $form .= form_textfield(t("Title"), "title", $edit["title"], 50, 64, t("The name of the bundle.")); + $form = form_textfield(t("Title"), "title", $edit["title"], 50, 64, t("The name of the bundle.")); $form .= form_textfield(t("Attributes"), "attributes", $edit["attributes"], 50, 128, t("A comma-separated list of keywords describing the bundle.")); $form .= form_submit(t("Submit")); @@ -461,7 +464,6 @@ function aggregator_save_bundle($edit) { } function aggregator_form_feed($edit = array()) { - $period = array(900 => format_interval(900), 1800 => format_interval(1800), 3600 => format_interval(3600), 7200 => format_interval(7200), 10800 => format_interval(10800), 21600 => format_interval(21600), 32400 => format_interval(32400), 43200 => format_interval(43200), 64800 => format_interval(64800), 86400 => format_interval(86400), 172800 => format_interval(172800), 259200 => format_interval(259200), 604800 => format_interval(604800), 1209600 => format_interval(1209600), 2419200 => format_interval(2419200)); if ($edit["refresh"] == "") { @@ -515,14 +517,14 @@ function aggregator_get_bundle($bid) { } function aggregator_view() { - $result = db_query("SELECT f.*, COUNT(i.iid) AS items FROM {feed} f LEFT JOIN {item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.timestamp, f.attributes, f.link, f.description ORDER BY f.title"); + $result = db_query("SELECT f.*, COUNT(i.iid) AS items FROM {feed} f LEFT JOIN {item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.attributes, f.link, f.description ORDER BY f.title"); $output .= "<h3>". t("Feed overview") ."</h3>"; $header = array(t("title"), t("attributes"), t("items"), t("last update"), t("next update"), array("data" => t("operations"), "colspan" => 3)); - unset($rows); + $rows = array(); while ($feed = db_fetch_object($result)) { - $rows[] = array($feed->title, $feed->attributes, format_plural($feed->items, "1 item", "%count items"), ($feed->timestamp ? t("%time ago", array("%time" => format_interval(time() - $feed->timestamp))) : t("never")), ($feed->timestamp ? t("%time left", array("%time" => format_interval($feed->timestamp + $feed->refresh - time()))) : t("never")), l(t("edit feed"), "admin/node/syndication/news/edit/feed/$feed->fid"), l(t("remove items"), "admin/node/syndication/news/remove/$feed->fid"), l(t("update items"), "admin/node/syndication/news/update/$feed->fid")); + $rows[] = array($feed->title, $feed->attributes, format_plural($feed->items, "1 item", "%count items"), ($feed->checked ? t("%time ago", array("%time" => format_interval(time() - $feed->checked))) : t("never")), ($feed->checked ? t("%time left", array("%time" => format_interval($feed->checked + $feed->refresh - time()))) : t("never")), l(t("edit feed"), "admin/node/syndication/news/edit/feed/$feed->fid"), l(t("remove items"), "admin/node/syndication/news/remove/$feed->fid"), l(t("update items"), "admin/node/syndication/news/update/$feed->fid")); } $output .= theme("table", $header, $rows); @@ -531,7 +533,7 @@ function aggregator_view() { $output .= "<h3>". t("Bundle overview") ."</h3>"; $header = array(t("title"), t("attributes"), t("operations")); - unset($rows); + $rows = array(); while ($bundle = db_fetch_object($result)) { $rows[] = array($bundle->title, $bundle->attributes, l(t("edit bundle"), "admin/node/syndication/news/edit/bundle/$bundle->bid")); } @@ -541,8 +543,7 @@ function aggregator_view() { } function aggregator_tag() { - - $result = db_query_range("SELECT i.*, f.title AS feed FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.iid DESC", 0, 50); + $result = db_query_range("SELECT i.*, f.title AS feed FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC", 0, 50); $header = array(t("date"), t("feed"), t("news item")); while ($item = db_fetch_object($result)) { @@ -556,16 +557,11 @@ function aggregator_tag() { } function aggregator_admin() { - $op = $_POST["op"]; $edit = $_POST["edit"]; if (user_access("administer news feeds")) { - if (empty($op)) { - $op = arg(4); - } - - switch ($op) { + switch ($_POST["op"] ? $_POST["op"] : arg(4)) { case "add": if (arg(5) == "bundle") { $output = aggregator_form_bundle(); @@ -619,12 +615,11 @@ function aggregator_admin() { } function aggregator_page_last() { + $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC", 0, variable_get("aggregator_page_limit", 75)); - - $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.iid DESC", 0, variable_get("aggregator_page_limit", 75)); - - $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; + $output = "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { + $links = array(format_date($item->timestamp)); if (module_exist("blog") && user_access("maintain personal blog")) { $links[] = l(t("blog it"), "node/add/blog", array("title" => t("Comment on this news item in your personal blog.")), "iid=$item->iid"); } @@ -637,8 +632,6 @@ function aggregator_page_last() { if ($item->description) { $output .= "<tr><td colspan=\"2\"><div style=\"margin-left: 20px;\">$item->description</div><br /></td></tr>"; } - - unset($links); } $output .= "</table>\n"; @@ -646,18 +639,17 @@ function aggregator_page_last() { } function aggregator_page_feed($fid) { - - $feed = db_fetch_object(db_query("SELECT * FROM {feed} WHERE fid = %d", $fid)); - $header .= "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\"><a href=\"$feed->link\">$feed->link</a></div></p>"; + $header = "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\"><a href=\"$feed->link\">$feed->link</a></div></p>"; $header .= "<p><strong>". t("Description") .":</strong><div style=\"margin-left: 20px;\">$feed->description</div></p>"; - $header .= "<p><strong>". t("Last update") .":</strong><div style=\"margin-left: 20px; text-align: right;\">". t("%time ago", array("%time" => format_interval(time() - $feed->timestamp))) ." <a href=\"$feed->url\"><img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" alt=\"\" title=\"\" /></a><br /><br /></div></p>\n"; + $header .= "<p><strong>". t("Last update") .":</strong><div style=\"margin-left: 20px; text-align: right;\">". t("%time ago", array("%time" => format_interval(time() - $feed->checked))) ." <a href=\"$feed->url\"><img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" alt=\"\" title=\"\" /></a><br /><br /></div></p>\n"; - $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY iid DESC", $fid, 0, variable_get("aggregator_page_limit", 75)); + $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY timestamp DESC", $fid, 0, variable_get("aggregator_page_limit", 75)); $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { + $links = array(); if (module_exist("blog") && user_access("maintain personal blog")) { $links[] = l(t("blog it"), "node/add/blog", array("title" => t("Comment on this news item in your personal blog.")), "iid=$item->iid"); } @@ -669,8 +661,6 @@ function aggregator_page_feed($fid) { if ($item->description) { $output .= "<tr><td colspan=\"2\"><div style=\"margin-left: 20px;\">$item->description</div><br /></td></tr>"; } - - unset($links); } $output .= "</table>\n"; @@ -681,8 +671,6 @@ function aggregator_page_feed($fid) { } function aggregator_page_bundle($bid) { - - $bundle = db_fetch_object(db_query("SELECT * FROM {bundle} WHERE bid = %d", $bid)); $header .= "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\">". l($bundle->title, "aggregator/bundle/$bundle->bid") ."</div></p>"; @@ -690,7 +678,7 @@ function aggregator_page_bundle($bid) { $keys = explode(",", $bundle->attributes); foreach ($keys as $key) $where[] = "i.attributes LIKE '%". trim($key) ."%'"; - $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i, {feed} f WHERE (". implode(" OR ", $where) .") AND i.fid = f.fid ORDER BY iid DESC", 0, variable_get("aggregator_page_limit", 75)); + $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i, {feed} f WHERE (". implode(" OR ", $where) .") AND i.fid = f.fid ORDER BY timestamp DESC", 0, variable_get("aggregator_page_limit", 75)); $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { @@ -716,12 +704,9 @@ function aggregator_page_bundle($bid) { print theme("box", $bundle->title, $header); print theme("box", t("Latest news"), $output); print theme("footer"); - } function aggregator_page_sources() { - - $result = db_query("SELECT * FROM {feed} ORDER BY title"); while ($feed = db_fetch_object($result)) { @@ -729,26 +714,28 @@ function aggregator_page_sources() { $output .= "<div style=\"margin-left: 20px;\">$feed->description</div><br />"; } - $output .= "<div style=\"xml-icon\">". l("<img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" />", "aggregator/fd", array("title" => t("View the list of syndicated web sites in XML format."))) ."</div><br />"; + $output .= "<div style=\"xml-icon\">". l("<img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" />", "aggregator/opml", array("title" => t("View the list of syndicated web sites in XML format."))) ."</div><br />"; print theme("page", $output); } -function aggregator_page_fd() { - +function aggregator_page_opml() { $result = db_query("SELECT * FROM {feed} ORDER BY title"); - $output .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n"; - $output .= "<rssfeeds version=\"0.1\">\n\n"; + $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + $output .= "<opml version=\"1.1\">\n"; + $output .= "<head>\n"; + $output .= "<title>". drupal_specialchars(variable_get('site_name', 'Drupal')) ."</title>\n"; + $output .= "<dateModified>". gmdate('r') ."</dateModified>\n"; + $output .= "</head>\n"; + $output .= "<body>\n"; while ($feed = db_fetch_object($result)) { - $output .= "<channel>\n"; - $output .= " <title>". drupal_specialchars($feed->title) ."</title>\n"; - $output .= " <link>". drupal_specialchars($feed->url) ."</link>\n"; - $output .= "</channel>\n\n"; + $output .= '<outline text="'. drupal_specialchars($feed->title) .'" xmlUrl="'. drupal_specialchars($feed->url) ."\" />\n"; } - $output .= "</rssfeeds>\n"; + $output .= "</body>\n"; + $output .= "</opml>\n"; header("Content-Type: text/xml"); @@ -764,8 +751,6 @@ function aggregator_page_feeds() { } function aggregator_page_blocks($blocks) { - - print theme("header"); print "<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\" style=\"width: 100%;\">\n"; print " <tr>\n"; @@ -806,8 +791,8 @@ function aggregator_page() { case "sources": aggregator_page_sources(); break; - case "fd": - aggregator_page_fd(); + case "opml": + aggregator_page_opml(); break; default: aggregator_page_last(); diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 1983718bc..91752bf8a 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -1,13 +1,10 @@ <?php -// $Id$ - - -function aggregator_help($section = "admin/help#aggregator") { - $output = ""; +/* $Id$ */ +function aggregator_help($section) { switch ($section) { case 'admin/help#aggregator': - $output .= "<p>Thousands of web sites, especially news sites and weblogs, syndicate their most recent site content for others to display. The syndicated content always includes titles, also known as headlines, for the newest published stories. Each headline acts as a direct link to the stories on the remote site. Along with the headline, most sites typically provide either the first few paragraphs of the story or a short summary. Many individuals use client-based news aggregators on their personal computer to aggregate content, such as %amphetadesk</p>"; + $output = "<p>Thousands of web sites, especially news sites and weblogs, syndicate their most recent site content for others to display. The syndicated content always includes titles, also known as headlines, for the newest published stories. Each headline acts as a direct link to the stories on the remote site. Along with the headline, most sites typically provide either the first few paragraphs of the story or a short summary. Many individuals use client-based news aggregators on their personal computer to aggregate content, such as %amphetadesk</p>"; $output .= "<p>Drupal also has a news aggregator built in as a standard feature. With it, you can subscribe to feeds from other sites and display their content for your site users. Simply enable the aggregator module in site administration and enter the feeds that you choose.</p>"; $output .= "<h3>What do I need to subscribe to a feed?</h3>"; $output .= "<p>The standard method of syndication is using the XML-based %rss (RSS). To syndicate a site's content, obtain the full URL of the RSS page providing syndication. Common file tags for RSS pages are .rss, .xml and .rdf. Example: %slashdot-rss.</p>"; @@ -50,84 +47,77 @@ function aggregator_help($section = "admin/help#aggregator") { $output .= "</ul>"; $output .= "<h3>RSS feed blocks</h3>"; $output .= "<p>In addition to providing subscribed content through the news aggregator, Drupal automatically creates a block for each subscribed feed and every bundle created. Beside each headline in each block, Drupal includes an icon which acts a blog it link. Enable any or all of the blocks using block management.</p>"; - $output = t($output, array("%amphetadesk" => "<a href=\"http://www.disobey.com/amphetadesk/\">AmphetaDesk</a>", "%rss" => "<a href=\"http://groups.yahoo.com/group/rss-dev/files/specification.html\">Rich Site Summary</a>", "%slashdot-rss" => "<a href=\"http://slashdot.org/slashdot.rdf\">http://slashdot.org/slashdot.rdf</a>", "%syndic8" => "<a href=\"http://www.syndic8.com/\">Syndic8</a>", "%rss-what" => "<a href=\"http://www.xml.com/pub/a/2002/12/18/dive-into-xml.html\">What is RSS</a>", "%rss-evolution" => "<a href=\"http://www.webreference.com/authoring/languages/xml/rss/1/\">The Evolution of RSS</a>", "%admin-news" => l(t("RSS/RDF"), "admin/node/syndication/news"), "%new-feed" => l(t("new feed"), "admin/node/syndication/news/add/feed"), "%update-items" => l(t("update items"), "admin/node/syndication/news"))); - break; + $output .= "<h3>Subscription list</h3>"; + $output .= "<p>Drupal automatically generates an OPML feed file that is available by selecting the XML icon on the News Sources page.</p>"; + $output .= "<h3>Technical details</h3>"; + $output .= "<p>When fetching feeds Drupal supports conditional GETs, this reduces the bandwidth usage for feeds that have not been updated since the last check.</p>"; + $output .= "<p>If a feed is permanently moved to a new location Drupal will automatically update the feed URL to the new address.</p>"; + return t($output, array("%amphetadesk" => "<a href=\"http://www.disobey.com/amphetadesk/\">AmphetaDesk</a>", "%rss" => "<a href=\"http://groups.yahoo.com/group/rss-dev/files/specification.html\">Rich Site Summary</a>", "%slashdot-rss" => "<a href=\"http://slashdot.org/slashdot.rdf\">http://slashdot.org/slashdot.rdf</a>", "%syndic8" => "<a href=\"http://www.syndic8.com/\">Syndic8</a>", "%rss-what" => "<a href=\"http://www.xml.com/pub/a/2002/12/18/dive-into-xml.html\">What is RSS</a>", "%rss-evolution" => "<a href=\"http://www.webreference.com/authoring/languages/xml/rss/1/\">The Evolution of RSS</a>", "%admin-news" => l(t("RSS/RDF"), "admin/node/syndication/news"), "%new-feed" => l(t("new feed"), "admin/node/syndication/news/add/feed"), "%update-items" => l(t("update items"), "admin/node/syndication/news"))); case 'admin/system/modules#description': - $output = t("Used to aggregate syndicated content (RSS and RDF)."); - break; + return t("Used to aggregate syndicated content (RSS and RDF)."); case 'admin/system/modules/aggregator': - $output = t("Drupal's news aggregator controls how many RSS/RDF items from a single source are displayed in a \"Block\", and on the page that goes with that block."); - break; + return t("Drupal's news aggregator controls how many RSS/RDF items from a single source are displayed in a \"Block\", and on the page that goes with that block."); case 'admin/node/syndication/news': - $output = t("Several web sites, especially news related sites, syndicate parts of their site's content for other web sites to display. Usually, the syndicated content includes the latest headlines with a direct link to that story on the remote site. Some syndicated content also includes a description of the headline. The standard method of syndication is using the XML based Rich Site Summary (RSS). To get a feed to work you <strong>must</strong> run \"cron.php\". To display the feed in a block you must turn on the %block. <br /><ul><li>To delete a feed choose \"edit feed\"</li><li>To clear all of the entries from a feed choose \"Remove items\"</li><li>To check whether a feed is working, and to get new items <strong>now</strong> click on \"update items\"</li></ul><ul><li>To delete a bundle choose \"edit bundle\".</li></ul>", array("%block" => l(t("feed's block"), "admin/system/block"))); - break; + return t("Several web sites, especially news related sites, syndicate parts of their site's content for other web sites to display. Usually, the syndicated content includes the latest headlines with a direct link to that story on the remote site. Some syndicated content also includes a description of the headline. The standard method of syndication is using the XML based Rich Site Summary (RSS). To get a feed to work you <strong>must</strong> run \"cron.php\". To display the feed in a block you must turn on the %block. <br /><ul><li>To delete a feed choose \"edit feed\"</li><li>To clear all of the entries from a feed choose \"Remove items\"</li><li>To check whether a feed is working, and to get new items <strong>now</strong> click on \"update items\"</li></ul><ul><li>To delete a bundle choose \"edit bundle\".</li></ul>", array("%block" => l(t("feed's block"), "admin/system/block"))); case 'admin/node/syndication/news/add/feed': - $output = t("Add a site that has an RSS/RDF feed. The URL is the full path to the RSS feed file. For the feed to update automatically you must run \"cron.php\". The \"Attributes\" are used to bundle this feed with other feeds (See %bundle), and to tag articles from this feed.<br />Note: If you already have a feed with the URL you are planning to use, the system will not accept another feed with the same URL.", array("%bundle" => l(t("add new bundle"), "admin/node/syndication/news/add/bundle"))); - break; + return t("Add a site that has an RSS/RDF feed. The URL is the full path to the RSS feed file. For the feed to update automatically you must run \"cron.php\". The \"Attributes\" are used to bundle this feed with other feeds (See %bundle), and to tag articles from this feed.<br />Note: If you already have a feed with the URL you are planning to use, the system will not accept another feed with the same URL.", array("%bundle" => l(t("add new bundle"), "admin/node/syndication/news/add/bundle"))); case 'admin/node/syndication/news/add/bundle': - $output = t("Bundles provide a generalized way of creating composite feeds. They allow you, for example, to combine various sport-related feeds into one bundle called <i>Sport</i>. If an article from a feed has been \"tag\"-ged (See %tag too look at and change tags.) with a matching \"Attribute\" then it will be added to the bundle.", array("%tag" => l(t("tag news item"), "admin/node/syndication/news/tag"))); - break; + return t("Bundles provide a generalized way of creating composite feeds. They allow you, for example, to combine various sport-related feeds into one bundle called <i>Sport</i>. If an article from a feed has been \"tag\"-ged (See %tag too look at and change tags.) with a matching \"Attribute\" then it will be added to the bundle.", array("%tag" => l(t("tag news item"), "admin/node/syndication/news/tag"))); case 'admin/node/syndication/news/tag': - $output = t("This allows you to see and change an news item's \"tag\". All articles are originally tagged with the \"Attributes\" of their feed."); - break; + return t("This allows you to see and change an news item's \"tag\". All articles are originally tagged with the \"Attributes\" of their feed."); } - - return $output; } function aggregator_help_page() { - print theme("page", aggregator_help()); + print theme('page', aggregator_help('admin/help#aggregator')); } function aggregator_settings() { $number = array(5 => 5, 10 => 10, 15 => 15, 20 => 20, 25 => 25, 30 => 30, 35 => 35, 40 => 40, 45 => 45, 50 => 50, 55 => 55, 60 => 60, 65 => 65, 70 => 70, 75 => 75, 80 => 80, 85 => 85, 90 => 90, 95 => 95, 100 => 100); - $output .= form_select(t("Items per block"), "aggregator_block_limit", variable_get("aggregator_block_limit", 15), $number, t("The maximum number of news items displayed in one block.")); - $output .= form_select(t("Items per page"), "aggregator_page_limit", variable_get("aggregator_page_limit", 75), $number, t("The maximum number of news items displayed on one page.")); + + $output = form_select(t('Items per block'), 'aggregator_block_limit', variable_get('aggregator_block_limit', 15), $number, t('The maximum number of news items displayed in one block.')); + $output .= form_select(t('Items per page'), 'aggregator_page_limit', variable_get('aggregator_page_limit', 75), $number, t('The maximum number of news items displayed on one page.')); return $output; } function aggregator_perm() { - return array("administer news feeds", "access news feeds"); + return array('administer news feeds', 'access news feeds'); } function aggregator_link($type) { - - $links = array(); - - if ($type == "page" && user_access("access news feeds")) { - $links[] = l(t("news feeds"), "aggregator", array("title" => t("Read the latest news from syndicated web sites."))); - } - - if ($type == "system") { - if (user_access("administer news feeds")) { - menu("admin/node/syndication", t("syndication"), NULL, 5); - menu("admin/node/syndication/news", t("RSS/RDF"), "aggregator_admin"); - menu("admin/node/syndication/news/add/feed", t("new feed"), "aggregator_admin", 2); - menu("admin/node/syndication/news/add/bundle", t("new bundle"), "aggregator_admin", 3); - menu("admin/node/syndication/news/tag", t("tag items"), "aggregator_admin", 4); - menu("admin/node/syndication/news/help", t("help"), "aggregator_help_page", 9); + if ($type == 'page' && user_access('access news feeds')) { + return array(l(t('news feeds'), 'aggregator', array('title' => t('Read the latest news from syndicated web sites.')))); + } + + if ($type == 'system') { + if (user_access('administer news feeds')) { + menu('admin/node/syndication', t('syndication'), NULL, 5); + menu('admin/node/syndication/news', t('RSS/RDF'), 'aggregator_admin'); + menu('admin/node/syndication/news/add/feed', t('new feed'), 'aggregator_admin', 2); + menu('admin/node/syndication/news/add/bundle', t('new bundle'), 'aggregator_admin', 3); + menu('admin/node/syndication/news/tag', t('tag items'), 'aggregator_admin', 4); + menu('admin/node/syndication/news/help', t('help'), 'aggregator_help_page', 9); } - if (user_access("access news feeds")) { - menu("aggregator", t('news aggregator'), 'aggregator_page', 5); - menu("aggregator/feeds", t('news by source'), 'aggregator_page'); - menu("aggregator/bundles", t('news by topic'), 'aggregator_page'); - menu("aggregator/sources", t('news sources'), 'aggregator_page'); + + if (user_access('access news feeds')) { + menu('aggregator', t('news aggregator'), 'aggregator_page', 5); + menu('aggregator/feeds', t('news by source'), 'aggregator_page'); + menu('aggregator/bundles', t('news by topic'), 'aggregator_page'); + menu('aggregator/sources', t('news sources'), 'aggregator_page'); } } - - return $links; } function aggregator_cron() { - $result = db_query("SELECT * FROM {feed} WHERE timestamp + refresh < ". time()); + $result = db_query("SELECT * FROM {feed} WHERE checked + refresh < %d", time()); while ($feed = db_fetch_array($result)) { aggregator_refresh($feed); } } function aggregator_update() { - $result = db_query("SELECT * FROM {feed} "); + $result = db_query("SELECT * FROM {feed}"); while ($feed = db_fetch_array($result)) { aggregator_refresh($feed); } @@ -147,12 +137,13 @@ function theme_aggregator_format_item($item, $feed = 0) { } function aggregator_bundle_block($attributes) { - if ($attributes) { $keys = explode(",", $attributes); - foreach ($keys as $key) $where[] = "attributes LIKE '%". trim($key) ."%'"; + foreach ($keys as $key) { + $where[] = "attributes LIKE '%". check_query(trim($key)) ."%'"; + } - $result = db_query_range("SELECT * FROM {item} WHERE ". implode(" OR ", $where) ." ORDER BY iid DESC", 0, variable_get("aggregator_block_limit", 15)); + $result = db_query_range("SELECT * FROM {item} WHERE ". implode(" OR ", $where) ." ORDER BY timestamp DESC", 0, variable_get("aggregator_block_limit", 15)); } $items = array(); @@ -164,7 +155,7 @@ function aggregator_bundle_block($attributes) { } function aggregator_feed_block($feed) { - $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY iid DESC ", $feed->fid, 0, variable_get("aggregator_block_limit", 15)); + $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY timestamp DESC ", $feed->fid, 0, variable_get("aggregator_block_limit", 15)); $items = array(); while ($item = db_fetch_object($result)) { @@ -175,18 +166,18 @@ function aggregator_feed_block($feed) { } function aggregator_block($op, $delta) { - if (user_access("access news feeds")) { + if (user_access('access news feeds')) { if ($op == "list") { $result = db_query("SELECT * FROM {bundle} ORDER BY title"); while ($bundle = db_fetch_object($result)) { $block["bundle:$bundle->bid"]["info"] = "$bundle->title bundle"; } - + $result = db_query("SELECT * FROM {feed} ORDER BY fid"); while ($feed = db_fetch_object($result)) { $block["feed:$feed->fid"]["info"] = "$feed->title feed"; } - + return $block; } else { @@ -197,21 +188,19 @@ function aggregator_block($op, $delta) { $block["subject"] = $feed->title; $block["content"] .= aggregator_feed_block($feed) ."<div class=\"more-link\">". l(t("more"), "aggregator/feed/$feed->fid", array("title" => t("View this feed's recent news."))) ."</div>"; break; - case "bundle": $bundle = db_fetch_object(db_query("SELECT * FROM {bundle} WHERE bid = %d", $id)); $block["subject"] = $bundle->title; $block["content"] .= aggregator_bundle_block($bundle->attributes) ."<div class=\"more-link\">". l(t("more"), "aggregator/bundle/$bundle->bid", array("title" => t("View this bundle's recent news."))) ."</div>"; break; } - + return $block; } } } function aggregator_get_bundles($attributes = 0) { - $block = array(); $result = db_query("SELECT * FROM {bundle} ORDER BY title"); @@ -225,7 +214,6 @@ function aggregator_get_bundles($attributes = 0) { } function aggregator_get_feeds($attributes = 0) { - $block = array(); $result = db_query("SELECT * FROM {feed} ORDER BY fid"); @@ -264,7 +252,7 @@ function aggregator_element_start($parser, $name, $attributes) { function aggregator_element_end($parser, $name) { global $element; - switch ($name) { + switch ($name) { case "IMAGE": case "TEXTINPUT": case "ITEM": @@ -294,129 +282,145 @@ function aggregator_element_data($parser, $data) { } function aggregator_refresh($feed) { + // Generate conditional GET headers. + $headers = array(); + if ($feed['etag']) { + $headers['If-None-Match'] = $feed['etag']; + } + if ($feed['modified']) { + $headers['If-Modified-Since'] = gmdate("D, d M Y H:i:s", $feed['modified']) ." GMT"; + } + + // Request feed. + $result = drupal_http_request($feed['url'], $headers); + + // Process HTTP reponse code. + switch ($result->code) { + case 304: + db_query("UPDATE {feed} SET checked = %d WHERE fid = %d", time(), $feed['fid']); + return t("no new syndicated content from '%site'.", array('%site' => $feed['title'])); + case 301: + $feed['url'] = $result->redirect_url; + watchdog('special', "aggregator: updated URL for feed '$feed[title]' to $feed[url]"); + case 200: + case 302: + case 307: + // Filter the input data: + if (!valid_input_data($result->data)) { + return t("failed to parse RSS feed '%site': suspicious input data.", array("%site" => $feed["title"])); + } - // unset the global variables before we use them: - unset($GLOBALS["channel"], $GLOBALS["element"], $GLOBALS["item"], $GLOBALS["items"], $GLOBALS["tag"]); + $channel = aggregator_parse_feed($result->data, $feed); - // after we unset the variables, we can global them again: - global $items, $channel; + if ($result->headers['Last-Modified']) { + $modified = strtotime($result->headers['Last-Modified']); + } - /* - ** Check whether the feed is properly configured: - */ + db_query("UPDATE {feed} SET url = '%s', checked = %d, link = '%s', description = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel["LINK"], $channel["DESCRIPTION"], $result->headers['ETag'], $modified, $feed["fid"]); - if (!ereg("^http://|ftp://", $feed["url"])) { - return t("failed to parse RSS feed '%site': incorrect or missing URL.", array("%site" => $feed["title"])); + return t("syndicated content from '%site'.", array("%site" => $feed["title"])); + default: + return t("failed to parse RSS feed '%site': %error.", array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error)); } +} - /* - ** Grab the news items: - */ - - if ($fp = @fopen($feed["url"], "r")) { - // fetch data: - while (!feof($fp)) { - $data .= fgets($fp, 128); - } - fclose($fp); - - // filter the input data: - if (!valid_input_data($data)) { - return t("failed to parse RSS feed '%site': suspicious input data.", array("%site" => $feed["title"])); - } - - // parse the data: - $xml_parser = drupal_xml_parser_create($data); - xml_set_element_handler($xml_parser, "aggregator_element_start", "aggregator_element_end"); - xml_set_character_data_handler($xml_parser, "aggregator_element_data"); +function aggregator_parse_feed(&$data, $feed) { + global $items, $channel; - if (!xml_parse($xml_parser, $data, 1)) { - return t("failed to parse RSS feed '%site': %error at line %line.", array("%site" => $feed["title"], "%error" => xml_error_string(xml_get_error_code($xml_parser)), "%line" => xml_get_current_line_number($xml_parser))); - } - xml_parser_free($xml_parser); + // Unset the global variables before we use them: + unset($GLOBALS["element"], $GLOBALS["item"], $GLOBALS["tag"]); + $items = array(); + $channel = array(); - // initialize the translation table: - $tt = array_flip(get_html_translation_table(HTML_ENTITIES)); - $tt["'"] = "'"; + // parse the data: + $xml_parser = drupal_xml_parser_create($data); + xml_set_element_handler($xml_parser, "aggregator_element_start", "aggregator_element_end"); + xml_set_character_data_handler($xml_parser, "aggregator_element_data"); - db_query("UPDATE {feed} SET timestamp = %d, link = '%s', description = '%s' WHERE fid = %d", time(), $channel["LINK"], $channel["DESCRIPTION"], $feed["fid"]); + if (!xml_parse($xml_parser, $data, 1)) { + return t("failed to parse RSS feed '%site': %error at line %line.", array("%site" => $feed["title"], "%error" => xml_error_string(xml_get_error_code($xml_parser)), "%line" => xml_get_current_line_number($xml_parser))); + } + xml_parser_free($xml_parser); - /* - ** We reverse the array such that we store the first item last, - ** and the last item first. In the database, the newest item - ** should be at the top. - */ + // initialize the translation table: + $tt = array_flip(get_html_translation_table(HTML_ENTITIES)); + $tt["'"] = "'"; - $items = array_reverse($items); + /* + ** We reverse the array such that we store the first item last, + ** and the last item first. In the database, the newest item + ** should be at the top. + */ - foreach ($items as $item) { - unset($title, $link, $author, $description); + $items = array_reverse($items); - // Prepare the item: - foreach ($item as $key => $value) { - $item[$key] = filter_default(strtr(trim($value), $tt)); - } + foreach ($items as $item) { + unset($title, $link, $author, $description); - if ($item["TITLE"]) { - $title = $item["TITLE"]; - } - else { - /* - ** Use up to 40 characters of the description, ending at - ** word boundary, but don't split potential entities. - */ - $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", substr($item["DESCRIPTION"], 0, 40)); - } - - if ($item["LINK"]) { - $link = $item["LINK"]; - } - elseif ($item["GUID"] && (strncmp($item["GUID"], "http://", 7) == 0)) { - $link = $item["GUID"]; - } - else { - $link = $feed["link"]; - } + // Prepare the item: + foreach ($item as $key => $value) { + $item[$key] = filter_default(strtr(trim($value), $tt)); + } + if ($item["TITLE"]) { + $title = $item["TITLE"]; + } + else { /* - ** Save this item. Try to avoid duplicate entries as much as - ** possible. If we find a duplicate entry, we resolve it and - ** pass along it's ID such that we can update it if needed. - */ + ** Use up to 40 characters of the description, ending at + ** word boundary, but don't split potential entities. + */ + $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", substr($item["DESCRIPTION"], 0, 40)); + } - if ($link && $link != $feed["link"] && $link != $feed["url"]) { - $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND link = '%s'", $feed["fid"], $link)); - } - else { - $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND title = '%s'", $feed["fid"], $title)); - } + if ($item["LINK"]) { + $link = $item["LINK"]; + } + elseif ($item["GUID"] && (strncmp($item["GUID"], "http://", 7) == 0)) { + $link = $item["GUID"]; + } + else { + $link = $feed["link"]; + } - aggregator_save_item(array(iid => $entry->iid, fid => $feed["fid"], title => $title, link => $link, author => $item["AUTHOR"], description => $item["DESCRIPTION"], attributes => $feed["attributes"])); + if (!$timestamp = strtotime($item['PUBDATE'])) { + $timestamp = time(); } /* - ** Remove all the old, expired items: + ** Save this item. Try to avoid duplicate entries as much as + ** possible. If we find a duplicate entry, we resolve it and + ** pass along it's ID such that we can update it if needed. */ - unset($items); + if ($link && $link != $feed["link"] && $link != $feed["url"]) { + $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND link = '%s'", $feed["fid"], $link)); + } + else { + $entry = db_fetch_object(db_query("SELECT iid FROM {item} WHERE fid = %d AND title = '%s'", $feed["fid"], $title)); + } - $result = db_query("SELECT iid FROM {item} WHERE fid = %d ORDER BY timestamp", $feed["fid"]); + aggregator_save_item(array('iid' => $entry->iid, 'fid' => $feed["fid"], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item["AUTHOR"], 'description' => $item["DESCRIPTION"], 'attributes' => $feed["attributes"])); + } + /* + ** Remove all the old, expired items: + */ - while ($item = db_fetch_object($result)) { - $items[] = "iid = '$item->iid'"; - } + unset($items); - if (sizeof($items) > 50) { - db_query("DELETE FROM {item} WHERE ". implode(" OR ", array_slice($items, 0, - 50))); - } + $result = db_query("SELECT iid FROM {item} WHERE fid = %d ORDER BY timestamp", $feed["fid"]); - cache_clear_all(); + while ($item = db_fetch_object($result)) { + $items[] = "iid = '$item->iid'"; } - else { - return t("failed to parse RSS feed '%site': no data.", array("%site" => $feed["tite"])); + + if (sizeof($items) > 50) { + db_query("DELETE FROM {item} WHERE ". implode(" OR ", array_slice($items, 0, - 50))); } - return t("syndicated content from '%site'.", array("%site" => $feed["title"])); + cache_clear_all(); + + return $channel; } function aggregator_save_item($edit) { @@ -427,13 +431,12 @@ function aggregator_save_item($edit) { db_query("DELETE FROM {item} WHERE iid = %d", $edit["iid"]); } else if ($edit["title"] && $edit["link"]) { - db_query("INSERT INTO {item} (fid, title, link, author, description, attributes, timestamp) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $edit["fid"], $edit["title"], $edit["link"], $edit["author"], $edit["description"], $edit["attributes"], time()); + db_query("INSERT INTO {item} (fid, title, link, author, description, attributes, timestamp) VALUES (%d, '%s', '%s', '%s', '%s', '%s', %d)", $edit["fid"], $edit["title"], $edit["link"], $edit["author"], $edit["description"], $edit["attributes"], $edit["timestamp"]); } } function aggregator_form_bundle($edit = array()) { - - $form .= form_textfield(t("Title"), "title", $edit["title"], 50, 64, t("The name of the bundle.")); + $form = form_textfield(t("Title"), "title", $edit["title"], 50, 64, t("The name of the bundle.")); $form .= form_textfield(t("Attributes"), "attributes", $edit["attributes"], 50, 128, t("A comma-separated list of keywords describing the bundle.")); $form .= form_submit(t("Submit")); @@ -461,7 +464,6 @@ function aggregator_save_bundle($edit) { } function aggregator_form_feed($edit = array()) { - $period = array(900 => format_interval(900), 1800 => format_interval(1800), 3600 => format_interval(3600), 7200 => format_interval(7200), 10800 => format_interval(10800), 21600 => format_interval(21600), 32400 => format_interval(32400), 43200 => format_interval(43200), 64800 => format_interval(64800), 86400 => format_interval(86400), 172800 => format_interval(172800), 259200 => format_interval(259200), 604800 => format_interval(604800), 1209600 => format_interval(1209600), 2419200 => format_interval(2419200)); if ($edit["refresh"] == "") { @@ -515,14 +517,14 @@ function aggregator_get_bundle($bid) { } function aggregator_view() { - $result = db_query("SELECT f.*, COUNT(i.iid) AS items FROM {feed} f LEFT JOIN {item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.timestamp, f.attributes, f.link, f.description ORDER BY f.title"); + $result = db_query("SELECT f.*, COUNT(i.iid) AS items FROM {feed} f LEFT JOIN {item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.attributes, f.link, f.description ORDER BY f.title"); $output .= "<h3>". t("Feed overview") ."</h3>"; $header = array(t("title"), t("attributes"), t("items"), t("last update"), t("next update"), array("data" => t("operations"), "colspan" => 3)); - unset($rows); + $rows = array(); while ($feed = db_fetch_object($result)) { - $rows[] = array($feed->title, $feed->attributes, format_plural($feed->items, "1 item", "%count items"), ($feed->timestamp ? t("%time ago", array("%time" => format_interval(time() - $feed->timestamp))) : t("never")), ($feed->timestamp ? t("%time left", array("%time" => format_interval($feed->timestamp + $feed->refresh - time()))) : t("never")), l(t("edit feed"), "admin/node/syndication/news/edit/feed/$feed->fid"), l(t("remove items"), "admin/node/syndication/news/remove/$feed->fid"), l(t("update items"), "admin/node/syndication/news/update/$feed->fid")); + $rows[] = array($feed->title, $feed->attributes, format_plural($feed->items, "1 item", "%count items"), ($feed->checked ? t("%time ago", array("%time" => format_interval(time() - $feed->checked))) : t("never")), ($feed->checked ? t("%time left", array("%time" => format_interval($feed->checked + $feed->refresh - time()))) : t("never")), l(t("edit feed"), "admin/node/syndication/news/edit/feed/$feed->fid"), l(t("remove items"), "admin/node/syndication/news/remove/$feed->fid"), l(t("update items"), "admin/node/syndication/news/update/$feed->fid")); } $output .= theme("table", $header, $rows); @@ -531,7 +533,7 @@ function aggregator_view() { $output .= "<h3>". t("Bundle overview") ."</h3>"; $header = array(t("title"), t("attributes"), t("operations")); - unset($rows); + $rows = array(); while ($bundle = db_fetch_object($result)) { $rows[] = array($bundle->title, $bundle->attributes, l(t("edit bundle"), "admin/node/syndication/news/edit/bundle/$bundle->bid")); } @@ -541,8 +543,7 @@ function aggregator_view() { } function aggregator_tag() { - - $result = db_query_range("SELECT i.*, f.title AS feed FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.iid DESC", 0, 50); + $result = db_query_range("SELECT i.*, f.title AS feed FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC", 0, 50); $header = array(t("date"), t("feed"), t("news item")); while ($item = db_fetch_object($result)) { @@ -556,16 +557,11 @@ function aggregator_tag() { } function aggregator_admin() { - $op = $_POST["op"]; $edit = $_POST["edit"]; if (user_access("administer news feeds")) { - if (empty($op)) { - $op = arg(4); - } - - switch ($op) { + switch ($_POST["op"] ? $_POST["op"] : arg(4)) { case "add": if (arg(5) == "bundle") { $output = aggregator_form_bundle(); @@ -619,12 +615,11 @@ function aggregator_admin() { } function aggregator_page_last() { + $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC", 0, variable_get("aggregator_page_limit", 75)); - - $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i INNER JOIN {feed} f ON i.fid = f.fid ORDER BY i.iid DESC", 0, variable_get("aggregator_page_limit", 75)); - - $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; + $output = "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { + $links = array(format_date($item->timestamp)); if (module_exist("blog") && user_access("maintain personal blog")) { $links[] = l(t("blog it"), "node/add/blog", array("title" => t("Comment on this news item in your personal blog.")), "iid=$item->iid"); } @@ -637,8 +632,6 @@ function aggregator_page_last() { if ($item->description) { $output .= "<tr><td colspan=\"2\"><div style=\"margin-left: 20px;\">$item->description</div><br /></td></tr>"; } - - unset($links); } $output .= "</table>\n"; @@ -646,18 +639,17 @@ function aggregator_page_last() { } function aggregator_page_feed($fid) { - - $feed = db_fetch_object(db_query("SELECT * FROM {feed} WHERE fid = %d", $fid)); - $header .= "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\"><a href=\"$feed->link\">$feed->link</a></div></p>"; + $header = "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\"><a href=\"$feed->link\">$feed->link</a></div></p>"; $header .= "<p><strong>". t("Description") .":</strong><div style=\"margin-left: 20px;\">$feed->description</div></p>"; - $header .= "<p><strong>". t("Last update") .":</strong><div style=\"margin-left: 20px; text-align: right;\">". t("%time ago", array("%time" => format_interval(time() - $feed->timestamp))) ." <a href=\"$feed->url\"><img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" alt=\"\" title=\"\" /></a><br /><br /></div></p>\n"; + $header .= "<p><strong>". t("Last update") .":</strong><div style=\"margin-left: 20px; text-align: right;\">". t("%time ago", array("%time" => format_interval(time() - $feed->checked))) ." <a href=\"$feed->url\"><img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" alt=\"\" title=\"\" /></a><br /><br /></div></p>\n"; - $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY iid DESC", $fid, 0, variable_get("aggregator_page_limit", 75)); + $result = db_query_range("SELECT * FROM {item} WHERE fid = %d ORDER BY timestamp DESC", $fid, 0, variable_get("aggregator_page_limit", 75)); $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { + $links = array(); if (module_exist("blog") && user_access("maintain personal blog")) { $links[] = l(t("blog it"), "node/add/blog", array("title" => t("Comment on this news item in your personal blog.")), "iid=$item->iid"); } @@ -669,8 +661,6 @@ function aggregator_page_feed($fid) { if ($item->description) { $output .= "<tr><td colspan=\"2\"><div style=\"margin-left: 20px;\">$item->description</div><br /></td></tr>"; } - - unset($links); } $output .= "</table>\n"; @@ -681,8 +671,6 @@ function aggregator_page_feed($fid) { } function aggregator_page_bundle($bid) { - - $bundle = db_fetch_object(db_query("SELECT * FROM {bundle} WHERE bid = %d", $bid)); $header .= "<p><strong>". t("Website") .":</strong><div style=\"margin-left: 20px;\">". l($bundle->title, "aggregator/bundle/$bundle->bid") ."</div></p>"; @@ -690,7 +678,7 @@ function aggregator_page_bundle($bid) { $keys = explode(",", $bundle->attributes); foreach ($keys as $key) $where[] = "i.attributes LIKE '%". trim($key) ."%'"; - $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i, {feed} f WHERE (". implode(" OR ", $where) .") AND i.fid = f.fid ORDER BY iid DESC", 0, variable_get("aggregator_page_limit", 75)); + $result = db_query_range("SELECT i.*, f.title AS ftitle, f.link AS flink FROM {item} i, {feed} f WHERE (". implode(" OR ", $where) .") AND i.fid = f.fid ORDER BY timestamp DESC", 0, variable_get("aggregator_page_limit", 75)); $output .= "<table border=\"0\" cellpadding=\"4\" cellspacing=\"2\">"; while ($item = db_fetch_object($result)) { @@ -716,12 +704,9 @@ function aggregator_page_bundle($bid) { print theme("box", $bundle->title, $header); print theme("box", t("Latest news"), $output); print theme("footer"); - } function aggregator_page_sources() { - - $result = db_query("SELECT * FROM {feed} ORDER BY title"); while ($feed = db_fetch_object($result)) { @@ -729,26 +714,28 @@ function aggregator_page_sources() { $output .= "<div style=\"margin-left: 20px;\">$feed->description</div><br />"; } - $output .= "<div style=\"xml-icon\">". l("<img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" />", "aggregator/fd", array("title" => t("View the list of syndicated web sites in XML format."))) ."</div><br />"; + $output .= "<div style=\"xml-icon\">". l("<img src=\"". theme("image", "xml.gif") ."\" width=\"36\" height=\"14\" style=\"border: 0px;\" />", "aggregator/opml", array("title" => t("View the list of syndicated web sites in XML format."))) ."</div><br />"; print theme("page", $output); } -function aggregator_page_fd() { - +function aggregator_page_opml() { $result = db_query("SELECT * FROM {feed} ORDER BY title"); - $output .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n"; - $output .= "<rssfeeds version=\"0.1\">\n\n"; + $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + $output .= "<opml version=\"1.1\">\n"; + $output .= "<head>\n"; + $output .= "<title>". drupal_specialchars(variable_get('site_name', 'Drupal')) ."</title>\n"; + $output .= "<dateModified>". gmdate('r') ."</dateModified>\n"; + $output .= "</head>\n"; + $output .= "<body>\n"; while ($feed = db_fetch_object($result)) { - $output .= "<channel>\n"; - $output .= " <title>". drupal_specialchars($feed->title) ."</title>\n"; - $output .= " <link>". drupal_specialchars($feed->url) ."</link>\n"; - $output .= "</channel>\n\n"; + $output .= '<outline text="'. drupal_specialchars($feed->title) .'" xmlUrl="'. drupal_specialchars($feed->url) ."\" />\n"; } - $output .= "</rssfeeds>\n"; + $output .= "</body>\n"; + $output .= "</opml>\n"; header("Content-Type: text/xml"); @@ -764,8 +751,6 @@ function aggregator_page_feeds() { } function aggregator_page_blocks($blocks) { - - print theme("header"); print "<table cellpadding=\"0\" cellspacing=\"5\" border=\"0\" style=\"width: 100%;\">\n"; print " <tr>\n"; @@ -806,8 +791,8 @@ function aggregator_page() { case "sources": aggregator_page_sources(); break; - case "fd": - aggregator_page_fd(); + case "opml": + aggregator_page_opml(); break; default: aggregator_page_last(); diff --git a/modules/drupal.module b/modules/drupal.module index 5f3bb5ada..61c73f8ab 100644 --- a/modules/drupal.module +++ b/modules/drupal.module @@ -36,7 +36,7 @@ function drupal_settings() { $error["drupal_directory"] = theme("error", t("You must set your site's slogan at the <a href=\"%url\">site configuration</a>.", array("%url" => url("admin/system")))); else if (variable_get("site_mission", "") == "") $error["drupal_directory"] = theme("error", t("You must set your site's mission at the <a href=\"%url\">site configuration</a>.", array("%url" => url("admin/system")))); - + $output = form_textfield(t("Drupal XML-RPC server"), "drupal_server", variable_get("drupal_server", "http://www.drupal.org/xmlrpc.php"), 55, 128, t("The URL of your root Drupal XML-RPC server.")); $output .= form_radios(t("Drupal directory"), "drupal_directory", variable_get("drupal_directory", 0), array(t("Disabled"), t("Enabled")), t("If enabled, your Drupal site will make itself known to the Drupal directory at the specified Drupal XML-RPC server. For this to work properly, you must set your site's name, e-mail address, slogan and mission statement. When the \"%xml-rpc-server\" field is set to \"%drupal-xml-rpc\", your web site will get listed on the %drupal-sites page. Requires the cron feature to be enabled.", array("%xml-rpc-server" => t("Drupal XML-RPC server"), "%drupal-xml-rpc" => "http://www.drupal.org/xmlrpc.php", "%drupal-sites" => "<a href=\"http://www.drupal.org/sites\">".t("Drupal sites")."</a>")) . $error["drupal_directory"]); diff --git a/modules/drupal/drupal.module b/modules/drupal/drupal.module index 5f3bb5ada..61c73f8ab 100644 --- a/modules/drupal/drupal.module +++ b/modules/drupal/drupal.module @@ -36,7 +36,7 @@ function drupal_settings() { $error["drupal_directory"] = theme("error", t("You must set your site's slogan at the <a href=\"%url\">site configuration</a>.", array("%url" => url("admin/system")))); else if (variable_get("site_mission", "") == "") $error["drupal_directory"] = theme("error", t("You must set your site's mission at the <a href=\"%url\">site configuration</a>.", array("%url" => url("admin/system")))); - + $output = form_textfield(t("Drupal XML-RPC server"), "drupal_server", variable_get("drupal_server", "http://www.drupal.org/xmlrpc.php"), 55, 128, t("The URL of your root Drupal XML-RPC server.")); $output .= form_radios(t("Drupal directory"), "drupal_directory", variable_get("drupal_directory", 0), array(t("Disabled"), t("Enabled")), t("If enabled, your Drupal site will make itself known to the Drupal directory at the specified Drupal XML-RPC server. For this to work properly, you must set your site's name, e-mail address, slogan and mission statement. When the \"%xml-rpc-server\" field is set to \"%drupal-xml-rpc\", your web site will get listed on the %drupal-sites page. Requires the cron feature to be enabled.", array("%xml-rpc-server" => t("Drupal XML-RPC server"), "%drupal-xml-rpc" => "http://www.drupal.org/xmlrpc.php", "%drupal-sites" => "<a href=\"http://www.drupal.org/sites\">".t("Drupal sites")."</a>")) . $error["drupal_directory"]); diff --git a/modules/filter.module b/modules/filter.module index 19849c5a7..abf4399cd 100644 --- a/modules/filter.module +++ b/modules/filter.module @@ -85,7 +85,7 @@ function filter_default($text) { } if (variable_get("filter_html", 0) == 2) { - // Escape HTML + // Escape HTML $text = htmlspecialchars($text); } diff --git a/modules/filter/filter.module b/modules/filter/filter.module index 19849c5a7..abf4399cd 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -85,7 +85,7 @@ function filter_default($text) { } if (variable_get("filter_html", 0) == 2) { - // Escape HTML + // Escape HTML $text = htmlspecialchars($text); } diff --git a/modules/path.module b/modules/path.module index 561a0292c..9d5c9c06f 100644 --- a/modules/path.module +++ b/modules/path.module @@ -103,7 +103,6 @@ function path_form($edit = "", $error = "") { } function path_help($section = "admin/help#path") { - $output = ""; switch ($section) { case "admin/system/modules#description": @@ -116,10 +115,38 @@ function path_help($section = "admin/help#path") { $output = t("Enter the path you wish to create the alias for, followed by the name of the new alias. Each path can be associated with only one alias."); break; case "admin/help#path": - $output .= "<h3>Background</h3><p>A very powerful feature of Drupal is the ability to have control over all paths. The path module is the tool that provides this functionality and is part of the basic Drupal installation, although it is not enabled by default. Some examples of re-mapping paths are:<pre>user/login => login\n\nimage/tid/16 => store\n\ntaxonomy/page/or/7,19,20,21 => store/products/whirlygigs\n\nnode/view/3 => contact</pre></p>"; - $output .= "<p>This functionality integrates seamlessly into node forms and also provides the administrator an interface to view all aliases that have been created.</p><p>Aliases have a 1 to 1 relationship with their original Drupal URLs. In other words you cannot have an alias map to more than one path. Likewise, a Drupal URL can't be mapped to more than one alias.</p>"; - $output .= "<h3>Permissions</h3><p>Two new permissions are introduced for aliasing URLs: <i>create url aliases</i> and <i>administer url aliases</i>.</p><ol><li><strong>create url aliases</strong> - Allows users to create aliases for nodes. Enabling this permission will display a new path field to the user in any node form, allowing them to enter an alias for that node. They will be able to edit/delete the alias after it is created using the same form.</li><li><strong>administer url aliases</strong> - Allows users to access the alias administration interface. They must also have the <i>access administration pages</i> permission set as well. This interface displays all aliases and provides a way to create and modify them. This is also the location to build aliases for things other than nodes. For example, you can create an alias for a taxonomy URL or even re-map the admin path (although the original admin path will still be accessible since aliases do not cancel out original paths).</li></ol>"; - $output = t($output); + $output = t("<h3>Background</h3> +<p>A very powerful feature of Drupal is the ability to have control over all paths. The path module is the tool that provides this functionality and is part of the basic Drupal installation, although it is not enabled by default. Some examples of re-mapping paths are:</p> +<pre> +user/login => login + +image/tid/16 => store + +taxonomy/page/or/7,19,20,21 => store/products/whirlygigs + +node/view/3 => contact +</pre> +<p>This functionality integrates seamlessly into node forms and also provides the administrator an interface to view all aliases that have been created.</p> +<p>Aliases have a 1 to 1 relationship with their original Drupal URLs. In other words you cannot have an alias map to more than one path. Likewise, a Drupal URL can't be mapped to more than one alias.</p> + +<h3>Permissions</h3> +<p>Two permissions are related to URL aliasing: <i>create url aliases</i> and <i>administer url aliases</i>.</p> +<ol><li><strong>create url aliases</strong> - Allows users to create aliases for nodes. Enabling this permission will display a path field to the user in any node form, allowing them to enter an alias for that node. They will be able to edit/delete the alias after it is created using the same form.</li><li><strong>administer url aliases</strong> - Allows users to access the alias administration interface. They must also have the <i>access administration pages</i> permission set as well. This interface displays all aliases and provides a way to create and modify them. This is also the location to build aliases for things other than nodes. For example, you can create an alias for a taxonomy URL or even re-map the admin path (although the original admin path will still be accessible since aliases do not cancel out original paths).</li></ol> + +<h3>Mass URL aliasing</h3> +<p>Drupal also comes with user defined mass URL aliasing capabilities. You might like to see completely different URLs used by Drupal, or even URLs translated to the visitors' native language, in which case this feature is handy. Only an administrator with access to the website source code can set up this kind of aliases. You can define a <code>conf_url_rewrite</code> function in conf.php, following this example:</p> +<pre> +function conf_url_rewrite(\$path, \$mode = 'incoming') { + if (\$mode == 'incoming') { // URL coming from a client + return preg_replace('!^display/(\d+)$!', 'node/view/\1', \$path); + } + else { // URL going out to a client + return preg_replace('!^node/view/(\d+)$!', 'display/\1', \$path); + } +} +</pre> +<p>This function will shorten every <code>node/view/\$node_id</code> type of URL to <code>display/\$node_id</code>. Individual URL aliases defined on the browser interface of Drupal take precedence, so if you have the 'contact' page alias from the example above, then the <code>display/3</code> alias will not be effective when outgoing links are created. Incoming URLs however always work with the mass URL aliased variant. Only the 'incoming' and 'outgoing' modes are supposed to be supported by your <code>conf_url_rewrite</code> function.</p> +<p>You cannot only use this feature to shorten the URLs, or to translate them to you own language, but also to add completely new subURLs to an already existing module's URL space, or to compose a bunch of existing stuff together to a common URL space. You can create a <code>news</code> section for example aliasing nodes and taxonomy overview pages falling under a 'news' vocabulary, thus having <code>news/15</code> and <code>news/sections/3</code> instead of <code>node/view/15</code> and <code>taxonomy/view/or/3</code>.</p>"); break; } diff --git a/modules/path/path.module b/modules/path/path.module index 561a0292c..9d5c9c06f 100644 --- a/modules/path/path.module +++ b/modules/path/path.module @@ -103,7 +103,6 @@ function path_form($edit = "", $error = "") { } function path_help($section = "admin/help#path") { - $output = ""; switch ($section) { case "admin/system/modules#description": @@ -116,10 +115,38 @@ function path_help($section = "admin/help#path") { $output = t("Enter the path you wish to create the alias for, followed by the name of the new alias. Each path can be associated with only one alias."); break; case "admin/help#path": - $output .= "<h3>Background</h3><p>A very powerful feature of Drupal is the ability to have control over all paths. The path module is the tool that provides this functionality and is part of the basic Drupal installation, although it is not enabled by default. Some examples of re-mapping paths are:<pre>user/login => login\n\nimage/tid/16 => store\n\ntaxonomy/page/or/7,19,20,21 => store/products/whirlygigs\n\nnode/view/3 => contact</pre></p>"; - $output .= "<p>This functionality integrates seamlessly into node forms and also provides the administrator an interface to view all aliases that have been created.</p><p>Aliases have a 1 to 1 relationship with their original Drupal URLs. In other words you cannot have an alias map to more than one path. Likewise, a Drupal URL can't be mapped to more than one alias.</p>"; - $output .= "<h3>Permissions</h3><p>Two new permissions are introduced for aliasing URLs: <i>create url aliases</i> and <i>administer url aliases</i>.</p><ol><li><strong>create url aliases</strong> - Allows users to create aliases for nodes. Enabling this permission will display a new path field to the user in any node form, allowing them to enter an alias for that node. They will be able to edit/delete the alias after it is created using the same form.</li><li><strong>administer url aliases</strong> - Allows users to access the alias administration interface. They must also have the <i>access administration pages</i> permission set as well. This interface displays all aliases and provides a way to create and modify them. This is also the location to build aliases for things other than nodes. For example, you can create an alias for a taxonomy URL or even re-map the admin path (although the original admin path will still be accessible since aliases do not cancel out original paths).</li></ol>"; - $output = t($output); + $output = t("<h3>Background</h3> +<p>A very powerful feature of Drupal is the ability to have control over all paths. The path module is the tool that provides this functionality and is part of the basic Drupal installation, although it is not enabled by default. Some examples of re-mapping paths are:</p> +<pre> +user/login => login + +image/tid/16 => store + +taxonomy/page/or/7,19,20,21 => store/products/whirlygigs + +node/view/3 => contact +</pre> +<p>This functionality integrates seamlessly into node forms and also provides the administrator an interface to view all aliases that have been created.</p> +<p>Aliases have a 1 to 1 relationship with their original Drupal URLs. In other words you cannot have an alias map to more than one path. Likewise, a Drupal URL can't be mapped to more than one alias.</p> + +<h3>Permissions</h3> +<p>Two permissions are related to URL aliasing: <i>create url aliases</i> and <i>administer url aliases</i>.</p> +<ol><li><strong>create url aliases</strong> - Allows users to create aliases for nodes. Enabling this permission will display a path field to the user in any node form, allowing them to enter an alias for that node. They will be able to edit/delete the alias after it is created using the same form.</li><li><strong>administer url aliases</strong> - Allows users to access the alias administration interface. They must also have the <i>access administration pages</i> permission set as well. This interface displays all aliases and provides a way to create and modify them. This is also the location to build aliases for things other than nodes. For example, you can create an alias for a taxonomy URL or even re-map the admin path (although the original admin path will still be accessible since aliases do not cancel out original paths).</li></ol> + +<h3>Mass URL aliasing</h3> +<p>Drupal also comes with user defined mass URL aliasing capabilities. You might like to see completely different URLs used by Drupal, or even URLs translated to the visitors' native language, in which case this feature is handy. Only an administrator with access to the website source code can set up this kind of aliases. You can define a <code>conf_url_rewrite</code> function in conf.php, following this example:</p> +<pre> +function conf_url_rewrite(\$path, \$mode = 'incoming') { + if (\$mode == 'incoming') { // URL coming from a client + return preg_replace('!^display/(\d+)$!', 'node/view/\1', \$path); + } + else { // URL going out to a client + return preg_replace('!^node/view/(\d+)$!', 'display/\1', \$path); + } +} +</pre> +<p>This function will shorten every <code>node/view/\$node_id</code> type of URL to <code>display/\$node_id</code>. Individual URL aliases defined on the browser interface of Drupal take precedence, so if you have the 'contact' page alias from the example above, then the <code>display/3</code> alias will not be effective when outgoing links are created. Incoming URLs however always work with the mass URL aliased variant. Only the 'incoming' and 'outgoing' modes are supposed to be supported by your <code>conf_url_rewrite</code> function.</p> +<p>You cannot only use this feature to shorten the URLs, or to translate them to you own language, but also to add completely new subURLs to an already existing module's URL space, or to compose a bunch of existing stuff together to a common URL space. You can create a <code>news</code> section for example aliasing nodes and taxonomy overview pages falling under a 'news' vocabulary, thus having <code>news/15</code> and <code>news/sections/3</code> instead of <code>node/view/15</code> and <code>taxonomy/view/or/3</code>.</p>"); break; } diff --git a/update.php b/update.php index a28fb8820..9b4c0d7a7 100644 --- a/update.php +++ b/update.php @@ -63,7 +63,8 @@ $mysql_updates = array( "2003-11-17" => "update_71", "2003-11-27" => "update_72", "2003-12-03" => "update_73", - "2003-12-06" => "update_74" + "2003-12-06" => "update_74", + "2004-01-06" => "update_75" ); function update_32() { @@ -590,6 +591,14 @@ function update_74() { update_sql("ALTER TABLE {system} ADD throttle tinyint(1) NOT NULL DEFAULT '0'"); } +function update_75() { + update_sql("ALTER TABLE {feed} ADD etag varchar(255) NOT NULL DEFAULT ''"); + update_sql("ALTER TABLE {feed} ADD modified int(10) NOT NULL DEFAULT 0"); + update_sql("ALTER TABLE {feed} CHANGE timestamp checked int(10) NOT NULL DEFAULT 0"); + update_sql("UPDATE {blocks} SET module = 'aggregator' WHERE module = 'import'"); + update_sql("UPDATE {system} SET filename = 'modules/aggregator.module', name = 'aggregator' WHERE filename = 'modules/import.module'"); +} + /* ** System functions */ |