diff --git a/plugins/ExtendedProfile/ExtendedProfilePlugin.php b/plugins/ExtendedProfile/ExtendedProfilePlugin.php new file mode 100644 index 0000000000..3f541c0008 --- /dev/null +++ b/plugins/ExtendedProfile/ExtendedProfilePlugin.php @@ -0,0 +1,121 @@ +. + */ + +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; + case 'profile_detail': + require_once dirname(__FILE__) . '/' . ucfirst($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 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. + $title = _('Change additional profile settings'); + // TRANS: Link description in user account settings menu. + $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/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php new file mode 100644 index 0000000000..6fd96cca70 --- /dev/null +++ b/plugins/ExtendedProfile/Profile_detail.php @@ -0,0 +1,150 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class Profile_detail extends Memcached_DataObject +{ + public $__table = 'submirror'; + + public $id; + + public $profile_id; + public $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 + + 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); + } + +} diff --git a/plugins/ExtendedProfile/extendedprofile.php b/plugins/ExtendedProfile/extendedprofile.php new file mode 100644 index 0000000000..7f69f90899 --- /dev/null +++ b/plugins/ExtendedProfile/extendedprofile.php @@ -0,0 +1,139 @@ +. + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ExtendedProfile +{ + function __construct(Profile $profile) + { + $this->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( + '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..bf9b4056cd --- /dev/null +++ b/plugins/ExtendedProfile/extendedprofilewidget.php @@ -0,0 +1,102 @@ +. + */ + +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'); + 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/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..a4bb12956e --- /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); + $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(); + } +}