From 7a97243abfb64838b2108124b91e72a43b08bf3b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 2 Feb 2011 16:23:24 -0800 Subject: [PATCH 1/4] ExtendedProfile plugin initial checkin: stub mockup page --- .../ExtendedProfile/ExtendedProfilePlugin.php | 98 ++++++++++++++ plugins/ExtendedProfile/extendedprofile.php | 120 ++++++++++++++++++ .../ExtendedProfile/extendedprofilewidget.php | 72 +++++++++++ plugins/ExtendedProfile/profiledetail.css | 22 ++++ .../ExtendedProfile/profiledetailaction.php | 71 +++++++++++ .../profiledetailsettingsaction.php | 63 +++++++++ 6 files changed, 446 insertions(+) create mode 100644 plugins/ExtendedProfile/ExtendedProfilePlugin.php create mode 100644 plugins/ExtendedProfile/extendedprofile.php create mode 100644 plugins/ExtendedProfile/extendedprofilewidget.php create mode 100644 plugins/ExtendedProfile/profiledetail.css create mode 100644 plugins/ExtendedProfile/profiledetailaction.php create mode 100644 plugins/ExtendedProfile/profiledetailsettingsaction.php diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php new file mode 100644 index 0000000000..36c8eaa232 --- /dev/null +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -0,0 +1,98 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Extra profile bio-like fields + * + * @package ExtendedProfilePlugin + * @maintainer Brion Vibber + */ +class ExtendedProfilePlugin extends Plugin +{ + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'ExtendedProfile', + 'version' => STATUSNET_VERSION, + 'author' => 'Brion Vibber', + 'homepage' => 'http://status.net/wiki/Plugin:ExtendedProfile', + 'rawdescription' => + _m('UI extensions for additional profile fields.')); + + return true; + } + + /** + * Autoloader + * + * Loads our classes if they're requested. + * + * @param string $cls Class requested + * + * @return boolean hook return + */ + function onAutoload($cls) + { + $lower = strtolower($cls); + switch ($lower) + { + case 'extendedprofile': + case 'extendedprofilewidget': + case 'profiledetailaction': + case 'profiledetailsettingsaction': + require_once dirname(__FILE__) . '/' . $lower . '.php'; + return false; + default: + return true; + } + } + + /** + * Add paths to the router table + * + * Hook for RouterInitialized event. + * + * @param Net_URL_Mapper $m URL mapper + * + * @return boolean hook return + */ + function onStartInitializeRouter($m) + { + $m->connect(':nickname/detail', + array('action' => 'profiledetail'), + array('nickname' => Nickname::DISPLAY_FMT)); + $m->connect('settings/profile/detail', + array('action' => 'profiledetailsettings')); + + return true; + } + + function onEndAccountSettingsProfileMenuItem($widget, $menu) + { + // TRANS: Link title attribute in user account settings menu. + $title = _('Change additional profile settings'); + // TRANS: Link description in user account settings menu. + $widget->showMenuItem('profiledetailsettings',_m('Details'),$title); + return true; + } +} diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php new file mode 100644 index 0000000000..18fef20331 --- /dev/null +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -0,0 +1,120 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ExtendedProfile +{ + function getSections() + { + return array( + 'basic' => array( + 'label' => _m('Personal'), + 'fields' => array( + 'fullname' => array( + 'label' => _m('Full name'), + 'profile' => 'fullname', + 'vcard' => 'fn', + ), + 'title' => array( + 'label' => _m('Title'), + 'vcard' => 'title', + ), + 'manager' => array( + 'label' => _m('Manager'), + 'type' => 'person', + 'vcard' => 'x-manager', + ), + 'location' => array( + 'label' => _m('Location'), + 'profile' => 'location' + ), + 'bio' => array( + 'label' => _m('Bio'), + 'type' => 'textarea', + 'profile' => 'bio', + ), + 'tags' => array( + 'label' => _m('Tags'), + 'type' => 'tags', + 'profile' => 'tags', + ), + ), + ), + 'contact' => array( + 'label' => _m('Contact'), + 'fields' => array( + 'phone' => array( + 'label' => _m('Phone'), + 'type' => 'phone', + 'multi' => true, + 'vcard' => 'tel', + ), + 'im' => array( + 'label' => _m('IM'), + 'type' => 'im', + 'multi' => true, + ), + 'website' => array( + 'label' => _m('Websites'), + 'type' => 'website', + 'multi' => true, + ), + ), + ), + 'personal' => array( + 'label' => _m('Personal'), + 'fields' => array( + 'birthday' => array( + 'label' => _m('Birthday'), + 'type' => 'date', + 'vcard' => 'bday', + ), + 'spouse' => array( + 'label' => _m('Spouse\'s name'), + 'vcard' => 'x-spouse', + ), + 'kids' => array( + 'label' => _m('Kids\' names') + ), + ), + ), + 'experience' => array( + 'label' => _m('Work experience'), + 'fields' => array( + 'experience' => array( + 'type' => 'experience', + 'label' => _m('Employer'), + ), + ), + ), + 'education' => array( + 'label' => _m('Education'), + 'fields' => array( + 'education' => array( + 'type' => 'education', + 'label' => _m('Institution'), + ), + ), + ), + ); + } +} diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php new file mode 100644 index 0000000000..2ec99b1992 --- /dev/null +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -0,0 +1,72 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ExtendedProfileWidget extends Widget +{ + const EDITABLE=true; + + protected $profile; + protected $ext; + + public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false) + { + parent::__construct($out); + + $this->profile = $profile; + $this->ext = new ExtendedProfile($this->profile); + + $this->editable = $editable; + } + + public function show() + { + $sections = $this->ext->getSections(); + foreach ($sections as $name => $section) { + $this->showExtendedProfileSection($name, $section); + } + } + + protected function showExtendedProfileSection($name, $section) + { + $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); + } + $this->out->elementEnd('table'); + } + + protected function showExtendedProfileField($name, $field) + { + $this->out->elementStart('tr'); + + $this->out->element('th', null, $field['label']); + + $this->out->elementStart('td'); + // @fixme field value + $this->out->text($name); + $this->out->elementEnd('td'); + + $this->out->elementEnd('tr'); + } +} diff --git a/plugins/ExtendedProfile/profiledetail.css b/plugins/ExtendedProfile/profiledetail.css new file mode 100644 index 0000000000..836b647a10 --- /dev/null +++ b/plugins/ExtendedProfile/profiledetail.css @@ -0,0 +1,22 @@ +/* Note the #content is only needed to override weird crap in default styles */ + +#content table.extended-profile { + width: 100%; + border-collapse: separate; + border-spacing: 8px; +} +#content table.extended-profile th { + color: #777; + background-color: #eee; + width: 150px; + + padding-top: 0; /* override bizarre theme defaults */ + + text-align: right; + padding-right: 8px; +} +#content table.extended-profile td { + padding: 0; /* override bizarre theme defaults */ + + padding-left: 8px; +} \ No newline at end of file diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php new file mode 100644 index 0000000000..8c907eec2a --- /dev/null +++ b/plugins/ExtendedProfile/profiledetailaction.php @@ -0,0 +1,71 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ProfileDetailAction extends ProfileAction +{ + function isReadOnly($args) + { + return true; + } + + function title() + { + 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(); + if ($cur && $cur->id == $this->profile->id) { // your own page + $this->elementStart('div', 'entity_actions'); + $this->elementStart('li', 'entity_edit'); + $this->element('a', array('href' => common_local_url('profiledetailsettings'), + // TRANS: Link title for link on user profile. + 'title' => _m('Edit extended profile settings')), + // TRANS: Link text for link on user profile. + _m('Edit')); + $this->elementEnd('li'); + $this->elementEnd('div'); + } + + $widget = new ExtendedProfileWidget($this, $this->profile, ExtendedProfileWidget::EDITABLE); + $widget->show(); + } +} diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php new file mode 100644 index 0000000000..77d755c0b0 --- /dev/null +++ b/plugins/ExtendedProfile/profiledetailsettingsaction.php @@ -0,0 +1,63 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ProfileDetailSettingsAction extends AccountSettingsAction +{ + + function title() + { + return _m('Extended profile settings'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + function getInstructions() + { + // TRANS: Usage instructions for profile settings. + return _('You can update your personal profile info here '. + 'so people know more about you.'); + } + + function showStylesheets() { + parent::showStylesheets(); + $this->cssLink('plugins/ExtendedProfile/profiledetail.css'); + return true; + } + + function handle($args) + { + $this->showPage(); + } + + function showContent() + { + $cur = common_current_user(); + $profile = $cur->getProfile(); + + $widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE); + $widget->show(); + } +} From 59f47349853fbd20ce74fbb2d6cb8f4b30e53f6f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 2 Feb 2011 16:38:01 -0800 Subject: [PATCH 2/4] Edit page placeholder, link on main profile to details --- .../ExtendedProfile/ExtendedProfilePlugin.php | 10 ++++++ .../ExtendedProfile/extendedprofilewidget.php | 34 +++++++++++++++++-- .../ExtendedProfile/profiledetailaction.php | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php index 36c8eaa232..c981a7e2fb 100644 --- a/plugins/ExtendedProfile/ExtendedProfilePlugin.php +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -95,4 +95,14 @@ class ExtendedProfilePlugin extends Plugin $widget->showMenuItem('profiledetailsettings',_m('Details'),$title); return true; } + + function onEndProfilePageProfileElements(HTMLOutputter $out, Profile $profile) { + $user = User::staticGet('id', $profile->id); + if ($user) { + $url = common_local_url('profiledetail', array('nickname' => $user->nickname)); + $out->element('a', array('href' => $url), _m('More details...')); + } + return; + } + } diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php index 2ec99b1992..bf9b4056cd 100644 --- a/plugins/ExtendedProfile/extendedprofilewidget.php +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -63,10 +63,40 @@ class ExtendedProfileWidget extends Widget $this->out->element('th', null, $field['label']); $this->out->elementStart('td'); - // @fixme field value - $this->out->text($name); + if ($this->editable) { + $this->showEditableField($name, $field); + } else { + $this->showFieldValue($name, $field); + } $this->out->elementEnd('td'); $this->out->elementEnd('tr'); } + + protected function showFieldValue($name, $field) + { + $this->out->text($name); + } + + 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"); + } + } } diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php index 8c907eec2a..a4bb12956e 100644 --- a/plugins/ExtendedProfile/profiledetailaction.php +++ b/plugins/ExtendedProfile/profiledetailaction.php @@ -65,7 +65,7 @@ class ProfileDetailAction extends ProfileAction $this->elementEnd('div'); } - $widget = new ExtendedProfileWidget($this, $this->profile, ExtendedProfileWidget::EDITABLE); + $widget = new ExtendedProfileWidget($this, $this->profile); $widget->show(); } } From d1a96dc7afe487b4adf757b75f7060ff767609ad Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 2 Feb 2011 17:29:34 -0800 Subject: [PATCH 3/4] work in progress: prepping for storage of extended profile details --- plugins/ExtendedProfile/Profile_detail.php | 16 ++++++++++++++++ plugins/ExtendedProfile/extendedprofile.php | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 plugins/ExtendedProfile/Profile_detail.php diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php new file mode 100644 index 0000000000..ae8cdcee50 --- /dev/null +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -0,0 +1,16 @@ +profile = $profile; + $this->sections = $this->getSections(); + $this->fields = $this->loadFields(); + } + + function loadFields() + { + $detail = new Profile_detail(); + $detail->profile_id = $this->profile->id; + $detail->find(); + + while ($detail->get()) { + $fields[$detail->field][] = clone($detail); + } + return $fields; + } + function getSections() { return array( From 672eb17e942709b6b315c89b78f42aed47dfcb85 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 3 Feb 2011 17:15:12 -0800 Subject: [PATCH 4/4] Work in progress: partway through making profile_detail DB-accessible --- .../ExtendedProfile/ExtendedProfilePlugin.php | 13 ++ plugins/ExtendedProfile/Profile_detail.php | 144 +++++++++++++++++- 2 files changed, 152 insertions(+), 5 deletions(-) diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php index c981a7e2fb..3f541c0008 100644 --- a/plugins/ExtendedProfile/ExtendedProfilePlugin.php +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -62,6 +62,9 @@ class ExtendedProfilePlugin extends Plugin case 'profiledetailsettingsaction': require_once dirname(__FILE__) . '/' . $lower . '.php'; return false; + case 'profile_detail': + require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php'; + return false; default: return true; } @@ -87,6 +90,16 @@ class ExtendedProfilePlugin extends Plugin return true; } + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('profile_detail', Profile_detail::schemaDef()); + + // @hack until key definition support is merged + Profile_detail::fixIndexes($schema); + return true; + } + function onEndAccountSettingsProfileMenuItem($widget, $menu) { // TRANS: Link title attribute in user account settings menu. diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php index ae8cdcee50..6fd96cca70 100644 --- a/plugins/ExtendedProfile/Profile_detail.php +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -1,16 +1,150 @@ . */ + +if (!defined('STATUSNET')) { + exit(1); +} + class Profile_detail extends Memcached_DataObject { + public $__table = 'submirror'; + public $id; + public $profile_id; public $field; - public $index; // relative ordering of multiple values in the same field + 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 -} \ No newline at end of file + public $created; + public $modified; + + public /*static*/ function staticGet($k, $v=null) + { + return parent::staticGet(__CLASS__, $k, $v); + } + + /** + * return table definition for DB_DataObject + * + * DB_DataObject needs to know something about the table to manipulate + * instances. This method provides all the DB_DataObject needs to know. + * + * @return array array of column definitions + */ + + function table() + { + 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_ID, + + '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); + } + + static function schemaDef() + { + // @fixme need a reverse key on (subscribed, subscriber) as well + 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'); + } + + function sequenceKey() + { + return array('id', true); + } + +}