From ade2d04cb30cd249794f53a2a92e1c601073402c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 18 Jan 2011 15:58:28 -0800 Subject: [PATCH 01/69] Work in progress on helper wizard for feed mirror discovery/setup --- plugins/SubMirror/actions/mirrorsettings.php | 2 + .../SubMirror/images/providers/facebook.png | Bin 0 -> 958 bytes plugins/SubMirror/images/providers/feed.png | Bin 0 -> 2434 bytes .../SubMirror/images/providers/linkedin.png | Bin 0 -> 2100 bytes .../SubMirror/images/providers/statusnet.png | Bin 0 -> 6168 bytes .../SubMirror/images/providers/twitter.png | Bin 0 -> 1192 bytes .../SubMirror/images/providers/wordpress.png | Bin 0 -> 4861 bytes plugins/SubMirror/lib/addmirrorwizard.php | 155 ++++++++++++++++++ 8 files changed, 157 insertions(+) create mode 100644 plugins/SubMirror/images/providers/facebook.png create mode 100644 plugins/SubMirror/images/providers/feed.png create mode 100644 plugins/SubMirror/images/providers/linkedin.png create mode 100644 plugins/SubMirror/images/providers/statusnet.png create mode 100644 plugins/SubMirror/images/providers/twitter.png create mode 100644 plugins/SubMirror/images/providers/wordpress.png create mode 100644 plugins/SubMirror/lib/addmirrorwizard.php diff --git a/plugins/SubMirror/actions/mirrorsettings.php b/plugins/SubMirror/actions/mirrorsettings.php index 20e1807b3d..195946c884 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -88,6 +88,8 @@ class MirrorSettingsAction extends AccountSettingsAction function showAddFeedForm() { + $form = new AddMirrorWizard($this); + $form->show(); $form = new AddMirrorForm($this); $form->show(); } diff --git a/plugins/SubMirror/images/providers/facebook.png b/plugins/SubMirror/images/providers/facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..13a53aa63cfb8204e93237a364f795717c16f58d GIT binary patch literal 958 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 A82|tP literal 0 HcmV?d00001 diff --git a/plugins/SubMirror/images/providers/linkedin.png b/plugins/SubMirror/images/providers/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..82103d1f3f17839c6fe0bd90222d4f94c351feb5 GIT binary patch literal 2100 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. + * + * @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 Form +{ + /** + * 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 formData() + { + $this->out->elementStart('fieldset'); + + $providers = $this->providers(); + $this->showProviders($providers); + + $this->out->elementEnd('fieldset'); + } + + function providers() + { + return array( + array( + 'id' => 'statusnet', + 'name' => _m('StatusNet'), + ), + array( + 'id' => 'twitter', + 'name' => _m('Twitter'), + ), + array( + 'id' => 'wordpress', + 'name' => _m('WordPress'), + ), + array( + 'id' => 'linkedin', + 'name' => _m('LinkedIn'), + ), + array( + 'id' => 'feed', + 'name' => _m('RSS or Atom feed'), + ), + ); + } + + function showProviders(array $providers) + { + $out = $this->out; + + $out->elementStart('table', array('width' => '100%')); + foreach ($providers as $provider) { + $icon = common_path('plugins/SubMirror/images/providers/' . $provider['id'] . '.png'); + $out->elementStart('tr'); + + $out->elementStart('td', array('style' => 'text-align: right; vertical-align: middle')); + $out->element('img', array('src' => $icon)); + $out->elementEnd('td'); + + $out->elementStart('td', array('style' => 'text-align: left; vertical-align: middle')); + $out->text($provider['name']); + $out->elementEnd('td'); + + $out->elementEnd('tr'); + } + $out->elementEnd('table'); + } + + /** + * 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'; + } +} From aa901bb61cd7bf3fc38d55f5b041abb0b766bcb6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 18 Jan 2011 18:01:57 -0800 Subject: [PATCH 02/69] Work in progress: AJAXy interface for grabbing feed subscription helper detail forms. Currently they all show the regular subform. :) --- plugins/SubMirror/SubMirrorPlugin.php | 3 + plugins/SubMirror/actions/mirrorsettings.php | 64 +++++++++++++++++--- plugins/SubMirror/css/mirrorsettings.css | 17 ++++++ plugins/SubMirror/js/mirrorsettings.js | 45 ++++++++++++++ plugins/SubMirror/lib/addmirrorwizard.php | 18 +++--- 5 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 plugins/SubMirror/css/mirrorsettings.css create mode 100644 plugins/SubMirror/js/mirrorsettings.js 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/mirrorsettings.php b/plugins/SubMirror/actions/mirrorsettings.php index 195946c884..114b8b40fe 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -65,18 +65,30 @@ class MirrorSettingsAction extends AccountSettingsAction 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,12 +100,34 @@ class MirrorSettingsAction extends AccountSettingsAction function showAddFeedForm() { - $form = new AddMirrorWizard($this); - $form->show(); $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 * @@ -110,4 +144,16 @@ class MirrorSettingsAction extends AccountSettingsAction $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..d17c794f40 --- /dev/null +++ b/plugins/SubMirror/css/mirrorsettings.css @@ -0,0 +1,17 @@ +/* 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 .provider-list table { + width: 100%; +} + +#add-mirror-wizard .provider-heading img { + vertical-align: middle; +} +#add-mirror-wizard .provider-heading { + cursor: pointer; +} diff --git a/plugins/SubMirror/js/mirrorsettings.js b/plugins/SubMirror/js/mirrorsettings.js new file mode 100644 index 0000000000..e772af3dea --- /dev/null +++ b/plugins/SubMirror/js/mirrorsettings.js @@ -0,0 +1,45 @@ +$(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(); + }); + } + + var old = addMirror.find('.provider-detail'); + if (old.length) { + old.slideUp(function() { + old.remove(); + showNew(); + }); + } else { + showNew(); + } + } + }); + } +}); \ No newline at end of file diff --git a/plugins/SubMirror/lib/addmirrorwizard.php b/plugins/SubMirror/lib/addmirrorwizard.php index 1ac8aa1388..0994819b41 100644 --- a/plugins/SubMirror/lib/addmirrorwizard.php +++ b/plugins/SubMirror/lib/addmirrorwizard.php @@ -87,22 +87,26 @@ class AddMirrorWizard extends Form { $out = $this->out; - $out->elementStart('table', array('width' => '100%')); + $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'); - $out->elementStart('tr'); + $targetUrl = common_local_url('mirrorsettings', array('provider' => $provider['id'])); - $out->elementStart('td', array('style' => 'text-align: right; vertical-align: middle')); + $out->elementStart('tr', array('class' => 'provider')); + $out->elementStart('td'); + + $out->elementStart('div', 'provider-heading'); $out->element('img', array('src' => $icon)); - $out->elementEnd('td'); + $out->element('a', array('href' => $targetUrl), $provider['name']); + $out->elementEnd('div'); - $out->elementStart('td', array('style' => 'text-align: left; vertical-align: middle')); - $out->text($provider['name']); $out->elementEnd('td'); - $out->elementEnd('tr'); } $out->elementEnd('table'); + $out->elementEnd('div'); } /** From b1897e019021af1128540bbde4682538ac0a0dc5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 18 Jan 2011 18:13:24 -0800 Subject: [PATCH 03/69] viz cleanup on AddMirrorWizard --- plugins/SubMirror/css/mirrorsettings.css | 9 +++++++++ plugins/SubMirror/js/mirrorsettings.js | 6 ++++-- plugins/SubMirror/lib/addmirrorwizard.php | 8 ++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/SubMirror/css/mirrorsettings.css b/plugins/SubMirror/css/mirrorsettings.css index d17c794f40..c91bb73b6b 100644 --- a/plugins/SubMirror/css/mirrorsettings.css +++ b/plugins/SubMirror/css/mirrorsettings.css @@ -5,6 +5,11 @@ /* 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%; } @@ -15,3 +20,7 @@ #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/js/mirrorsettings.js b/plugins/SubMirror/js/mirrorsettings.js index e772af3dea..a27abe7ad5 100644 --- a/plugins/SubMirror/js/mirrorsettings.js +++ b/plugins/SubMirror/js/mirrorsettings.js @@ -26,13 +26,15 @@ $(function() { function showNew() { var detail = $('').insertAfter(node); detail.load(ajaxize(targetUrl), function(responseText, testStatus, xhr) { - detail.slideDown(); + detail.slideDown('fast', function() { + detail.find('input[type="text"]').focus(); + }); }); } var old = addMirror.find('.provider-detail'); if (old.length) { - old.slideUp(function() { + old.slideUp('fast', function() { old.remove(); showNew(); }); diff --git a/plugins/SubMirror/lib/addmirrorwizard.php b/plugins/SubMirror/lib/addmirrorwizard.php index 0994819b41..7a63f8366b 100644 --- a/plugins/SubMirror/lib/addmirrorwizard.php +++ b/plugins/SubMirror/lib/addmirrorwizard.php @@ -26,7 +26,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class AddMirrorWizard extends Form +class AddMirrorWizard extends Widget { /** * Name of the form @@ -47,14 +47,14 @@ class AddMirrorWizard extends Form * * @return void */ - function formData() + function show() { - $this->out->elementStart('fieldset'); + $this->out->elementStart('div', array('id' => 'add-mirror-wizard')); $providers = $this->providers(); $this->showProviders($providers); - $this->out->elementEnd('fieldset'); + $this->out->elementEnd('div'); } function providers() From be53b94bfdb0498b335aaa558f5631c95a23e3d5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 7 Mar 2011 17:26:19 -0800 Subject: [PATCH 04/69] - Fix table name; add comments --- plugins/ExtendedProfile/Profile_detail.php | 63 +++++++++++++++++++--- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index 6fd96cca70..25eca4d09c 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -21,18 +21,52 @@ if (!defined('STATUSNET')) { exit(1); } +/** + * DataObject class to store extended profile fields. Allows for storing + * multiple values per a "field" (field property is not unique). + * + * Example: + * + * Jed's Phone Numbers + * home : 510-384-1992 + * mobile: 510-719-1139 + * work : 415-231-1121 + * + * We can store these phone numbers in a "field" represented by three + * Profile_detail objects, each named 'phone_number' like this: + * + * $phone1 = new Profile_detail(); + * $phone1->field = 'phone_number'; + * $phone1->rel = 'home'; + * $phone1->field_index = 1; + * $phone1->value = '510-384-1992'; + * + * $phone1 = new Profile_detail(); + * $phone1->field = 'phone_number'; + * $phone1->rel = 'mobile'; + * $phone1->field_index = 2; + * $phone1->value = '510-719-1139'; + * + * $phone1 = new Profile_detail(); + * $phone1->field = 'phone_number'; + * $phone1->rel = 'work'; + * $phone1->field_index = 3; + * $phone1->value = '415-231-1121'; + * + */ class Profile_detail extends Memcached_DataObject { - public $__table = 'submirror'; + public $__table = 'profile_detail'; public $id; public $profile_id; + public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM public $field; + + public $value; // primary text value public $field_index; // relative ordering of multiple values in the same field - public $value; // primary text value - public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM public $ref_profile; // for people types, allows pointing to a known profile in the system public $created; @@ -68,9 +102,17 @@ class Profile_detail extends Memcached_DataObject 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); } + /** + * Database schema setup + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + static function schemaDef() { - // @fixme need a reverse key on (subscribed, subscriber) as well return array(new ColumnDef('id', 'integer', null, false, 'PRI'), @@ -113,7 +155,7 @@ class Profile_detail extends Memcached_DataObject } /** - * return key definitions for DB_DataObject + * Return key definitions for DB_DataObject * * DB_DataObject needs to know about keys that the table has; this function * defines them. @@ -127,7 +169,7 @@ class Profile_detail extends Memcached_DataObject } /** - * return key definitions for Memcached_DataObject + * Return key definitions for Memcached_DataObject * * Our caching system uses the same key definitions, but uses a different * method to get them. @@ -142,6 +184,15 @@ class Profile_detail extends Memcached_DataObject return array('id' => 'K'); } + /** + * Get the sequence key + * + * Returns the first serial column defined in the table, if any. + * + * @access private + * @return array (column,use_native,sequence_name) + */ + function sequenceKey() { return array('id', true); From 99bd8c670c3a1be5be944879ffed248c040c2770 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 7 Mar 2011 21:34:57 -0800 Subject: [PATCH 05/69] Fix a couple things --- plugins/ExtendedProfile/Profile_detail.php | 2 +- plugins/ExtendedProfile/extendedprofile.php | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index 25eca4d09c..4b193e54ee 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -96,7 +96,7 @@ class Profile_detail extends Memcached_DataObject 'value' => DB_DATAOBJECT_STR, 'rel' => DB_DATAOBJECT_STR, - 'ref_profile' => DB_DATAOBJECT_ID, + 'ref_profile' => DB_DATAOBJECT_INT, 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 7f69f90899..3be753b23f 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -35,8 +35,10 @@ class ExtendedProfile $detail = new Profile_detail(); $detail->profile_id = $this->profile->id; $detail->find(); - - while ($detail->get()) { + + $fields = array(); + + while ($detail->fetch()) { $fields[$detail->field][] = clone($detail); } return $fields; From 794cb5609b94befce80f7ebaf68419e993821897 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 8 Mar 2011 19:20:43 -0800 Subject: [PATCH 06/69] Save basic profile data to the right place --- plugins/ExtendedProfile/extendedprofile.php | 57 +++++- .../ExtendedProfile/extendedprofilewidget.php | 165 ++++++++++++++++-- .../ExtendedProfile/profiledetailaction.php | 1 + .../profiledetailsettingsaction.php | 134 +++++++++++++- 4 files changed, 338 insertions(+), 19 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 3be753b23f..61a66b4b43 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -21,15 +21,29 @@ if (!defined('STATUSNET')) { exit(1); } +/** + * Class to represent extended profile data + */ class ExtendedProfile { + /** + * Constructor + * + * @param Profile $profile + */ function __construct(Profile $profile) { - $this->profile = $profile; + $this->profile = $profile; + $this->user = $profile->getUser(); $this->sections = $this->getSections(); - $this->fields = $this->loadFields(); + $this->fields = $this->loadFields(); } + /** + * Load extended profile fields + * + * @return array $fields the list of fields + */ function loadFields() { $detail = new Profile_detail(); @@ -41,9 +55,48 @@ class ExtendedProfile while ($detail->fetch()) { $fields[$detail->field][] = clone($detail); } + return $fields; } + /** + * Get a the self-tags associated with this profile + * + * @return string the concatenated string of tags + */ + function getTags() + { + return implode(' ', $this->user->getSelfTags()); + } + + /** + * Return a simple string value. Checks for fields that should + * be stored in the regular profile and returns values from it + * if appropriate. + * + * @param string $name name of the detail field to get the + * value from + * + * @return string the value + */ + function getTextValue($name) + { + $profileFields = array('fullname', 'location', 'bio'); + + if (in_array(strtolower($name), $profileFields)) { + return $this->profile->$name; + } else if (in_array($name, $this->fields)) { + return $this->fields[$name]->value; + } else { + return null; + } + } + + /** + * Return all the sections of the extended profile + * + * @return array the big list of sections and fields + */ function getSections() { return array( diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index bf9b4056cd..fbb3ff3c23 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -21,13 +21,35 @@ if (!defined('STATUSNET')) { exit(1); } -class ExtendedProfileWidget extends Widget +/** + * Class for outputting a widget to display or edit + * extended profiles + */ +class ExtendedProfileWidget extends Form { - const EDITABLE=true; + const EDITABLE = true; + /** + * The parent profile + * + * @var Profile + */ protected $profile; + + /** + * The extended profile + * + * @var Extended_profile + */ protected $ext; + /** + * Constructor + * + * @param XMLOutputter $out + * @param Profile $profile + * @param boolean $editable + */ public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false) { parent::__construct($out); @@ -38,7 +60,30 @@ class ExtendedProfileWidget extends Widget $this->editable = $editable; } + /** + * Show the extended profile, or the edit form + */ public function show() + { + if ($this->editable) { + parent::show(); + } else { + $this->showSections(); + } + } + + /** + * Show form data + */ + public function formData() + { + $this->showSections(); + } + + /** + * Show each section of the extended profile + */ + public function showSections() { $sections = $this->ext->getSections(); foreach ($sections as $name => $section) { @@ -46,6 +91,12 @@ class ExtendedProfileWidget extends Widget } } + /** + * Show an extended profile section + * + * @param string $name name of the section + * @param array $section array of fields for the section + */ protected function showExtendedProfileSection($name, $section) { $this->out->element('h3', null, $section['label']); @@ -56,6 +107,12 @@ class ExtendedProfileWidget extends Widget $this->out->elementEnd('table'); } + /** + * Show an extended profile field + * + * @param string $name name of the field + * @param array $field set of key/value pairs for the field + */ protected function showExtendedProfileField($name, $field) { $this->out->elementStart('tr'); @@ -73,30 +130,110 @@ class ExtendedProfileWidget extends Widget $this->out->elementEnd('tr'); } + /** + * Outputs the value of a field + * + * @param string $name name of the field + * @param array $field set of key/value pairs for the field + */ protected function showFieldValue($name, $field) { - $this->out->text($name); + $type = strval(@$field['type']); + + switch($type) + { + case '': + case 'text': + case 'textarea': + $this->out->text($this->ext->getTextValue($name)); + break; + case 'tags': + $this->out->text($this->ext->getTags()); + break; + default: + $this->out->text("TYPE: $type"); + } + } + /** + * Show an editable version of the field + * + * @param string $name name fo the field + * @param array $field array of key/value pairs for the field + */ protected function showEditableField($name, $field) { $out = $this->out; - //$out = new HTMLOutputter(); - // @fixme + $type = strval(@$field['type']); $id = "extprofile-" . $name; + $value = 'placeholder'; switch ($type) { - case '': - case 'text': - $out->input($id, null, $value); - break; - case 'textarea': - $out->textarea($id, null, $value); - break; - default: - $out->input($id, null, "TYPE: $type"); + case '': + case 'text': + $out->input($id, null, $this->ext->getTextValue($name)); + break; + case 'textarea': + $out->textarea($id, null, $this->ext->getTextValue($name)); + break; + case 'tags': + $out->input($id, null, $this->ext->getTags()); + break; + default: + $out->input($id, null, "TYPE: $type"); } } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit( + 'save', + _m('BUTTON','Save'), + 'submit form_action-secondary', + 'save', + _('Save details') + ); + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'profile-details-' . $this->profile->id; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_profile_details'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('profiledetailsettings'); + } } diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php index a4bb12956e..d2eb06775c 100644 --- a/plugins/ExtendedProfile/profiledetailaction.php +++ b/plugins/ExtendedProfile/profiledetailaction.php @@ -23,6 +23,7 @@ if (!defined('STATUSNET')) { class ProfileDetailAction extends ProfileAction { + function isReadOnly($args) { return true; diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 77d755c0b0..d1153cc075 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -21,7 +21,7 @@ if (!defined('STATUSNET')) { exit(1); } -class ProfileDetailSettingsAction extends AccountSettingsAction +class ProfileDetailSettingsAction extends SettingsAction { function title() @@ -47,9 +47,26 @@ class ProfileDetailSettingsAction extends AccountSettingsAction return true; } - function handle($args) + function handlePost() { - $this->showPage(); + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->show_form( + _m( + 'There was a problem with your session token. ' + . 'Try again, please.' + ) + ); + return; + } + + if ($this->arg('save')) { + $this->saveDetails(); + } else { + // TRANS: Message given submitting a form with an unknown action + $this->showForm(_m('Unexpected form submission.')); + } } function showContent() @@ -60,4 +77,115 @@ class ProfileDetailSettingsAction extends AccountSettingsAction $widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE); $widget->show(); } + + function saveDetails() + { + common_debug(var_export($_POST, true)); + + $user = common_current_user(); + + try { + $this->saveStandardProfileDetails($user); + } catch (Exception $e) { + $this->showForm($e->getMessage(), false); + return; + } + + $profile = $user->getProfile(); + + + $simple_fields = array('title', 'manager', 'tags', 'spouse'); + + $this->showForm(_('Details saved.'), true); + + } + + /** + * Save fields that should be stored in the main profile object + * + * XXX: There's a lot of dupe code here from ProfileSettingsAction. + * Do not want. + * + * @param User $user the current user + */ + function saveStandardProfileDetails($user) + { + $user->query('BEGIN'); + + $fullname = $this->trimmed('extprofile-fullname'); + $location = $this->trimmed('extprofile-location'); + $tagstring = $this->trimmed('extprofile-tags'); + $bio = $this->trimmed('extprofile-bio'); + + if ($tagstring) { + $tags = array_map( + 'common_canonical_tag', + preg_split('/[\s,]+/', $tagstring) + ); + } else { + $tags = array(); + } + + foreach ($tags as $tag) { + if (!common_valid_profile_tag($tag)) { + // TRANS: Validation error in form for profile settings. + // TRANS: %s is an invalid tag. + throw new Exception(sprintf(_m('Invalid tag: "%s".'), $tag)); + } + } + + $profile = $user->getProfile(); + + if ($fullname != $profile->fullname + || $location != $profile->location + || $tags != $profile->tags + || $bio != $profile->bio) { + + $orig = clone($profile); + + $profile->nickname = $user->nickname; + $profile->fullname = $fullname; + $profile->bio = $bio; + $profile->location = $location; + + $loc = Location::fromName($location); + + if (empty($loc)) { + $profile->lat = null; + $profile->lon = null; + $profile->location_id = null; + $profile->location_ns = null; + } else { + $profile->lat = $loc->lat; + $profile->lon = $loc->lon; + $profile->location_id = $loc->location_id; + $profile->location_ns = $loc->location_ns; + } + + $profile->profileurl = common_profile_url($user->nickname); + + $result = $profile->update($orig); + + if ($result === false) { + common_log_db_error($profile, 'UPDATE', __FILE__); + // TRANS: Server error thrown when user profile settings could not be saved. + $this->serverError(_('Could not save profile.')); + return; + } + + // Set the user tags + $result = $user->setSelfTags($tags); + + if (!$result) { + // TRANS: Server error thrown when user profile settings tags could not be saved. + $this->serverError(_('Could not save tags.')); + return; + } + + $user->query('COMMIT'); + Event::handle('EndProfileSaveForm', array($this)); + common_broadcast_profile($profile); + } + } + } From 65f9b5d954f35e9a89edaad9dfdb48f61bc9c2c5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 9 Mar 2011 11:27:29 -0800 Subject: [PATCH 07/69] Attempt to save field (doesn't work right yet) --- .../profiledetailsettingsaction.php | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index d1153cc075..ce0828c3e1 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -93,13 +93,50 @@ class ProfileDetailSettingsAction extends SettingsAction $profile = $user->getProfile(); + $simpleFieldNames = array('title'); - $simple_fields = array('title', 'manager', 'tags', 'spouse'); + foreach ($simpleFieldNames as $name) { + $value = $this->trimmed('extprofile-' . $name); + $this->saveSimpleField($user, $name, $value); + } $this->showForm(_('Details saved.'), true); } + function saveSimpleField($user, $name, $value) + { + $profile = $user->getProfile(); + + $detail = new Profile_detail(); + + $detail->profile_id = $profile->id; + $detail->field = $name; + $detail->field_index = 1; + + $result = $detail->find(true); + + + if (empty($result)) { + $detail->value = $value; + + $detail->created = common_sql_now(); + + common_debug("XXXXXXXXXXXXXXX not found"); + //common_debug('bbbbbbbbbbbb ' . var_export($detail, true)); + + $result = $detail->insert(); + } else { + common_debug('zzzzz FOUND'); + $orig = clone($detail); + $detail->value = $value; + $result = $detail->update($orig); + } + + $detail->free(); + + } + /** * Save fields that should be stored in the main profile object * @@ -136,9 +173,12 @@ class ProfileDetailSettingsAction extends SettingsAction $profile = $user->getProfile(); + $oldTags = $user->getSelfTags(); + $newTags = array_diff($tags, $oldTags); + if ($fullname != $profile->fullname || $location != $profile->location - || $tags != $profile->tags + || !empty($newTags) || $bio != $profile->bio) { $orig = clone($profile); From 0429a52c6e8bf135b03a86398c1aee95ddebea88 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 9 Mar 2011 18:00:24 -0800 Subject: [PATCH 08/69] Update to use new Managed_dataobject --- .../ExtendedProfile/ExtendedProfilePlugin.php | 2 - plugins/ExtendedProfile/Profile_detail.php | 193 ++++++------------ plugins/ExtendedProfile/extendedprofile.php | 4 +- .../profiledetailsettingsaction.php | 17 +- 4 files changed, 80 insertions(+), 136 deletions(-) diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php index 3f541c0008..933b43cad7 100644 --- a/plugins/ExtendedProfile/ExtendedProfilePlugin.php +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -95,8 +95,6 @@ class ExtendedProfilePlugin extends Plugin $schema = Schema::get(); $schema->ensureTable('profile_detail', Profile_detail::schemaDef()); - // @hack until key definition support is merged - Profile_detail::fixIndexes($schema); return true; } diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index 4b193e54ee..ebbeb86054 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -23,7 +23,7 @@ if (!defined('STATUSNET')) { /** * DataObject class to store extended profile fields. Allows for storing - * multiple values per a "field" (field property is not unique). + * multiple values per a "field_name" (field_name property is not unique). * * Example: * @@ -36,166 +36,105 @@ if (!defined('STATUSNET')) { * Profile_detail objects, each named 'phone_number' like this: * * $phone1 = new Profile_detail(); - * $phone1->field = 'phone_number'; + * $phone1->field_name = 'phone_number'; * $phone1->rel = 'home'; - * $phone1->field_index = 1; * $phone1->value = '510-384-1992'; + * $phone1->value_index = 1; * * $phone1 = new Profile_detail(); - * $phone1->field = 'phone_number'; + * $phone1->field_name = 'phone_number'; * $phone1->rel = 'mobile'; - * $phone1->field_index = 2; * $phone1->value = '510-719-1139'; + * $phone1->value_index = 2; * * $phone1 = new Profile_detail(); - * $phone1->field = 'phone_number'; + * $phone1->field_name = 'phone_number'; * $phone1->rel = 'work'; - * $phone1->field_index = 3; - * $phone1->value = '415-231-1121'; + * $phone1->field_value = '415-231-1121'; + * $phone1->value_index = 3; * */ -class Profile_detail extends Memcached_DataObject +class Profile_detail extends Managed_DataObject { public $__table = 'profile_detail'; public $id; - - public $profile_id; + public $profile_id; // profile this is for public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM - public $field; - + public $field_name; // name public $value; // primary text value - public $field_index; // relative ordering of multiple values in the same field - + public $value_index; // relative ordering of multiple values in the same field public $ref_profile; // for people types, allows pointing to a known profile in the system - public $created; public $modified; - public /*static*/ function staticGet($k, $v=null) + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup + * @param mixed $v Value to lookup + * + * @return User_greeting_count object found, or null for no hits + * + */ + + function staticGet($k, $v=null) { - return parent::staticGet(__CLASS__, $k, $v); + return Memcached_DataObject::staticGet('Profile_detail', $k, $v); } /** - * return table definition for DB_DataObject + * Get an instance by compound key * - * DB_DataObject needs to know something about the table to manipulate - * instances. This method provides all the DB_DataObject needs to know. + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return Bookmark object found, or null for no hits * - * @return array array of column definitions */ - function table() + function pkeyGet($kv) { - return array('id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - - 'profile_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'field' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL, - 'field_index' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - - 'value' => DB_DATAOBJECT_STR, - 'rel' => DB_DATAOBJECT_STR, - 'ref_profile' => DB_DATAOBJECT_INT, - - 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL, - 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL); + return Memcached_DataObject::pkeyGet('Profile_detail', $kv); } - /** - * Database schema setup - * - * @see Schema - * @see ColumnDef - * - * @return boolean hook value; true means continue processing, false means stop. - */ - static function schemaDef() { - return array(new ColumnDef('id', 'integer', - null, false, 'PRI'), - - // @fixme need a unique index on these three - new ColumnDef('profile_id', 'integer', - null, false), - new ColumnDef('field', 'varchar', - 16, false), - new ColumnDef('field_index', 'integer', - null, false), - - new ColumnDef('value', 'text', - null, true), - new ColumnDef('rel', 'varchar', - 16, true), - new ColumnDef('ref_profile', 'integer', - null, true), - - new ColumnDef('created', 'datetime', - null, false), - new ColumnDef('modified', 'datetime', - null, false)); - } - - /** - * Temporary hack to set up the compound index, since we can't do - * it yet through regular Schema interface. (Coming for 1.0...) - * - * @param Schema $schema - * @return void - */ - static function fixIndexes($schema) - { - try { - // @fixme this won't be a unique index... SIGH - $schema->createIndex('profile_detail', array('profile_id', 'field', 'field_index')); - } catch (Exception $e) { - common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage()); - } - } - - /** - * Return key definitions for DB_DataObject - * - * DB_DataObject needs to know about keys that the table has; this function - * defines them. - * - * @return array key definitions - */ - - function keys() - { - return array_keys($this->keyTypes()); - } - - /** - * Return key definitions for Memcached_DataObject - * - * Our caching system uses the same key definitions, but uses a different - * method to get them. - * - * @return array key definitions - */ - - function keyTypes() - { - // @fixme keys - // need a sane key for reverse lookup too - return array('id' => 'K'); - } - - /** - * Get the sequence key - * - * Returns the first serial column defined in the table, if any. - * - * @access private - * @return array (column,use_native,sequence_name) - */ - - function sequenceKey() - { - return array('id', true); + return array( + 'description' + => 'Additional profile details for the ExtendedProfile plugin', + 'fields' => array( + 'id' => array('type' => 'serial', 'not null' => true), + 'profile_id' => array('type' => 'int', 'not null' => true), + 'field_name' => array( + 'type' => 'varchar', + 'length' => 16, + 'not null' => true + ), + 'value_index' => array('type' => 'int'), + 'field_value' => array('type' => 'text'), + 'rel' => array('type' => 'varchar', 'length' => 16), + 'rel_profile' => array('type' => 'int'), + 'created' => array( + 'type' => 'datetime', + 'not null' => true + ), + 'modified' => array( + 'type' => 'timestamp', + 'not null' => true + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'profile_detial_profile_id_field_name_value_index' + => array('profile_id', 'field_name', 'value_index'), + ) + ); } } diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 61a66b4b43..5731c808ae 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -53,7 +53,7 @@ class ExtendedProfile $fields = array(); while ($detail->fetch()) { - $fields[$detail->field][] = clone($detail); + $fields[$detail->field_name][] = clone($detail); } return $fields; @@ -86,7 +86,7 @@ class ExtendedProfile if (in_array(strtolower($name), $profileFields)) { return $this->profile->$name; } else if (in_array($name, $this->fields)) { - return $this->fields[$name]->value; + return $this->fields[$name]->field_value; } else { return null; } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index ce0828c3e1..1f2be5a060 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -111,26 +111,33 @@ class ProfileDetailSettingsAction extends SettingsAction $detail = new Profile_detail(); $detail->profile_id = $profile->id; - $detail->field = $name; - $detail->field_index = 1; + $detail->field_name = $name; $result = $detail->find(true); if (empty($result)) { - $detail->value = $value; + + $detail->field_value = $value; $detail->created = common_sql_now(); common_debug("XXXXXXXXXXXXXXX not found"); - //common_debug('bbbbbbbbbbbb ' . var_export($detail, true)); $result = $detail->insert(); + + if (empty($result)) { + common_log_db_error($detail, 'INSERT', __FILE__); + $this->serverError(_m('Could not save profile details.')); + } + } else { + common_debug('zzzzz FOUND'); $orig = clone($detail); - $detail->value = $value; + $detail->field_value = $value; $result = $detail->update($orig); + } $detail->free(); From 3d61d003bc03dd18aa46dc9e96ecb9b0ed9034c7 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 9 Mar 2011 18:16:02 -0800 Subject: [PATCH 09/69] Fix property declaration --- plugins/ExtendedProfile/Profile_detail.php | 8 ++++---- plugins/ExtendedProfile/extendedprofile.php | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index ebbeb86054..f9f4d00098 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -38,13 +38,13 @@ if (!defined('STATUSNET')) { * $phone1 = new Profile_detail(); * $phone1->field_name = 'phone_number'; * $phone1->rel = 'home'; - * $phone1->value = '510-384-1992'; + * $phone1->field_value = '510-384-1992'; * $phone1->value_index = 1; * * $phone1 = new Profile_detail(); * $phone1->field_name = 'phone_number'; * $phone1->rel = 'mobile'; - * $phone1->value = '510-719-1139'; + * $phone1->field_value = '510-719-1139'; * $phone1->value_index = 2; * * $phone1 = new Profile_detail(); @@ -62,7 +62,7 @@ class Profile_detail extends Managed_DataObject public $profile_id; // profile this is for public $rel; // detail for some field types; eg "home", "mobile", "work" for phones or "aim", "irc", "xmpp" for IM public $field_name; // name - public $value; // primary text value + public $field_value; // primary text value public $value_index; // relative ordering of multiple values in the same field public $ref_profile; // for people types, allows pointing to a known profile in the system public $created; @@ -131,7 +131,7 @@ class Profile_detail extends Managed_DataObject ), 'primary key' => array('id'), 'unique keys' => array( - 'profile_detial_profile_id_field_name_value_index' + 'profile_detail_profile_id_field_name_value_index' => array('profile_id', 'field_name', 'value_index'), ) ); diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 5731c808ae..43f5b55b9c 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -37,6 +37,7 @@ class ExtendedProfile $this->user = $profile->getUser(); $this->sections = $this->getSections(); $this->fields = $this->loadFields(); + common_debug(var_export($this->fields, true)); } /** From adcda00e7668532b1261ba9808834ba45421c01a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 9 Mar 2011 19:27:21 -0800 Subject: [PATCH 10/69] * Remove evil transaction * Fix text value retrieval method --- plugins/ExtendedProfile/extendedprofile.php | 9 ++++++--- .../ExtendedProfile/profiledetailsettingsaction.php | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 43f5b55b9c..5ad6db114f 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -26,6 +26,8 @@ if (!defined('STATUSNET')) { */ class ExtendedProfile { + protected $fields; + /** * Constructor * @@ -82,12 +84,13 @@ class ExtendedProfile */ function getTextValue($name) { + $key = strtolower($name); $profileFields = array('fullname', 'location', 'bio'); - if (in_array(strtolower($name), $profileFields)) { + if (in_array($key, $profileFields)) { return $this->profile->$name; - } else if (in_array($name, $this->fields)) { - return $this->fields[$name]->field_value; + } else if (array_key_exists($key, $this->fields)) { + return $this->fields[$key][0]->field_value; } else { return null; } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 1f2be5a060..7c68216972 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -74,7 +74,11 @@ class ProfileDetailSettingsAction extends SettingsAction $cur = common_current_user(); $profile = $cur->getProfile(); - $widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE); + $widget = new ExtendedProfileWidget( + $this, + $profile, + ExtendedProfileWidget::EDITABLE + ); $widget->show(); } @@ -154,8 +158,6 @@ class ProfileDetailSettingsAction extends SettingsAction */ function saveStandardProfileDetails($user) { - $user->query('BEGIN'); - $fullname = $this->trimmed('extprofile-fullname'); $location = $this->trimmed('extprofile-location'); $tagstring = $this->trimmed('extprofile-tags'); @@ -187,7 +189,7 @@ class ProfileDetailSettingsAction extends SettingsAction || $location != $profile->location || !empty($newTags) || $bio != $profile->bio) { - + $orig = clone($profile); $profile->nickname = $user->nickname; @@ -229,7 +231,6 @@ class ProfileDetailSettingsAction extends SettingsAction return; } - $user->query('COMMIT'); Event::handle('EndProfileSaveForm', array($this)); common_broadcast_profile($profile); } From 5203fa7151c099e0ba5a9ef80565ed681a3a200a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 9 Mar 2011 19:31:37 -0800 Subject: [PATCH 11/69] Make all simple fields save --- plugins/ExtendedProfile/profiledetailsettingsaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 7c68216972..0f84dc0582 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -97,7 +97,7 @@ class ProfileDetailSettingsAction extends SettingsAction $profile = $user->getProfile(); - $simpleFieldNames = array('title'); + $simpleFieldNames = array('title', 'spouse', 'kids'); foreach ($simpleFieldNames as $name) { $value = $this->trimmed('extprofile-' . $name); From c456e998c7de9b70171db4524dc7ac90e908b7ad Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 10 Mar 2011 14:14:21 -0800 Subject: [PATCH 12/69] Make phone numbers save --- plugins/ExtendedProfile/extendedprofile.php | 24 ++-- .../ExtendedProfile/extendedprofilewidget.php | 48 ++++++++ .../profiledetailsettingsaction.php | 109 +++++++++++++++--- 3 files changed, 155 insertions(+), 26 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 5ad6db114f..908f0bc721 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -39,7 +39,7 @@ class ExtendedProfile $this->user = $profile->getUser(); $this->sections = $this->getSections(); $this->fields = $this->loadFields(); - common_debug(var_export($this->fields, true)); + //common_debug(var_export($this->fields, true)); } /** @@ -96,6 +96,21 @@ class ExtendedProfile } } + + function getPhones() + { + return array( + 'label' => _m('Phone'), + 'type' => 'phone', + 'multi' => true, + 'index' => 8123, + 'rel' => 'home', + 'value' => '510-528-0079', + 'vcard' => 'tel' + + ); + } + /** * Return all the sections of the extended profile * @@ -140,12 +155,7 @@ class ExtendedProfile 'contact' => array( 'label' => _m('Contact'), 'fields' => array( - 'phone' => array( - 'label' => _m('Phone'), - 'type' => 'phone', - 'multi' => true, - 'vcard' => 'tel', - ), + 'phone' => $this->getPhones(), 'im' => array( 'label' => _m('IM'), 'type' => 'im', diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index fbb3ff3c23..f5685e9981 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -150,10 +150,55 @@ class ExtendedProfileWidget extends Form case 'tags': $this->out->text($this->ext->getTags()); break; + case 'phone': + common_debug("GOT a PHONE!"); + $this->showPhone($field); + break; default: $this->out->text("TYPE: $type"); } + } + protected function showPhone($field) + { + $this->out->elementStart('div', array('class' => 'phone-display')); + $this->out->text($field['value']); + $this->out->text(' (' . $field['rel'] . ')'); + $this->out->elementEnd('div'); + } + + protected function showEditablePhone($name, $field) + { + $index = $field['index']; + $id = "extprofile-$name-$index"; + $rel = $id . '-rel'; + + $this->out->elementStart('div', array('class' => 'phone-edit')); + $this->out->input($id, null, $field['value']); + $this->out->dropdown( + $id . '-rel', + 'Type', + array( + 'office' => 'Office', + 'mobile' => 'Mobile', + 'home' => 'Home', + 'pager' => 'Pager', + 'other' => 'Other' + ), + null, + false, + $field['rel'] + ); + if ($field['multi']) { + $this->out->element( + 'a', + array( + 'name' => $name, + 'href' => 'javascript://'), + '+' + ); + } + $this->out->elementEnd('div'); } /** @@ -182,6 +227,9 @@ class ExtendedProfileWidget extends Form case 'tags': $out->input($id, null, $this->ext->getTags()); break; + case 'phone': + $this->showEditablePhone($name, $field); + break; default: $out->input($id, null, "TYPE: $type"); } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 0f84dc0582..c18732c058 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -101,51 +101,122 @@ class ProfileDetailSettingsAction extends SettingsAction foreach ($simpleFieldNames as $name) { $value = $this->trimmed('extprofile-' . $name); - $this->saveSimpleField($user, $name, $value); + $this->saveField($user, $name, $value); } + $this->savePhoneNumbers($user); + $this->showForm(_('Details saved.'), true); } - function saveSimpleField($user, $name, $value) + function savePhoneNumbers($user) { + $phones = $this->findPhoneNumbers(); + + foreach ($phones as $phone) { + $this->saveField( + $user, + 'phone', + $phone['value'], + $phone['rel'], + $phone['index'] + ); + } + } + + function findPhoneNumbers() { + $phones = array(); + $phoneParams = $this->findMultiParams('phone'); + ksort($phoneParams); // this sorts them into pairs + $phones = $this->arraySplit($phoneParams, sizeof($phoneParams) / 2); + + $phoneTuples = array(); + + foreach($phones as $phone) { + $firstkey = array_shift(array_keys($phone)); + $index = substr($firstkey, strrpos($firstkey, '-') + 1); + list($number, $rel) = array_values($phone); + + $phoneTuples[] = array( + 'value' => $number, + 'index' => $index, + 'rel' => $rel + ); + + return $phoneTuples; + } + + return $phones; + } + + function arraySplit($array, $pieces) + { + if ($pieces < 2) { + return array($array); + } + + $newCount = ceil(count($array) / $pieces); + $a = array_slice($array, 0, $newCount); + $b = array_split(array_slice($array, $newCount), $pieces - 1); + + return array_merge(array($a), $b); + } + + function findMultiParams($type) { + $formVals = array(); + $target = $type; + foreach ($_POST as $key => $val) { + if (strrpos('extprofile-' . $key, $target) !== false) { + $formVals[$key] = $val; + } + } + return $formVals; + } + + /** + * Save an extended profile field as a Profile_detail + * + * @param User $user the current user + * @param string $name field name + * @param string $value field value + * @param string $rel field rel (type) + * @param int $index index (fields can have multiple values) + */ + function saveField($user, $name, $value, $rel = null, $index = null) { $profile = $user->getProfile(); - - $detail = new Profile_detail(); + $detail = new Profile_detail(); $detail->profile_id = $profile->id; $detail->field_name = $name; + $detail->value_index = $index; $result = $detail->find(true); - if (empty($result)) { - + $detial->value_index = $index; + $detail->rel = $rel; $detail->field_value = $value; - - $detail->created = common_sql_now(); - - common_debug("XXXXXXXXXXXXXXX not found"); - + $detail->created = common_sql_now(); $result = $detail->insert(); - if (empty($result)) { common_log_db_error($detail, 'INSERT', __FILE__); $this->serverError(_m('Could not save profile details.')); } - } else { - - common_debug('zzzzz FOUND'); $orig = clone($detail); - $detail->field_value = $value; - $result = $detail->update($orig); + $detail->field_value = $value; + $detail->rel = $rel; + + $result = $detail->update($orig); + if (empty($result)) { + common_log_db_error($detail, 'UPDATE', __FILE__); + $this->serverError(_m('Could not save profile details.')); + } } $detail->free(); - } /** @@ -189,7 +260,7 @@ class ProfileDetailSettingsAction extends SettingsAction || $location != $profile->location || !empty($newTags) || $bio != $profile->bio) { - + $orig = clone($profile); $profile->nickname = $user->nickname; From 8efd2cf04dfb3343b88cb51f5740bb1688ba3c05 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 10 Mar 2011 16:57:41 -0800 Subject: [PATCH 13/69] Make phone number save and display from DB --- plugins/ExtendedProfile/extendedprofile.php | 39 ++++++++++++++----- .../ExtendedProfile/extendedprofilewidget.php | 18 +++++++-- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 908f0bc721..2a10759d91 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -37,8 +37,10 @@ class ExtendedProfile { $this->profile = $profile; $this->user = $profile->getUser(); - $this->sections = $this->getSections(); $this->fields = $this->loadFields(); + $this->sections = $this->getSections(); + //common_debug(var_export($this->sections, true)); + //common_debug(var_export($this->fields, true)); } @@ -96,19 +98,36 @@ class ExtendedProfile } } - function getPhones() { - return array( + $phones = $this->fields['phone']; + $pArrays = array(); + + if (empty($phones)) { + $pArrays[] = array( 'label' => _m('Phone'), 'type' => 'phone', - 'multi' => true, - 'index' => 8123, - 'rel' => 'home', - 'value' => '510-528-0079', - 'vcard' => 'tel' - - ); + 'vcard' => 'tel', + 'multi' => true + ); + } else { + for ($i = 0; $i < sizeof($phones); $i++) { + $pa = array( + 'label' => _m('Phone'), + 'type' => 'phone', + 'index' => $phones[$i]->value_index, + 'rel' => $phones[$i]->rel, + 'value' => $phones[$i]->field_value, + 'vcard' => 'tel' + ); + // Last phone record should allow adding more + if ($i == sizeof($phones) - 1) { + $pa['multi'] = true; + } + $pArrays[] = $pa; + } + } + return $pArrays; } /** diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index f5685e9981..d31a34b1e2 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -101,8 +101,13 @@ class ExtendedProfileWidget extends Form { $this->out->element('h3', null, $section['label']); $this->out->elementStart('table', array('class' => 'extended-profile')); + foreach ($section['fields'] as $fieldName => $field) { - $this->showExtendedProfileField($fieldName, $field); + if ($fieldName == 'phone') { + $this->showPhones($fieldName, $field); + } else { + $this->showExtendedProfileField($fieldName, $field); + } } $this->out->elementEnd('table'); } @@ -151,7 +156,6 @@ class ExtendedProfileWidget extends Form $this->out->text($this->ext->getTags()); break; case 'phone': - common_debug("GOT a PHONE!"); $this->showPhone($field); break; default: @@ -159,11 +163,19 @@ class ExtendedProfileWidget extends Form } } + protected function showPhones($name, $field) { + foreach ($field as $phone) { + $this->showExtendedProfileField($name, $phone); + } + } + protected function showPhone($field) { $this->out->elementStart('div', array('class' => 'phone-display')); $this->out->text($field['value']); - $this->out->text(' (' . $field['rel'] . ')'); + if (!empty($field['rel'])) { + $this->out->text(' (' . $field['rel'] . ')'); + } $this->out->elementEnd('div'); } From 302f0236bdee73afc9183f724432df2f43fb480a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 10 Mar 2011 17:13:34 -0800 Subject: [PATCH 14/69] Make correct nav menus show --- plugins/ExtendedProfile/profiledetailaction.php | 13 +------------ .../ExtendedProfile/profiledetailsettingsaction.php | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php index d2eb06775c..beac7d6321 100644 --- a/plugins/ExtendedProfile/profiledetailaction.php +++ b/plugins/ExtendedProfile/profiledetailaction.php @@ -21,7 +21,7 @@ if (!defined('STATUSNET')) { exit(1); } -class ProfileDetailAction extends ProfileAction +class ProfileDetailAction extends ShowstreamAction { function isReadOnly($args) @@ -34,23 +34,12 @@ class ProfileDetailAction extends ProfileAction return $this->profile->getFancyName(); } - function showLocalNav() - { - $nav = new PersonalGroupNav($this); - $nav->show(); - } - function showStylesheets() { parent::showStylesheets(); $this->cssLink('plugins/ExtendedProfile/profiledetail.css'); return true; } - function handle($args) - { - $this->showPage(); - } - function showContent() { $cur = common_current_user(); diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index c18732c058..ec2c4935a2 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -21,7 +21,7 @@ if (!defined('STATUSNET')) { exit(1); } -class ProfileDetailSettingsAction extends SettingsAction +class ProfileDetailSettingsAction extends ProfileSettingsAction { function title() From deb40602d2bc61772eb0b2631f3f4d6c6b592aac Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sun, 13 Mar 2011 16:32:13 -0700 Subject: [PATCH 15/69] Extended profile - more work on getting complex fields to save --- .../{ => css}/profiledetail.css | 0 plugins/ExtendedProfile/extendedprofile.php | 3 +- .../ExtendedProfile/extendedprofilewidget.php | 19 ++++- plugins/ExtendedProfile/js/profiledetail.js | 83 +++++++++++++++++++ .../profiledetailsettingsaction.php | 16 ++-- 5 files changed, 111 insertions(+), 10 deletions(-) rename plugins/ExtendedProfile/{ => css}/profiledetail.css (100%) create mode 100644 plugins/ExtendedProfile/js/profiledetail.js diff --git a/plugins/ExtendedProfile/profiledetail.css b/plugins/ExtendedProfile/css/profiledetail.css similarity index 100% rename from plugins/ExtendedProfile/profiledetail.css rename to plugins/ExtendedProfile/css/profiledetail.css diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 2a10759d91..94a5ef4826 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -106,6 +106,7 @@ class ExtendedProfile if (empty($phones)) { $pArrays[] = array( 'label' => _m('Phone'), + 'index' => 0, 'type' => 'phone', 'vcard' => 'tel', 'multi' => true @@ -115,7 +116,7 @@ class ExtendedProfile $pa = array( 'label' => _m('Phone'), 'type' => 'phone', - 'index' => $phones[$i]->value_index, + 'index' => intva($phones[$i]->value_index), 'rel' => $phones[$i]->rel, 'value' => $phones[$i]->field_value, 'vcard' => 'tel' diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index d31a34b1e2..7eb195e369 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -184,8 +184,12 @@ class ExtendedProfileWidget extends Form $index = $field['index']; $id = "extprofile-$name-$index"; $rel = $id . '-rel'; - - $this->out->elementStart('div', array('class' => 'phone-edit')); + $this->out->elementStart( + 'div', array( + 'id' => $id . '-edit', + 'class' => 'phone-edit' + ) + ); $this->out->input($id, null, $field['value']); $this->out->dropdown( $id . '-rel', @@ -205,10 +209,19 @@ class ExtendedProfileWidget extends Form $this->out->element( 'a', array( - 'name' => $name, + 'class' => 'add_row', 'href' => 'javascript://'), '+' ); + $this->out->element( + 'a', + array( + 'class' => 'remove_row', + 'href' => 'javascript://', + 'style' => 'display: none; ' + ), + '-' + ); } $this->out->elementEnd('div'); } diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js new file mode 100644 index 0000000000..a021a32645 --- /dev/null +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -0,0 +1,83 @@ +var removeRow = function() { + var cnt = rowCount(this); + var table = $(this).closest('table'); + console.log("row count = " + cnt); + if (cnt > 1) { + var target = $(this).closest('tr'); + target.remove(); + reorder(table); + } +}; + +var rowCount = function(row) { + var top = $(row).closest('table'); + var trs = $(top).find('tr'); + return trs.length - 1; // exclude th section header row +}; + +var reorder = function(table) { + var trs = $(table).find('tr').has('td'); + + $(trs).find('a').hide(); + + $(trs).each(function(i, tr) { + console.log("ROW " + i); + $(tr).find('a.remove_row').show(); + replaceIndex(rowIndex(tr), i); + }); + + $(trs).last().find('a.add_row').show(); + + if (trs.length == 1) { + $(trs).find('a.remove_row').hide(); + } + +}; + +var rowIndex = function(elem) { + var idStr = $(elem).find('div').attr('id'); + var id = idStr.match(/\d+/); + console.log("id = " + id); +}; + +var replaceIndex = function(elem, oldIndex, newIndex) { + $(elem).find('*').each(function() { + $.each(this.attributes, function(i, attrib) { + var regexp = /extprofile-.*-\d.*/; + var value = attrib.value; + var match = value.match(regexp); + if (match != null) { + attrib.value = value.replace("-" + oldIndex, "-" + newIndex); + console.log('match: oldIndex = ' + oldIndex + ' newIndex = ' + newIndex + ' name = ' + attrib.name + ' value = ' + attrib.value); + } + }); + }); +} + +var resetRow = function(elem) { + $(elem).find('input').attr('value', ''); + $(elem).find("select option[value='office']").attr("selected", true); +} + +var addRow = function() { + var divId = $(this).closest('div').attr('id'); + var index = divId.match(/\d+/); + console.log("Current row = " + index); + var tr = $(this).closest('tr'); + var newtr = $(tr).clone(); + var newIndex = parseInt(index) + 1; + replaceIndex(newtr, index, newIndex); + resetRow(newtr); + $(tr).after(newtr); + console.log("number of rows: " + rowCount(tr)); + reorder($(this).closest('table')); +}; + +$(document).ready( + +function() { + $('.add_row').live('click', addRow); + $('.remove_row').live('click', removeRow); +} + +); \ No newline at end of file diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index ec2c4935a2..ee450118c3 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -43,7 +43,13 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('plugins/ExtendedProfile/profiledetail.css'); + $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css'); + return true; + } + + function showScripts() { + parent::showScripts(); + $this->script('plugins/ExtendedProfile/js/profiledetail.js'); return true; } @@ -133,7 +139,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $phoneTuples = array(); foreach($phones as $phone) { - $firstkey = array_shift(array_keys($phone)); + $firstkey = current(array_keys($phone)); $index = substr($firstkey, strrpos($firstkey, '-') + 1); list($number, $rel) = array_values($phone); @@ -142,11 +148,9 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction 'index' => $index, 'rel' => $rel ); - - return $phoneTuples; } - return $phones; + return $phoneTuples; } function arraySplit($array, $pieces) @@ -157,7 +161,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $newCount = ceil(count($array) / $pieces); $a = array_slice($array, 0, $newCount); - $b = array_split(array_slice($array, $newCount), $pieces - 1); + $b = $this->arraySplit(array_slice($array, $newCount), $pieces - 1); return array_merge(array($a), $b); } From 04c8bf2743db51cd724449b4cbec1aaef531597c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 01:49:46 -0700 Subject: [PATCH 16/69] Extended profile - finished basic pattern for adding/removing/saving multiple complex fields --- plugins/ExtendedProfile/extendedprofile.php | 12 ++- .../ExtendedProfile/extendedprofilewidget.php | 53 ++++++------ plugins/ExtendedProfile/js/profiledetail.js | 80 +++++++++++-------- .../profiledetailsettingsaction.php | 36 ++++++--- 4 files changed, 107 insertions(+), 74 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 94a5ef4826..711fdcf1bb 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -100,7 +100,7 @@ class ExtendedProfile function getPhones() { - $phones = $this->fields['phone']; + $phones = (isset($this->fields['phone'])) ? $this->fields['phone'] : null; $pArrays = array(); if (empty($phones)) { @@ -109,22 +109,20 @@ class ExtendedProfile 'index' => 0, 'type' => 'phone', 'vcard' => 'tel', - 'multi' => true + 'rel' => 'office', + 'value' => null ); } else { for ($i = 0; $i < sizeof($phones); $i++) { $pa = array( 'label' => _m('Phone'), 'type' => 'phone', - 'index' => intva($phones[$i]->value_index), + 'index' => intval($phones[$i]->value_index), 'rel' => $phones[$i]->rel, 'value' => $phones[$i]->field_value, 'vcard' => 'tel' ); - // Last phone record should allow adding more - if ($i == sizeof($phones) - 1) { - $pa['multi'] = true; - } + $pArrays[] = $pa; } } diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 7eb195e369..d8c42df6a3 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -156,7 +156,7 @@ class ExtendedProfileWidget extends Form $this->out->text($this->ext->getTags()); break; case 'phone': - $this->showPhone($field); + $this->showPhone($name, $field); break; default: $this->out->text("TYPE: $type"); @@ -169,7 +169,7 @@ class ExtendedProfileWidget extends Form } } - protected function showPhone($field) + protected function showPhone($name, $field) { $this->out->elementStart('div', array('class' => 'phone-display')); $this->out->text($field['value']); @@ -181,7 +181,7 @@ class ExtendedProfileWidget extends Form protected function showEditablePhone($name, $field) { - $index = $field['index']; + $index = isset($field['index']) ? $field['index'] : 0; $id = "extprofile-$name-$index"; $rel = $id . '-rel'; $this->out->elementStart( @@ -190,7 +190,11 @@ class ExtendedProfileWidget extends Form 'class' => 'phone-edit' ) ); - $this->out->input($id, null, $field['value']); + $this->out->input( + $id, + null, + isset($field['value']) ? $field['value'] : null + ); $this->out->dropdown( $id . '-rel', 'Type', @@ -203,26 +207,29 @@ class ExtendedProfileWidget extends Form ), null, false, - $field['rel'] + isset($field['rel']) ? $field['rel'] : null ); - if ($field['multi']) { - $this->out->element( - 'a', - array( - 'class' => 'add_row', - 'href' => 'javascript://'), - '+' - ); - $this->out->element( - 'a', - array( - 'class' => 'remove_row', - 'href' => 'javascript://', - 'style' => 'display: none; ' - ), - '-' - ); - } + + $this->out->element( + 'a', + array( + 'class' => 'add_row', + 'href' => 'javascript://', + 'style' => 'display: none; ' + ), + '+' + ); + + $this->out->element( + 'a', + array( + 'class' => 'remove_row', + 'href' => 'javascript://', + 'style' => 'display: none; ' + ), + '-' + ); + $this->out->elementEnd('div'); } diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index a021a32645..7d7eceddc1 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -1,43 +1,35 @@ -var removeRow = function() { - var cnt = rowCount(this); - var table = $(this).closest('table'); - console.log("row count = " + cnt); - if (cnt > 1) { - var target = $(this).closest('tr'); - target.remove(); - reorder(table); - } -}; +var reorder = function(class) { + console.log("QQQ Enter reorder"); -var rowCount = function(row) { - var top = $(row).closest('table'); - var trs = $(top).find('tr'); - return trs.length - 1; // exclude th section header row -}; + var divs = $.find('div[class=' + class + ']'); + console.log('divs length = ' + divs.length); -var reorder = function(table) { - var trs = $(table).find('tr').has('td'); + $(divs).find('a').hide(); - $(trs).find('a').hide(); - - $(trs).each(function(i, tr) { + $(divs).each(function(i, div) { console.log("ROW " + i); - $(tr).find('a.remove_row').show(); - replaceIndex(rowIndex(tr), i); + $(div).find('a.remove_row').show(); + replaceIndex(rowIndex(div), i); }); - $(trs).last().find('a.add_row').show(); + $(divs).last().find('a.add_row').show(); - if (trs.length == 1) { - $(trs).find('a.remove_row').hide(); + if (divs.length == 1) { + $(divs).find('a.remove_row').hide(); } }; -var rowIndex = function(elem) { - var idStr = $(elem).find('div').attr('id'); - var id = idStr.match(/\d+/); +var rowIndex = function(div) { + var idstr = $(div).attr('id'); + var id = idstr.match(/\d+/); console.log("id = " + id); + return id; +}; + +var rowCount = function(class) { + var divs = $.find('div[class=' + class + ']'); + return divs.length; }; var replaceIndex = function(elem, oldIndex, newIndex) { @@ -46,7 +38,7 @@ var replaceIndex = function(elem, oldIndex, newIndex) { var regexp = /extprofile-.*-\d.*/; var value = attrib.value; var match = value.match(regexp); - if (match != null) { + if (match !== null) { attrib.value = value.replace("-" + oldIndex, "-" + newIndex); console.log('match: oldIndex = ' + oldIndex + ' newIndex = ' + newIndex + ' name = ' + attrib.name + ' value = ' + attrib.value); } @@ -60,22 +52,42 @@ var resetRow = function(elem) { } var addRow = function() { - var divId = $(this).closest('div').attr('id'); - var index = divId.match(/\d+/); - console.log("Current row = " + index); + var div = $(this).closest('div'); + var id = $(div).attr('id'); + var class = $(div).attr('class'); + var index = id.match(/\d+/); + console.log("Current row = " + index + ', class = ' + class); var tr = $(this).closest('tr'); var newtr = $(tr).clone(); var newIndex = parseInt(index) + 1; replaceIndex(newtr, index, newIndex); resetRow(newtr); $(tr).after(newtr); - console.log("number of rows: " + rowCount(tr)); - reorder($(this).closest('table')); + reorder(class); }; +var removeRow = function() { + var div = $(this).closest('div'); + var id = $(div).attr('id'); + var class = $(div).attr('class'); + + cnt = rowCount(class); + console.debug("removeRow - cnt = " + cnt); + if (cnt > 1) { + var target = $(this).closest('tr'); + target.remove(); + reorder(class); + } +}; + +var init = function() { + reorder('phone-edit'); +} + $(document).ready( function() { + init(); $('.add_row').live('click', addRow); $('.remove_row').live('click', removeRow); } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index ee450118c3..89b8d61cf9 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -107,7 +107,9 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction foreach ($simpleFieldNames as $name) { $value = $this->trimmed('extprofile-' . $name); - $this->saveField($user, $name, $value); + if (!empty($value)) { + $this->saveField($user, $name, $value); + } } $this->savePhoneNumbers($user); @@ -118,15 +120,19 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction function savePhoneNumbers($user) { $phones = $this->findPhoneNumbers(); - - foreach ($phones as $phone) { - $this->saveField( - $user, - 'phone', - $phone['value'], - $phone['rel'], - $phone['index'] - ); + $this->removeAll($user, 'phone'); + $i = 0; + foreach($phones as $phone) { + if (!empty($phone['value'])) { + ++$i; + $this->saveField( + $user, + 'phone', + $phone['value'], + $phone['rel'], + $i + ); + } } } @@ -223,6 +229,16 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $detail->free(); } + function removeAll($user, $name) + { + $profile = $user->getProfile(); + $detail = new Profile_detail(); + $detail->profile_id = $profile->id; + $detail->field_name = $name; + $detail->delete(); + $detail->free(); + } + /** * Save fields that should be stored in the main profile object * From eaef01233a9b0d1bf3f21ae4a0065d078238973f Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Mon, 14 Mar 2011 18:01:22 +0100 Subject: [PATCH 17/69] Fix incorrect parameter documentation. Spotted by Nikerabbit. --- actions/apigrouplist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/apigrouplist.php b/actions/apigrouplist.php index 1f6d44363f..f80fbce932 100644 --- a/actions/apigrouplist.php +++ b/actions/apigrouplist.php @@ -100,7 +100,7 @@ class ApiGroupListAction extends ApiBareAuthAction ); $subtitle = sprintf( - // TRANS: Used as subtitle in check for group membership. %1$s is a user name, %2$s is the site name. + // TRANS: Used as subtitle in check for group membership. %1$s is the site name, %2$s is a user name. _('%1$s groups %2$s is a member of.'), $sitename, $this->user->nickname From ed2d224df5e52b2fe5ef5f076d147791e48c4dcf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Mar 2011 12:22:49 -0700 Subject: [PATCH 18/69] Check the site minifications etting for realtime plugin; debugging aid. --- plugins/Realtime/RealtimePlugin.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 246b1f9735..f617c39060 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -323,7 +323,12 @@ class RealtimePlugin extends Plugin function _getScripts() { - return array(Plugin::staticPath('Realtime', 'realtimeupdate.min.js')); + if (common_config('site', 'minify')) { + $js = 'realtimeupdate.min.js'; + } else { + $js = 'realtimeupdate.js'; + } + return array(Plugin::staticPath('Realtime', $js)); } /** From 4334bb71326d8b8bcc78c636942fa4bdec21bd60 Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Mon, 14 Mar 2011 15:30:51 -0400 Subject: [PATCH 19/69] Adding wrapper divs for equal height columns. --- lib/action.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/action.php b/lib/action.php index 233ac78567..fce59ba8a0 100644 --- a/lib/action.php +++ b/lib/action.php @@ -681,6 +681,9 @@ class Action extends HTMLOutputter // lawsuit function showCore() { $this->elementStart('div', array('id' => 'core')); + $this->elementStart('div', array('id' => 'aside_primary_wrapper')); + $this->elementStart('div', array('id' => 'content_wrapper')); + $this->elementStart('div', array('id' => 'site_nav_local_views_wrapper')); if (Event::handle('StartShowLocalNavBlock', array($this))) { $this->showLocalNavBlock(); Event::handle('EndShowLocalNavBlock', array($this)); @@ -694,6 +697,9 @@ class Action extends HTMLOutputter // lawsuit Event::handle('EndShowAside', array($this)); } $this->elementEnd('div'); + $this->elementEnd('div'); + $this->elementEnd('div'); + $this->elementEnd('div'); } /** From 640a033f8b7d17352f19a30990c106522b0ccd7d Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Mon, 14 Mar 2011 15:31:19 -0400 Subject: [PATCH 20/69] Style changes for equal column heights. --- theme/neo/css/display.css | 70 ++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/theme/neo/css/display.css b/theme/neo/css/display.css index 22556bb891..682b6dd6dc 100644 --- a/theme/neo/css/display.css +++ b/theme/neo/css/display.css @@ -137,21 +137,46 @@ address { #core { clear: both; margin: 0px; - width: 960px; + width: 958px; border-top: 5px solid #FB6104; -} - -#site_nav_local_views { - display: block; - float: left; - width: 138px; - margin-top: 0px; - padding: 10px; - padding-top: 22px; - background-color: #ececf2; border-left: 1px solid #d8dae6; border-right: 1px solid #d8dae6; - min-height: 800px; /* XXX set up equal column heights! */ +} + +#aside_primary_wrapper { + width: 100%; + float: left; + overflow: hidden; + position: relative; + background-color: #ececf2; +} + +#content_wrapper { + width: 100%; + float: left; + position: relative; + right: 239px; + background-color: #fff; + border-right: 1px solid #d8dae6; +} + +#site_nav_local_views_wrapper { + width: 100%; + float: left; + position: relative; + right: 561px; + background-color: #ececf2; + border-right: 1px solid #d8dae6; +} + +#site_nav_local_views { + width: 138px; + float: left; + overflow: hidden; + position: relative; + left: 800px; + margin-top: 0px; + padding: 22px 10px 40px 10px; } #site_nav_local_views H3 { @@ -196,8 +221,12 @@ address { #content { width: 520px; - margin-right: 0px; - padding: 20px; + float: left; + overflow: hidden; + position: relative; + left: 801px; + margin: 0px; + padding: 20px 20px 40px 20px; } /* Input forms */ @@ -357,13 +386,12 @@ address { #aside_primary { width: 218px; - padding: 10px; - padding-top: 22px; + float: left; + overflow: hidden; + position: relative; + left: 802px; + padding: 22px 10px 40px 10px; margin-top: 0px; - background-color: #ececf2; - border-left: 1px solid #d8dae6; - border-right: 1px solid #d8dae6; - min-height: 800px; /* XXX set up equal column heights! */ } #aside_primary .section { @@ -665,6 +693,8 @@ div.entry-content a.response:after { } #footer { + position: relative; + top: -6px; color: #000; margin-left: 0px; margin-right: 0px; From 1e36593a23bd8e50e50d115c28a7f8fa024fb768 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Mar 2011 12:32:39 -0700 Subject: [PATCH 21/69] Realtime work in progress: switch makeNoticeData to async -- next we'll load fresh copies from server, maintaining proper language and plugin enhancements. --- plugins/Realtime/realtimeupdate.js | 82 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index e615895cab..73516a8ed9 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -163,50 +163,51 @@ RealtimeUpdate = { return; } - var noticeItem = RealtimeUpdate.makeNoticeItem(data); - var noticeItemID = $(noticeItem).attr('id'); + RealtimeUpdate.makeNoticeItem(data, function(noticeItem) { + var noticeItemID = $(noticeItem).attr('id'); - var list = $("#notices_primary .notices:first") - var prepend = true; + var list = $("#notices_primary .notices:first") + var prepend = true; - var threaded = list.hasClass('threaded-notices'); - if (threaded && data.in_reply_to_status_id) { - // aho! - var parent = $('#notice-' + data.in_reply_to_status_id); - if (parent.length == 0) { - // @todo fetch the original, insert it, and finish the rest - } else { - // Check the parent notice to make sure it's not a reply itself. - // If so, use it's parent as the parent. - var parentList = parent.closest('.notices'); - if (parentList.hasClass('threaded-replies')) { - parent = parentList.closest('.notice'); + var threaded = list.hasClass('threaded-notices'); + if (threaded && data.in_reply_to_status_id) { + // aho! + var parent = $('#notice-' + data.in_reply_to_status_id); + if (parent.length == 0) { + // @todo fetch the original, insert it, and finish the rest + } else { + // Check the parent notice to make sure it's not a reply itself. + // If so, use it's parent as the parent. + var parentList = parent.closest('.notices'); + if (parentList.hasClass('threaded-replies')) { + parent = parentList.closest('.notice'); + } + list = parent.find('.threaded-replies'); + if (list.length == 0) { + list = $('
    '); + parent.append(list); + } + prepend = false; } - list = parent.find('.threaded-replies'); - if (list.length == 0) { - list = $('
      '); - parent.append(list); - } - prepend = false; } - } - var newNotice = $(noticeItem); - if (prepend) { - list.prepend(newNotice); - } else { - var placeholder = list.find('li.notice-reply-placeholder') - if (placeholder.length > 0) { - newNotice.insertBefore(placeholder) + var newNotice = $(noticeItem); + if (prepend) { + list.prepend(newNotice); } else { - newNotice.appendTo(list); - SN.U.NoticeInlineReplyPlaceholder(parent); + var placeholder = list.find('li.notice-reply-placeholder') + if (placeholder.length > 0) { + newNotice.insertBefore(placeholder) + } else { + newNotice.appendTo(list); + SN.U.NoticeInlineReplyPlaceholder(parent); + } } - } - newNotice.css({display:"none"}).fadeIn(1000); + newNotice.css({display:"none"}).fadeIn(1000); - SN.U.NoticeReplyTo($('#'+noticeItemID)); - SN.U.NoticeWithAttachment($('#'+noticeItemID)); + SN.U.NoticeReplyTo($('#'+noticeItemID)); + SN.U.NoticeWithAttachment($('#'+noticeItemID)); + }); }, /** @@ -263,10 +264,11 @@ RealtimeUpdate = { }, /** - * Builds a notice HTML block from JSON API-style data. + * Builds a notice HTML block from JSON API-style data; + * loads data from server, so runs async. * * @param {Object} data: extended JSON API-formatted notice - * @return {String} HTML fragment + * @param {function} callback: function(str) to receive HTML fragment * * @fixme this replicates core StatusNet code, making maintenance harder * @fixme sloppy HTML building (raw concat without escaping) @@ -275,7 +277,7 @@ RealtimeUpdate = { * * @access private */ - makeNoticeItem: function(data) + makeNoticeItem: function(data, callback) { if (data.hasOwnProperty('retweeted_status')) { original = data['retweeted_status']; @@ -342,7 +344,7 @@ RealtimeUpdate = { ni = ni+""; ni = ni+""; - return ni; + callback(ni); }, /** From 894bcd3bcddfec63544aa1f95a6e3b7fcb1a3f5d Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Mon, 14 Mar 2011 15:36:00 -0400 Subject: [PATCH 22/69] Whoops, need to reset the background color on the aside. --- theme/neo/css/display.css | 1 + 1 file changed, 1 insertion(+) diff --git a/theme/neo/css/display.css b/theme/neo/css/display.css index 682b6dd6dc..7cb5e11911 100644 --- a/theme/neo/css/display.css +++ b/theme/neo/css/display.css @@ -392,6 +392,7 @@ address { left: 802px; padding: 22px 10px 40px 10px; margin-top: 0px; + background: none; } #aside_primary .section { From f11b788b5af29d325f17ebb77c321309a41e5e70 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 12:53:49 -0700 Subject: [PATCH 23/69] Extended profile - add a date value for fields --- plugins/ExtendedProfile/Profile_detail.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index f9f4d00098..96869b0e63 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -64,6 +64,7 @@ class Profile_detail extends Managed_DataObject public $field_name; // name public $field_value; // primary text value public $value_index; // relative ordering of multiple values in the same field + public $date; // related date public $ref_profile; // for people types, allows pointing to a known profile in the system public $created; public $modified; @@ -118,6 +119,7 @@ class Profile_detail extends Managed_DataObject ), 'value_index' => array('type' => 'int'), 'field_value' => array('type' => 'text'), + 'date' => array('type' => 'datetime'), 'rel' => array('type' => 'varchar', 'length' => 16), 'rel_profile' => array('type' => 'int'), 'created' => array( From c7e7cc79da4255c42dbc8500fc5d6db25d1f90c3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Mar 2011 13:05:30 -0700 Subject: [PATCH 24/69] 'ajax' param on shownotice action so we can pull items in full html version for realtime --- actions/shownotice.php | 48 ++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index 3978f03ea9..8d8ab326df 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -188,22 +188,27 @@ class ShownoticeAction extends OwnerDesignAction { parent::handle($args); - if ($this->notice->is_local == Notice::REMOTE_OMB) { - if (!empty($this->notice->url)) { - $target = $this->notice->url; - } else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) { - // Old OMB posts saved the remote URL only into the URI field. - $target = $this->notice->uri; - } else { - // Shouldn't happen. - $target = false; - } - if ($target && $target != $this->selfUrl()) { - common_redirect($target, 301); - return false; + if ($this->boolean('ajax')) { + StatusNet::setApi(true); + $this->showAjax(); + } else { + if ($this->notice->is_local == Notice::REMOTE_OMB) { + if (!empty($this->notice->url)) { + $target = $this->notice->url; + } else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) { + // Old OMB posts saved the remote URL only into the URI field. + $target = $this->notice->uri; + } else { + // Shouldn't happen. + $target = false; + } + if ($target && $target != $this->selfUrl()) { + common_redirect($target, 301); + return false; + } } + $this->showPage(); } - $this->showPage(); } /** @@ -232,6 +237,21 @@ class ShownoticeAction extends OwnerDesignAction $this->elementEnd('ol'); } + function showAjax() + { + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + $this->element('title', null, _('Notice')); + $this->elementEnd('head'); + $this->elementStart('body'); + $nli = new NoticeListItem($this->notice, $this); + $nli->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } + /** * Don't show page notice * From 90d35885aeed68b11a80225ff255b6ac70a349b9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Mar 2011 13:29:35 -0700 Subject: [PATCH 25/69] Realtime plugin: fix i18n, thumbnails, location display, OStatus server display, micro-apps display. Switch from manual local formatting of notices (which lacks all the server-side goodness) to calling into the server-side with an AJAX variant of shownotice. --- plugins/Realtime/README | 7 +- plugins/Realtime/RealtimePlugin.php | 11 +-- plugins/Realtime/realtimeupdate.js | 98 ++++---------------------- plugins/Realtime/realtimeupdate.min.js | 2 +- 4 files changed, 22 insertions(+), 96 deletions(-) diff --git a/plugins/Realtime/README b/plugins/Realtime/README index 9b36d87f37..0c52427eb6 100644 --- a/plugins/Realtime/README +++ b/plugins/Realtime/README @@ -1,9 +1,12 @@ +As of StatusNet 1.0.x, actual formatting of the notices is done server-side, +loaded by AJAX after the real-time notification comes in. This has the drawback +that we may make extra HTTP requests and delay incoming notices a little, but +means that formatting and internationalization is consistent. + == TODO == -* i18n * Update mark behaviour (on notice send) * Pause, Send a notice ~ should not update counter * Pause ~ retain up to 50-100 most recent notices -* Add geo data * Make it work for Conversation page (perhaps a little tricky) * IE is updating the counter in document title all the time (Not sure if this is still an issue) diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index f617c39060..108a6c3b60 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -45,9 +45,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { */ class RealtimePlugin extends Plugin { - protected $replyurl = null; - protected $favorurl = null; - protected $deleteurl = null; + protected $showurl = null; /** * When it's time to initialize the plugin, calculate and @@ -56,11 +54,8 @@ class RealtimePlugin extends Plugin function onInitializePlugin() { - $this->replyurl = common_local_url('newnotice'); - $this->favorurl = common_local_url('favor'); - $this->repeaturl = common_local_url('repeat'); // FIXME: need to find a better way to pass this pattern in - $this->deleteurl = common_local_url('deletenotice', + $this->showurl = common_local_url('shownotice', array('notice' => '0000000000')); return true; } @@ -359,7 +354,7 @@ class RealtimePlugin extends Plugin function _updateInitialize($timeline, $user_id) { - return "RealtimeUpdate.init($user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->repeaturl\", \"$this->deleteurl\"); "; + return "RealtimeUpdate.init($user_id, \"$this->showurl\"); "; } function _connect() diff --git a/plugins/Realtime/realtimeupdate.js b/plugins/Realtime/realtimeupdate.js index 73516a8ed9..59e3fe72d7 100644 --- a/plugins/Realtime/realtimeupdate.js +++ b/plugins/Realtime/realtimeupdate.js @@ -44,10 +44,7 @@ */ RealtimeUpdate = { _userid: 0, - _replyurl: '', - _favorurl: '', - _repeaturl: '', - _deleteurl: '', + _showurl: '', _updatecounter: 0, _maxnotices: 50, _windowhasfocus: true, @@ -66,21 +63,15 @@ RealtimeUpdate = { * feed data into the RealtimeUpdate object! * * @param {int} userid: local profile ID of the currently logged-in user - * @param {String} replyurl: URL for newnotice action, used when generating reply buttons - * @param {String} favorurl: URL for favor action, used when generating fave buttons - * @param {String} repeaturl: URL for repeat action, used when generating repeat buttons - * @param {String} deleteurl: URL template for deletenotice action, used when generating delete buttons. + * @param {String} showurl: URL for shownotice action, used when fetching formatting notices. * This URL contains a stub value of 0000000000 which will be replaced with the notice ID. * * @access public */ - init: function(userid, replyurl, favorurl, repeaturl, deleteurl) + init: function(userid, showurl) { RealtimeUpdate._userid = userid; - RealtimeUpdate._replyurl = replyurl; - RealtimeUpdate._favorurl = favorurl; - RealtimeUpdate._repeaturl = repeaturl; - RealtimeUpdate._deleteurl = deleteurl; + RealtimeUpdate._showurl = showurl; RealtimeUpdate._documenttitle = document.title; @@ -268,83 +259,20 @@ RealtimeUpdate = { * loads data from server, so runs async. * * @param {Object} data: extended JSON API-formatted notice - * @param {function} callback: function(str) to receive HTML fragment - * - * @fixme this replicates core StatusNet code, making maintenance harder - * @fixme sloppy HTML building (raw concat without escaping) - * @fixme no i18n support - * @fixme local variables pollute global namespace + * @param {function} callback: function(DOMNode) to receive new code * * @access private */ makeNoticeItem: function(data, callback) { - if (data.hasOwnProperty('retweeted_status')) { - original = data['retweeted_status']; - repeat = data; - data = original; - unique = repeat['id']; - responsible = repeat['user']; - } else { - original = null; - repeat = null; - unique = data['id']; - responsible = data['user']; - } - - user = data['user']; - html = data['html'].replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/&/g,'&'); - source = data['source'].replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/&/g,'&'); - - ni = "
    • "+ - "
      "+ - ""+ - ""+ - "\""+user['screen_name']+"\"/"+ - ""+user['screen_name']+""+ - ""+ - ""+ - "

      "+html+"

      "+ - "
      "+ - "
      "+ - ""+ - "a few seconds ago"+ - " "+ - ""+ - "from "+ - ""+source+""+ // may have a link - ""; - if (data['conversation_url']) { - ni = ni+" in context"; - } - - if (repeat) { - ru = repeat['user']; - ni = ni + "Repeated by " + - "" + - ""+ ru['screen_name'] + ""; - } - - ni = ni+"
      "; - - ni = ni + "
      "; - - if (RealtimeUpdate._userid != 0) { - var input = $("form#form_notice fieldset input#token"); - var session_key = input.val(); - ni = ni+RealtimeUpdate.makeFavoriteForm(data['id'], session_key); - ni = ni+RealtimeUpdate.makeReplyLink(data['id'], data['user']['screen_name']); - if (RealtimeUpdate._userid == responsible['id']) { - ni = ni+RealtimeUpdate.makeDeleteLink(data['id']); - } else if (RealtimeUpdate._userid != user['id']) { - ni = ni+RealtimeUpdate.makeRepeatForm(data['id'], session_key); - } - } - - ni = ni+"
      "; - - ni = ni+"
    • "; - callback(ni); + var url = RealtimeUpdate._showurl.replace('0000000000', data.id); + $.get(url, {ajax: 1}, function(data, textStatus, xhr) { + var notice = $('li.notice:first', data); + if (notice.length) { + var node = document._importNode(notice[0], true); + callback(node); + } + }); }, /** diff --git a/plugins/Realtime/realtimeupdate.min.js b/plugins/Realtime/realtimeupdate.min.js index 931de982ef..7e77f90709 100644 --- a/plugins/Realtime/realtimeupdate.min.js +++ b/plugins/Realtime/realtimeupdate.min.js @@ -1 +1 @@ -RealtimeUpdate={_userid:0,_replyurl:"",_favorurl:"",_repeaturl:"",_deleteurl:"",_updatecounter:0,_maxnotices:50,_windowhasfocus:true,_documenttitle:"",_paused:false,_queuedNotices:[],init:function(c,b,d,e,a){RealtimeUpdate._userid=c;RealtimeUpdate._replyurl=b;RealtimeUpdate._favorurl=d;RealtimeUpdate._repeaturl=e;RealtimeUpdate._deleteurl=a;RealtimeUpdate._documenttitle=document.title;$(window).bind("focus",function(){RealtimeUpdate._windowhasfocus=true;RealtimeUpdate._updatecounter=0;RealtimeUpdate.removeWindowCounter()});$(window).bind("blur",function(){$("#notices_primary .notice").removeClass("mark-top");$("#notices_primary .notice:first").addClass("mark-top");RealtimeUpdate._windowhasfocus=false;return false})},receive:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}if(RealtimeUpdate._paused===false){RealtimeUpdate.purgeLastNoticeItem();RealtimeUpdate.insertNoticeItem(a)}else{RealtimeUpdate._queuedNotices.push(a);RealtimeUpdate.updateQueuedCounter()}RealtimeUpdate.updateWindowCounter()},insertNoticeItem:function(b){if(RealtimeUpdate.isNoticeVisible(b.id)){return}var a=RealtimeUpdate.makeNoticeItem(b);var c=$(a).attr("id");var d=$("#notices_primary .notices:first");var j=true;var e=d.hasClass("threaded-notices");if(e&&b.in_reply_to_status_id){var g=$("#notice-"+b.in_reply_to_status_id);if(g.length==0){}else{var h=g.closest(".notices");if(h.hasClass("threaded-replies")){g=h.closest(".notice")}d=g.find(".threaded-replies");if(d.length==0){d=$('
        ');g.append(d)}j=false}}var i=$(a);if(j){d.prepend(i)}else{var f=d.find("li.notice-reply-placeholder");if(f.length>0){i.insertBefore(f)}else{i.appendTo(d);SN.U.NoticeInlineReplyPlaceholder(g)}}i.css({display:"none"}).fadeIn(1000);SN.U.NoticeReplyTo($("#"+c));SN.U.NoticeWithAttachment($("#"+c))},isNoticeVisible:function(a){return($("#notice-"+a).length>0)},purgeLastNoticeItem:function(){if($("#notices_primary .notice").length>RealtimeUpdate._maxnotices){$("#notices_primary .notice:last").remove()}},updateWindowCounter:function(){if(RealtimeUpdate._windowhasfocus===false){RealtimeUpdate._updatecounter+=1;document.title="("+RealtimeUpdate._updatecounter+") "+RealtimeUpdate._documenttitle}},removeWindowCounter:function(){document.title=RealtimeUpdate._documenttitle},makeNoticeItem:function(c){if(c.hasOwnProperty("retweeted_status")){original=c.retweeted_status;repeat=c;c=original;unique=repeat.id;responsible=repeat.user}else{original=null;repeat=null;unique=c.id;responsible=c.user}user=c.user;html=c.html.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/&/g,"&");source=c.source.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/&/g,"&");ni='
      • '+user.screen_name+''+user.screen_name+'

        '+html+'

        a few seconds ago from '+source+"";if(c.conversation_url){ni=ni+' in context'}if(repeat){ru=repeat.user;ni=ni+'Repeated by '+ru.screen_name+""}ni=ni+"
        ";ni=ni+'
        ';if(RealtimeUpdate._userid!=0){var a=$("form#form_notice fieldset input#token");var b=a.val();ni=ni+RealtimeUpdate.makeFavoriteForm(c.id,b);ni=ni+RealtimeUpdate.makeReplyLink(c.id,c.user["screen_name"]);if(RealtimeUpdate._userid==responsible.id){ni=ni+RealtimeUpdate.makeDeleteLink(c.id)}else{if(RealtimeUpdate._userid!=user.id){ni=ni+RealtimeUpdate.makeRepeatForm(c.id,b)}}}ni=ni+"
        ";ni=ni+"
      • ";return ni},makeFavoriteForm:function(c,b){var a;a='
        Favor this notice
        ';return a},makeReplyLink:function(c,a){var b;b='Reply '+c+"";return b},makeRepeatForm:function(c,b){var a;a='
        Repeat this notice?
        ';return a},makeDeleteLink:function(c){var b,a;a=RealtimeUpdate._deleteurl.replace("0000000000",c);b='Delete';return b},initActions:function(a,b,c){$("#notices_primary").prepend('
        ');RealtimeUpdate._pluginPath=c;RealtimeUpdate.initPlayPause();RealtimeUpdate.initAddPopup(a,b,RealtimeUpdate._pluginPath)},initPlayPause:function(){if(typeof(localStorage)=="undefined"){RealtimeUpdate.showPause()}else{if(localStorage.getItem("RealtimeUpdate_paused")==="true"){RealtimeUpdate.showPlay()}else{RealtimeUpdate.showPause()}}},showPause:function(){RealtimeUpdate.setPause(false);RealtimeUpdate.showQueuedNotices();RealtimeUpdate.addNoticesHover();$("#realtime_playpause").remove();$("#realtime_actions").prepend('
      • ');$("#realtime_pause").text(SN.msg("realtime_pause")).attr("title",SN.msg("realtime_pause_tooltip")).bind("click",function(){RealtimeUpdate.removeNoticesHover();RealtimeUpdate.showPlay();return false})},showPlay:function(){RealtimeUpdate.setPause(true);$("#realtime_playpause").remove();$("#realtime_actions").prepend('
      • ');$("#realtime_play").text(SN.msg("realtime_play")).attr("title",SN.msg("realtime_play_tooltip")).bind("click",function(){RealtimeUpdate.showPause();return false})},setPause:function(a){RealtimeUpdate._paused=a;if(typeof(localStorage)!="undefined"){localStorage.setItem("RealtimeUpdate_paused",RealtimeUpdate._paused)}},showQueuedNotices:function(){$.each(RealtimeUpdate._queuedNotices,function(a,b){RealtimeUpdate.insertNoticeItem(b)});RealtimeUpdate._queuedNotices=[];RealtimeUpdate.removeQueuedCounter()},updateQueuedCounter:function(){$("#realtime_playpause #queued_counter").html("("+RealtimeUpdate._queuedNotices.length+")")},removeQueuedCounter:function(){$("#realtime_playpause #queued_counter").empty()},addNoticesHover:function(){$("#notices_primary .notices").hover(function(){if(RealtimeUpdate._paused===false){RealtimeUpdate.showPlay()}},function(){if(RealtimeUpdate._paused===true){RealtimeUpdate.showPause()}})},removeNoticesHover:function(){$("#notices_primary .notices").unbind()},initAddPopup:function(a,b,c){$("#realtime_timeline").append('');$("#realtime_popup").text(SN.msg("realtime_popup")).attr("title",SN.msg("realtime_popup_tooltip")).bind("click",function(){window.open(a,"","toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550");return false})},initPopupWindow:function(){$(".notices .entry-title a, .notices .entry-content a").bind("click",function(){window.open(this.href,"");return false});$("#showstream .entity_profile").css({width:"69%"})}}; \ No newline at end of file +RealtimeUpdate={_userid:0,_showurl:"",_updatecounter:0,_maxnotices:50,_windowhasfocus:true,_documenttitle:"",_paused:false,_queuedNotices:[],init:function(a,b){RealtimeUpdate._userid=a;RealtimeUpdate._showurl=b;RealtimeUpdate._documenttitle=document.title;$(window).bind("focus",function(){RealtimeUpdate._windowhasfocus=true;RealtimeUpdate._updatecounter=0;RealtimeUpdate.removeWindowCounter()});$(window).bind("blur",function(){$("#notices_primary .notice").removeClass("mark-top");$("#notices_primary .notice:first").addClass("mark-top");RealtimeUpdate._windowhasfocus=false;return false})},receive:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}if(RealtimeUpdate._paused===false){RealtimeUpdate.purgeLastNoticeItem();RealtimeUpdate.insertNoticeItem(a)}else{RealtimeUpdate._queuedNotices.push(a);RealtimeUpdate.updateQueuedCounter()}RealtimeUpdate.updateWindowCounter()},insertNoticeItem:function(a){if(RealtimeUpdate.isNoticeVisible(a.id)){return}RealtimeUpdate.makeNoticeItem(a,function(b){var c=$(b).attr("id");var d=$("#notices_primary .notices:first");var j=true;var e=d.hasClass("threaded-notices");if(e&&a.in_reply_to_status_id){var g=$("#notice-"+a.in_reply_to_status_id);if(g.length==0){}else{var h=g.closest(".notices");if(h.hasClass("threaded-replies")){g=h.closest(".notice")}d=g.find(".threaded-replies");if(d.length==0){d=$('
          ');g.append(d)}j=false}}var i=$(b);if(j){d.prepend(i)}else{var f=d.find("li.notice-reply-placeholder");if(f.length>0){i.insertBefore(f)}else{i.appendTo(d);SN.U.NoticeInlineReplyPlaceholder(g)}}i.css({display:"none"}).fadeIn(1000);SN.U.NoticeReplyTo($("#"+c));SN.U.NoticeWithAttachment($("#"+c))})},isNoticeVisible:function(a){return($("#notice-"+a).length>0)},purgeLastNoticeItem:function(){if($("#notices_primary .notice").length>RealtimeUpdate._maxnotices){$("#notices_primary .notice:last").remove()}},updateWindowCounter:function(){if(RealtimeUpdate._windowhasfocus===false){RealtimeUpdate._updatecounter+=1;document.title="("+RealtimeUpdate._updatecounter+") "+RealtimeUpdate._documenttitle}},removeWindowCounter:function(){document.title=RealtimeUpdate._documenttitle},makeNoticeItem:function(b,c){var a=RealtimeUpdate._showurl.replace("0000000000",b.id);$.get(a,{ajax:1},function(f,h,g){var e=$("li.notice:first",f);if(e.length){var d=document._importNode(e[0],true);c(d)}})},makeFavoriteForm:function(c,b){var a;a='
          Favor this notice
          ';return a},makeReplyLink:function(c,a){var b;b='Reply '+c+"";return b},makeRepeatForm:function(c,b){var a;a='
          Repeat this notice?
          ';return a},makeDeleteLink:function(c){var b,a;a=RealtimeUpdate._deleteurl.replace("0000000000",c);b='Delete';return b},initActions:function(a,b,c){$("#notices_primary").prepend('
          ');RealtimeUpdate._pluginPath=c;RealtimeUpdate.initPlayPause();RealtimeUpdate.initAddPopup(a,b,RealtimeUpdate._pluginPath)},initPlayPause:function(){if(typeof(localStorage)=="undefined"){RealtimeUpdate.showPause()}else{if(localStorage.getItem("RealtimeUpdate_paused")==="true"){RealtimeUpdate.showPlay()}else{RealtimeUpdate.showPause()}}},showPause:function(){RealtimeUpdate.setPause(false);RealtimeUpdate.showQueuedNotices();RealtimeUpdate.addNoticesHover();$("#realtime_playpause").remove();$("#realtime_actions").prepend('
        • ');$("#realtime_pause").text(SN.msg("realtime_pause")).attr("title",SN.msg("realtime_pause_tooltip")).bind("click",function(){RealtimeUpdate.removeNoticesHover();RealtimeUpdate.showPlay();return false})},showPlay:function(){RealtimeUpdate.setPause(true);$("#realtime_playpause").remove();$("#realtime_actions").prepend('
        • ');$("#realtime_play").text(SN.msg("realtime_play")).attr("title",SN.msg("realtime_play_tooltip")).bind("click",function(){RealtimeUpdate.showPause();return false})},setPause:function(a){RealtimeUpdate._paused=a;if(typeof(localStorage)!="undefined"){localStorage.setItem("RealtimeUpdate_paused",RealtimeUpdate._paused)}},showQueuedNotices:function(){$.each(RealtimeUpdate._queuedNotices,function(a,b){RealtimeUpdate.insertNoticeItem(b)});RealtimeUpdate._queuedNotices=[];RealtimeUpdate.removeQueuedCounter()},updateQueuedCounter:function(){$("#realtime_playpause #queued_counter").html("("+RealtimeUpdate._queuedNotices.length+")")},removeQueuedCounter:function(){$("#realtime_playpause #queued_counter").empty()},addNoticesHover:function(){$("#notices_primary .notices").hover(function(){if(RealtimeUpdate._paused===false){RealtimeUpdate.showPlay()}},function(){if(RealtimeUpdate._paused===true){RealtimeUpdate.showPause()}})},removeNoticesHover:function(){$("#notices_primary .notices").unbind()},initAddPopup:function(a,b,c){$("#realtime_timeline").append('');$("#realtime_popup").text(SN.msg("realtime_popup")).attr("title",SN.msg("realtime_popup_tooltip")).bind("click",function(){window.open(a,"","toolbar=no,resizable=yes,scrollbars=yes,status=no,menubar=no,personalbar=no,location=no,width=500,height=550");return false})},initPopupWindow:function(){$(".notices .entry-title a, .notices .entry-content a").bind("click",function(){window.open(this.href,"");return false});$("#showstream .entity_profile").css({width:"69%"})}}; \ No newline at end of file From 34e7d8ddf2cc95de329789eb67513d77e6245b28 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 14 Mar 2011 13:44:39 -0700 Subject: [PATCH 26/69] For good measure; trip short error mode in earlier on ajax shownotice --- actions/shownotice.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actions/shownotice.php b/actions/shownotice.php index 8d8ab326df..b8927372bb 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -78,6 +78,9 @@ class ShownoticeAction extends OwnerDesignAction function prepare($args) { parent::prepare($args); + if ($this->boolean('ajax')) { + StatusNet::setApi(true); + } $id = $this->arg('notice'); @@ -189,7 +192,6 @@ class ShownoticeAction extends OwnerDesignAction parent::handle($args); if ($this->boolean('ajax')) { - StatusNet::setApi(true); $this->showAjax(); } else { if ($this->notice->is_local == Notice::REMOTE_OMB) { From 27c75dd4bb1092199c1604700f6e5a5a6dbcb61b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 15:29:11 -0700 Subject: [PATCH 27/69] Extended profile - show and edit experience --- plugins/ExtendedProfile/extendedprofile.php | 28 +++- .../ExtendedProfile/extendedprofilewidget.php | 146 +++++++++++++----- plugins/ExtendedProfile/js/profiledetail.js | 1 + .../profiledetailsettingsaction.php | 93 ++++++++++- 4 files changed, 222 insertions(+), 46 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 711fdcf1bb..2277e4d769 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -129,6 +129,29 @@ class ExtendedProfile return $pArrays; } + function getExperiences() + { + $companies = (isset($this->fields['companies'])) ? $this->fields['company'] : null; + $start = (isset($this->fields['start'])) ? $this->fields['start'] : null; + $end = (isset($this->fields['end'])) ? $this->fields['end'] : null; + + $cArrays = array(); + + if (empty($experiences)) { + $eArrays[] = array( + 'label' => _m('Employer'), + 'type' => 'experience', + 'company' => "Bozotronix", + 'start' => '1/5/10', + 'end' => '2/3/11', + 'current' => true, + 'index' => 0 + ); + } + + return $eArrays; + } + /** * Return all the sections of the extended profile * @@ -206,10 +229,7 @@ class ExtendedProfile 'experience' => array( 'label' => _m('Work experience'), 'fields' => array( - 'experience' => array( - 'type' => 'experience', - 'label' => _m('Employer'), - ), + 'experience' => $this->getExperiences(), ), ), 'education' => array( diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index d8c42df6a3..9dfbaa716e 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -103,9 +103,13 @@ class ExtendedProfileWidget extends Form $this->out->elementStart('table', array('class' => 'extended-profile')); foreach ($section['fields'] as $fieldName => $field) { - if ($fieldName == 'phone') { - $this->showPhones($fieldName, $field); - } else { + + switch($fieldName) { + case 'phone': + case 'experience': + $this->showMultiple($fieldName, $field); + break; + default: $this->showExtendedProfileField($fieldName, $field); } } @@ -135,37 +139,9 @@ class ExtendedProfileWidget extends Form $this->out->elementEnd('tr'); } - /** - * Outputs the value of a field - * - * @param string $name name of the field - * @param array $field set of key/value pairs for the field - */ - protected function showFieldValue($name, $field) - { - $type = strval(@$field['type']); - - switch($type) - { - case '': - case 'text': - case 'textarea': - $this->out->text($this->ext->getTextValue($name)); - break; - case 'tags': - $this->out->text($this->ext->getTags()); - break; - case 'phone': - $this->showPhone($name, $field); - break; - default: - $this->out->text("TYPE: $type"); - } - } - - protected function showPhones($name, $field) { - foreach ($field as $phone) { - $this->showExtendedProfileField($name, $phone); + protected function showMultiple($name, $fields) { + foreach ($fields as $field) { + $this->showExtendedProfileField($name, $field); } } @@ -210,6 +186,74 @@ class ExtendedProfileWidget extends Form isset($field['rel']) ? $field['rel'] : null ); + $this->showMultiControls(); + $this->out->elementEnd('div'); + } + + protected function showExperience($name, $field) + { + $this->out->elementStart('div', array('class' => 'experience-display')); + $this->out->text($field['company']); + $this->out->elementStart('dl', 'experience-start-and-end'); + $this->out->element('dt', null, _m('Start')); + $this->out->element('dd', null, $field['start']); + $this->out->element('dt', null, _m('End')); + if ($field['current']) { + $this->out->element('dd', null, '(' . _m('Current') . ')'); + } else { + $this->out->element('dd', null, $field['end']); + } + $this->out->elementEnd('dl'); + $this->out->elementEnd('div'); + } + + protected function showEditableExperience($name, $field) + { + $index = isset($field['index']) ? $field['index'] : 0; + $id = "extprofile-$name-$index"; + $this->out->elementStart( + 'div', array( + 'id' => $id . '-edit', + 'class' => 'experience-edit' + ) + ); + + $this->out->input( + $id, + null, + isset($field['company']) ? $field['company'] : null + ); + + $this->out->elementStart('ul', 'experience-start-and-end'); + $this->out->elementStart('li'); + $this->out->input( + $id . '-start', + _m('Start'), + isset($field['start']) ? $field['start'] : null + ); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input( + $id . '-end', + _m('End'), + isset($field['end']) ? $field['end'] : null + ); + $this->out->elementEnd('li'); + $this->out->elementStart('li'); + $this->out->checkbox( + $id . '-current', + _m('Current'), + $field['current'] + ); + $this->out->elementEnd('li'); + $this->out->elementEnd('ul'); + $this->showMultiControls(); + $this->out->elementEnd('div'); + } + + function showMultiControls() + { $this->out->element( 'a', array( @@ -229,8 +273,37 @@ class ExtendedProfileWidget extends Form ), '-' ); + } - $this->out->elementEnd('div'); + /** + * Outputs the value of a field + * + * @param string $name name of the field + * @param array $field set of key/value pairs for the field + */ + protected function showFieldValue($name, $field) + { + $type = strval(@$field['type']); + + switch($type) + { + case '': + case 'text': + case 'textarea': + $this->out->text($this->ext->getTextValue($name)); + break; + case 'tags': + $this->out->text($this->ext->getTags()); + break; + case 'phone': + $this->showPhone($name, $field); + break; + case 'experience': + $this->showExperience($name, $field); + break; + default: + $this->out->text("TYPE: $type"); + } } /** @@ -262,6 +335,9 @@ class ExtendedProfileWidget extends Form case 'phone': $this->showEditablePhone($name, $field); break; + case 'experience': + $this->showEditableExperience($name, $field); + break; default: $out->input($id, null, "TYPE: $type"); } diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 7d7eceddc1..4dc2faf2aa 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -82,6 +82,7 @@ var removeRow = function() { var init = function() { reorder('phone-edit'); + reorder('experience-edit'); } $(document).ready( diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 89b8d61cf9..5e505f6c28 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -113,6 +113,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } $this->savePhoneNumbers($user); + $this->saveExperiences($user); $this->showForm(_('Details saved.'), true); @@ -141,17 +142,12 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $phoneParams = $this->findMultiParams('phone'); ksort($phoneParams); // this sorts them into pairs $phones = $this->arraySplit($phoneParams, sizeof($phoneParams) / 2); - $phoneTuples = array(); - foreach($phones as $phone) { - $firstkey = current(array_keys($phone)); - $index = substr($firstkey, strrpos($firstkey, '-') + 1); + foreach ($phones as $phone) { list($number, $rel) = array_values($phone); - $phoneTuples[] = array( 'value' => $number, - 'index' => $index, 'rel' => $rel ); } @@ -159,6 +155,86 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $phoneTuples; } + function findExperiences() { + + // Form vals look like this: + // 'extprofile-experience-0' => 'Bozotronix', + // 'extprofile-experience-0-current' => 'true' + // 'extprofile-experience-0-start' => '1/5/10', + // 'extprofile-experience-0-end' => '2/3/11', + + $experiences = array(); + $expParams = $this->findMultiParams('experience'); + ksort($expParams); + $experiences = $this->arraySplit($expParams, sizeof($expParams) / 4); + $expArray = array(); + + foreach ($experiences as $exp) { + list($company, $current, $start, $end) = array_values($exp); + $expArray[] = array( + 'company' => $company, + 'start' => $start, + 'end' => $end, + 'current' => $current, + ); + } + + return $expArray; + } + + function saveExperiences($user) { + common_debug('save experiences'); + $experiences = $this->findExperiences(); + + $this->removeAll($user, 'company'); + $this->removeAll($user, 'start'); + $this->removeAll($user, 'end'); // also stores 'current' + + $i = 0; + foreach($experiences as $experience) { + if (!empty($experience['company'])) { + ++$i; + $this->saveField( + $user, + 'company', + $experience['company'], + null, + $i + ); + /* + $this->saveField( + $user, + 'start', + null, + null, + $i, + $experience['start'] + ); + + // Save "current" employer indicator in rel + if ($experience['current']) { + $this->saveField( + $user, + 'end', + null, + 'current', // rel + $i + ); + } else { + $this->saveField( + $user, + 'end', + null, + null, + $i, + $experience['end'] + ); + } + */ + } + } + } + function arraySplit($array, $pieces) { if ($pieces < 2) { @@ -191,8 +267,9 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction * @param string $value field value * @param string $rel field rel (type) * @param int $index index (fields can have multiple values) + * @param date $date related date */ - function saveField($user, $name, $value, $rel = null, $index = null) + function saveField($user, $name, $value, $rel = null, $index = null, $date = null) { $profile = $user->getProfile(); $detail = new Profile_detail(); @@ -207,6 +284,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $detial->value_index = $index; $detail->rel = $rel; $detail->field_value = $value; + $detail->date = $date; $detail->created = common_sql_now(); $result = $detail->insert(); if (empty($result)) { @@ -218,6 +296,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $detail->field_value = $value; $detail->rel = $rel; + $detail->date = $date; $result = $detail->update($orig); if (empty($result)) { From 07ccb6a9f839f39ab6c1763dc4f49e6d29b98147 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 17:27:50 -0700 Subject: [PATCH 28/69] Extended profile - make experience save and display --- plugins/ExtendedProfile/extendedprofile.php | 28 ++++++--- .../ExtendedProfile/extendedprofilewidget.php | 4 ++ .../profiledetailsettingsaction.php | 61 +++++++++++++------ 3 files changed, 65 insertions(+), 28 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 2277e4d769..b6844e205f 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -131,24 +131,36 @@ class ExtendedProfile function getExperiences() { - $companies = (isset($this->fields['companies'])) ? $this->fields['company'] : null; + $companies = (isset($this->fields['company'])) ? $this->fields['company'] : null; $start = (isset($this->fields['start'])) ? $this->fields['start'] : null; $end = (isset($this->fields['end'])) ? $this->fields['end'] : null; - $cArrays = array(); + $eArrays = array(); - if (empty($experiences)) { + if (empty($companies)) { $eArrays[] = array( 'label' => _m('Employer'), 'type' => 'experience', - 'company' => "Bozotronix", - 'start' => '1/5/10', - 'end' => '2/3/11', - 'current' => true, + 'company' => null, + 'start' => null, + 'end' => null, + 'current' => false, 'index' => 0 ); + } else { + for ($i = 0; $i < sizeof($companies); $i++) { + $ea = array( + 'label' => _m('Employer'), + 'type' => 'experience', + 'company' => $companies[$i]->field_value, + 'index' => intval($companies[$i]->value_index), + 'current' => $end[$i]->rel, + 'start' => $start[$i]->date, + 'end' => $end[$i]->date + ); + $eArrays[] = $ea; + } } - return $eArrays; } diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 9dfbaa716e..478990ac63 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -241,6 +241,10 @@ class ExtendedProfileWidget extends Form ); $this->out->elementEnd('li'); $this->out->elementStart('li'); + $this->out->hidden( + $id . '-current', + 'false' + ); $this->out->checkbox( $id . '-current', _m('Current'), diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 5e505f6c28..56f81d7a2d 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -95,26 +95,27 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $user = common_current_user(); try { - $this->saveStandardProfileDetails($user); + $this->saveStandardProfileDetails($user); + + $profile = $user->getProfile(); + + $simpleFieldNames = array('title', 'spouse', 'kids'); + + foreach ($simpleFieldNames as $name) { + $value = $this->trimmed('extprofile-' . $name); + if (!empty($value)) { + $this->saveField($user, $name, $value); + } + } + + $this->savePhoneNumbers($user); + $this->saveExperiences($user); + } catch (Exception $e) { $this->showForm($e->getMessage(), false); return; } - $profile = $user->getProfile(); - - $simpleFieldNames = array('title', 'spouse', 'kids'); - - foreach ($simpleFieldNames as $name) { - $value = $this->trimmed('extprofile-' . $name); - if (!empty($value)) { - $this->saveField($user, $name, $value); - } - } - - $this->savePhoneNumbers($user); - $this->saveExperiences($user); - $this->showForm(_('Details saved.'), true); } @@ -170,11 +171,31 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $expArray = array(); foreach ($experiences as $exp) { - list($company, $current, $start, $end) = array_values($exp); + + common_debug('Experience: ' . var_export($exp, true)); + + list($company, $current, $end, $start) = array_values($exp); + + $startTs = strtotime($start); + + if ($startTs === false) { + throw new Exception( + sprintf(_m("Invalid start date: %s"), $start) + ); + } + + $endTs = strtotime($end); + + if ($current === 'false' && $endTs === false) { + throw new Exception( + sprintf(_m("Invalid end date: %s"), $start) + ); + } + $expArray[] = array( 'company' => $company, - 'start' => $start, - 'end' => $end, + 'start' => common_sql_date($startTs), + 'end' => common_sql_date($endTs), 'current' => $current, ); } @@ -201,7 +222,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction null, $i ); - /* + $this->saveField( $user, 'start', @@ -230,7 +251,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $experience['end'] ); } - */ + } } } From 0fd4b84eb87b30fb2ab322225119c73195c144c2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 17:53:54 -0700 Subject: [PATCH 29/69] Extended profile - better error handling for bad dates --- .../profiledetailsettingsaction.php | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 56f81d7a2d..74b2fa667b 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -139,10 +139,8 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } function findPhoneNumbers() { - $phones = array(); - $phoneParams = $this->findMultiParams('phone'); - ksort($phoneParams); // this sorts them into pairs - $phones = $this->arraySplit($phoneParams, sizeof($phoneParams) / 2); + + $phones = $this->sliceParams('phone', 2); $phoneTuples = array(); foreach ($phones as $phone) { @@ -156,6 +154,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $phoneTuples; } + function sliceParams($key, $size) { + $slice = array(); + $params = $this->findMultiParams($key); + ksort($params); + $slice = $this->arraySplit($params, sizeof($params) / $size); + return $slice; + } + function findExperiences() { // Form vals look like this: @@ -164,10 +170,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction // 'extprofile-experience-0-start' => '1/5/10', // 'extprofile-experience-0-end' => '2/3/11', - $experiences = array(); - $expParams = $this->findMultiParams('experience'); - ksort($expParams); - $experiences = $this->arraySplit($expParams, sizeof($expParams) / 4); + $experiences = $this->sliceParams('experience', 4); $expArray = array(); foreach ($experiences as $exp) { @@ -179,24 +182,24 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $startTs = strtotime($start); if ($startTs === false) { - throw new Exception( - sprintf(_m("Invalid start date: %s"), $start) - ); + $msg = empty($start) ? _m('You must supply a start date.') + : sprintf(_m("Invalid start date: %s"), $start); + throw new Exception($msg); } $endTs = strtotime($end); if ($current === 'false' && $endTs === false) { - throw new Exception( - sprintf(_m("Invalid end date: %s"), $start) - ); + $msg = empty($end) ? _m('You must supply an end date.') + : sprintf(_m("Invalid end date: %s"), $end); + throw new Exception($msg); } $expArray[] = array( 'company' => $company, 'start' => common_sql_date($startTs), 'end' => common_sql_date($endTs), - 'current' => $current, + 'current' => ($current == 'false') ? false : true ); } From c6a6d41dab2286caa387e6a8a5c3ffc46b773f22 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 02:09:22 +0000 Subject: [PATCH 30/69] Extended profile - change HTML output for displaying work experience --- .../ExtendedProfile/extendedprofilewidget.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 478990ac63..4c47987406 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -192,18 +192,19 @@ class ExtendedProfileWidget extends Form protected function showExperience($name, $field) { - $this->out->elementStart('div', array('class' => 'experience-display')); - $this->out->text($field['company']); - $this->out->elementStart('dl', 'experience-start-and-end'); - $this->out->element('dt', null, _m('Start')); - $this->out->element('dd', null, $field['start']); - $this->out->element('dt', null, _m('End')); + $this->out->elementStart('div', 'experience-item'); + $this->out->element('div', 'field', $field['company']); + $this->out->element('div', 'label', _m('Start')); + $this->out->element('div', array('class' => 'field date'), $field['start']); + $this->out->element('div', 'label', _m('End')); + $this->out->element('div', array('class' => 'field date'), $field['end']); if ($field['current']) { - $this->out->element('dd', null, '(' . _m('Current') . ')'); - } else { - $this->out->element('dd', null, $field['end']); + $this->out->element( + 'div', + array('class' => 'field current'), + '(' . _m('Current') . ')' + ); } - $this->out->elementEnd('dl'); $this->out->elementEnd('div'); } From 0ff7bf77e4c208030ece6de13e8488d3845e224c Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Mon, 14 Mar 2011 22:40:31 -0400 Subject: [PATCH 31/69] Couple quick fixes for profile view. --- plugins/ExtendedProfile/profiledetailaction.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php index beac7d6321..a777a28e03 100644 --- a/plugins/ExtendedProfile/profiledetailaction.php +++ b/plugins/ExtendedProfile/profiledetailaction.php @@ -36,7 +36,7 @@ class ProfileDetailAction extends ShowstreamAction function showStylesheets() { parent::showStylesheets(); - $this->cssLink('plugins/ExtendedProfile/profiledetail.css'); + $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css'); return true; } @@ -45,6 +45,7 @@ class ProfileDetailAction extends ShowstreamAction $cur = common_current_user(); if ($cur && $cur->id == $this->profile->id) { // your own page $this->elementStart('div', 'entity_actions'); + $this->elementStart('ul'); $this->elementStart('li', 'entity_edit'); $this->element('a', array('href' => common_local_url('profiledetailsettings'), // TRANS: Link title for link on user profile. @@ -52,6 +53,7 @@ class ProfileDetailAction extends ShowstreamAction // TRANS: Link text for link on user profile. _m('Edit')); $this->elementEnd('li'); + $this->elementEnd('ul'); $this->elementEnd('div'); } From bd238e9a4de7aafbb9dd70307e965b2ba9bab6e9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 20:58:35 -0700 Subject: [PATCH 32/69] Extended profile - HTML layout for education entries --- plugins/ExtendedProfile/extendedprofile.php | 46 ++++++++++-- .../ExtendedProfile/extendedprofilewidget.php | 75 +++++++++++++++++++ .../profiledetailsettingsaction.php | 32 ++++---- 3 files changed, 132 insertions(+), 21 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index b6844e205f..713d5c601a 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -164,6 +164,45 @@ class ExtendedProfile return $eArrays; } + function getEducation() + { + $schools = (isset($this->fields['school'])) ? $this->fields['school'] : null; + $degrees = (isset($this->fields['degree'])) ? $this->fields['degree'] : null; + $descs = (isset($this->fields['degree_description'])) ? $this->fields['degree_description'] : null; + $start = (isset($this->fields['school_start'])) ? $this->fields['school_start'] : null; + $end = (isset($this->fields['school_end'])) ? $this->fields['school_end'] : null; + $iArrays = array(); + + if (empty($schools)) { + $iArrays[] = array( + 'type' => 'education', + 'label' => _m('Institution'), + 'school' => null, + 'degree' => null, + 'description' => null, + 'start' => null, + 'end' => null, + 'index' => 0 + ); + } else { + for ($i = 0; $i < sizeof($schools); $i++) { + $ia = array( + 'type' => 'education', + 'label' => _m('Institution'), + 'school' => $schools[$i]->field_value, + 'degree' => $degrees[$i]->field_value, + 'description' => $descs[$i]->field_value, + 'index' => intval($schools[$i]->value_index), + 'start' => $start[$i]->date, + 'end' => $end[$i]->date + ); + $iArrays[] = $ia; + } + } + + return $iArrays; + } + /** * Return all the sections of the extended profile * @@ -241,16 +280,13 @@ class ExtendedProfile 'experience' => array( 'label' => _m('Work experience'), 'fields' => array( - 'experience' => $this->getExperiences(), + 'experience' => $this->getExperiences() ), ), 'education' => array( 'label' => _m('Education'), 'fields' => array( - 'education' => array( - 'type' => 'education', - 'label' => _m('Institution'), - ), + 'education' => $this->getEducation() ), ), ); diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 4c47987406..967cf6dd20 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -107,6 +107,7 @@ class ExtendedProfileWidget extends Form switch($fieldName) { case 'phone': case 'experience': + case 'education': $this->showMultiple($fieldName, $field); break; default: @@ -257,6 +258,74 @@ class ExtendedProfileWidget extends Form $this->out->elementEnd('div'); } + protected function showEducation($name, $field) + { + $this->out->elementStart('div', 'education-item'); + $this->out->element('div', 'field', $field['school']); + $this->out->element('div', 'label', _m('Degree')); + $this->out->element('div', 'field', $field['degree']); + $this->out->element('div', 'label', _m('Description')); + $this->out->element('div', 'field', $field['description']); + $this->out->element('div', 'label', _m('Start')); + $this->out->element('div', array('class' => 'field date'), $field['start']); + $this->out->element('div', 'label', _m('End')); + $this->out->element('div', array('class' => 'field date'), $field['end']); + $this->out->elementEnd('div'); + } + + protected function showEditableEducation($name, $field) + { + $index = isset($field['index']) ? $field['index'] : 0; + $id = "extprofile-$name-$index"; + $this->out->elementStart( + 'div', array( + 'id' => $id . '-edit', + 'class' => 'education-edit' + ) + ); + $this->out->input( + $id, + null, + isset($field['school']) ? $field['school'] : null + ); + + $this->out->element('div', 'label', _m('Degree')); + $this->out->input( + $id, + null, + isset($field['degree']) ? $field['degree'] : null + ); + + $this->out->element('div', 'label', _m('Description')); + $this->out->element('div', 'field', $field['description']); + + $this->out->input( + $id, + null, + isset($field['description']) ? $field['description'] : null + ); + + $this->out->elementStart('ul', 'education-start-and-end'); + $this->out->elementStart('li'); + $this->out->input( + $id . '-start', + _m('Start'), + isset($field['start']) ? $field['start'] : null + ); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->input( + $id . '-end', + _m('End'), + isset($field['end']) ? $field['end'] : null + ); + $this->out->elementEnd('ul'); + + $this->showMultiControls(); + $this->out->elementEnd('div'); + } + function showMultiControls() { $this->out->element( @@ -306,6 +375,9 @@ class ExtendedProfileWidget extends Form case 'experience': $this->showExperience($name, $field); break; + case 'education': + $this->showEducation($name, $field); + break; default: $this->out->text("TYPE: $type"); } @@ -343,6 +415,9 @@ class ExtendedProfileWidget extends Form case 'experience': $this->showEditableExperience($name, $field); break; + case 'education': + $this->showEditableEducation($name, $field); + break; default: $out->input($id, null, "TYPE: $type"); } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 74b2fa667b..7870c273ba 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -140,26 +140,22 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction function findPhoneNumbers() { - $phones = $this->sliceParams('phone', 2); - $phoneTuples = array(); + // Form vals look like this: + // 'extprofile-phone-1' => '11332', + // 'extprofile-phone-1-rel' => 'mobile', + + $phones = $this->sliceParams('phone', 2); + $phoneArray = array(); foreach ($phones as $phone) { list($number, $rel) = array_values($phone); - $phoneTuples[] = array( + $phoneArray[] = array( 'value' => $number, 'rel' => $rel ); } - return $phoneTuples; - } - - function sliceParams($key, $size) { - $slice = array(); - $params = $this->findMultiParams($key); - ksort($params); - $slice = $this->arraySplit($params, sizeof($params) / $size); - return $slice; + return $phoneArray; } function findExperiences() { @@ -174,11 +170,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $expArray = array(); foreach ($experiences as $exp) { - - common_debug('Experience: ' . var_export($exp, true)); - list($company, $current, $end, $start) = array_values($exp); - $startTs = strtotime($start); if ($startTs === false) { @@ -283,6 +275,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $formVals; } + function sliceParams($key, $size) { + $slice = array(); + $params = $this->findMultiParams($key); + ksort($params); + $slice = $this->arraySplit($params, sizeof($params) / $size); + return $slice; + } + /** * Save an extended profile field as a Profile_detail * From 79c4af6073ab7aca718ec797c43bba957380164f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 21:07:14 -0700 Subject: [PATCH 33/69] Add in missing --- plugins/ExtendedProfile/extendedprofilewidget.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 967cf6dd20..a4913eed12 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -320,6 +320,7 @@ class ExtendedProfileWidget extends Form _m('End'), isset($field['end']) ? $field['end'] : null ); + $this->out->elementEnd('li'); $this->out->elementEnd('ul'); $this->showMultiControls(); From 8559fbb2ca1f7f2e154be0ec0084a324d936f22c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 21:09:03 -0700 Subject: [PATCH 34/69] Extended profile - intialize controls for multiple education entries --- plugins/ExtendedProfile/js/profiledetail.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 4dc2faf2aa..d491b46ba6 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -83,6 +83,7 @@ var removeRow = function() { var init = function() { reorder('phone-edit'); reorder('experience-edit'); + reorder('education-edit'); } $(document).ready( From 6d34818b5d9ca72dc1dbc2a9e484b1f7c0a45940 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 22:02:24 -0700 Subject: [PATCH 35/69] Extended profile - make education entries save --- plugins/ExtendedProfile/extendedprofile.php | 6 +- .../ExtendedProfile/extendedprofilewidget.php | 4 +- .../profiledetailsettingsaction.php | 147 +++++++++++++++--- 3 files changed, 132 insertions(+), 25 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 713d5c601a..09d010b090 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -168,7 +168,7 @@ class ExtendedProfile { $schools = (isset($this->fields['school'])) ? $this->fields['school'] : null; $degrees = (isset($this->fields['degree'])) ? $this->fields['degree'] : null; - $descs = (isset($this->fields['degree_description'])) ? $this->fields['degree_description'] : null; + $descs = (isset($this->fields['degree_descr'])) ? $this->fields['degree_descr'] : null; $start = (isset($this->fields['school_start'])) ? $this->fields['school_start'] : null; $end = (isset($this->fields['school_end'])) ? $this->fields['school_end'] : null; $iArrays = array(); @@ -190,8 +190,8 @@ class ExtendedProfile 'type' => 'education', 'label' => _m('Institution'), 'school' => $schools[$i]->field_value, - 'degree' => $degrees[$i]->field_value, - 'description' => $descs[$i]->field_value, + 'degree' => isset($degrees[$i]->field_value) ? $degrees[$i]->field_value : null, + 'description' => isset($descs[$i]->field_value) ? $descs[$i]->field_value : null, 'index' => intval($schools[$i]->value_index), 'start' => $start[$i]->date, 'end' => $end[$i]->date diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index a4913eed12..03f70592fc 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -291,7 +291,7 @@ class ExtendedProfileWidget extends Form $this->out->element('div', 'label', _m('Degree')); $this->out->input( - $id, + $id . '-degree', null, isset($field['degree']) ? $field['degree'] : null ); @@ -300,7 +300,7 @@ class ExtendedProfileWidget extends Form $this->out->element('div', 'field', $field['description']); $this->out->input( - $id, + $id . '-description', null, isset($field['description']) ? $field['description'] : null ); diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 7870c273ba..3708f54d65 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -110,6 +110,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $this->savePhoneNumbers($user); $this->saveExperiences($user); + $this->saveEducations($user); } catch (Exception $e) { $this->showForm($e->getMessage(), false); @@ -171,28 +172,30 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction foreach ($experiences as $exp) { list($company, $current, $end, $start) = array_values($exp); - $startTs = strtotime($start); + if (!empty($company)) { + $startTs = strtotime($start); - if ($startTs === false) { - $msg = empty($start) ? _m('You must supply a start date.') - : sprintf(_m("Invalid start date: %s"), $start); - throw new Exception($msg); + if ($startTs === false) { + $msg = empty($start) ? _m('You must supply a start date.') + : sprintf(_m("Invalid start date: %s"), $start); + throw new Exception($msg); + } + + $endTs = strtotime($end); + + if ($current === 'false' && $endTs === false) { + $msg = empty($end) ? _m('You must supply an end date.') + : sprintf(_m("Invalid end date: %s"), $end); + throw new Exception($msg); + } + + $expArray[] = array( + 'company' => $company, + 'start' => common_sql_date($startTs), + 'end' => common_sql_date($endTs), + 'current' => ($current == 'false') ? false : true + ); } - - $endTs = strtotime($end); - - if ($current === 'false' && $endTs === false) { - $msg = empty($end) ? _m('You must supply an end date.') - : sprintf(_m("Invalid end date: %s"), $end); - throw new Exception($msg); - } - - $expArray[] = array( - 'company' => $company, - 'start' => common_sql_date($startTs), - 'end' => common_sql_date($endTs), - 'current' => ($current == 'false') ? false : true - ); } return $expArray; @@ -251,6 +254,110 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } } + function findEducations() { + + // Form vals look like this: + // 'extprofile-education-0-school' => 'Pigdog', + // 'extprofile-education-0-degree' => 'BA', + // 'extprofile-education-0-description' => 'Blar', + // 'extprofile-education-0-start' => '05/22/99', + // 'extprofile-education-0-end' => '05/22/05', + + $edus = $this->sliceParams('education', 5); + $eduArray = array(); + + foreach ($edus as $edu) { + list($school, $degree, $description, $end, $start) = array_values($edu); + + if (!empty($school)) { + + $startTs = strtotime($start); + + if ($startTs === false) { + $msg = empty($start) ? _m('You must supply a start date.') + : sprintf(_m("Invalid start date: %s"), $start); + throw new Exception($msg); + } + + $endTs = strtotime($end); + + if ($endTs === false) { + $msg = empty($end) ? _m('You must supply an end date.') + : sprintf(_m("Invalid end date: %s"), $end); + throw new Exception($msg); + } + + $eduArray[] = array( + 'school' => $school, + 'degree' => $degree, + 'description' => $description, + 'start' => common_sql_date($startTs), + 'end' => common_sql_date($endTs) + ); + } + } + + return $eduArray; + } + + + function saveEducations($user) { + common_debug('save education'); + $edus = $this->findEducations(); + common_debug(var_export($edus, true)); + + $this->removeAll($user, 'school'); + $this->removeAll($user, 'degree'); + $this->removeAll($user, 'degree_descr'); + $this->removeAll($user, 'school_start'); + $this->removeAll($user, 'school_end'); + + $i = 0; + foreach($edus as $edu) { + if (!empty($edu['school'])) { + ++$i; + $this->saveField( + $user, + 'school', + $edu['school'], + null, + $i + ); + $this->saveField( + $user, + 'degree', + $edu['degree'], + null, + $i + ); + $this->saveField( + $user, + 'degree_descr', + $edu['description'], + null, + $i + ); + $this->saveField( + $user, + 'school_start', + null, + null, + $i, + $edu['start'] + ); + + $this->saveField( + $user, + 'school_end', + null, + null, + $i, + $edu['end'] + ); + } + } + } + function arraySplit($array, $pieces) { if ($pieces < 2) { From cd82ff2dcf6a5c7f33778282c9840652829074c3 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 22:38:34 -0700 Subject: [PATCH 36/69] Extended profile - make IMs display and save --- plugins/ExtendedProfile/extendedprofile.php | 32 +++++++++-- .../ExtendedProfile/extendedprofilewidget.php | 54 +++++++++++++++++++ plugins/ExtendedProfile/js/profiledetail.js | 1 + .../profiledetailsettingsaction.php | 39 ++++++++++++++ 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 09d010b090..6d78ddd3d8 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -129,6 +129,32 @@ class ExtendedProfile return $pArrays; } + function getIms() + { + $ims = (isset($this->fields['im'])) ? $this->fields['im'] : null; + $iArrays = array(); + + if (empty($ims)) { + $iArrays[] = array( + 'label' => _m('IM'), + 'type' => 'im' + ); + } else { + for ($i = 0; $i < sizeof($ims); $i++) { + $ia = array( + 'label' => _m('IM'), + 'type' => 'im', + 'index' => intval($ims[$i]->value_index), + 'rel' => $ims[$i]->rel, + 'value' => $ims[$i]->field_value, + ); + + $iArrays[] = $ia; + } + } + return $iArrays; + } + function getExperiences() { $companies = (isset($this->fields['company'])) ? $this->fields['company'] : null; @@ -248,11 +274,7 @@ class ExtendedProfile 'label' => _m('Contact'), 'fields' => array( 'phone' => $this->getPhones(), - 'im' => array( - 'label' => _m('IM'), - 'type' => 'im', - 'multi' => true, - ), + 'im' => $this->getIms(), 'website' => array( 'label' => _m('Websites'), 'type' => 'website', diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 03f70592fc..5d5f1b7ab9 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -106,6 +106,7 @@ class ExtendedProfileWidget extends Form switch($fieldName) { case 'phone': + case 'im': case 'experience': case 'education': $this->showMultiple($fieldName, $field); @@ -156,6 +157,53 @@ class ExtendedProfileWidget extends Form $this->out->elementEnd('div'); } + protected function showIm($name, $field) + { + $this->out->elementStart('div', array('class' => 'im-display')); + $this->out->text($field['value']); + if (!empty($field['rel'])) { + $this->out->text(' (' . $field['rel'] . ')'); + } + $this->out->elementEnd('div'); + } + + protected function showEditableIm($name, $field) + { + $index = isset($field['index']) ? $field['index'] : 0; + $id = "extprofile-$name-$index"; + $rel = $id . '-rel'; + $this->out->elementStart( + 'div', array( + 'id' => $id . '-edit', + 'class' => 'im-edit' + ) + ); + $this->out->input( + $id, + null, + isset($field['value']) ? $field['value'] : null + ); + $this->out->dropdown( + $id . '-rel', + 'Type', + array( + 'jabber' => 'Jabber', + 'gtalk' => 'GTalk', + 'aim' => 'AIM', + 'yahoo' => 'Yahoo! Messenger', + 'msn' => 'MSN', + 'skype' => 'Skype', + 'other' => 'Other' + ), + null, + false, + isset($field['rel']) ? $field['rel'] : null + ); + + $this->showMultiControls(); + $this->out->elementEnd('div'); + } + protected function showEditablePhone($name, $field) { $index = isset($field['index']) ? $field['index'] : 0; @@ -373,6 +421,9 @@ class ExtendedProfileWidget extends Form case 'phone': $this->showPhone($name, $field); break; + case 'im': + $this->showIm($name, $field); + break; case 'experience': $this->showExperience($name, $field); break; @@ -413,6 +464,9 @@ class ExtendedProfileWidget extends Form case 'phone': $this->showEditablePhone($name, $field); break; + case 'im': + $this->showEditableIm($name, $field); + break; case 'experience': $this->showEditableExperience($name, $field); break; diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index d491b46ba6..cdd7467e93 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -84,6 +84,7 @@ var init = function() { reorder('phone-edit'); reorder('experience-edit'); reorder('education-edit'); + reorder('im-edit'); } $(document).ready( diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 3708f54d65..baac80a482 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -109,6 +109,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } $this->savePhoneNumbers($user); + $this->saveIms($user); $this->saveExperiences($user); $this->saveEducations($user); @@ -159,6 +160,44 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $phoneArray; } + function findIms() { + + // Form vals look like this: + // 'extprofile-im-0' => 'jed', + // 'extprofile-im-0-rel' => 'yahoo', + + $ims = $this->sliceParams('im', 2); + $imArray = array(); + + foreach ($ims as $im) { + list($id, $rel) = array_values($im); + $imArray[] = array( + 'value' => $id, + 'rel' => $rel + ); + } + + return $imArray; + } + + function saveIms($user) { + $ims = $this->findIms(); + $this->removeAll($user, 'im'); + $i = 0; + foreach($ims as $im) { + if (!empty($im['value'])) { + ++$i; + $this->saveField( + $user, + 'im', + $im['value'], + $im['rel'], + $i + ); + } + } + } + function findExperiences() { // Form vals look like this: From 7a7e0a2b69e9367c33e52e656c8e28cc9e78adaa Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Mon, 14 Mar 2011 22:47:20 -0700 Subject: [PATCH 37/69] Extended profile - change the name of divs used for sort order --- plugins/ExtendedProfile/js/profiledetail.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index cdd7467e93..c198132399 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -81,10 +81,10 @@ var removeRow = function() { }; var init = function() { - reorder('phone-edit'); - reorder('experience-edit'); - reorder('education-edit'); - reorder('im-edit'); + reorder('phone-item'); + reorder('experience-item'); + reorder('education-item'); + reorder('im-item'); } $(document).ready( From b37c33dea2d151dcabf84357b2ea2a0b4f36be3c Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Tue, 15 Mar 2011 13:08:41 -0400 Subject: [PATCH 38/69] Whole bunch of style-related changes, including some tasty hackery for the experience and education fields. --- plugins/ExtendedProfile/css/profiledetail.css | 137 ++++++++++++++++-- .../ExtendedProfile/extendedprofilewidget.php | 65 ++++----- plugins/ExtendedProfile/js/profiledetail.js | 8 +- 3 files changed, 164 insertions(+), 46 deletions(-) diff --git a/plugins/ExtendedProfile/css/profiledetail.css b/plugins/ExtendedProfile/css/profiledetail.css index 836b647a10..797d32d415 100644 --- a/plugins/ExtendedProfile/css/profiledetail.css +++ b/plugins/ExtendedProfile/css/profiledetail.css @@ -1,22 +1,139 @@ /* Note the #content is only needed to override weird crap in default styles */ +#profiledetail .entity_actions { + margin-top: 0px; + margin-bottom: 0px; +} + +#profiledetail #content h3 { + margin-bottom: 5px; +} + #content table.extended-profile { width: 100%; border-collapse: separate; - border-spacing: 8px; + border-spacing: 0px 8px; + margin-bottom: 10px; } + #content table.extended-profile th { color: #777; - background-color: #eee; + background-color: #ECECF2; width: 150px; - - padding-top: 0; /* override bizarre theme defaults */ - text-align: right; - padding-right: 8px; + padding: 2px 8px 2px 0px; } -#content table.extended-profile td { - padding: 0; /* override bizarre theme defaults */ - padding-left: 8px; -} \ No newline at end of file +#content table.extended-profile th.employer, #content table.extended-profile th.institution { + display: none; +} + +#content table.extended-profile td { + padding: 2px 0px 2px 8px; +} + +.experience-item, .education-item { + float: left; + padding-bottom: 4px; +} + +.experience-item .label, .education-item .label { + float: left; + clear: left; + position: relative; + left: -8px; + margin-right: 2px; + margin-bottom: 8px; + color: #777; + background-color: #ECECF2; + width: 150px; + text-align: right; + padding: 2px 8px 2px 0px; +} + +.experience-item .field, .education-item .field { + float: left; + padding-top: 2px; + padding-bottom: 2px; +} + +#profiledetailsettings #content table.extended-profile td { + padding: 0px 0px 0px 8px; +} + +#profiledetailsettings input { + margin-right: 8px; +} + +.experience-item input[type=text], .education-item input[type=text] { + float: left; +} + +.extended-profile .current-checkbox { + float: left; + position: relative; + top: 2px; +} + +.form_settings .extended-profile input.checkbox { + margin-left: 0px; + left: 0px; + top: 2px; +} + +.form_settings .extended-profile label.checkbox { + max-width: 100%; + float: none; + left: -20px; +} + +.phone-item label, .im-item label { + display: none; +} + +.extended-profile select { + padding-right: 2px; + font-size: 0.88em; +} + +.extended-profile a.add_row, .extended-profile a.remove_row { + display: block; + height: 16px; + width: 16px; + line-height: 4em; + overflow: hidden; + background-image: url('../../../theme/rebase/images/icons/icons-01.gif'); + background-repeat: no-repeat; +} + +.extended-profile a.remove_row { + background-position: 0px -1252px; + float: right; + position: relative; + top: 6px; +} + +.extended-profile a.add_row { + clear: both; + position: relative; + top: 6px; + left: 2px; + background-position: 0px -1186px; +} + +#content table.extended-profile .supersizeme th { + border-bottom: 28px solid #fff; +} + +#profiledetailsettings .experience-item, #profiledetailsettings .education-item { + margin-bottom: 10px; + width: 100%; +} + +#profiledetailsettings tr:last-child .experience-item, #profiledetailsettings tr:last-child .education-item { + margin-bottom: 0px; +} + +#profiledetailsettings .experience-item a.add_row, #profiledetailsettings .education-item a.add_row { + left: 160px; +} diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 5d5f1b7ab9..161a524f94 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -128,7 +128,7 @@ class ExtendedProfileWidget extends Form { $this->out->elementStart('tr'); - $this->out->element('th', null, $field['label']); + $this->out->element('th', str_replace(' ','_',strtolower($field['label'])), $field['label']); $this->out->elementStart('td'); if ($this->editable) { @@ -175,7 +175,7 @@ class ExtendedProfileWidget extends Form $this->out->elementStart( 'div', array( 'id' => $id . '-edit', - 'class' => 'im-edit' + 'class' => 'im-item' ) ); $this->out->input( @@ -212,7 +212,7 @@ class ExtendedProfileWidget extends Form $this->out->elementStart( 'div', array( 'id' => $id . '-edit', - 'class' => 'phone-edit' + 'class' => 'phone-item' ) ); $this->out->input( @@ -242,6 +242,7 @@ class ExtendedProfileWidget extends Form protected function showExperience($name, $field) { $this->out->elementStart('div', 'experience-item'); + $this->out->element('div', 'label', _m('Company')); $this->out->element('div', 'field', $field['company']); $this->out->element('div', 'label', _m('Start')); $this->out->element('div', array('class' => 'field date'), $field['start']); @@ -264,44 +265,42 @@ class ExtendedProfileWidget extends Form $this->out->elementStart( 'div', array( 'id' => $id . '-edit', - 'class' => 'experience-edit' + 'class' => 'experience-item' ) ); + $this->out->element('div', 'label', _m('Company')); $this->out->input( $id, null, isset($field['company']) ? $field['company'] : null ); - $this->out->elementStart('ul', 'experience-start-and-end'); - $this->out->elementStart('li'); + $this->out->element('div', 'label', _m('Start')); $this->out->input( $id . '-start', - _m('Start'), + null, isset($field['start']) ? $field['start'] : null ); - $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->element('div', 'label', _m('End')); $this->out->input( $id . '-end', - _m('End'), + null, isset($field['end']) ? $field['end'] : null ); - $this->out->elementEnd('li'); - $this->out->elementStart('li'); $this->out->hidden( $id . '-current', 'false' ); + $this->out->elementStart('div', 'current-checkbox'); $this->out->checkbox( $id . '-current', _m('Current'), $field['current'] ); - $this->out->elementEnd('li'); - $this->out->elementEnd('ul'); + $this->out->elementEnd('div'); + $this->showMultiControls(); $this->out->elementEnd('div'); } @@ -309,6 +308,7 @@ class ExtendedProfileWidget extends Form protected function showEducation($name, $field) { $this->out->elementStart('div', 'education-item'); + $this->out->element('div', 'label', _m('Institution')); $this->out->element('div', 'field', $field['school']); $this->out->element('div', 'label', _m('Degree')); $this->out->element('div', 'field', $field['degree']); @@ -328,9 +328,10 @@ class ExtendedProfileWidget extends Form $this->out->elementStart( 'div', array( 'id' => $id . '-edit', - 'class' => 'education-edit' + 'class' => 'education-item' ) ); + $this->out->element('div', 'label', _m('Institution')); $this->out->input( $id, null, @@ -353,23 +354,19 @@ class ExtendedProfileWidget extends Form isset($field['description']) ? $field['description'] : null ); - $this->out->elementStart('ul', 'education-start-and-end'); - $this->out->elementStart('li'); + $this->out->element('div', 'label', _m('Start')); $this->out->input( $id . '-start', - _m('Start'), + null, isset($field['start']) ? $field['start'] : null ); - $this->out->elementEnd('li'); - $this->out->elementStart('li'); + $this->out->element('div', 'label', _m('End')); $this->out->input( $id . '-end', - _m('End'), + null, isset($field['end']) ? $field['end'] : null ); - $this->out->elementEnd('li'); - $this->out->elementEnd('ul'); $this->showMultiControls(); $this->out->elementEnd('div'); @@ -377,16 +374,6 @@ class ExtendedProfileWidget extends Form function showMultiControls() { - $this->out->element( - 'a', - array( - 'class' => 'add_row', - 'href' => 'javascript://', - 'style' => 'display: none; ' - ), - '+' - ); - $this->out->element( 'a', array( @@ -396,6 +383,16 @@ class ExtendedProfileWidget extends Form ), '-' ); + + $this->out->element( + 'a', + array( + 'class' => 'add_row', + 'href' => 'javascript://', + 'style' => 'display: none; ' + ), + '+' + ); } /** @@ -514,7 +511,7 @@ class ExtendedProfileWidget extends Form function formClass() { - return 'form_profile_details'; + return 'form_profile_details form_settings'; } /** diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index c198132399..2821bed275 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -12,6 +12,9 @@ var reorder = function(class) { replaceIndex(rowIndex(div), i); }); + $this = $(divs).last().closest('tr'); + $this.addClass('supersizeme'); + $(divs).last().find('a.add_row').show(); if (divs.length == 1) { @@ -57,7 +60,8 @@ var addRow = function() { var class = $(div).attr('class'); var index = id.match(/\d+/); console.log("Current row = " + index + ', class = ' + class); - var tr = $(this).closest('tr'); + var trold = $(this).closest('tr'); + var tr = $(trold).removeClass('supersizeme'); var newtr = $(tr).clone(); var newIndex = parseInt(index) + 1; replaceIndex(newtr, index, newIndex); @@ -95,4 +99,4 @@ function() { $('.remove_row').live('click', removeRow); } -); \ No newline at end of file +); From 9e9cbdf505e9ec66c7e9d60bfe170f8d41a146e2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 10:09:20 -0700 Subject: [PATCH 39/69] Suppress PHP warnings/notices during AtomPub XML parsing to avoid HTTP header problems when given bad input. If display_errors is on, typical settings would cause PHP error messages to spew to output before the HTTP headers for setting a 400 error go through. Also switched from deprecated static DOMDocument::loadXML() to non-static call. --- actions/apitimelineuser.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php index 66984b5abd..3fe73c691c 100644 --- a/actions/apitimelineuser.php +++ b/actions/apitimelineuser.php @@ -322,8 +322,11 @@ class ApiTimelineUserAction extends ApiBareAuthAction $this->clientError(_('Atom post must not be empty.')); } - $dom = DOMDocument::loadXML($xml); - if (!$dom) { + $old = error_reporting(error_reporting() & ~(E_WARNING | E_NOTICE)); + $dom = new DOMDocument(); + $ok = $dom->loadXML($xml); + error_reporting($old); + if (!$ok) { // TRANS: Client error displayed attempting to post an API that is not well-formed XML. $this->clientError(_('Atom post must be well-formed XML.')); } From a6ae9ddd418a4bee2173a4188d5c382168f5d51e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 10:10:18 -0700 Subject: [PATCH 40/69] Extended profile - make websites save --- plugins/ExtendedProfile/extendedprofile.php | 38 ++++++++++--- .../ExtendedProfile/extendedprofilewidget.php | 57 +++++++++++++++++++ .../profiledetailsettingsaction.php | 37 ++++++++++++ 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 6d78ddd3d8..673680f027 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -98,6 +98,8 @@ class ExtendedProfile } } + // XXX: getPhones, getIms, and getWebsites pretty much do the same thing, + // so refactor. function getPhones() { $phones = (isset($this->fields['phone'])) ? $this->fields['phone'] : null; @@ -155,6 +157,32 @@ class ExtendedProfile return $iArrays; } + function getWebsites() + { + $sites = (isset($this->fields['website'])) ? $this->fields['website'] : null; + $wArrays = array(); + + if (empty($sites)) { + $wArrays[] = array( + 'label' => _m('Website'), + 'type' => 'website' + ); + } else { + for ($i = 0; $i < sizeof($sites); $i++) { + $wa = array( + 'label' => _m('Website'), + 'type' => 'website', + 'index' => intval($sites[$i]->value_index), + 'rel' => $sites[$i]->rel, + 'value' => $sites[$i]->field_value, + ); + + $wArrays[] = $wa; + } + } + return $wArrays; + } + function getExperiences() { $companies = (isset($this->fields['company'])) ? $this->fields['company'] : null; @@ -273,13 +301,9 @@ class ExtendedProfile 'contact' => array( 'label' => _m('Contact'), 'fields' => array( - 'phone' => $this->getPhones(), - 'im' => $this->getIms(), - 'website' => array( - 'label' => _m('Websites'), - 'type' => 'website', - 'multi' => true, - ), + 'phone' => $this->getPhones(), + 'im' => $this->getIms(), + 'website' => $this->getWebsites() ), ), 'personal' => array( diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 5d5f1b7ab9..99d5852276 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -107,6 +107,7 @@ class ExtendedProfileWidget extends Form switch($fieldName) { case 'phone': case 'im': + case 'website': case 'experience': case 'education': $this->showMultiple($fieldName, $field); @@ -147,6 +148,8 @@ class ExtendedProfileWidget extends Form } } + // XXX: showPhone, showIm and showWebsite all work the same, so + // combine protected function showPhone($name, $field) { $this->out->elementStart('div', array('class' => 'phone-display')); @@ -167,6 +170,16 @@ class ExtendedProfileWidget extends Form $this->out->elementEnd('div'); } + protected function showWebsite($name, $field) + { + $this->out->elementStart('div', array('class' => 'website-display')); + $this->out->text($field['value']); + if (!empty($field['rel'])) { + $this->out->text(' (' . $field['rel'] . ')'); + } + $this->out->elementEnd('div'); + } + protected function showEditableIm($name, $field) { $index = isset($field['index']) ? $field['index'] : 0; @@ -239,6 +252,44 @@ class ExtendedProfileWidget extends Form $this->out->elementEnd('div'); } + protected function showEditableWebsite($name, $field) + { + $index = isset($field['index']) ? $field['index'] : 0; + $id = "extprofile-$name-$index"; + $rel = $id . '-rel'; + $this->out->elementStart( + 'div', array( + 'id' => $id . '-edit', + 'class' => 'website-edit' + ) + ); + $this->out->input( + $id, + null, + isset($field['value']) ? $field['value'] : null + ); + $this->out->dropdown( + $id . '-rel', + 'Type', + array( + 'blog' => 'Blog', + 'homepage' => 'Homepage', + 'facebook' => 'Facebook', + 'linkedin' => 'LinkedIn', + 'flickr' => 'Flickr', + 'google' => 'Google Profile', + 'other' => 'Other', + 'twitter' => 'Twitter' + ), + null, + false, + isset($field['rel']) ? $field['rel'] : null + ); + + $this->showMultiControls(); + $this->out->elementEnd('div'); + } + protected function showExperience($name, $field) { $this->out->elementStart('div', 'experience-item'); @@ -421,6 +472,9 @@ class ExtendedProfileWidget extends Form case 'phone': $this->showPhone($name, $field); break; + case 'website': + $this->showWebsite($name, $field); + break; case 'im': $this->showIm($name, $field); break; @@ -467,6 +521,9 @@ class ExtendedProfileWidget extends Form case 'im': $this->showEditableIm($name, $field); break; + case 'website': + $this->showEditableWebsite($name, $field); + break; case 'experience': $this->showEditableExperience($name, $field); break; diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index baac80a482..2357860884 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -110,6 +110,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $this->savePhoneNumbers($user); $this->saveIms($user); + $this->saveWebsites($user); $this->saveExperiences($user); $this->saveEducations($user); @@ -198,6 +199,42 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } } + function findWebsites() { + + // Form vals look like this: + + $sites = $this->sliceParams('website', 2); + $wsArray = array(); + + foreach ($sites as $site) { + list($id, $rel) = array_values($site); + $wsArray[] = array( + 'value' => $id, + 'rel' => $rel + ); + } + + return $wsArray; + } + + function saveWebsites($user) { + $sites = $this->findWebsites(); + $this->removeAll($user, 'website'); + $i = 0; + foreach($sites as $site) { + if (!empty($site['value'])) { + ++$i; + $this->saveField( + $user, + 'website', + $site['value'], + $site['rel'], + $i + ); + } + } + } + function findExperiences() { // Form vals look like this: From b66250c6aa2d04aa1a7c15bf385a73397589e78d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 12:49:17 -0700 Subject: [PATCH 41/69] Add StartNoticeWhoGets, EndNoticeWhoGets events to allow upcoming TagSub plugin to do extra inbox delivery. --- EVENTS.txt | 8 +++++++ classes/Notice.php | 59 ++++++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 1443a94fbe..54d06655ee 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -1131,3 +1131,11 @@ StartActivityObjectOutputJson: Called at start of JSON output generation for Act EndActivityObjectOutputJson: Called at end of JSON output generation for ActivityObject chunks: the array has not yet been filled out. - $obj ActivityObject - &$out: array to be serialized; you're free to modify it + +StartNoticeWhoGets: Called at start of inbox delivery prep; plugins can schedule notices to go to particular profiles that would otherwise not have reached them. Canceling will take over the entire addressing operation. Be aware that output can be cached or used several times, so should remain idempotent. +- $notice Notice +- &$ni: in/out array mapping profile IDs to constants: NOTICE_INBOX_SOURCE_SUB etc + +EndNoticeWhoGets: Called at end of inbox delivery prep; plugins can filter out profiles from receiving inbox delivery here. Be aware that output can be cached or used several times, so should remain idempotent. +- $notice Notice +- &$ni: in/out array mapping profile IDs to constants: NOTICE_INBOX_SOURCE_SUB etc diff --git a/classes/Notice.php b/classes/Notice.php index d520f4728f..e5c758005d 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -812,41 +812,48 @@ class Notice extends Memcached_DataObject $ni = array(); - foreach ($users as $id) { - $ni[$id] = NOTICE_INBOX_SOURCE_SUB; - } + // Give plugins a chance to add folks in at start... + if (Event::handle('StartNoticeWhoGets', array($this, &$ni))) { - foreach ($groups as $group) { - $users = $group->getUserMembers(); foreach ($users as $id) { - if (!array_key_exists($id, $ni)) { - $ni[$id] = NOTICE_INBOX_SOURCE_GROUP; + $ni[$id] = NOTICE_INBOX_SOURCE_SUB; + } + + foreach ($groups as $group) { + $users = $group->getUserMembers(); + foreach ($users as $id) { + if (!array_key_exists($id, $ni)) { + $ni[$id] = NOTICE_INBOX_SOURCE_GROUP; + } } } - } - foreach ($recipients as $recipient) { - if (!array_key_exists($recipient, $ni)) { - $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY; + foreach ($recipients as $recipient) { + if (!array_key_exists($recipient, $ni)) { + $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY; + } } - } - // Exclude any deleted, non-local, or blocking recipients. - $profile = $this->getProfile(); - $originalProfile = null; - if ($this->repeat_of) { - // Check blocks against the original notice's poster as well. - $original = Notice::staticGet('id', $this->repeat_of); - if ($original) { - $originalProfile = $original->getProfile(); + // Exclude any deleted, non-local, or blocking recipients. + $profile = $this->getProfile(); + $originalProfile = null; + if ($this->repeat_of) { + // Check blocks against the original notice's poster as well. + $original = Notice::staticGet('id', $this->repeat_of); + if ($original) { + $originalProfile = $original->getProfile(); + } } - } - foreach ($ni as $id => $source) { - $user = User::staticGet('id', $id); - if (empty($user) || $user->hasBlocked($profile) || - ($originalProfile && $user->hasBlocked($originalProfile))) { - unset($ni[$id]); + foreach ($ni as $id => $source) { + $user = User::staticGet('id', $id); + if (empty($user) || $user->hasBlocked($profile) || + ($originalProfile && $user->hasBlocked($originalProfile))) { + unset($ni[$id]); + } } + + // Give plugins a chance to filter out... + Event::handle('EndNoticeWhoGets', array($this, &$ni)); } if (!empty($c)) { From 8de24335d2e65ada6f32e15b919c552b74c0039d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 12:51:29 -0700 Subject: [PATCH 42/69] Doc comment on Notice->getTags() to clarify the return data type --- classes/Notice.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index e5c758005d..664e5dab9f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -2006,6 +2006,11 @@ class Notice extends Memcached_DataObject $this->is_local == Notice::LOCAL_NONPUBLIC); } + /** + * Get the list of hash tags saved with this notice. + * + * @return array of strings + */ public function getTags() { $tags = array(); From 119885d964f6f5402471f4874c7482c4d6a22fc0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 12:57:11 -0700 Subject: [PATCH 43/69] Stub TagSubPlugin: plugin guts with no UI to setup subs --- plugins/TagSub/TagSub.php | 109 ++++++++++++++++++++++ plugins/TagSub/TagSubPlugin.php | 156 ++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 plugins/TagSub/TagSub.php create mode 100644 plugins/TagSub/TagSubPlugin.php diff --git a/plugins/TagSub/TagSub.php b/plugins/TagSub/TagSub.php new file mode 100644 index 0000000000..fdae3e2db0 --- /dev/null +++ b/plugins/TagSub/TagSub.php @@ -0,0 +1,109 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2011, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * For storing the tag subscriptions + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class TagSub extends Managed_DataObject +{ + public $__table = 'tagsub'; // table name + public $tag; // text + public $profile_id; // int -> profile.id + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return TagSub object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('TagSub', $k, $v); + } + + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return TagSub object found, or null for no hits + * + */ + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('TagSub', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array( + 'description' => 'TagSubPlugin tag subscription records', + 'fields' => array( + 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this subscription'), + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile ID of subscribing user'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + ), + 'primary key' => array('tag', 'profile_id'), + 'foreign keys' => array( + 'tagsub_profile_id_fkey' => array('profile', array('profile_id' => 'id')), + ), + 'indexes' => array( + 'tagsub_created_idx' => array('created'), + 'tagsub_profile_id_tag_idx' => array('profile_id', 'tag'), + ), + ); + } + +} diff --git a/plugins/TagSub/TagSubPlugin.php b/plugins/TagSub/TagSubPlugin.php new file mode 100644 index 0000000000..b43fcf32ba --- /dev/null +++ b/plugins/TagSub/TagSubPlugin.php @@ -0,0 +1,156 @@ +. + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * TagSub plugin main class + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class TagSubPlugin extends Plugin +{ + const VERSION = '0.1'; + + /** + * Database schema setup + * + * @see Schema + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('tagsub', TagSub::schemaDef()); + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'TagsubAction': + case 'TagunsubAction': + include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; + return false; + case 'TagSub': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'TagSubForm': + case 'TagUnsubForm': + include_once $dir.'/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + /** + * Map URLs to actions + * + * @param Net_URL_Mapper $m path-to-action mapper + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onRouterInitialized($m) + { + $m->connect('tag/:tag/subscribe', + array('action' => 'tagsub'), + array('tag' => Router::REGEX_TAG)); + $m->connect('tag/:tag/unsubscribe', + array('action' => 'tagunsub'), + array('tag' => Router::REGEX_TAG)); + + return true; + } + + /** + * Plugin version data + * + * @param array &$versions array of version data + * + * @return value + */ + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'TagSub', + 'version' => self::VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:TagSub', + 'rawdescription' => + // TRANS: Plugin description. + _m('Plugin to allow following all messages with a given tag.')); + return true; + } + + /** + * Hook inbox delivery setup so tag subscribers receive all + * notices with that tag in their inbox. + * + * Currently makes no distinction between local messages and + * remote ones which happen to come in to the system. Remote + * notices that don't come in at all won't ever reach this. + * + * @param Notice $notice + * @param array $ni in/out map of profile IDs to inbox constants + * @return boolean hook result + */ + function onStartNoticeWhoGets(Notice $notice, array &$ni) + { + foreach ($notice->getTags() as $tag) { + $tagsub = new TagSub(); + $tagsub->tag = $tag; + $tagsub->find(); + + while ($tagsub->fetch()) { + // These constants are currently not actually used, iirc + $ni[$tagsub->profile_id] = NOTICE_INBOX_SOURCE_SUB; + } + } + return true; + } +} From 0bb2f1470474f3818f6c3357581ea04efa23a15f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 13:17:06 -0700 Subject: [PATCH 44/69] AJAX submit actions for tag subscribe/unsubscribe --- plugins/TagSub/TagSub.php | 31 +++++++ plugins/TagSub/TagSubPlugin.php | 34 ++++++- plugins/TagSub/tagsubaction.php | 149 ++++++++++++++++++++++++++++++ plugins/TagSub/tagsubform.php | 142 ++++++++++++++++++++++++++++ plugins/TagSub/tagunsubaction.php | 89 ++++++++++++++++++ plugins/TagSub/tagunsubform.php | 109 ++++++++++++++++++++++ 6 files changed, 550 insertions(+), 4 deletions(-) create mode 100644 plugins/TagSub/tagsubaction.php create mode 100644 plugins/TagSub/tagsubform.php create mode 100644 plugins/TagSub/tagunsubaction.php create mode 100644 plugins/TagSub/tagunsubform.php diff --git a/plugins/TagSub/TagSub.php b/plugins/TagSub/TagSub.php index fdae3e2db0..a734b4fc5f 100644 --- a/plugins/TagSub/TagSub.php +++ b/plugins/TagSub/TagSub.php @@ -106,4 +106,35 @@ class TagSub extends Managed_DataObject ); } + /** + * Start a tag subscription! + * + * @param profile $profile subscriber + * @param string $tag subscribee + * @return TagSub + */ + static function start(Profile $profile, $tag) + { + $ts = new TagSub(); + $ts->tag = $tag; + $ts->profile_id = $profile->id; + $ts->created = common_sql_now(); + $ts->insert(); + return $ts; + } + + /** + * End a tag subscription! + * + * @param profile $profile subscriber + * @param string $tag subscribee + */ + static function cancel(Profile $profile, $tag) + { + $ts = TagSub::pkeyGet(array('tag' => $tag, + 'profile_id' => $profile->id)); + if ($ts) { + $ts->delete(); + } + } } diff --git a/plugins/TagSub/TagSubPlugin.php b/plugins/TagSub/TagSubPlugin.php index b43fcf32ba..e51a7a8b39 100644 --- a/plugins/TagSub/TagSubPlugin.php +++ b/plugins/TagSub/TagSubPlugin.php @@ -73,13 +73,11 @@ class TagSubPlugin extends Plugin switch ($cls) { - case 'TagsubAction': - case 'TagunsubAction': - include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; - return false; case 'TagSub': include_once $dir.'/'.$cls.'.php'; return false; + case 'TagsubAction': + case 'TagunsubAction': case 'TagSubForm': case 'TagUnsubForm': include_once $dir.'/'.strtolower($cls).'.php'; @@ -153,4 +151,32 @@ class TagSubPlugin extends Plugin } return true; } + + /** + * + * @param TagAction $action + * @return boolean hook result + */ + function onStartTagShowContent(TagAction $action) + { + $user = common_current_user(); + if ($user) { + $tag = $action->trimmed('tag'); + $tagsub = TagSub::pkeyGet(array('tag' => $tag, + 'profile_id' => $user->id)); + if ($tagsub) { + $form = new TagUnsubForm($action, $tag); + } else { + $form = new TagSubForm($action, $tag); + } + $action->elementStart('div', 'entity_actions'); + $action->elementStart('ul'); + $action->elementStart('li', 'entity_subscribe'); + $form->show(); + $action->elementEnd('li'); + $action->elementEnd('ul'); + $action->elementEnd('div'); + } + return true; + } } diff --git a/plugins/TagSub/tagsubaction.php b/plugins/TagSub/tagsubaction.php new file mode 100644 index 0000000000..2e4e25d6e1 --- /dev/null +++ b/plugins/TagSub/tagsubaction.php @@ -0,0 +1,149 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Tag subscription action + * + * Takes parameters: + * + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @copyright 2008-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ +class TagsubAction extends Action +{ + var $user; + var $tag; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + if ($this->boolean('ajax')) { + StatusNet::setApi(true); + } + + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + // TRANS: Client error displayed trying to perform any request method other than POST. + // TRANS: Do not translate POST. + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + // TRANS: Client error displayed when the session token is not okay. + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + // TRANS: Client error displayed trying to subscribe when not logged in. + $this->clientError(_('Not logged in.')); + return false; + } + + // Profile to subscribe to + + $this->tag = $this->arg('tag'); + + if (empty($this->tag)) { + // TRANS: Client error displayed trying to subscribe to a non-existing profile. + $this->clientError(_('No such profile.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + function handle($args) + { + // Throws exception on error + + TagSub::start($this->user->getProfile(), + $this->tag); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Page title when tag subscription succeeded. + $this->element('title', null, _m('Subscribed')); + $this->elementEnd('head'); + $this->elementStart('body'); + $unsubscribe = new TagUnsubForm($this, $this->tag); + $unsubscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('tag', + array('tag' => $this->tag)); + common_redirect($url, 303); + } + } +} diff --git a/plugins/TagSub/tagsubform.php b/plugins/TagSub/tagsubform.php new file mode 100644 index 0000000000..108558be24 --- /dev/null +++ b/plugins/TagSub/tagsubform.php @@ -0,0 +1,142 @@ +. + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009-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); +} + +/** + * Form for subscribing to a user + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see UnsubscribeForm + */ + +class TagSubForm extends Form +{ + /** + * Name of tag to subscribe to + */ + + var $tag = ''; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param string $tag name of tag to subscribe to + */ + + function __construct($out=null, $tag=null) + { + parent::__construct($out); + + $this->tag = $tag; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'tag-subscribe-' . $this->tag; + } + + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + // class to match existing styles... + return 'form_user_subscribe ajax'; + } + + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('tagsub', array('tag' => $this->tag)); + } + + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _m('Subscribe to this tag')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->hidden('subscribeto-' . $this->tag, + $this->tag, + 'subscribeto'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Subscribe'), 'submit', null, _m('Subscribe to this tag')); + } +} diff --git a/plugins/TagSub/tagunsubaction.php b/plugins/TagSub/tagunsubaction.php new file mode 100644 index 0000000000..26fb9ffec8 --- /dev/null +++ b/plugins/TagSub/tagunsubaction.php @@ -0,0 +1,89 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Tag unsubscription action + * + * Takes parameters: + * + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @copyright 2008-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ +class TagunsubAction extends TagsubAction +{ + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + function handle($args) + { + // Throws exception on error + + TagSub::cancel($this->user->getProfile(), + $this->tag); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Page title when tag unsubscription succeeded. + $this->element('title', null, _m('Unsubscribed')); + $this->elementEnd('head'); + $this->elementStart('body'); + $subscribe = new TagSubForm($this, $this->tag); + $subscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('tag', + array('tag' => $this->tag)); + common_redirect($url, 303); + } + } +} diff --git a/plugins/TagSub/tagunsubform.php b/plugins/TagSub/tagunsubform.php new file mode 100644 index 0000000000..0b44648071 --- /dev/null +++ b/plugins/TagSub/tagunsubform.php @@ -0,0 +1,109 @@ +. + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009-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); +} + +/** + * Form for subscribing to a user + * + * @category TagSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see UnsubscribeForm + */ + +class TagUnsubForm extends TagSubForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'tag-unsubscribe-' . $this->tag; + } + + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + // class to match existing styles... + return 'form_user_unsubscribe ajax'; + } + + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('tagunsub', array('tag' => $this->tag)); + } + + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _m('Unsubscribe from this tag')); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Unsubscribe'), 'submit', null, _m('Unsubscribe from this tag')); + } +} From 579fc11862173c8be3a623ebc3248ce9d61835a8 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 14:58:09 -0700 Subject: [PATCH 45/69] copy-paste TagSub to SearchSub :D --- plugins/SearchSub/SearchSub.php | 140 ++++++++++++++++++ plugins/SearchSub/SearchSubPlugin.php | 182 ++++++++++++++++++++++++ plugins/SearchSub/searchsubaction.php | 149 +++++++++++++++++++ plugins/SearchSub/searchsubform.php | 142 ++++++++++++++++++ plugins/SearchSub/searchunsubaction.php | 89 ++++++++++++ plugins/SearchSub/searchunsubform.php | 109 ++++++++++++++ 6 files changed, 811 insertions(+) create mode 100644 plugins/SearchSub/SearchSub.php create mode 100644 plugins/SearchSub/SearchSubPlugin.php create mode 100644 plugins/SearchSub/searchsubaction.php create mode 100644 plugins/SearchSub/searchsubform.php create mode 100644 plugins/SearchSub/searchunsubaction.php create mode 100644 plugins/SearchSub/searchunsubform.php diff --git a/plugins/SearchSub/SearchSub.php b/plugins/SearchSub/SearchSub.php new file mode 100644 index 0000000000..cbf64d39cc --- /dev/null +++ b/plugins/SearchSub/SearchSub.php @@ -0,0 +1,140 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2011, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * For storing the search subscriptions + * + * @category PollPlugin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://status.net/ + * + * @see DB_DataObject + */ + +class SearchSub extends Managed_DataObject +{ + public $__table = 'searchsub'; // table name + public $search; // text + public $profile_id; // int -> profile.id + public $created; // datetime + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param mixed $v Value to lookup + * + * @return SearchSub object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('SearchSub', $k, $v); + } + + /** + * Get an instance by compound key + * + * This is a utility method to get a single instance with a given set of + * key-value pairs. Usually used for the primary key for a compound key; thus + * the name. + * + * @param array $kv array of key-value mappings + * + * @return SearchSub object found, or null for no hits + * + */ + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('SearchSub', $kv); + } + + /** + * The One True Thingy that must be defined and declared. + */ + public static function schemaDef() + { + return array( + 'description' => 'SearchSubPlugin search subscription records', + 'fields' => array( + 'search' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash search associated with this subscription'), + 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile ID of subscribing user'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + ), + 'primary key' => array('search', 'profile_id'), + 'foreign keys' => array( + 'searchsub_profile_id_fkey' => array('profile', array('profile_id' => 'id')), + ), + 'indexes' => array( + 'searchsub_created_idx' => array('created'), + 'searchsub_profile_id_tag_idx' => array('profile_id', 'search'), + ), + ); + } + + /** + * Start a search subscription! + * + * @param profile $profile subscriber + * @param string $search subscribee + * @return SearchSub + */ + static function start(Profile $profile, $search) + { + $ts = new SearchSub(); + $ts->search = $search; + $ts->profile_id = $profile->id; + $ts->created = common_sql_now(); + $ts->insert(); + return $ts; + } + + /** + * End a search subscription! + * + * @param profile $profile subscriber + * @param string $search subscribee + */ + static function cancel(Profile $profile, $search) + { + $ts = SearchSub::pkeyGet(array('search' => $search, + 'profile_id' => $profile->id)); + if ($ts) { + $ts->delete(); + } + } +} diff --git a/plugins/SearchSub/SearchSubPlugin.php b/plugins/SearchSub/SearchSubPlugin.php new file mode 100644 index 0000000000..59db0ffcbb --- /dev/null +++ b/plugins/SearchSub/SearchSubPlugin.php @@ -0,0 +1,182 @@ +. + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * SearchSub plugin main class + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class SearchSubPlugin extends Plugin +{ + const VERSION = '0.1'; + + /** + * Database schema setup + * + * @see Schema + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('searchsub', SearchSub::schemaDef()); + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'SearchSub': + include_once $dir.'/'.$cls.'.php'; + return false; + case 'SearchsubAction': + case 'SearchunsubAction': + case 'SearchSubForm': + case 'SearchUnsubForm': + include_once $dir.'/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + /** + * Map URLs to actions + * + * @param Net_URL_Mapper $m path-to-action mapper + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onRouterInitialized($m) + { + $m->connect('search/:search/subscribe', + array('action' => 'searchsub'), + array('search' => Router::REGEX_TAG)); + $m->connect('search/:search/unsubscribe', + array('action' => 'searchunsub'), + array('search' => Router::REGEX_TAG)); + + return true; + } + + /** + * Plugin version data + * + * @param array &$versions array of version data + * + * @return value + */ + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'SearchSub', + 'version' => self::VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:SearchSub', + 'rawdescription' => + // TRANS: Plugin description. + _m('Plugin to allow following all messages with a given search.')); + return true; + } + + /** + * Hook inbox delivery setup so search subscribers receive all + * notices with that search in their inbox. + * + * Currently makes no distinction between local messages and + * remote ones which happen to come in to the system. Remote + * notices that don't come in at all won't ever reach this. + * + * @param Notice $notice + * @param array $ni in/out map of profile IDs to inbox constants + * @return boolean hook result + */ + function onStartNoticeWhoGets(Notice $notice, array &$ni) + { + foreach ($notice->getTags() as $search) { + $searchsub = new SearchSub(); + $searchsub->search = $search; + $searchsub->find(); + + while ($searchsub->fetch()) { + // These constants are currently not actually used, iirc + $ni[$searchsub->profile_id] = NOTICE_INBOX_SOURCE_SUB; + } + } + return true; + } + + /** + * + * @param SearchAction $action + * @return boolean hook result + */ + function onStartTagShowContent(SearchAction $action) + { + $user = common_current_user(); + if ($user) { + $search = $action->trimmed('search'); + $searchsub = SearchSub::pkeyGet(array('search' => $search, + 'profile_id' => $user->id)); + if ($searchsub) { + $form = new SearchUnsubForm($action, $search); + } else { + $form = new SearchSubForm($action, $search); + } + $action->elementStart('div', 'entity_actions'); + $action->elementStart('ul'); + $action->elementStart('li', 'entity_subscribe'); + $form->show(); + $action->elementEnd('li'); + $action->elementEnd('ul'); + $action->elementEnd('div'); + } + return true; + } +} diff --git a/plugins/SearchSub/searchsubaction.php b/plugins/SearchSub/searchsubaction.php new file mode 100644 index 0000000000..67bc178df6 --- /dev/null +++ b/plugins/SearchSub/searchsubaction.php @@ -0,0 +1,149 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Search subscription action + * + * Takes parameters: + * + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @copyright 2008-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ +class SearchsubAction extends Action +{ + var $user; + var $search; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + if ($this->boolean('ajax')) { + StatusNet::setApi(true); + } + + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + // TRANS: Client error displayed trying to perform any request method other than POST. + // TRANS: Do not translate POST. + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + // TRANS: Client error displayed when the session token is not okay. + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + // TRANS: Client error displayed trying to subscribe when not logged in. + $this->clientError(_('Not logged in.')); + return false; + } + + // Profile to subscribe to + + $this->search = $this->arg('search'); + + if (empty($this->search)) { + // TRANS: Client error displayed trying to subscribe to a non-existing profile. + $this->clientError(_('No such profile.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + function handle($args) + { + // Throws exception on error + + SearchSub::start($this->user->getProfile(), + $this->search); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Page title when search subscription succeeded. + $this->element('title', null, _m('Subscribed')); + $this->elementEnd('head'); + $this->elementStart('body'); + $unsubscribe = new SearchUnsubForm($this, $this->search); + $unsubscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('search', + array('search' => $this->search)); + common_redirect($url, 303); + } + } +} diff --git a/plugins/SearchSub/searchsubform.php b/plugins/SearchSub/searchsubform.php new file mode 100644 index 0000000000..8078cdde1b --- /dev/null +++ b/plugins/SearchSub/searchsubform.php @@ -0,0 +1,142 @@ +. + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009-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); +} + +/** + * Form for subscribing to a user + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see UnsubscribeForm + */ + +class SearchSubForm extends Form +{ + /** + * Name of search to subscribe to + */ + + var $search = ''; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param string $search name of search to subscribe to + */ + + function __construct($out=null, $search=null) + { + parent::__construct($out); + + $this->search = $search; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'search-subscribe-' . $this->search; + } + + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + // class to match existing styles... + return 'form_user_subscribe ajax'; + } + + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('searchsub', array('search' => $this->search)); + } + + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _m('Subscribe to this search')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->hidden('subscribeto-' . $this->search, + $this->search, + 'subscribeto'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Subscribe'), 'submit', null, _m('Subscribe to this search')); + } +} diff --git a/plugins/SearchSub/searchunsubaction.php b/plugins/SearchSub/searchunsubaction.php new file mode 100644 index 0000000000..f7f006e21c --- /dev/null +++ b/plugins/SearchSub/searchunsubaction.php @@ -0,0 +1,89 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Search unsubscription action + * + * Takes parameters: + * + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @copyright 2008-2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ +class SearchunsubAction extends SearchsubAction +{ + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + function handle($args) + { + // Throws exception on error + + SearchSub::cancel($this->user->getProfile(), + $this->search); + + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + // TRANS: Page title when search unsubscription succeeded. + $this->element('title', null, _m('Unsubscribed')); + $this->elementEnd('head'); + $this->elementStart('body'); + $subscribe = new SearchSubForm($this, $this->search); + $subscribe->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('search', + array('search' => $this->search)); + common_redirect($url, 303); + } + } +} diff --git a/plugins/SearchSub/searchunsubform.php b/plugins/SearchSub/searchunsubform.php new file mode 100644 index 0000000000..296b74f4a1 --- /dev/null +++ b/plugins/SearchSub/searchunsubform.php @@ -0,0 +1,109 @@ +. + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009-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); +} + +/** + * Form for subscribing to a user + * + * @category SearchSubPlugin + * @package StatusNet + * @author Brion Vibber + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see UnsubscribeForm + */ + +class SearchUnsubForm extends SearchSubForm +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'search-unsubscribe-' . $this->search; + } + + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + // class to match existing styles... + return 'form_user_unsubscribe ajax'; + } + + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('searchunsub', array('search' => $this->search)); + } + + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _m('Unsubscribe from this search')); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _('Unsubscribe'), 'submit', null, _m('Unsubscribe from this search')); + } +} From 08b430a24736e3bd92c2b8464f825c3ec00f6b79 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 15:33:37 -0700 Subject: [PATCH 46/69] Event hook for notice search pages --- actions/noticesearch.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/actions/noticesearch.php b/actions/noticesearch.php index 4f4c7a05ba..1f43af800d 100644 --- a/actions/noticesearch.php +++ b/actions/noticesearch.php @@ -138,11 +138,14 @@ class NoticesearchAction extends SearchAction $this->elementEnd('div'); return; } - $terms = preg_split('/[\s,]+/', $q); - $nl = new SearchNoticeList($notice, $this, $terms); - $cnt = $nl->show(); - $this->pagination($page > 1, $cnt > NOTICES_PER_PAGE, - $page, 'noticesearch', array('q' => $q)); + if (Event::handle('StartNoticeSearchShowResults', array($this, $q, $notice))) { + $terms = preg_split('/[\s,]+/', $q); + $nl = new SearchNoticeList($notice, $this, $terms); + $cnt = $nl->show(); + $this->pagination($page > 1, $cnt > NOTICES_PER_PAGE, + $page, 'noticesearch', array('q' => $q)); + Event::handle('EndNoticeSearchShowResults', array($this, $q, $notice)); + } } function showScripts() From 341bef5e017d655a6c098639f08f46bc6efc6d60 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 15:34:06 -0700 Subject: [PATCH 47/69] tag -> search stuff: basic search subscription implementation in SearchSub --- plugins/SearchSub/SearchSubPlugin.php | 52 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/plugins/SearchSub/SearchSubPlugin.php b/plugins/SearchSub/SearchSubPlugin.php index 59db0ffcbb..130600a41a 100644 --- a/plugins/SearchSub/SearchSubPlugin.php +++ b/plugins/SearchSub/SearchSubPlugin.php @@ -139,31 +139,61 @@ class SearchSubPlugin extends Plugin */ function onStartNoticeWhoGets(Notice $notice, array &$ni) { - foreach ($notice->getTags() as $search) { - $searchsub = new SearchSub(); - $searchsub->search = $search; - $searchsub->find(); + // Warning: this is potentially very slow + // with a lot of searches! + $sub = new SearchSub(); + $sub->groupBy('search'); + $sub->find(); + while ($sub->fetch()) { + $search = $sub->search; - while ($searchsub->fetch()) { - // These constants are currently not actually used, iirc - $ni[$searchsub->profile_id] = NOTICE_INBOX_SOURCE_SUB; + if ($this->matchSearch($notice, $search)) { + // Match? Find all those who subscribed to this + // search term and get our delivery on... + $searchsub = new SearchSub(); + $searchsub->search = $search; + $searchsub->find(); + + while ($searchsub->fetch()) { + // These constants are currently not actually used, iirc + $ni[$searchsub->profile_id] = NOTICE_INBOX_SOURCE_SUB; + } } } return true; } /** + * Does the given notice match the given fulltext search query? * - * @param SearchAction $action + * Warning: not guaranteed to match other search engine behavior, etc. + * Currently using a basic case-insensitive substring match, which + * probably fits with the 'LIKE' search but not the default MySQL + * or Sphinx search backends. + * + * @param Notice $notice + * @param string $search + * @return boolean + */ + function matchSearch(Notice $notice, $search) + { + return (mb_stripos($notice->content, $search) !== false); + } + + /** + * + * @param NoticeSearchAction $action + * @param string $q + * @param Notice $notice * @return boolean hook result */ - function onStartTagShowContent(SearchAction $action) + function onStartNoticeSearchShowResults($action, $q, $notice) { $user = common_current_user(); if ($user) { - $search = $action->trimmed('search'); + $search = $q; $searchsub = SearchSub::pkeyGet(array('search' => $search, - 'profile_id' => $user->id)); + 'profile_id' => $user->id)); if ($searchsub) { $form = new SearchUnsubForm($action, $search); } else { From e4eb6719a5e270430b57425bcb344f0c7971254b Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 15:35:00 -0700 Subject: [PATCH 48/69] Extended profile - make birthday save --- .../ExtendedProfile/extendedprofilewidget.php | 8 +- .../profiledetailsettingsaction.php | 76 +++++++++---------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 05cfadacc3..fc3d7ca51d 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -399,7 +399,7 @@ class ExtendedProfileWidget extends Form $this->out->element('div', 'label', _m('Description')); $this->out->element('div', 'field', $field['description']); - $this->out->input( + $this->out->textarea( $id . '-description', null, isset($field['description']) ? $field['description'] : null @@ -463,6 +463,9 @@ class ExtendedProfileWidget extends Form case 'textarea': $this->out->text($this->ext->getTextValue($name)); break; + case 'date': + $this->out->text($this->ext->getTextValue($name)); + break; case 'tags': $this->out->text($this->ext->getTags()); break; @@ -506,6 +509,9 @@ class ExtendedProfileWidget extends Form case 'text': $out->input($id, null, $this->ext->getTextValue($name)); break; + case 'date': + $out->input($id, null, $this->ext->getTextValue($name)); + break; case 'textarea': $out->textarea($id, null, $this->ext->getTextValue($name)); break; diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 2357860884..d3b653bad2 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -100,6 +100,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $profile = $user->getProfile(); $simpleFieldNames = array('title', 'spouse', 'kids'); + $dateFieldNames = array('birthday'); foreach ($simpleFieldNames as $name) { $value = $this->trimmed('extprofile-' . $name); @@ -108,6 +109,15 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } } + foreach ($dateFieldNames as $name) { + $value = $this->trimmed('extprofile-' . $name); + $this->saveField( + $user, + $name, + $this->parseDate($name, $value) + ); + } + $this->savePhoneNumbers($user); $this->saveIms($user); $this->saveWebsites($user); @@ -123,6 +133,30 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } + function parseDate($fieldname, $datestr, $required = false) + { + if (empty($datestr) && $required) { + $msg = sprintf( + _m('You must supply a date for "%s".'), + $fieldname + ); + throw new Exception($msg); + } else { + $ts = strtotime($datestr); + if ($ts === false) { + throw new Exception( + sprintf( + _m('Invalid date entered for "%s": %s'), + $fieldname, + $ts + ) + ); + } + return common_sql_date($ts); + } + return null; + } + function savePhoneNumbers($user) { $phones = $this->findPhoneNumbers(); $this->removeAll($user, 'phone'); @@ -249,26 +283,10 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction foreach ($experiences as $exp) { list($company, $current, $end, $start) = array_values($exp); if (!empty($company)) { - $startTs = strtotime($start); - - if ($startTs === false) { - $msg = empty($start) ? _m('You must supply a start date.') - : sprintf(_m("Invalid start date: %s"), $start); - throw new Exception($msg); - } - - $endTs = strtotime($end); - - if ($current === 'false' && $endTs === false) { - $msg = empty($end) ? _m('You must supply an end date.') - : sprintf(_m("Invalid end date: %s"), $end); - throw new Exception($msg); - } - $expArray[] = array( 'company' => $company, - 'start' => common_sql_date($startTs), - 'end' => common_sql_date($endTs), + 'start' => $this->parseDate('Start', $start, true), + 'end' => $this->parseDate('End', $end, true), 'current' => ($current == 'false') ? false : true ); } @@ -344,31 +362,13 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction foreach ($edus as $edu) { list($school, $degree, $description, $end, $start) = array_values($edu); - if (!empty($school)) { - - $startTs = strtotime($start); - - if ($startTs === false) { - $msg = empty($start) ? _m('You must supply a start date.') - : sprintf(_m("Invalid start date: %s"), $start); - throw new Exception($msg); - } - - $endTs = strtotime($end); - - if ($endTs === false) { - $msg = empty($end) ? _m('You must supply an end date.') - : sprintf(_m("Invalid end date: %s"), $end); - throw new Exception($msg); - } - $eduArray[] = array( 'school' => $school, 'degree' => $degree, 'description' => $description, - 'start' => common_sql_date($startTs), - 'end' => common_sql_date($endTs) + 'start' => $this->parseDate('Start', $start, true), + 'end' => $this->parseDate('End', $end, true) ); } } From ce05a78d0843b7d6f1664d128d0cfd3eac5e2801 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 17:15:25 -0700 Subject: [PATCH 49/69] SubMirror wizard work in progress: Twitter option now lets you type in a username and picks the feed. Should in theory work via superfeedr or other compatible hub --- plugins/SubMirror/actions/addmirror.php | 18 +++++- plugins/SubMirror/actions/basemirror.php | 2 +- plugins/SubMirror/actions/mirrorsettings.php | 15 ++++- plugins/SubMirror/lib/addmirrorform.php | 3 +- .../SubMirror/lib/addtwittermirrorform.php | 60 +++++++++++++++++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 plugins/SubMirror/lib/addtwittermirrorform.php 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 ae09111ec4..90bbf3dffb 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -100,7 +100,20 @@ 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(); } 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/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'); + } +} From 7345de120221da277fff253e59a7259ae3b0e9cb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 17:32:25 -0700 Subject: [PATCH 50/69] Add layout divs to InfoAction's core block; fixes error display layout in Neo skin --- lib/info.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/info.php b/lib/info.php index 395c6522ec..f72bed59d6 100644 --- a/lib/info.php +++ b/lib/info.php @@ -93,8 +93,14 @@ class InfoAction extends Action function showCore() { $this->elementStart('div', array('id' => 'core')); + $this->elementStart('div', array('id' => 'aside_primary_wrapper')); + $this->elementStart('div', array('id' => 'content_wrapper')); + $this->elementStart('div', array('id' => 'site_nav_local_views_wrapper')); $this->showContentBlock(); $this->elementEnd('div'); + $this->elementEnd('div'); + $this->elementEnd('div'); + $this->elementEnd('div'); } function showHeader() From 73b5821fc33225978e51cb04c9e5ad1434bc018c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 15 Mar 2011 17:32:09 -0700 Subject: [PATCH 51/69] Submirror wizard updates... commenting out Wordpress (not sure what to do with it), LinkedIn (can't find documentation on any public RSS/Atom feeds from it, it seems that a feed of your updates doesn't currently exist?), and StatusNet (we'd need to know what to do with it; could take webfinger-style addresses or such.) Also added a commented-out Facebook section; it seems that there may not be a current way to get at public updates via RSS/Atom either, or if it is it seems really inconsistent and undocumented. (You can get at your friends updates by jumping through some hoops, but it seems we'd want to mirror a single account's own update feed?) --- plugins/SubMirror/lib/addmirrorwizard.php | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/plugins/SubMirror/lib/addmirrorwizard.php b/plugins/SubMirror/lib/addmirrorwizard.php index 7a63f8366b..920db0bc9c 100644 --- a/plugins/SubMirror/lib/addmirrorwizard.php +++ b/plugins/SubMirror/lib/addmirrorwizard.php @@ -60,22 +60,50 @@ class AddMirrorWizard extends Widget 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'), From 903ce7d10cff41b91f6fb83adc3f32bfdfc62acd Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Tue, 15 Mar 2011 21:43:28 -0400 Subject: [PATCH 52/69] Hide all unnecessarylabels from profile edit view. --- plugins/ExtendedProfile/css/profiledetail.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/ExtendedProfile/css/profiledetail.css b/plugins/ExtendedProfile/css/profiledetail.css index 797d32d415..e0650d40a1 100644 --- a/plugins/ExtendedProfile/css/profiledetail.css +++ b/plugins/ExtendedProfile/css/profiledetail.css @@ -65,6 +65,10 @@ margin-right: 8px; } +.form_settings .extended-profile label { + display: none; +} + .experience-item input[type=text], .education-item input[type=text] { float: left; } @@ -84,13 +88,10 @@ .form_settings .extended-profile label.checkbox { max-width: 100%; float: none; + display: inline; left: -20px; } -.phone-item label, .im-item label { - display: none; -} - .extended-profile select { padding-right: 2px; font-size: 0.88em; From 95d03e74e940ef8f50e26d8e7a3a4b68509dd9c2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 18:45:08 -0700 Subject: [PATCH 53/69] Extended profile - autocomplete for manager --- .../ExtendedProfile/ExtendedProfilePlugin.php | 22 +++- .../action/userautocomplete.php | 113 ++++++++++++++++++ .../ExtendedProfile/extendedprofilewidget.php | 6 + plugins/ExtendedProfile/js/profiledetail.js | 5 + .../profiledetailsettingsaction.php | 4 +- 5 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 plugins/ExtendedProfile/action/userautocomplete.php diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php index 933b43cad7..d1572ce9fd 100644 --- a/plugins/ExtendedProfile/ExtendedProfilePlugin.php +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -54,6 +54,7 @@ class ExtendedProfilePlugin extends Plugin function onAutoload($cls) { $lower = strtolower($cls); + switch ($lower) { case 'extendedprofile': @@ -62,6 +63,9 @@ class ExtendedProfilePlugin extends Plugin case 'profiledetailsettingsaction': require_once dirname(__FILE__) . '/' . $lower . '.php'; return false; + case 'userautocompleteaction': + require_once dirname(__FILE__) . '/action/' . mb_substr($lower, 0, -6) . '.php'; + return false; case 'profile_detail': require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php'; return false; @@ -81,11 +85,19 @@ class ExtendedProfilePlugin extends Plugin */ function onStartInitializeRouter($m) { - $m->connect(':nickname/detail', - array('action' => 'profiledetail'), - array('nickname' => Nickname::DISPLAY_FMT)); - $m->connect('settings/profile/detail', - array('action' => 'profiledetailsettings')); + $m->connect( + ':nickname/detail', + array('action' => 'profiledetail'), + array('nickname' => Nickname::DISPLAY_FMT) + ); + $m->connect( + '/settings/profile/finduser', + array('action' => 'Userautocomplete') + ); + $m->connect( + 'settings/profile/detail', + array('action' => 'profiledetailsettings') + ); return true; } diff --git a/plugins/ExtendedProfile/action/userautocomplete.php b/plugins/ExtendedProfile/action/userautocomplete.php new file mode 100644 index 0000000000..d4857429e0 --- /dev/null +++ b/plugins/ExtendedProfile/action/userautocomplete.php @@ -0,0 +1,113 @@ +. + * + * @category Search + * @package StatusNet + * @author Zach Copley + * @copyright 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')) { + exit(1); +} + + +class UserautocompleteAction extends Action +{ + var $query; + + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean true if nothing goes wrong + */ + function prepare($args) + { + parent::prepare($args); + $this->query = $this->trimmed('term'); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + function handle($args) + { + parent::handle($args); + $this->showResults(); + } + + /** + * Search for users matching the query and spit the results out + * as a quick-n-dirty JSON document + * + * @return void + */ + function showResults() + { + $people = array(); + + $profile = new Profile(); + + $search_engine = $profile->getSearchEngine('profile'); + $search_engine->set_sort_mode('nickname_desc'); + $search_engine->limit(0, 10); + $search_engine->query(strtolower($this->query . '*')); + + $cnt = $profile->find(); + + if ($cnt > 0) { + + $sql = 'SELECT profile.* FROM profile, user WHERE profile.id = user.id ' + . ' AND LEFT(LOWER(profile.nickname), ' + . strlen($this->query) + . ') = \'%s\' ' + . ' LIMIT 0, 10'; + + $profile->query(sprintf($sql, $this->query)); + } + + while ($profile->fetch()) { + $people[] = $profile->nickname; + } + + header('Content-Type: application/json; charset=utf-8'); + print json_encode($people); + } + + /** + * Do we need to write to the database? + * + * @return boolean true + */ + function isReadOnly($args) + { + return true; + } +} diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index fc3d7ca51d..dec0512be4 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -466,6 +466,9 @@ class ExtendedProfileWidget extends Form case 'date': $this->out->text($this->ext->getTextValue($name)); break; + case 'person': + $this->out->text($this->ext->getTextValue($name)); + break; case 'tags': $this->out->text($this->ext->getTags()); break; @@ -512,6 +515,9 @@ class ExtendedProfileWidget extends Form case 'date': $out->input($id, null, $this->ext->getTextValue($name)); break; + case 'person': + $out->input($id, null, $this->ext->getTextValue($name)); + break; case 'textarea': $out->textarea($id, null, $this->ext->getTextValue($name)); break; diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 2821bed275..7510323a43 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -89,6 +89,11 @@ var init = function() { reorder('experience-item'); reorder('education-item'); reorder('im-item'); + + $("input#extprofile-manager").autocomplete({ + source: 'finduser', + minLength: 2 }); + } $(document).ready( diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index d3b653bad2..b0590f7316 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -44,12 +44,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction function showStylesheets() { parent::showStylesheets(); $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css'); + $this->cssLink('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css'); return true; } function showScripts() { parent::showScripts(); $this->script('plugins/ExtendedProfile/js/profiledetail.js'); + $this->script('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js'); return true; } @@ -99,7 +101,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $profile = $user->getProfile(); - $simpleFieldNames = array('title', 'spouse', 'kids'); + $simpleFieldNames = array('title', 'spouse', 'kids', 'manager'); $dateFieldNames = array('birthday'); foreach ($simpleFieldNames as $name) { From b11a2faf5412a73690535df66034a06467e4208e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 19:33:05 -0700 Subject: [PATCH 54/69] Extended profile - namespace JavaScript functions --- plugins/ExtendedProfile/js/profiledetail.js | 55 ++++++++++----------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 7510323a43..98594a46b7 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -1,4 +1,6 @@ -var reorder = function(class) { +var SN_EXTENDED = SN_EXTENDED || {}; + +SN_EXTENDED.reorder = function(class) { console.log("QQQ Enter reorder"); var divs = $.find('div[class=' + class + ']'); @@ -9,7 +11,7 @@ var reorder = function(class) { $(divs).each(function(i, div) { console.log("ROW " + i); $(div).find('a.remove_row').show(); - replaceIndex(rowIndex(div), i); + SN_EXTENDED.replaceIndex(SN_EXTENDED.rowIndex(div), i); }); $this = $(divs).last().closest('tr'); @@ -23,19 +25,19 @@ var reorder = function(class) { }; -var rowIndex = function(div) { +SN_EXTENDED.rowIndex = function(div) { var idstr = $(div).attr('id'); var id = idstr.match(/\d+/); console.log("id = " + id); return id; }; -var rowCount = function(class) { +SN_EXTENDED.rowCount = function(class) { var divs = $.find('div[class=' + class + ']'); return divs.length; }; -var replaceIndex = function(elem, oldIndex, newIndex) { +SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) { $(elem).find('*').each(function() { $.each(this.attributes, function(i, attrib) { var regexp = /extprofile-.*-\d.*/; @@ -49,12 +51,12 @@ var replaceIndex = function(elem, oldIndex, newIndex) { }); } -var resetRow = function(elem) { +SN_EXTENDED.resetRow = function(elem) { $(elem).find('input').attr('value', ''); $(elem).find("select option[value='office']").attr("selected", true); } -var addRow = function() { +SN_EXTENDED.addRow = function() { var div = $(this).closest('div'); var id = $(div).attr('id'); var class = $(div).attr('class'); @@ -64,44 +66,41 @@ var addRow = function() { var tr = $(trold).removeClass('supersizeme'); var newtr = $(tr).clone(); var newIndex = parseInt(index) + 1; - replaceIndex(newtr, index, newIndex); - resetRow(newtr); + SN_EXTENDED.replaceIndex(newtr, index, newIndex); + SN_EXTENDED.resetRow(newtr); $(tr).after(newtr); - reorder(class); + SN_EXTENDED.reorder(class); }; -var removeRow = function() { +SN_EXTENDED.removeRow = function() { var div = $(this).closest('div'); var id = $(div).attr('id'); var class = $(div).attr('class'); - cnt = rowCount(class); + cnt = SN_EXTENDED.rowCount(class); console.debug("removeRow - cnt = " + cnt); if (cnt > 1) { var target = $(this).closest('tr'); target.remove(); - reorder(class); + SN_EXTENDED.reorder(class); } }; -var init = function() { - reorder('phone-item'); - reorder('experience-item'); - reorder('education-item'); - reorder('im-item'); +$(document).ready( + +function() { + + var multifields = ["phone-item", "experience-item", "education-item", "im-item"]; + + for (f in multifields) { + SN_EXTENDED.reorder(multifields[f]); + } $("input#extprofile-manager").autocomplete({ source: 'finduser', minLength: 2 }); -} + $('.add_row').live('click', SN_EXTENDED.addRow); + $('.remove_row').live('click', SN_EXTENDED.removeRow); -$(document).ready( - -function() { - init(); - $('.add_row').live('click', addRow); - $('.remove_row').live('click', removeRow); -} - -); +}); From bde85a668551251493ab5343d3a8a02db29bb87e Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 21:23:34 -0700 Subject: [PATCH 55/69] Extended profile - fix issue with JavaScript not executing in Firefox --- .../ExtendedProfile/extendedprofilewidget.php | 4 +- plugins/ExtendedProfile/js/profiledetail.js | 49 ++++++++----------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index dec0512be4..5a9eb7e4f9 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -430,7 +430,7 @@ class ExtendedProfileWidget extends Form array( 'class' => 'remove_row', 'href' => 'javascript://', - 'style' => 'display: none; ' + 'style' => 'display: none;' ), '-' ); @@ -440,7 +440,7 @@ class ExtendedProfileWidget extends Form array( 'class' => 'add_row', 'href' => 'javascript://', - 'style' => 'display: none; ' + 'style' => 'display: none;' ), '+' ); diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 98594a46b7..9fb935f153 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -1,39 +1,32 @@ var SN_EXTENDED = SN_EXTENDED || {}; -SN_EXTENDED.reorder = function(class) { - console.log("QQQ Enter reorder"); +SN_EXTENDED.reorder = function(cls) { - var divs = $.find('div[class=' + class + ']'); - console.log('divs length = ' + divs.length); - - $(divs).find('a').hide(); + var divs = $('div[class=' + cls + ']'); $(divs).each(function(i, div) { - console.log("ROW " + i); $(div).find('a.remove_row').show(); SN_EXTENDED.replaceIndex(SN_EXTENDED.rowIndex(div), i); }); - $this = $(divs).last().closest('tr'); - $this.addClass('supersizeme'); + var lastDiv = $(divs).last().closest('tr'); + lastDiv.addClass('supersizeme'); $(divs).last().find('a.add_row').show(); if (divs.length == 1) { $(divs).find('a.remove_row').hide(); } - }; SN_EXTENDED.rowIndex = function(div) { var idstr = $(div).attr('id'); var id = idstr.match(/\d+/); - console.log("id = " + id); return id; }; -SN_EXTENDED.rowCount = function(class) { - var divs = $.find('div[class=' + class + ']'); +SN_EXTENDED.rowCount = function(cls) { + var divs = $.find('div[class=' + cls + ']'); return divs.length; }; @@ -45,7 +38,6 @@ SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) { var match = value.match(regexp); if (match !== null) { attrib.value = value.replace("-" + oldIndex, "-" + newIndex); - console.log('match: oldIndex = ' + oldIndex + ' newIndex = ' + newIndex + ' name = ' + attrib.name + ' value = ' + attrib.value); } }); }); @@ -54,41 +46,40 @@ SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) { SN_EXTENDED.resetRow = function(elem) { $(elem).find('input').attr('value', ''); $(elem).find("select option[value='office']").attr("selected", true); -} +}; SN_EXTENDED.addRow = function() { var div = $(this).closest('div'); - var id = $(div).attr('id'); - var class = $(div).attr('class'); + var id = div.attr('id'); + var cls = div.attr('class'); var index = id.match(/\d+/); - console.log("Current row = " + index + ', class = ' + class); - var trold = $(this).closest('tr'); - var tr = $(trold).removeClass('supersizeme'); - var newtr = $(tr).clone(); var newIndex = parseInt(index) + 1; + var newtr = $(div).closest('tr').clone(); SN_EXTENDED.replaceIndex(newtr, index, newIndex); + $(newtr).removeClass('supersizeme'); SN_EXTENDED.resetRow(newtr); - $(tr).after(newtr); - SN_EXTENDED.reorder(class); + $(div).closest('tr').after(newtr); + SN_EXTENDED.reorder(cls); }; SN_EXTENDED.removeRow = function() { var div = $(this).closest('div'); var id = $(div).attr('id'); - var class = $(div).attr('class'); + var cls = $(div).attr('class'); - cnt = SN_EXTENDED.rowCount(class); - console.debug("removeRow - cnt = " + cnt); + var cnt = SN_EXTENDED.rowCount(cls); if (cnt > 1) { var target = $(this).closest('tr'); target.remove(); - SN_EXTENDED.reorder(class); + SN_EXTENDED.reorder(cls); } }; -$(document).ready( +$(document).ready(function() { -function() { + $("input#extprofile-manager").autocomplete({ + source: 'finduser', + minLength: 2 }); var multifields = ["phone-item", "experience-item", "education-item", "im-item"]; From 40e1fc82466cf370d05ff7e380e3a99964a851c2 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 21:55:47 -0700 Subject: [PATCH 56/69] Extended profile - prettier date formatting --- .../ExtendedProfile/extendedprofilewidget.php | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 5a9eb7e4f9..ebf4c0422c 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -295,10 +295,22 @@ class ExtendedProfileWidget extends Form $this->out->elementStart('div', 'experience-item'); $this->out->element('div', 'label', _m('Company')); $this->out->element('div', 'field', $field['company']); + $this->out->element('div', 'label', _m('Start')); - $this->out->element('div', array('class' => 'field date'), $field['start']); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($field['start']) + ) + ); $this->out->element('div', 'label', _m('End')); - $this->out->element('div', array('class' => 'field date'), $field['end']); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($field['end']) + ) + ); + if ($field['current']) { $this->out->element( 'div', @@ -331,14 +343,14 @@ class ExtendedProfileWidget extends Form $this->out->input( $id . '-start', null, - isset($field['start']) ? $field['start'] : null + isset($field['start']) ? date('j M Y', strtotime($field['start'])) : null ); $this->out->element('div', 'label', _m('End')); $this->out->input( $id . '-end', null, - isset($field['end']) ? $field['end'] : null + isset($field['end']) ? date('j M Y', strtotime($field['end'])) : null ); $this->out->hidden( $id . '-current', @@ -366,9 +378,19 @@ class ExtendedProfileWidget extends Form $this->out->element('div', 'label', _m('Description')); $this->out->element('div', 'field', $field['description']); $this->out->element('div', 'label', _m('Start')); - $this->out->element('div', array('class' => 'field date'), $field['start']); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($field['start']) + ) + ); $this->out->element('div', 'label', _m('End')); - $this->out->element('div', array('class' => 'field date'), $field['end']); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($field['end']) + ) + ); $this->out->elementEnd('div'); } @@ -409,14 +431,14 @@ class ExtendedProfileWidget extends Form $this->out->input( $id . '-start', null, - isset($field['start']) ? $field['start'] : null + isset($field['start']) ? date('j M Y', strtotime($field['start'])) : null ); $this->out->element('div', 'label', _m('End')); $this->out->input( $id . '-end', null, - isset($field['end']) ? $field['end'] : null + isset($field['end']) ? date('j M Y', strtotime($field['end'])) : null ); $this->showMultiControls(); @@ -464,7 +486,11 @@ class ExtendedProfileWidget extends Form $this->out->text($this->ext->getTextValue($name)); break; case 'date': - $this->out->text($this->ext->getTextValue($name)); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($this->ext->getTextValue($name))) + ); break; case 'person': $this->out->text($this->ext->getTextValue($name)); @@ -513,7 +539,11 @@ class ExtendedProfileWidget extends Form $out->input($id, null, $this->ext->getTextValue($name)); break; case 'date': - $out->input($id, null, $this->ext->getTextValue($name)); + $out->input( + $id, + null, + date('j M Y', strtotime($this->ext->getTextValue($name))) + ); break; case 'person': $out->input($id, null, $this->ext->getTextValue($name)); From b9065d7bc19e55309204e8beb485b9cad496f0da Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 23:02:05 -0700 Subject: [PATCH 57/69] Extended profile - add fancy datepicker widgets --- plugins/ExtendedProfile/js/profiledetail.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 9fb935f153..d24b4aabe8 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -81,6 +81,10 @@ $(document).ready(function() { source: 'finduser', minLength: 2 }); + $.datepicker.formatDate('yy-mm-dd'); + + $("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' }); + var multifields = ["phone-item", "experience-item", "education-item", "im-item"]; for (f in multifields) { From bda9d43c56c2282583bd3a870219a4bb7a128800 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 23:51:28 -0700 Subject: [PATCH 58/69] Extended profile - add fancy JQuery UI confirm dialog when deleting items --- .../ExtendedProfile/extendedprofilewidget.php | 7 ++++ plugins/ExtendedProfile/js/profiledetail.js | 36 ++++++++++++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index ebf4c0422c..5be149a0cb 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -77,6 +77,13 @@ class ExtendedProfileWidget extends Form */ public function formData() { + // For JQuery UI modal dialog + $this->out->elementStart( + 'div', + array('id' => 'confirm-dialog', 'title' => 'Confirmation Required') + ); + $this->out->text('Really delete this entry?'); + $this->out->elementEnd('div'); $this->showSections(); } diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index d24b4aabe8..fbcefe730d 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -44,7 +44,7 @@ SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) { } SN_EXTENDED.resetRow = function(elem) { - $(elem).find('input').attr('value', ''); + $(elem).find('input, textarea').attr('value', ''); $(elem).find("select option[value='office']").attr("selected", true); }; @@ -63,27 +63,47 @@ SN_EXTENDED.addRow = function() { }; SN_EXTENDED.removeRow = function() { + var div = $(this).closest('div'); var id = $(div).attr('id'); var cls = $(div).attr('class'); - var cnt = SN_EXTENDED.rowCount(cls); + + var that = this; + + $("#confirm-dialog").dialog({ + buttons : { + "Confirm" : function() { + var target = $(that).closest('tr'); + target.fadeOut("slow", function() { + $(that).remove(); + }); + SN_EXTENDED.reorder(cls); + $(this).dialog("close"); + }, + "Cancel" : function() { + $(this).dialog("close"); + } + } + }); + if (cnt > 1) { - var target = $(this).closest('tr'); - target.remove(); - SN_EXTENDED.reorder(cls); + $("#confirm-dialog").dialog("open"); } }; $(document).ready(function() { + $("#confirm-dialog").dialog({ + autoOpen: false, + modal: true + }); + $("input#extprofile-manager").autocomplete({ source: 'finduser', minLength: 2 }); - $.datepicker.formatDate('yy-mm-dd'); - - $("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' }); + $("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' }); var multifields = ["phone-item", "experience-item", "education-item", "im-item"]; From d51625ab3ddc26a8819c3eb0e1682b4767ca057f Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 15 Mar 2011 23:58:45 -0700 Subject: [PATCH 59/69] Extended profile - hide add button when not needed (regression) --- plugins/ExtendedProfile/js/profiledetail.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index fbcefe730d..7960cee83f 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -5,6 +5,7 @@ SN_EXTENDED.reorder = function(cls) { var divs = $('div[class=' + cls + ']'); $(divs).each(function(i, div) { + $(div).find('a.add_row').hide(); $(div).find('a.remove_row').show(); SN_EXTENDED.replaceIndex(SN_EXTENDED.rowIndex(div), i); }); From 665dd1de78785e6a9176c5533f5a29bbc33166d5 Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Wed, 16 Mar 2011 03:09:06 -0400 Subject: [PATCH 60/69] Remove supersizeme class as appropriate. --- plugins/ExtendedProfile/js/profiledetail.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 7960cee83f..24191d4ad9 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -55,9 +55,8 @@ SN_EXTENDED.addRow = function() { var cls = div.attr('class'); var index = id.match(/\d+/); var newIndex = parseInt(index) + 1; - var newtr = $(div).closest('tr').clone(); + var newtr = $(div).closest('tr').removeClass('supersizeme').clone(); SN_EXTENDED.replaceIndex(newtr, index, newIndex); - $(newtr).removeClass('supersizeme'); SN_EXTENDED.resetRow(newtr); $(div).closest('tr').after(newtr); SN_EXTENDED.reorder(cls); From a715b133ffcda328f4dbc67e9490ccedfe4c0f77 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 00:23:34 -0700 Subject: [PATCH 61/69] Extended profile - fix regression whereby if there was only one item, you could still delete it! --- plugins/ExtendedProfile/js/profiledetail.js | 10 +++++----- .../ExtendedProfile/profiledetailsettingsaction.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index 7960cee83f..f475046385 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -68,19 +68,17 @@ SN_EXTENDED.removeRow = function() { var div = $(this).closest('div'); var id = $(div).attr('id'); var cls = $(div).attr('class'); - var cnt = SN_EXTENDED.rowCount(cls); - var that = this; $("#confirm-dialog").dialog({ buttons : { "Confirm" : function() { + $(this).dialog("close"); var target = $(that).closest('tr'); target.fadeOut("slow", function() { - $(that).remove(); + $(target).remove(); + SN_EXTENDED.reorder(cls); }); - SN_EXTENDED.reorder(cls); - $(this).dialog("close"); }, "Cancel" : function() { $(this).dialog("close"); @@ -88,6 +86,8 @@ SN_EXTENDED.removeRow = function() { } }); + var cnt = SN_EXTENDED.rowCount(cls); + if (cnt > 1) { $("#confirm-dialog").dialog("open"); } diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index b0590f7316..01a8fa9c7b 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -60,7 +60,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction // CSRF protection $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { - $this->show_form( + $this->showForm( _m( 'There was a problem with your session token. ' . 'Try again, please.' From b80b9f31fc3c2941c38d1977a554ff777c7e3399 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 00:25:02 -0700 Subject: [PATCH 62/69] Small smattering of pixie dust --- plugins/ExtendedProfile/js/profiledetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index ecc89f8151..c96732b835 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -16,7 +16,7 @@ SN_EXTENDED.reorder = function(cls) { $(divs).last().find('a.add_row').show(); if (divs.length == 1) { - $(divs).find('a.remove_row').hide(); + $(divs).find('a.remove_row').fadeOut("slow"); } }; From 724dba668af45690a9122f49e24d89f8c3e6ede7 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 00:34:00 -0700 Subject: [PATCH 63/69] Extended profile - allow adding more than one website --- plugins/ExtendedProfile/extendedprofilewidget.php | 2 +- plugins/ExtendedProfile/js/profiledetail.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 5be149a0cb..25a0ab8b20 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -267,7 +267,7 @@ class ExtendedProfileWidget extends Form $this->out->elementStart( 'div', array( 'id' => $id . '-edit', - 'class' => 'website-edit' + 'class' => 'website-item' ) ); $this->out->input( diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index c96732b835..e1b06562e0 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -105,7 +105,7 @@ $(document).ready(function() { $("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' }); - var multifields = ["phone-item", "experience-item", "education-item", "im-item"]; + var multifields = ["phone-item", "experience-item", "education-item", "im-item", 'website-item']; for (f in multifields) { SN_EXTENDED.reorder(multifields[f]); From 974d0c48f6234a99b0d28f6c54033434cace877c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 00:50:43 -0700 Subject: [PATCH 64/69] Extended profile - don't check end date if experience entry has current checked --- plugins/ExtendedProfile/profiledetailsettingsaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 01a8fa9c7b..13bf43b927 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -288,7 +288,7 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $expArray[] = array( 'company' => $company, 'start' => $this->parseDate('Start', $start, true), - 'end' => $this->parseDate('End', $end, true), + 'end' => ($current == 'false') ? $this->parseDate('End', $end, true) : null, 'current' => ($current == 'false') ? false : true ); } From bb087a965009fd93a5c02a9e10ab90adcc6b7963 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 01:09:38 -0700 Subject: [PATCH 65/69] Extended profile - fix some issues saving and displaying dates --- plugins/ExtendedProfile/extendedprofile.php | 9 +++++++++ plugins/ExtendedProfile/extendedprofilewidget.php | 15 +++++++++------ .../profiledetailsettingsaction.php | 6 +++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php index 673680f027..fa632e5073 100644 --- a/plugins/ExtendedProfile/extendedprofile.php +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -98,6 +98,15 @@ class ExtendedProfile } } + function getDateValue($name) { + $key = strtolower($name); + if (array_key_exists($key, $this->fields)) { + return $this->fields[$key][0]->date; + } else { + return null; + } + } + // XXX: getPhones, getIms, and getWebsites pretty much do the same thing, // so refactor. function getPhones() diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 25a0ab8b20..622beff32f 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -493,11 +493,14 @@ class ExtendedProfileWidget extends Form $this->out->text($this->ext->getTextValue($name)); break; case 'date': - $this->out->element( - 'div', - array('class' => 'field date'), - date('j M Y', strtotime($this->ext->getTextValue($name))) - ); + $value = $this->ext->getDateValue($name); + if (!empty($value)) { + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($value)) + ); + } break; case 'person': $this->out->text($this->ext->getTextValue($name)); @@ -549,7 +552,7 @@ class ExtendedProfileWidget extends Form $out->input( $id, null, - date('j M Y', strtotime($this->ext->getTextValue($name))) + date('j M Y', strtotime($this->ext->getDateValue($name))) ); break; case 'person': diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 13bf43b927..719717e088 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -113,10 +113,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction foreach ($dateFieldNames as $name) { $value = $this->trimmed('extprofile-' . $name); + $dateVal = $this->parseDate($name, $value); $this->saveField( $user, $name, - $this->parseDate($name, $value) + null, + null, + null, + $dateVal ); } From 17afe068059f426db04c4b2bf85d31bf4e03953a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 01:23:47 -0700 Subject: [PATCH 66/69] Extended profile - linkify related URLs added by the user --- .../ExtendedProfile/extendedprofilewidget.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 622beff32f..d4a3f21a52 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -79,7 +79,7 @@ class ExtendedProfileWidget extends Form { // For JQuery UI modal dialog $this->out->elementStart( - 'div', + 'div', array('id' => 'confirm-dialog', 'title' => 'Confirmation Required') ); $this->out->text('Really delete this entry?'); @@ -180,7 +180,19 @@ class ExtendedProfileWidget extends Form protected function showWebsite($name, $field) { $this->out->elementStart('div', array('class' => 'website-display')); - $this->out->text($field['value']); + + $url = $field['value']; + + $this->out->element( + "a", + array( + 'href' => $url, + 'class' => 'extended-profile-link', + 'target' => "_blank" + ), + $url + ); + if (!empty($field['rel'])) { $this->out->text(' (' . $field['rel'] . ')'); } From 82023d388a5a060b2b10964bf0cace27fa0a1ae5 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 01:26:53 -0700 Subject: [PATCH 67/69] Extended profile - don't show empty company entry in view --- .../ExtendedProfile/extendedprofilewidget.php | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index d4a3f21a52..fb519cfffc 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -313,29 +313,32 @@ class ExtendedProfileWidget extends Form { $this->out->elementStart('div', 'experience-item'); $this->out->element('div', 'label', _m('Company')); - $this->out->element('div', 'field', $field['company']); - $this->out->element('div', 'label', _m('Start')); - $this->out->element( - 'div', - array('class' => 'field date'), - date('j M Y', strtotime($field['start']) - ) - ); - $this->out->element('div', 'label', _m('End')); - $this->out->element( - 'div', - array('class' => 'field date'), - date('j M Y', strtotime($field['end']) - ) - ); + if (!empty($field['company'])) { + $this->out->element('div', 'field', $field['company']); - if ($field['current']) { + $this->out->element('div', 'label', _m('Start')); $this->out->element( 'div', - array('class' => 'field current'), - '(' . _m('Current') . ')' + array('class' => 'field date'), + date('j M Y', strtotime($field['start']) + ) ); + $this->out->element('div', 'label', _m('End')); + $this->out->element( + 'div', + array('class' => 'field date'), + date('j M Y', strtotime($field['end']) + ) + ); + + if ($field['current']) { + $this->out->element( + 'div', + array('class' => 'field current'), + '(' . _m('Current') . ')' + ); + } } $this->out->elementEnd('div'); } From 365b7ab56ea70f6fa70ab46358cd794c7892683b Mon Sep 17 00:00:00 2001 From: Samantha Doherty Date: Wed, 16 Mar 2011 04:32:33 -0400 Subject: [PATCH 68/69] More style for profile edit. --- plugins/ExtendedProfile/css/profiledetail.css | 28 +++++++++++++++++-- .../ExtendedProfile/extendedprofilewidget.php | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/ExtendedProfile/css/profiledetail.css b/plugins/ExtendedProfile/css/profiledetail.css index e0650d40a1..3af9bcba4a 100644 --- a/plugins/ExtendedProfile/css/profiledetail.css +++ b/plugins/ExtendedProfile/css/profiledetail.css @@ -69,6 +69,22 @@ display: none; } +.extended-profile textarea { + width: 280px; +} + +.extended-profile input[type=text] { + width: 280px; +} + +.extended-profile .phone-item input[type=text], .extended-profile .im-item input[type=text], .extended-profile .website-item input[type=text] { + width: 175px; +} + +.extended-profile input.hasDatepicker { + width: 100px; +} + .experience-item input[type=text], .education-item input[type=text] { float: left; } @@ -101,7 +117,6 @@ display: block; height: 16px; width: 16px; - line-height: 4em; overflow: hidden; background-image: url('../../../theme/rebase/images/icons/icons-01.gif'); background-repeat: no-repeat; @@ -112,14 +127,18 @@ float: right; position: relative; top: 6px; + line-height: 4em; } .extended-profile a.add_row { clear: both; position: relative; top: 6px; - left: 2px; + left: 2px; background-position: 0px -1186px; + width: 120px; + padding-left: 20px; + line-height: 1.2em; } #content table.extended-profile .supersizeme th { @@ -131,6 +150,11 @@ width: 100%; } +#profiledetailsettings .education-item textarea { + float: left; + margin-bottom: 8px; +} + #profiledetailsettings tr:last-child .experience-item, #profiledetailsettings tr:last-child .education-item { margin-bottom: 0px; } diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index fb519cfffc..7f62d3eae4 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -486,7 +486,7 @@ class ExtendedProfileWidget extends Form 'href' => 'javascript://', 'style' => 'display: none;' ), - '+' + 'Add another item' ); } From 368cfd8facac4e69a00b1d6c0e7eb43299e61cf4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 16 Mar 2011 02:41:32 -0700 Subject: [PATCH 69/69] * Extended profile - make cloned datefields work correctly with calendar popup * Validate URLs --- .../ExtendedProfile/extendedprofilewidget.php | 1 + plugins/ExtendedProfile/js/profiledetail.js | 23 +++++++++++++++++++ .../profiledetailsettingsaction.php | 15 +++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 7f62d3eae4..1ef6440ed6 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -369,6 +369,7 @@ class ExtendedProfileWidget extends Form ); $this->out->element('div', 'label', _m('End')); + $this->out->input( $id . '-end', null, diff --git a/plugins/ExtendedProfile/js/profiledetail.js b/plugins/ExtendedProfile/js/profiledetail.js index e1b06562e0..99a3f78a43 100644 --- a/plugins/ExtendedProfile/js/profiledetail.js +++ b/plugins/ExtendedProfile/js/profiledetail.js @@ -46,7 +46,13 @@ SN_EXTENDED.replaceIndex = function(elem, oldIndex, newIndex) { SN_EXTENDED.resetRow = function(elem) { $(elem).find('input, textarea').attr('value', ''); + $(elem).find('input').removeAttr('disabled'); $(elem).find("select option[value='office']").attr("selected", true); + $(elem).find("input:checkbox").attr('checked', false); + $(elem).find("input[name$=-start], input[name$=-end]").each(function() { + $(this).removeClass('hasDatepicker'); + $(this).datepicker({ dateFormat: 'd M yy' }); + }); }; SN_EXTENDED.addRow = function() { @@ -118,4 +124,21 @@ $(document).ready(function() { $('.add_row').live('click', SN_EXTENDED.addRow); $('.remove_row').live('click', SN_EXTENDED.removeRow); + $('input:checkbox[name$=current]').each(function() { + var input = $(this).parent().siblings('input[id$=-end]'); + if ($(this).is(':checked')) { + $(input).attr('disabled', 'true'); + } + }); + + $('input:checkbox[name$=current]').live('click', function() { + var input = $(this).parent().siblings('input[id$=-end]'); + if ($(this).is(':checked')) { + $(input).val(''); + $(input).attr('disabled', 'true'); + } else { + $(input).removeAttr('disabled'); + } + }); + }); diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php index 719717e088..7b03f247ed 100644 --- a/plugins/ExtendedProfile/profiledetailsettingsaction.php +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -262,6 +262,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $this->removeAll($user, 'website'); $i = 0; foreach($sites as $site) { + + if (!Validate::uri( + $site['value'], + array('allowed_schemes' => array('http', 'https'))) + ) { + throw new Exception(sprintf(_m('Invalid URL: %s'), $site['value'])); + } + if (!empty($site['value'])) { ++$i; $this->saveField( @@ -287,7 +295,12 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $expArray = array(); foreach ($experiences as $exp) { - list($company, $current, $end, $start) = array_values($exp); + if (sizeof($experiences) == 4) { + list($company, $current, $end, $start) = array_values($exp); + } else { + $end = null; + list($company, $current, $start) = array_values($exp); + } if (!empty($company)) { $expArray[] = array( 'company' => $company,