Merge branch 'profile-fixups' into 1.0.x

* profile-fixups: (46 commits)
  * Extended profile - make cloned datefields work correctly with calendar popup * Validate URLs
  More style for profile edit.
  Extended profile - don't show empty company entry in view
  Extended profile - linkify related URLs added by the user
  Extended profile - fix some issues saving and displaying dates
  Extended profile - don't check end date if experience entry has current checked
  Extended profile - allow adding more than one website
  Small smattering of pixie dust
  Extended profile - fix regression whereby if there was only one item, you could still delete it!
  Remove supersizeme class as appropriate.
  Extended profile - hide add button when not needed (regression)
  Extended profile - add fancy JQuery UI confirm dialog when deleting items
  Extended profile - add fancy datepicker widgets
  Extended profile - prettier date formatting
  Extended profile - fix issue with JavaScript not executing in Firefox
  Extended profile - namespace JavaScript functions
  Extended profile - autocomplete for manager
  Hide all unnecessarylabels from profile edit view.
  Extended profile - make birthday save
  Extended profile - make websites save
  ...
This commit is contained in:
Zach Copley 2011-03-16 02:44:36 -07:00
commit daf73f8231
10 changed files with 1923 additions and 198 deletions

View File

@ -54,6 +54,7 @@ class ExtendedProfilePlugin extends Plugin
function onAutoload($cls) function onAutoload($cls)
{ {
$lower = strtolower($cls); $lower = strtolower($cls);
switch ($lower) switch ($lower)
{ {
case 'extendedprofile': case 'extendedprofile':
@ -62,6 +63,9 @@ class ExtendedProfilePlugin extends Plugin
case 'profiledetailsettingsaction': case 'profiledetailsettingsaction':
require_once dirname(__FILE__) . '/' . $lower . '.php'; require_once dirname(__FILE__) . '/' . $lower . '.php';
return false; return false;
case 'userautocompleteaction':
require_once dirname(__FILE__) . '/action/' . mb_substr($lower, 0, -6) . '.php';
return false;
case 'profile_detail': case 'profile_detail':
require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php'; require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php';
return false; return false;
@ -81,11 +85,19 @@ class ExtendedProfilePlugin extends Plugin
*/ */
function onStartInitializeRouter($m) function onStartInitializeRouter($m)
{ {
$m->connect(':nickname/detail', $m->connect(
array('action' => 'profiledetail'), ':nickname/detail',
array('nickname' => Nickname::DISPLAY_FMT)); array('action' => 'profiledetail'),
$m->connect('settings/profile/detail', array('nickname' => Nickname::DISPLAY_FMT)
array('action' => 'profiledetailsettings')); );
$m->connect(
'/settings/profile/finduser',
array('action' => 'Userautocomplete')
);
$m->connect(
'settings/profile/detail',
array('action' => 'profiledetailsettings')
);
return true; return true;
} }
@ -95,8 +107,6 @@ class ExtendedProfilePlugin extends Plugin
$schema = Schema::get(); $schema = Schema::get();
$schema->ensureTable('profile_detail', Profile_detail::schemaDef()); $schema->ensureTable('profile_detail', Profile_detail::schemaDef());
// @hack until key definition support is merged
Profile_detail::fixIndexes($schema);
return true; return true;
} }

View File

@ -21,130 +21,122 @@ if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
class Profile_detail extends Memcached_DataObject /**
* DataObject class to store extended profile fields. Allows for storing
* multiple values per a "field_name" (field_name 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_name = 'phone_number';
* $phone1->rel = 'home';
* $phone1->field_value = '510-384-1992';
* $phone1->value_index = 1;
*
* $phone1 = new Profile_detail();
* $phone1->field_name = 'phone_number';
* $phone1->rel = 'mobile';
* $phone1->field_value = '510-719-1139';
* $phone1->value_index = 2;
*
* $phone1 = new Profile_detail();
* $phone1->field_name = 'phone_number';
* $phone1->rel = 'work';
* $phone1->field_value = '415-231-1121';
* $phone1->value_index = 3;
*
*/
class Profile_detail extends Managed_DataObject
{ {
public $__table = 'submirror'; public $__table = 'profile_detail';
public $id; public $id;
public $profile_id; // profile this is for
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 $field_name; // name
public $field_index; // relative ordering of multiple values in the same field public $field_value; // primary text value
public $value_index; // relative ordering of multiple values in the same field
public $value; // primary text value public $date; // related date
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 $ref_profile; // for people types, allows pointing to a known profile in the system
public $created; public $created;
public $modified; 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 * This is a utility method to get a single instance with a given set of
* instances. This method provides all the DB_DataObject needs to know. * 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, return Memcached_DataObject::pkeyGet('Profile_detail', $kv);
'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() static function schemaDef()
{ {
// @fixme need a reverse key on (subscribed, subscriber) as well return array(
return array(new ColumnDef('id', 'integer', 'description'
null, false, 'PRI'), => 'Additional profile details for the ExtendedProfile plugin',
'fields' => array(
// @fixme need a unique index on these three 'id' => array('type' => 'serial', 'not null' => true),
new ColumnDef('profile_id', 'integer', 'profile_id' => array('type' => 'int', 'not null' => true),
null, false), 'field_name' => array(
new ColumnDef('field', 'varchar', 'type' => 'varchar',
16, false), 'length' => 16,
new ColumnDef('field_index', 'integer', 'not null' => true
null, false), ),
'value_index' => array('type' => 'int'),
new ColumnDef('value', 'text', 'field_value' => array('type' => 'text'),
null, true), 'date' => array('type' => 'datetime'),
new ColumnDef('rel', 'varchar', 'rel' => array('type' => 'varchar', 'length' => 16),
16, true), 'rel_profile' => array('type' => 'int'),
new ColumnDef('ref_profile', 'integer', 'created' => array(
null, true), 'type' => 'datetime',
'not null' => true
new ColumnDef('created', 'datetime', ),
null, false), 'modified' => array(
new ColumnDef('modified', 'datetime', 'type' => 'timestamp',
null, false)); 'not null' => true
} ),
),
/** 'primary key' => array('id'),
* Temporary hack to set up the compound index, since we can't do 'unique keys' => array(
* it yet through regular Schema interface. (Coming for 1.0...) 'profile_detail_profile_id_field_name_value_index'
* => array('profile_id', 'field_name', 'value_index'),
* @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);
} }
} }

View File

@ -0,0 +1,113 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Action for showing Twitter-like JSON search results
*
* PHP version 5
*
* LICENCE: 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 <http://www.gnu.org/licenses/>.
*
* @category Search
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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;
}
}

View File

@ -0,0 +1,164 @@
/* 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: 0px 8px;
margin-bottom: 10px;
}
#content table.extended-profile th {
color: #777;
background-color: #ECECF2;
width: 150px;
text-align: right;
padding: 2px 8px 2px 0px;
}
#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;
}
.form_settings .extended-profile label {
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;
}
.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;
display: inline;
left: -20px;
}
.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;
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;
line-height: 4em;
}
.extended-profile a.add_row {
clear: both;
position: relative;
top: 6px;
left: 2px;
background-position: 0px -1186px;
width: 120px;
padding-left: 20px;
line-height: 1.2em;
}
#content table.extended-profile .supersizeme th {
border-bottom: 28px solid #fff;
}
#profiledetailsettings .experience-item, #profiledetailsettings .education-item {
margin-bottom: 10px;
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;
}
#profiledetailsettings .experience-item a.add_row, #profiledetailsettings .education-item a.add_row {
left: 160px;
}

View File

@ -21,27 +21,256 @@ if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
/**
* Class to represent extended profile data
*/
class ExtendedProfile class ExtendedProfile
{ {
protected $fields;
/**
* Constructor
*
* @param Profile $profile
*/
function __construct(Profile $profile) function __construct(Profile $profile)
{ {
$this->profile = $profile; $this->profile = $profile;
$this->user = $profile->getUser();
$this->fields = $this->loadFields();
$this->sections = $this->getSections(); $this->sections = $this->getSections();
$this->fields = $this->loadFields(); //common_debug(var_export($this->sections, true));
//common_debug(var_export($this->fields, true));
} }
/**
* Load extended profile fields
*
* @return array $fields the list of fields
*/
function loadFields() function loadFields()
{ {
$detail = new Profile_detail(); $detail = new Profile_detail();
$detail->profile_id = $this->profile->id; $detail->profile_id = $this->profile->id;
$detail->find(); $detail->find();
while ($detail->get()) { $fields = array();
$fields[$detail->field][] = clone($detail);
while ($detail->fetch()) {
$fields[$detail->field_name][] = clone($detail);
} }
return $fields; 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)
{
$key = strtolower($name);
$profileFields = array('fullname', 'location', 'bio');
if (in_array($key, $profileFields)) {
return $this->profile->$name;
} else if (array_key_exists($key, $this->fields)) {
return $this->fields[$key][0]->field_value;
} else {
return null;
}
}
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()
{
$phones = (isset($this->fields['phone'])) ? $this->fields['phone'] : null;
$pArrays = array();
if (empty($phones)) {
$pArrays[] = array(
'label' => _m('Phone'),
'index' => 0,
'type' => 'phone',
'vcard' => 'tel',
'rel' => 'office',
'value' => null
);
} else {
for ($i = 0; $i < sizeof($phones); $i++) {
$pa = array(
'label' => _m('Phone'),
'type' => 'phone',
'index' => intval($phones[$i]->value_index),
'rel' => $phones[$i]->rel,
'value' => $phones[$i]->field_value,
'vcard' => 'tel'
);
$pArrays[] = $pa;
}
}
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 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;
$start = (isset($this->fields['start'])) ? $this->fields['start'] : null;
$end = (isset($this->fields['end'])) ? $this->fields['end'] : null;
$eArrays = array();
if (empty($companies)) {
$eArrays[] = array(
'label' => _m('Employer'),
'type' => 'experience',
'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;
}
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_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();
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' => 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
);
$iArrays[] = $ia;
}
}
return $iArrays;
}
/**
* Return all the sections of the extended profile
*
* @return array the big list of sections and fields
*/
function getSections() function getSections()
{ {
return array( return array(
@ -81,22 +310,9 @@ class ExtendedProfile
'contact' => array( 'contact' => array(
'label' => _m('Contact'), 'label' => _m('Contact'),
'fields' => array( 'fields' => array(
'phone' => array( 'phone' => $this->getPhones(),
'label' => _m('Phone'), 'im' => $this->getIms(),
'type' => 'phone', 'website' => $this->getWebsites()
'multi' => true,
'vcard' => 'tel',
),
'im' => array(
'label' => _m('IM'),
'type' => 'im',
'multi' => true,
),
'website' => array(
'label' => _m('Websites'),
'type' => 'website',
'multi' => true,
),
), ),
), ),
'personal' => array( 'personal' => array(
@ -119,19 +335,13 @@ class ExtendedProfile
'experience' => array( 'experience' => array(
'label' => _m('Work experience'), 'label' => _m('Work experience'),
'fields' => array( 'fields' => array(
'experience' => array( 'experience' => $this->getExperiences()
'type' => 'experience',
'label' => _m('Employer'),
),
), ),
), ),
'education' => array( 'education' => array(
'label' => _m('Education'), 'label' => _m('Education'),
'fields' => array( 'fields' => array(
'education' => array( 'education' => $this->getEducation()
'type' => 'education',
'label' => _m('Institution'),
),
), ),
), ),
); );

View File

@ -21,13 +21,35 @@ if (!defined('STATUSNET')) {
exit(1); 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; protected $profile;
/**
* The extended profile
*
* @var Extended_profile
*/
protected $ext; protected $ext;
/**
* Constructor
*
* @param XMLOutputter $out
* @param Profile $profile
* @param boolean $editable
*/
public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false) public function __construct(XMLOutputter $out=null, Profile $profile=null, $editable=false)
{ {
parent::__construct($out); parent::__construct($out);
@ -38,7 +60,37 @@ class ExtendedProfileWidget extends Widget
$this->editable = $editable; $this->editable = $editable;
} }
/**
* Show the extended profile, or the edit form
*/
public function show() public function show()
{
if ($this->editable) {
parent::show();
} else {
$this->showSections();
}
}
/**
* Show form data
*/
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();
}
/**
* Show each section of the extended profile
*/
public function showSections()
{ {
$sections = $this->ext->getSections(); $sections = $this->ext->getSections();
foreach ($sections as $name => $section) { foreach ($sections as $name => $section) {
@ -46,21 +98,45 @@ 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) protected function showExtendedProfileSection($name, $section)
{ {
$this->out->element('h3', null, $section['label']); $this->out->element('h3', null, $section['label']);
$this->out->elementStart('table', array('class' => 'extended-profile')); $this->out->elementStart('table', array('class' => 'extended-profile'));
foreach ($section['fields'] as $fieldName => $field) { foreach ($section['fields'] as $fieldName => $field) {
$this->showExtendedProfileField($fieldName, $field);
switch($fieldName) {
case 'phone':
case 'im':
case 'website':
case 'experience':
case 'education':
$this->showMultiple($fieldName, $field);
break;
default:
$this->showExtendedProfileField($fieldName, $field);
}
} }
$this->out->elementEnd('table'); $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) protected function showExtendedProfileField($name, $field)
{ {
$this->out->elementStart('tr'); $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'); $this->out->elementStart('td');
if ($this->editable) { if ($this->editable) {
@ -73,30 +149,504 @@ class ExtendedProfileWidget extends Widget
$this->out->elementEnd('tr'); $this->out->elementEnd('tr');
} }
protected function showFieldValue($name, $field) protected function showMultiple($name, $fields) {
{ foreach ($fields as $field) {
$this->out->text($name); $this->showExtendedProfileField($name, $field);
}
} }
// XXX: showPhone, showIm and showWebsite all work the same, so
// combine
protected function showPhone($name, $field)
{
$this->out->elementStart('div', array('class' => 'phone-display'));
$this->out->text($field['value']);
if (!empty($field['rel'])) {
$this->out->text(' (' . $field['rel'] . ')');
}
$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 showWebsite($name, $field)
{
$this->out->elementStart('div', array('class' => 'website-display'));
$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'] . ')');
}
$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-item'
)
);
$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;
$id = "extprofile-$name-$index";
$rel = $id . '-rel';
$this->out->elementStart(
'div', array(
'id' => $id . '-edit',
'class' => 'phone-item'
)
);
$this->out->input(
$id,
null,
isset($field['value']) ? $field['value'] : null
);
$this->out->dropdown(
$id . '-rel',
'Type',
array(
'office' => 'Office',
'mobile' => 'Mobile',
'home' => 'Home',
'pager' => 'Pager',
'other' => 'Other'
),
null,
false,
isset($field['rel']) ? $field['rel'] : null
);
$this->showMultiControls();
$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-item'
)
);
$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');
$this->out->element('div', 'label', _m('Company'));
if (!empty($field['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 ($field['current']) {
$this->out->element(
'div',
array('class' => 'field current'),
'(' . _m('Current') . ')'
);
}
}
$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-item'
)
);
$this->out->element('div', 'label', _m('Company'));
$this->out->input(
$id,
null,
isset($field['company']) ? $field['company'] : null
);
$this->out->element('div', 'label', _m('Start'));
$this->out->input(
$id . '-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']) ? date('j M Y', strtotime($field['end'])) : null
);
$this->out->hidden(
$id . '-current',
'false'
);
$this->out->elementStart('div', 'current-checkbox');
$this->out->checkbox(
$id . '-current',
_m('Current'),
$field['current']
);
$this->out->elementEnd('div');
$this->showMultiControls();
$this->out->elementEnd('div');
}
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']);
$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'),
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'])
)
);
$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-item'
)
);
$this->out->element('div', 'label', _m('Institution'));
$this->out->input(
$id,
null,
isset($field['school']) ? $field['school'] : null
);
$this->out->element('div', 'label', _m('Degree'));
$this->out->input(
$id . '-degree',
null,
isset($field['degree']) ? $field['degree'] : null
);
$this->out->element('div', 'label', _m('Description'));
$this->out->element('div', 'field', $field['description']);
$this->out->textarea(
$id . '-description',
null,
isset($field['description']) ? $field['description'] : null
);
$this->out->element('div', 'label', _m('Start'));
$this->out->input(
$id . '-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']) ? date('j M Y', strtotime($field['end'])) : null
);
$this->showMultiControls();
$this->out->elementEnd('div');
}
function showMultiControls()
{
$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;'
),
'Add another item'
);
}
/**
* 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 'date':
$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));
break;
case 'tags':
$this->out->text($this->ext->getTags());
break;
case 'phone':
$this->showPhone($name, $field);
break;
case 'website':
$this->showWebsite($name, $field);
break;
case 'im':
$this->showIm($name, $field);
break;
case 'experience':
$this->showExperience($name, $field);
break;
case 'education':
$this->showEducation($name, $field);
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) protected function showEditableField($name, $field)
{ {
$out = $this->out; $out = $this->out;
//$out = new HTMLOutputter();
// @fixme
$type = strval(@$field['type']); $type = strval(@$field['type']);
$id = "extprofile-" . $name; $id = "extprofile-" . $name;
$value = 'placeholder'; $value = 'placeholder';
switch ($type) { switch ($type) {
case '': case '':
case 'text': case 'text':
$out->input($id, null, $value); $out->input($id, null, $this->ext->getTextValue($name));
break; break;
case 'textarea': case 'date':
$out->textarea($id, null, $value); $out->input(
break; $id,
default: null,
$out->input($id, null, "TYPE: $type"); date('j M Y', strtotime($this->ext->getDateValue($name)))
);
break;
case 'person':
$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;
case 'phone':
$this->showEditablePhone($name, $field);
break;
case 'im':
$this->showEditableIm($name, $field);
break;
case 'website':
$this->showEditableWebsite($name, $field);
break;
case 'experience':
$this->showEditableExperience($name, $field);
break;
case 'education':
$this->showEditableEducation($name, $field);
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 form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('profiledetailsettings');
}
} }

View File

@ -0,0 +1,144 @@
var SN_EXTENDED = SN_EXTENDED || {};
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);
});
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').fadeOut("slow");
}
};
SN_EXTENDED.rowIndex = function(div) {
var idstr = $(div).attr('id');
var id = idstr.match(/\d+/);
return id;
};
SN_EXTENDED.rowCount = function(cls) {
var divs = $.find('div[class=' + cls + ']');
return divs.length;
};
SN_EXTENDED.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);
}
});
});
}
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() {
var div = $(this).closest('div');
var id = div.attr('id');
var cls = div.attr('class');
var index = id.match(/\d+/);
var newIndex = parseInt(index) + 1;
var newtr = $(div).closest('tr').removeClass('supersizeme').clone();
SN_EXTENDED.replaceIndex(newtr, index, newIndex);
SN_EXTENDED.resetRow(newtr);
$(div).closest('tr').after(newtr);
SN_EXTENDED.reorder(cls);
};
SN_EXTENDED.removeRow = function() {
var div = $(this).closest('div');
var id = $(div).attr('id');
var cls = $(div).attr('class');
var that = this;
$("#confirm-dialog").dialog({
buttons : {
"Confirm" : function() {
$(this).dialog("close");
var target = $(that).closest('tr');
target.fadeOut("slow", function() {
$(target).remove();
SN_EXTENDED.reorder(cls);
});
},
"Cancel" : function() {
$(this).dialog("close");
}
}
});
var cnt = SN_EXTENDED.rowCount(cls);
if (cnt > 1) {
$("#confirm-dialog").dialog("open");
}
};
$(document).ready(function() {
$("#confirm-dialog").dialog({
autoOpen: false,
modal: true
});
$("input#extprofile-manager").autocomplete({
source: 'finduser',
minLength: 2 });
$("input[name$=-start], input[name$=-end], #extprofile-birthday").datepicker({ dateFormat: 'd M yy' });
var multifields = ["phone-item", "experience-item", "education-item", "im-item", 'website-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);
$('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');
}
});
});

View File

@ -1,22 +0,0 @@
/* 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;
}

View File

@ -21,8 +21,9 @@ if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
class ProfileDetailAction extends ProfileAction class ProfileDetailAction extends ShowstreamAction
{ {
function isReadOnly($args) function isReadOnly($args)
{ {
return true; return true;
@ -33,28 +34,18 @@ class ProfileDetailAction extends ProfileAction
return $this->profile->getFancyName(); return $this->profile->getFancyName();
} }
function showLocalNav()
{
$nav = new PersonalGroupNav($this);
$nav->show();
}
function showStylesheets() { function showStylesheets() {
parent::showStylesheets(); parent::showStylesheets();
$this->cssLink('plugins/ExtendedProfile/profiledetail.css'); $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css');
return true; return true;
} }
function handle($args)
{
$this->showPage();
}
function showContent() function showContent()
{ {
$cur = common_current_user(); $cur = common_current_user();
if ($cur && $cur->id == $this->profile->id) { // your own page if ($cur && $cur->id == $this->profile->id) { // your own page
$this->elementStart('div', 'entity_actions'); $this->elementStart('div', 'entity_actions');
$this->elementStart('ul');
$this->elementStart('li', 'entity_edit'); $this->elementStart('li', 'entity_edit');
$this->element('a', array('href' => common_local_url('profiledetailsettings'), $this->element('a', array('href' => common_local_url('profiledetailsettings'),
// TRANS: Link title for link on user profile. // TRANS: Link title for link on user profile.
@ -62,6 +53,7 @@ class ProfileDetailAction extends ProfileAction
// TRANS: Link text for link on user profile. // TRANS: Link text for link on user profile.
_m('Edit')); _m('Edit'));
$this->elementEnd('li'); $this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('div'); $this->elementEnd('div');
} }

View File

@ -21,7 +21,7 @@ if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
class ProfileDetailSettingsAction extends AccountSettingsAction class ProfileDetailSettingsAction extends ProfileSettingsAction
{ {
function title() function title()
@ -43,13 +43,38 @@ class ProfileDetailSettingsAction extends AccountSettingsAction
function showStylesheets() { function showStylesheets() {
parent::showStylesheets(); parent::showStylesheets();
$this->cssLink('plugins/ExtendedProfile/profiledetail.css'); $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; return true;
} }
function handle($args) 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;
}
function handlePost()
{ {
$this->showPage(); // CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(
_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() function showContent()
@ -57,7 +82,554 @@ class ProfileDetailSettingsAction extends AccountSettingsAction
$cur = common_current_user(); $cur = common_current_user();
$profile = $cur->getProfile(); $profile = $cur->getProfile();
$widget = new ExtendedProfileWidget($this, $profile, ExtendedProfileWidget::EDITABLE); $widget = new ExtendedProfileWidget(
$this,
$profile,
ExtendedProfileWidget::EDITABLE
);
$widget->show(); $widget->show();
} }
function saveDetails()
{
common_debug(var_export($_POST, true));
$user = common_current_user();
try {
$this->saveStandardProfileDetails($user);
$profile = $user->getProfile();
$simpleFieldNames = array('title', 'spouse', 'kids', 'manager');
$dateFieldNames = array('birthday');
foreach ($simpleFieldNames as $name) {
$value = $this->trimmed('extprofile-' . $name);
if (!empty($value)) {
$this->saveField($user, $name, $value);
}
}
foreach ($dateFieldNames as $name) {
$value = $this->trimmed('extprofile-' . $name);
$dateVal = $this->parseDate($name, $value);
$this->saveField(
$user,
$name,
null,
null,
null,
$dateVal
);
}
$this->savePhoneNumbers($user);
$this->saveIms($user);
$this->saveWebsites($user);
$this->saveExperiences($user);
$this->saveEducations($user);
} catch (Exception $e) {
$this->showForm($e->getMessage(), false);
return;
}
$this->showForm(_('Details saved.'), true);
}
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');
$i = 0;
foreach($phones as $phone) {
if (!empty($phone['value'])) {
++$i;
$this->saveField(
$user,
'phone',
$phone['value'],
$phone['rel'],
$i
);
}
}
}
function findPhoneNumbers() {
// 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);
$phoneArray[] = array(
'value' => $number,
'rel' => $rel
);
}
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 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 (!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(
$user,
'website',
$site['value'],
$site['rel'],
$i
);
}
}
}
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 = $this->sliceParams('experience', 4);
$expArray = array();
foreach ($experiences as $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,
'start' => $this->parseDate('Start', $start, true),
'end' => ($current == 'false') ? $this->parseDate('End', $end, true) : null,
'current' => ($current == 'false') ? false : true
);
}
}
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 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)) {
$eduArray[] = array(
'school' => $school,
'degree' => $degree,
'description' => $description,
'start' => $this->parseDate('Start', $start, true),
'end' => $this->parseDate('End', $end, true)
);
}
}
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) {
return array($array);
}
$newCount = ceil(count($array) / $pieces);
$a = array_slice($array, 0, $newCount);
$b = $this->arraySplit(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;
}
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
*
* @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)
* @param date $date related date
*/
function saveField($user, $name, $value, $rel = null, $index = null, $date = null)
{
$profile = $user->getProfile();
$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->date = $date;
$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 {
$orig = clone($detail);
$detail->field_value = $value;
$detail->rel = $rel;
$detail->date = $date;
$result = $detail->update($orig);
if (empty($result)) {
common_log_db_error($detail, 'UPDATE', __FILE__);
$this->serverError(_m('Could not save profile details.'));
}
}
$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
*
* XXX: There's a lot of dupe code here from ProfileSettingsAction.
* Do not want.
*
* @param User $user the current user
*/
function saveStandardProfileDetails($user)
{
$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();
$oldTags = $user->getSelfTags();
$newTags = array_diff($tags, $oldTags);
if ($fullname != $profile->fullname
|| $location != $profile->location
|| !empty($newTags)
|| $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;
}
Event::handle('EndProfileSaveForm', array($this));
common_broadcast_profile($profile);
}
}
} }