diff options
-rw-r--r-- | application/FeedBuilder.php | 16 | ||||
-rw-r--r-- | composer.json | 3 | ||||
-rw-r--r-- | index.php | 5 | ||||
-rw-r--r-- | plugins/pubsubhubbub/README.md | 20 | ||||
-rw-r--r-- | plugins/pubsubhubbub/hub.atom.xml | 1 | ||||
-rw-r--r-- | plugins/pubsubhubbub/hub.rss.xml | 1 | ||||
-rw-r--r-- | plugins/pubsubhubbub/pubsubhubbub.meta | 2 | ||||
-rw-r--r-- | plugins/pubsubhubbub/pubsubhubbub.php | 101 | ||||
-rw-r--r-- | tests/FeedBuilderTest.php | 14 | ||||
-rw-r--r-- | tests/plugins/PluginPubsubhubbubTest.php | 54 | ||||
-rw-r--r-- | tpl/feed.atom.html | 11 | ||||
-rw-r--r-- | tpl/feed.rss.html | 10 |
12 files changed, 193 insertions, 45 deletions
diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php index fedd90e6..a1f4da48 100644 --- a/application/FeedBuilder.php +++ b/application/FeedBuilder.php | |||
@@ -63,11 +63,6 @@ class FeedBuilder | |||
63 | protected $hideDates; | 63 | protected $hideDates; |
64 | 64 | ||
65 | /** | 65 | /** |
66 | * @var string PubSub hub URL. | ||
67 | */ | ||
68 | protected $pubsubhubUrl; | ||
69 | |||
70 | /** | ||
71 | * @var string server locale. | 66 | * @var string server locale. |
72 | */ | 67 | */ |
73 | protected $locale; | 68 | protected $locale; |
@@ -120,7 +115,6 @@ class FeedBuilder | |||
120 | } | 115 | } |
121 | 116 | ||
122 | $data['language'] = $this->getTypeLanguage(); | 117 | $data['language'] = $this->getTypeLanguage(); |
123 | $data['pubsubhub_url'] = $this->pubsubhubUrl; | ||
124 | $data['last_update'] = $this->getLatestDateFormatted(); | 118 | $data['last_update'] = $this->getLatestDateFormatted(); |
125 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; | 119 | $data['show_dates'] = !$this->hideDates || $this->isLoggedIn; |
126 | // Remove leading slash from REQUEST_URI. | 120 | // Remove leading slash from REQUEST_URI. |
@@ -183,16 +177,6 @@ class FeedBuilder | |||
183 | } | 177 | } |
184 | 178 | ||
185 | /** | 179 | /** |
186 | * Assign PubSub hub URL. | ||
187 | * | ||
188 | * @param string $pubsubhubUrl PubSub hub url. | ||
189 | */ | ||
190 | public function setPubsubhubUrl($pubsubhubUrl) | ||
191 | { | ||
192 | $this->pubsubhubUrl = $pubsubhubUrl; | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * Set this to true to use permalinks instead of direct links. | 180 | * Set this to true to use permalinks instead of direct links. |
197 | * | 181 | * |
198 | * @param boolean $usePermalinks true to force permalinks. | 182 | * @param boolean $usePermalinks true to force permalinks. |
diff --git a/composer.json b/composer.json index 4786fe94..cfbde1a0 100644 --- a/composer.json +++ b/composer.json | |||
@@ -13,7 +13,8 @@ | |||
13 | "php": ">=5.5", | 13 | "php": ">=5.5", |
14 | "shaarli/netscape-bookmark-parser": "1.*", | 14 | "shaarli/netscape-bookmark-parser": "1.*", |
15 | "erusev/parsedown": "1.6", | 15 | "erusev/parsedown": "1.6", |
16 | "slim/slim": "^3.0" | 16 | "slim/slim": "^3.0", |
17 | "pubsubhubbub/publisher": "dev-master" | ||
17 | }, | 18 | }, |
18 | "require-dev": { | 19 | "require-dev": { |
19 | "phpmd/phpmd" : "@stable", | 20 | "phpmd/phpmd" : "@stable", |
@@ -910,10 +910,6 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
910 | $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); | 910 | $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); |
911 | $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn()); | 911 | $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn()); |
912 | $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); | 912 | $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); |
913 | $pshUrl = $conf->get('config.PUBSUBHUB_URL'); | ||
914 | if (!empty($pshUrl)) { | ||
915 | $feedGenerator->setPubsubhubUrl($pshUrl); | ||
916 | } | ||
917 | $data = $feedGenerator->buildData(); | 913 | $data = $feedGenerator->buildData(); |
918 | 914 | ||
919 | // Process plugin hook. | 915 | // Process plugin hook. |
@@ -1289,7 +1285,6 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1289 | 1285 | ||
1290 | $LINKSDB[$id] = $link; | 1286 | $LINKSDB[$id] = $link; |
1291 | $LINKSDB->save($conf->get('resource.page_cache')); | 1287 | $LINKSDB->save($conf->get('resource.page_cache')); |
1292 | pubsubhub($conf); | ||
1293 | 1288 | ||
1294 | // If we are called from the bookmarklet, we must close the popup: | 1289 | // If we are called from the bookmarklet, we must close the popup: |
1295 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | 1290 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { |
diff --git a/plugins/pubsubhubbub/README.md b/plugins/pubsubhubbub/README.md new file mode 100644 index 00000000..3a65492a --- /dev/null +++ b/plugins/pubsubhubbub/README.md | |||
@@ -0,0 +1,20 @@ | |||
1 | # PubSubHubbub plugin | ||
2 | |||
3 | Enable this plugin to notify a Hub everytime you add or edit a link. | ||
4 | |||
5 | This allow hub subcribers to receive update notifications in real time, | ||
6 | which is useful for feed syndication service which supports PubSubHubbub. | ||
7 | |||
8 | ## Public Hub | ||
9 | |||
10 | By default, Shaarli will use [Google's public hub](http://pubsubhubbub.appspot.com/). | ||
11 | |||
12 | [Here](https://github.com/pubsubhubbub/PubSubHubbub/wiki/Hubs) is a list of public hubs. | ||
13 | |||
14 | You can also host your own PubSubHubbub server implementation, such as [phubb](https://github.com/cweiske/phubb). | ||
15 | |||
16 | ## cURL | ||
17 | |||
18 | While there is a fallback function to notify the hub, it's recommended that | ||
19 | you have PHP cURL extension enabled to use this plugin. | ||
20 | |||
diff --git a/plugins/pubsubhubbub/hub.atom.xml b/plugins/pubsubhubbub/hub.atom.xml new file mode 100644 index 00000000..24d93d3e --- /dev/null +++ b/plugins/pubsubhubbub/hub.atom.xml | |||
@@ -0,0 +1 @@ | |||
<link rel="hub" href="%s" /> \ No newline at end of file | |||
diff --git a/plugins/pubsubhubbub/hub.rss.xml b/plugins/pubsubhubbub/hub.rss.xml new file mode 100644 index 00000000..27bf67a6 --- /dev/null +++ b/plugins/pubsubhubbub/hub.rss.xml | |||
@@ -0,0 +1 @@ | |||
<atom:link rel="hub" href="%s" /> \ No newline at end of file | |||
diff --git a/plugins/pubsubhubbub/pubsubhubbub.meta b/plugins/pubsubhubbub/pubsubhubbub.meta new file mode 100644 index 00000000..289f5cdb --- /dev/null +++ b/plugins/pubsubhubbub/pubsubhubbub.meta | |||
@@ -0,0 +1,2 @@ | |||
1 | description="Enable PubSubHubbub feed publishing." | ||
2 | parameters="PUBSUBHUB_URL" | ||
diff --git a/plugins/pubsubhubbub/pubsubhubbub.php b/plugins/pubsubhubbub/pubsubhubbub.php new file mode 100644 index 00000000..03b6757b --- /dev/null +++ b/plugins/pubsubhubbub/pubsubhubbub.php | |||
@@ -0,0 +1,101 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * PubSubHubbub plugin. | ||
5 | * | ||
6 | * PubSub is a protocol which fasten up RSS fetching: | ||
7 | * - Every time a new link is posted, Shaarli notify the hub. | ||
8 | * - The hub notify all feed subscribers that a new link has been posted. | ||
9 | * - Subscribers retrieve the new link. | ||
10 | */ | ||
11 | |||
12 | use pubsubhubbub\publisher\Publisher; | ||
13 | |||
14 | /** | ||
15 | * Plugin init function - set the hub to the default appspot one. | ||
16 | * | ||
17 | * @param ConfigManager $conf instance. | ||
18 | */ | ||
19 | function pubsubhubbub_init($conf) | ||
20 | { | ||
21 | $hub = $conf->get('plugins.PUBSUBHUB_URL'); | ||
22 | if (empty($hub)) { | ||
23 | // Default hub. | ||
24 | $conf->set('plugins.PUBSUBHUB_URL', 'https://pubsubhubbub.appspot.com/'); | ||
25 | } | ||
26 | } | ||
27 | |||
28 | |||
29 | /** | ||
30 | * Render feed hook. | ||
31 | * Adds the hub URL in ATOM and RSS feed. | ||
32 | * | ||
33 | * @param array $data Template data. | ||
34 | * @param ConfigManager $conf instance. | ||
35 | * | ||
36 | * @return array updated template data. | ||
37 | */ | ||
38 | function hook_pubsubhubbub_render_feed($data, $conf) | ||
39 | { | ||
40 | $feedType = $data['_PAGE_'] == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM; | ||
41 | $template = file_get_contents(PluginManager::$PLUGINS_PATH . '/pubsubhubbub/hub.'. $feedType .'.xml'); | ||
42 | $data['feed_plugins_header'][] = sprintf($template, $conf->get('plugins.PUBSUBHUB_URL')); | ||
43 | |||
44 | return $data; | ||
45 | } | ||
46 | |||
47 | /** | ||
48 | * Save link hook. | ||
49 | * Publish to the hub when a link is saved. | ||
50 | * | ||
51 | * @param array $data Template data. | ||
52 | * @param ConfigManager $conf instance. | ||
53 | * | ||
54 | * @return array unaltered data. | ||
55 | */ | ||
56 | function hook_pubsubhubbub_save_link($data, $conf) | ||
57 | { | ||
58 | $feeds = array( | ||
59 | index_url($_SERVER) .'?do=atom', | ||
60 | index_url($_SERVER) .'?do=rss', | ||
61 | ); | ||
62 | |||
63 | $httpPost = function_exists('curl_version') ? false : 'nocurl_http_post'; | ||
64 | try { | ||
65 | $p = new Publisher($conf->get('plugins.PUBSUBHUB_URL')); | ||
66 | $p->publish_update($feeds, $httpPost); | ||
67 | } catch (Exception $e) { | ||
68 | error_log('Could not publish to PubSubHubbub: ' . $e->getMessage()); | ||
69 | } | ||
70 | |||
71 | return $data; | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Http function used to post to the hub endpoint without cURL extension. | ||
76 | * | ||
77 | * @param string $url Hub endpoint. | ||
78 | * @param string $postString String to POST. | ||
79 | * | ||
80 | * @return bool | ||
81 | * | ||
82 | * @throws Exception An error occurred. | ||
83 | */ | ||
84 | function nocurl_http_post($url, $postString) { | ||
85 | $params = array('http' => array( | ||
86 | 'method' => 'POST', | ||
87 | 'content' => $postString, | ||
88 | 'user_agent' => 'PubSubHubbub-Publisher-PHP/1.0', | ||
89 | )); | ||
90 | |||
91 | $context = stream_context_create($params); | ||
92 | $fp = @fopen($url, 'rb', false, $context); | ||
93 | if (!$fp) { | ||
94 | throw new Exception('Could not post to '. $url); | ||
95 | } | ||
96 | $response = @stream_get_contents($fp); | ||
97 | if ($response === false) { | ||
98 | throw new Exception('Bad response from the hub '. $url); | ||
99 | } | ||
100 | return $response; | ||
101 | } | ||
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php index 06a44506..a590306d 100644 --- a/tests/FeedBuilderTest.php +++ b/tests/FeedBuilderTest.php | |||
@@ -75,7 +75,6 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase | |||
75 | $data = $feedBuilder->buildData(); | 75 | $data = $feedBuilder->buildData(); |
76 | // Test headers (RSS) | 76 | // Test headers (RSS) |
77 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); | 77 | $this->assertEquals(self::$RSS_LANGUAGE, $data['language']); |
78 | $this->assertEmpty($data['pubsubhub_url']); | ||
79 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); | 78 | $this->assertRegExp('/Wed, 03 Aug 2016 09:30:33 \+\d{4}/', $data['last_update']); |
80 | $this->assertEquals(true, $data['show_dates']); | 79 | $this->assertEquals(true, $data['show_dates']); |
81 | $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); | 80 | $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']); |
@@ -211,19 +210,6 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase | |||
211 | } | 210 | } |
212 | 211 | ||
213 | /** | 212 | /** |
214 | * Test buildData with hide dates settings. | ||
215 | */ | ||
216 | public function testBuildDataPubsubhub() | ||
217 | { | ||
218 | $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false); | ||
219 | $feedBuilder->setLocale(self::$LOCALE); | ||
220 | $feedBuilder->setPubsubhubUrl('http://pubsubhub.io'); | ||
221 | $data = $feedBuilder->buildData(); | ||
222 | $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); | ||
223 | $this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']); | ||
224 | } | ||
225 | |||
226 | /** | ||
227 | * Test buildData when Shaarli is served from a subdirectory | 213 | * Test buildData when Shaarli is served from a subdirectory |
228 | */ | 214 | */ |
229 | public function testBuildDataServerSubdir() | 215 | public function testBuildDataServerSubdir() |
diff --git a/tests/plugins/PluginPubsubhubbubTest.php b/tests/plugins/PluginPubsubhubbubTest.php new file mode 100644 index 00000000..24dd7a11 --- /dev/null +++ b/tests/plugins/PluginPubsubhubbubTest.php | |||
@@ -0,0 +1,54 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'plugins/pubsubhubbub/pubsubhubbub.php'; | ||
4 | require_once 'application/Router.php'; | ||
5 | |||
6 | /** | ||
7 | * Class PluginPubsubhubbubTest | ||
8 | * Unit test for the pubsubhubbub plugin | ||
9 | */ | ||
10 | class PluginPubsubhubbubTest extends PHPUnit_Framework_TestCase | ||
11 | { | ||
12 | /** | ||
13 | * @var string Config file path (without extension). | ||
14 | */ | ||
15 | protected static $configFile = 'tests/utils/config/configJson'; | ||
16 | |||
17 | /** | ||
18 | * Reset plugin path | ||
19 | */ | ||
20 | function setUp() | ||
21 | { | ||
22 | PluginManager::$PLUGINS_PATH = 'plugins'; | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Test render_feed hook with an RSS feed. | ||
27 | */ | ||
28 | function testPubSubRssRenderFeed() | ||
29 | { | ||
30 | $hub = 'http://domain.hub'; | ||
31 | $conf = new ConfigManager(self::$configFile); | ||
32 | $conf->set('plugins.PUBSUBHUB_URL', $hub); | ||
33 | $data['_PAGE_'] = Router::$PAGE_FEED_RSS; | ||
34 | |||
35 | $data = hook_pubsubhubbub_render_feed($data, $conf); | ||
36 | $expected = '<atom:link rel="hub" href="'. $hub .'" />'; | ||
37 | $this->assertEquals($expected, $data['feed_plugins_header'][0]); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Test render_feed hook with an ATOM feed. | ||
42 | */ | ||
43 | function testPubSubAtomRenderFeed() | ||
44 | { | ||
45 | $hub = 'http://domain.hub'; | ||
46 | $conf = new ConfigManager(self::$configFile); | ||
47 | $conf->set('plugins.PUBSUBHUB_URL', $hub); | ||
48 | $data['_PAGE_'] = Router::$PAGE_FEED_ATOM; | ||
49 | |||
50 | $data = hook_pubsubhubbub_render_feed($data, $conf); | ||
51 | $expected = '<link rel="hub" href="'. $hub .'" />'; | ||
52 | $this->assertEquals($expected, $data['feed_plugins_header'][0]); | ||
53 | } | ||
54 | } | ||
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html index aead0459..ad7dd935 100644 --- a/tpl/feed.atom.html +++ b/tpl/feed.atom.html | |||
@@ -6,11 +6,9 @@ | |||
6 | <updated>{$last_update}</updated> | 6 | <updated>{$last_update}</updated> |
7 | {/if} | 7 | {/if} |
8 | <link rel="self" href="{$self_link}#" /> | 8 | <link rel="self" href="{$self_link}#" /> |
9 | {if="!empty($pubsubhub_url)"} | 9 | {loop="$feed_plugins_header"} |
10 | <!-- PubSubHubbub Discovery --> | 10 | {$value} |
11 | <link rel="hub" href="{$pubsubhub_url}#" /> | 11 | {/loop} |
12 | <!-- End Of PubSubHubbub Discovery --> | ||
13 | {/if} | ||
14 | <author> | 12 | <author> |
15 | <name>{$index_url}</name> | 13 | <name>{$index_url}</name> |
16 | <uri>{$index_url}</uri> | 14 | <uri>{$index_url}</uri> |
@@ -34,6 +32,9 @@ | |||
34 | {loop="$value.taglist"} | 32 | {loop="$value.taglist"} |
35 | <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" /> | 33 | <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" /> |
36 | {/loop} | 34 | {/loop} |
35 | {loop="$value.feed_plugins"} | ||
36 | {$value} | ||
37 | {/loop} | ||
37 | </entry> | 38 | </entry> |
38 | {/loop} | 39 | {/loop} |
39 | </feed> | 40 | </feed> |
diff --git a/tpl/feed.rss.html b/tpl/feed.rss.html index e18dbf9b..73791f63 100644 --- a/tpl/feed.rss.html +++ b/tpl/feed.rss.html | |||
@@ -8,10 +8,9 @@ | |||
8 | <copyright>{$index_url}</copyright> | 8 | <copyright>{$index_url}</copyright> |
9 | <generator>Shaarli</generator> | 9 | <generator>Shaarli</generator> |
10 | <atom:link rel="self" href="{$self_link}" /> | 10 | <atom:link rel="self" href="{$self_link}" /> |
11 | {if="!empty($pubsubhub_url)"} | 11 | {loop="$feed_plugins_header"} |
12 | <!-- PubSubHubbub Discovery --> | 12 | {$value} |
13 | <atom:link rel="hub" href="{$pubsubhub_url}" /> | 13 | {/loop} |
14 | {/if} | ||
15 | {loop="$links"} | 14 | {loop="$links"} |
16 | <item> | 15 | <item> |
17 | <title>{$value.title}</title> | 16 | <title>{$value.title}</title> |
@@ -29,6 +28,9 @@ | |||
29 | {loop="$value.taglist"} | 28 | {loop="$value.taglist"} |
30 | <category domain="{$index_url}?searchtags=">{$value}</category> | 29 | <category domain="{$index_url}?searchtags=">{$value}</category> |
31 | {/loop} | 30 | {/loop} |
31 | {loop="$value.feed_plugins"} | ||
32 | {$value} | ||
33 | {/loop} | ||
32 | </item> | 34 | </item> |
33 | {/loop} | 35 | {/loop} |
34 | </channel> | 36 | </channel> |