diff --git a/plugins/SubMirror/SubMirrorPlugin.php b/plugins/SubMirror/SubMirrorPlugin.php index 38a4c40d48..a9cb2315b4 100644 --- a/plugins/SubMirror/SubMirrorPlugin.php +++ b/plugins/SubMirror/SubMirrorPlugin.php @@ -35,6 +35,9 @@ class SubMirrorPlugin extends Plugin { $m->connect('settings/mirror', array('action' => 'mirrorsettings')); + $m->connect('settings/mirror/add/:provider', + array('action' => 'mirrorsettings'), + array('provider' => '[A-Za-z0-9_-]+')); $m->connect('settings/mirror/add', array('action' => 'addmirror')); $m->connect('settings/mirror/edit', diff --git a/plugins/SubMirror/actions/addmirror.php b/plugins/SubMirror/actions/addmirror.php index 8c3a9740f3..31805c1669 100644 --- a/plugins/SubMirror/actions/addmirror.php +++ b/plugins/SubMirror/actions/addmirror.php @@ -59,11 +59,27 @@ class AddMirrorAction extends BaseMirrorAction function prepare($args) { parent::prepare($args); - $this->feedurl = $this->validateFeedUrl($this->trimmed('feedurl')); + $feedurl = $this->getFeedUrl(); + $this->feedurl = $this->validateFeedUrl($feedurl); $this->profile = $this->profileForFeed($this->feedurl); return true; } + function getFeedUrl() + { + $provider = $this->trimmed('provider'); + switch ($provider) { + case 'feed': + return $this->trimmed('feedurl'); + case 'twitter': + $screenie = $this->trimmed('screen_name'); + $base = 'http://api.twitter.com/1/statuses/user_timeline.atom?screen_name='; + return $base . urlencode($screenie); + default: + throw new Exception('Internal form error: unrecognized feed provider.'); + } + } + function saveMirror() { if ($this->oprofile->subscribe()) { diff --git a/plugins/SubMirror/actions/basemirror.php b/plugins/SubMirror/actions/basemirror.php index 3e3431103f..843dfb92e1 100644 --- a/plugins/SubMirror/actions/basemirror.php +++ b/plugins/SubMirror/actions/basemirror.php @@ -68,7 +68,7 @@ abstract class BaseMirrorAction extends Action if (common_valid_http_url($url)) { return $url; } else { - $this->clientError(_m("Invalid feed URL.")); + $this->clientError(sprintf(_m("Invalid feed URL: %s"), $url)); } } diff --git a/plugins/SubMirror/actions/mirrorsettings.php b/plugins/SubMirror/actions/mirrorsettings.php index 856099afa3..90bbf3dffb 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -65,18 +65,30 @@ class MirrorSettingsAction extends SettingsAction function showContent() { $user = common_current_user(); + $provider = $this->trimmed('provider'); + if ($provider) { + $this->showAddFeedForm($provider); + } else { + $this->elementStart('div', array('id' => 'add-mirror')); + $this->showAddWizard(); + $this->elementEnd('div'); - $this->showAddFeedForm(); - - $mirror = new SubMirror(); - $mirror->subscriber = $user->id; - if ($mirror->find()) { - while ($mirror->fetch()) { - $this->showFeedForm($mirror); + $mirror = new SubMirror(); + $mirror->subscriber = $user->id; + if ($mirror->find()) { + while ($mirror->fetch()) { + $this->showFeedForm($mirror); + } } } } + function showAddWizard() + { + $form = new AddMirrorWizard($this); + $form->show(); + } + function showFeedForm($mirror) { $profile = Profile::staticGet('id', $mirror->subscribed); @@ -88,10 +100,47 @@ class MirrorSettingsAction extends SettingsAction function showAddFeedForm() { - $form = new AddMirrorForm($this); + switch ($this->arg('provider')) { + case 'statusnet': + break; + case 'twitter': + $form = new AddTwitterMirrorForm($this); + break; + case 'wordpress': + break; + case 'linkedin': + break; + case 'feed': + default: + $form = new AddMirrorForm($this); + } $form->show(); } + /** + * + * @param array $args + * + * @todo move the ajax display handling to common code + */ + function handle($args) + { + if ($this->boolean('ajax')) { + header('Content-Type: text/html;charset=utf-8'); + $this->elementStart('html'); + $this->elementStart('head'); + $this->element('title', null, _('Provider add')); + $this->elementEnd('head'); + $this->elementStart('body'); + + $this->showAddFeedForm(); + + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + return parent::handle($args); + } + } /** * Handle a POST request * @@ -108,4 +157,16 @@ class MirrorSettingsAction extends SettingsAction $nav = new SubGroupNav($this, common_current_user()); $nav->show(); } + + function showScripts() + { + parent::showScripts(); + $this->script('plugins/SubMirror/js/mirrorsettings.js'); + } + + function showStylesheets() + { + parent::showStylesheets(); + $this->cssLink('plugins/SubMirror/css/mirrorsettings.css'); + } } diff --git a/plugins/SubMirror/css/mirrorsettings.css b/plugins/SubMirror/css/mirrorsettings.css new file mode 100644 index 0000000000..c91bb73b6b --- /dev/null +++ b/plugins/SubMirror/css/mirrorsettings.css @@ -0,0 +1,26 @@ +/* undo insane stuff from core styles */ +#add-mirror-wizard img { + display: inline; +} + +/* we need #something to override most of the #content crap */ + +#add-mirror-wizard { + margin-left: 20px; + margin-right: 20px; +} + +#add-mirror-wizard .provider-list table { + width: 100%; +} + +#add-mirror-wizard .provider-heading img { + vertical-align: middle; +} +#add-mirror-wizard .provider-heading { + cursor: pointer; +} +#add-mirror-wizard .provider-detail fieldset { + margin-top: 8px; /* hack */ + margin-bottom: 8px; /* hack */ +} \ No newline at end of file diff --git a/plugins/SubMirror/images/providers/facebook.png b/plugins/SubMirror/images/providers/facebook.png new file mode 100644 index 0000000000..13a53aa63c Binary files /dev/null and b/plugins/SubMirror/images/providers/facebook.png differ diff --git a/plugins/SubMirror/images/providers/feed.png b/plugins/SubMirror/images/providers/feed.png new file mode 100644 index 0000000000..bd1da4f914 Binary files /dev/null and b/plugins/SubMirror/images/providers/feed.png differ diff --git a/plugins/SubMirror/images/providers/linkedin.png b/plugins/SubMirror/images/providers/linkedin.png new file mode 100644 index 0000000000..82103d1f3f Binary files /dev/null and b/plugins/SubMirror/images/providers/linkedin.png differ diff --git a/plugins/SubMirror/images/providers/statusnet.png b/plugins/SubMirror/images/providers/statusnet.png new file mode 100644 index 0000000000..6edca21697 Binary files /dev/null and b/plugins/SubMirror/images/providers/statusnet.png differ diff --git a/plugins/SubMirror/images/providers/twitter.png b/plugins/SubMirror/images/providers/twitter.png new file mode 100644 index 0000000000..41dabc883e Binary files /dev/null and b/plugins/SubMirror/images/providers/twitter.png differ diff --git a/plugins/SubMirror/images/providers/wordpress.png b/plugins/SubMirror/images/providers/wordpress.png new file mode 100644 index 0000000000..dfafc75a2f Binary files /dev/null and b/plugins/SubMirror/images/providers/wordpress.png differ diff --git a/plugins/SubMirror/js/mirrorsettings.js b/plugins/SubMirror/js/mirrorsettings.js new file mode 100644 index 0000000000..a27abe7ad5 --- /dev/null +++ b/plugins/SubMirror/js/mirrorsettings.js @@ -0,0 +1,47 @@ +$(function() { + /** + * Append 'ajax=1' parameter onto URL. + */ + function ajaxize(url) { + if (url.indexOf('?') == '-1') { + return url + '?ajax=1'; + } else { + return url + '&ajax=1'; + } + } + + var addMirror = $('#add-mirror'); + var wizard = $('#add-mirror-wizard'); + if (wizard.length > 0) { + var list = wizard.find('.provider-list'); + var providers = list.find('.provider-heading'); + providers.click(function(event) { + console.log(this); + var targetUrl = $(this).find('a').attr('href'); + if (targetUrl) { + // Make sure we don't accidentally follow the direct link + event.preventDefault(); + + var node = this; + function showNew() { + var detail = $('
').insertAfter(node); + detail.load(ajaxize(targetUrl), function(responseText, testStatus, xhr) { + detail.slideDown('fast', function() { + detail.find('input[type="text"]').focus(); + }); + }); + } + + var old = addMirror.find('.provider-detail'); + if (old.length) { + old.slideUp('fast', function() { + old.remove(); + showNew(); + }); + } else { + showNew(); + } + } + }); + } +}); \ No newline at end of file diff --git a/plugins/SubMirror/lib/addmirrorform.php b/plugins/SubMirror/lib/addmirrorform.php index e1d50c272c..17edbd5e96 100644 --- a/plugins/SubMirror/lib/addmirrorform.php +++ b/plugins/SubMirror/lib/addmirrorform.php @@ -49,6 +49,7 @@ class AddMirrorForm extends Form */ function formData() { + $this->out->hidden('provider', 'feed'); $this->out->elementStart('fieldset'); $this->out->elementStart('ul'); @@ -67,7 +68,7 @@ class AddMirrorForm extends Form $this->out->elementEnd('fieldset'); } - private function doInput($id, $name, $label, $value=null, $instructions=null) + protected function doInput($id, $name, $label, $value=null, $instructions=null) { $this->out->element('label', array('for' => $id), $label); $attrs = array('name' => $name, diff --git a/plugins/SubMirror/lib/addmirrorwizard.php b/plugins/SubMirror/lib/addmirrorwizard.php new file mode 100644 index 0000000000..920db0bc9c --- /dev/null +++ b/plugins/SubMirror/lib/addmirrorwizard.php @@ -0,0 +1,187 @@ +. + * + * @package StatusNet + * @copyright 2010-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class AddMirrorWizard extends Widget +{ + /** + * Name of the form + * + * Sub-classes should overload this with the name of their form. + * + * @return void + */ + function formLegend() + { + } + + /** + * Visible or invisible data elements + * + * Display the form fields that make up the data of the form. + * Sub-classes should overload this to show their data. + * + * @return void + */ + function show() + { + $this->out->elementStart('div', array('id' => 'add-mirror-wizard')); + + $providers = $this->providers(); + $this->showProviders($providers); + + $this->out->elementEnd('div'); + } + + function providers() + { + return array( + /* + // We could accept hostname & username combos here, or + // webfingery combinations as for remote users. + array( + 'id' => 'statusnet', + 'name' => _m('StatusNet'), + ), + */ + // Accepts a Twitter username and pulls their user timeline as a + // public Atom feed. Requires a working alternate hub which, one + // hopes, is getting timely updates. + array( + 'id' => 'twitter', + 'name' => _m('Twitter'), + ), + /* + // WordPress was on our list some whiles ago, but not sure + // what we can actually do here. Search on Wordpress.com hosted + // sites, or ? + array( + 'id' => 'wordpress', + 'name' => _m('WordPress'), + ), + */ + /* + // In theory, Facebook lets you pull public updates over RSS, + // but the URLs for your own update feed that I can find from + // 2009-era websites no longer seem to work and there's no + // good current documentation. May not still be available... + // Mirroring from an FB account is probably better done with + // the dedicated plugin. (As of March 2011) + array( + 'id' => 'facebook', + 'name' => _m('Facebook'), + ), + */ + /* + // LinkedIn doesn't currently seem to have public feeds + // for users or groups (March 2011) + array( + 'id' => 'linkedin', + 'name' => _m('LinkedIn'), + ), + */ + array( + 'id' => 'feed', + 'name' => _m('RSS or Atom feed'), + ), + ); + } + + function showProviders(array $providers) + { + $out = $this->out; + + $out->elementStart('div', 'provider-list'); + $out->element('h2', null, _m('Select a feed provider')); + $out->elementStart('table'); + foreach ($providers as $provider) { + $icon = common_path('plugins/SubMirror/images/providers/' . $provider['id'] . '.png'); + $targetUrl = common_local_url('mirrorsettings', array('provider' => $provider['id'])); + + $out->elementStart('tr', array('class' => 'provider')); + $out->elementStart('td'); + + $out->elementStart('div', 'provider-heading'); + $out->element('img', array('src' => $icon)); + $out->element('a', array('href' => $targetUrl), $provider['name']); + $out->elementEnd('div'); + + $out->elementEnd('td'); + $out->elementEnd('tr'); + } + $out->elementEnd('table'); + $out->elementEnd('div'); + } + + /** + * Buttons for form actions + * + * Submit and cancel buttons (or whatever) + * Sub-classes should overload this to show their own buttons. + * + * @return void + */ + function formActions() + { + } + + /** + * ID of the form + * + * Should be unique on the page. Sub-classes should overload this + * to show their own IDs. + * + * @return string ID of the form + */ + function id() + { + return 'add-mirror-wizard'; + } + + /** + * Action of the form. + * + * URL to post to. Should be overloaded by subclasses to give + * somewhere to post to. + * + * @return string URL to post to + */ + function action() + { + return common_local_url('addmirror'); + } + + /** + * Class of the form. + * + * @return string the form's class + */ + function formClass() + { + return 'form_settings'; + } +} diff --git a/plugins/SubMirror/lib/addtwittermirrorform.php b/plugins/SubMirror/lib/addtwittermirrorform.php new file mode 100644 index 0000000000..eb28aa038f --- /dev/null +++ b/plugins/SubMirror/lib/addtwittermirrorform.php @@ -0,0 +1,60 @@ +. + * + * @package StatusNet + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class AddTwitterMirrorForm extends AddMirrorForm +{ + + /** + * Visible or invisible data elements + * + * Display the form fields that make up the data of the form. + * Sub-classes should overload this to show their data. + * + * @return void + */ + function formData() + { + $this->out->hidden('provider', 'twitter'); + $this->out->elementStart('fieldset'); + + $this->out->elementStart('ul'); + + $this->li(); + $this->doInput('addmirror-feedurl', + 'screen_name', + _m('Twitter username:'), + $this->out->trimmed('screen_name')); + $this->unli(); + + $this->li(); + $this->out->submit('addmirror-save', _m('BUTTON','Add feed')); + $this->unli(); + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } +}