]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '1.0.x' of gitorious.org:statusnet/mainline into 1.0.x
authorZach Copley <zach@status.net>
Wed, 16 Mar 2011 23:17:49 +0000 (16:17 -0700)
committerZach Copley <zach@status.net>
Wed, 16 Mar 2011 23:17:49 +0000 (16:17 -0700)
13 files changed:
plugins/ExtendedProfile/ExtendedProfilePlugin.php
plugins/ExtendedProfile/Profile_detail.php [deleted file]
plugins/ExtendedProfile/action/userautocomplete.php [deleted file]
plugins/ExtendedProfile/actions/profiledetail.php [new file with mode: 0644]
plugins/ExtendedProfile/actions/profiledetailsettings.php [new file with mode: 0644]
plugins/ExtendedProfile/actions/userautocomplete.php [new file with mode: 0644]
plugins/ExtendedProfile/classes/Profile_detail.php [new file with mode: 0644]
plugins/ExtendedProfile/extendedprofile.php [deleted file]
plugins/ExtendedProfile/extendedprofilewidget.php [deleted file]
plugins/ExtendedProfile/lib/extendedprofile.php [new file with mode: 0644]
plugins/ExtendedProfile/lib/extendedprofilewidget.php [new file with mode: 0644]
plugins/ExtendedProfile/profiledetailaction.php [deleted file]
plugins/ExtendedProfile/profiledetailsettingsaction.php [deleted file]

index 127c91a543ef18238e023c1a3335e537b7bcd040..ce1593dad52b86b403c06a70dca8d09413763d35 100644 (file)
@@ -32,12 +32,14 @@ class ExtendedProfilePlugin extends Plugin
 
     function onPluginVersion(&$versions)
     {
-        $versions[] = array('name' => 'ExtendedProfile',
-                            'version' => STATUSNET_VERSION,
-                            'author' => 'Brion Vibber, Zach Copley',
-                            'homepage' => 'http://status.net/wiki/Plugin:ExtendedProfile',
-                            'rawdescription' =>
-                            _m('UI extensions for additional profile fields.'));
+        $versions[] = array(
+            'name' => 'ExtendedProfile',
+            'version' => STATUSNET_VERSION,
+            'author' => 'Brion Vibber, Samantha Doherty, Zach Copley',
+            'homepage' => 'http://status.net/wiki/Plugin:ExtendedProfile',
+            'rawdescription' => _m(
+                'UI extensions for additional profile fields.')
+        );
 
         return true;
     }
@@ -53,22 +55,26 @@ class ExtendedProfilePlugin extends Plugin
      */
     function onAutoload($cls)
     {
-        $lower = strtolower($cls);
+        $dir = dirname(__FILE__);
 
-        switch ($lower)
+        switch (strtolower($cls))
         {
-        case 'extendedprofile':
-        case 'extendedprofilewidget':
         case 'profiledetailaction':
         case 'profiledetailsettingsaction':
-            require_once dirname(__FILE__) . '/' . $lower . '.php';
-            return false;
         case 'userautocompleteaction':
-            require_once dirname(__FILE__) . '/action/' . mb_substr($lower, 0, -6) . '.php';
+            include_once $dir . '/actions/'
+                . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            return false;
+            break; // Safety first!
+        case 'extendedprofile':
+        case 'extendedprofilewidget':
+            include_once $dir . '/lib/' . strtolower($cls) . '.php';
             return false;
+            break;
         case 'profile_detail':
-            require_once dirname(__FILE__) . '/' . ucfirst($lower) . '.php';
+            include_once $dir . '/classes/' . ucfirst($cls) . '.php';
             return false;
+            break;
         default:
             return true;
         }
diff --git a/plugins/ExtendedProfile/Profile_detail.php b/plugins/ExtendedProfile/Profile_detail.php
deleted file mode 100644 (file)
index 96869b0..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * 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 = 'profile_detail';
-
-    public $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_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;
-
-    /**
-     * 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 Memcached_DataObject::staticGet('Profile_detail', $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 Bookmark object found, or null for no hits
-     *
-     */
-
-    function pkeyGet($kv)
-    {
-        return Memcached_DataObject::pkeyGet('Profile_detail', $kv);
-    }
-
-    static function schemaDef()
-    {
-        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'),
-                'date'        => array('type' => 'datetime'),
-                '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_detail_profile_id_field_name_value_index'
-                    => array('profile_id', 'field_name', 'value_index'),
-            )
-        );
-    }
-
-}
diff --git a/plugins/ExtendedProfile/action/userautocomplete.php b/plugins/ExtendedProfile/action/userautocomplete.php
deleted file mode 100644 (file)
index d485742..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-<?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;
-    }
-}
diff --git a/plugins/ExtendedProfile/actions/profiledetail.php b/plugins/ExtendedProfile/actions/profiledetail.php
new file mode 100644 (file)
index 0000000..a777a28
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class ProfileDetailAction extends ShowstreamAction
+{
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function title()
+    {
+        return $this->profile->getFancyName();
+    }
+
+    function showStylesheets() {
+        parent::showStylesheets();
+        $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css');
+        return true;
+    }
+
+    function showContent()
+    {
+        $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.
+                                      'title' => _m('Edit extended profile settings')),
+                           // TRANS: Link text for link on user profile.
+                           _m('Edit'));
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+            $this->elementEnd('div');
+        }
+
+        $widget = new ExtendedProfileWidget($this, $this->profile);
+        $widget->show();
+    }
+}
diff --git a/plugins/ExtendedProfile/actions/profiledetailsettings.php b/plugins/ExtendedProfile/actions/profiledetailsettings.php
new file mode 100644 (file)
index 0000000..23a6ce5
--- /dev/null
@@ -0,0 +1,634 @@
+<?php
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+class ProfileDetailSettingsAction extends ProfileSettingsAction
+{
+
+    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/css/profiledetail.css');
+        return true;
+    }
+
+    function  showScripts() {
+        parent::showScripts();
+        $this->script('plugins/ExtendedProfile/js/profiledetail.js');
+        return true;
+    }
+
+    function handlePost()
+    {
+        // 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()
+    {
+        $cur = common_current_user();
+        $profile = $cur->getProfile();
+
+        $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);
+
+            $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)) {
+            if ($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 (!empty($site['value']) && !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);
+        }
+    }
+
+}
diff --git a/plugins/ExtendedProfile/actions/userautocomplete.php b/plugins/ExtendedProfile/actions/userautocomplete.php
new file mode 100644 (file)
index 0000000..d485742
--- /dev/null
@@ -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;
+    }
+}
diff --git a/plugins/ExtendedProfile/classes/Profile_detail.php b/plugins/ExtendedProfile/classes/Profile_detail.php
new file mode 100644 (file)
index 0000000..96869b0
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * 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 = 'profile_detail';
+
+    public $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_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;
+
+    /**
+     * 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 Memcached_DataObject::staticGet('Profile_detail', $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 Bookmark object found, or null for no hits
+     *
+     */
+
+    function pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Profile_detail', $kv);
+    }
+
+    static function schemaDef()
+    {
+        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'),
+                'date'        => array('type' => 'datetime'),
+                '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_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
deleted file mode 100644 (file)
index fa632e5..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-<?php
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * Class to represent extended profile data
- */
-class ExtendedProfile
-{
-    protected $fields;
-
-    /**
-     * Constructor
-     *
-     * @param Profile $profile
-     */
-    function __construct(Profile $profile)
-    {
-        $this->profile  = $profile;
-        $this->user     = $profile->getUser();
-        $this->fields   = $this->loadFields();
-        $this->sections = $this->getSections();
-        //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()
-    {
-        $detail = new Profile_detail();
-        $detail->profile_id = $this->profile->id;
-        $detail->find();
-
-        $fields = array();
-
-        while ($detail->fetch()) {
-            $fields[$detail->field_name][] = 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)
-    {
-        $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()
-    {
-        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'   => $this->getPhones(),
-                    'im'      => $this->getIms(),
-                    'website' => $this->getWebsites()
-                ),
-            ),
-            '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' => $this->getExperiences()
-                ),
-            ),
-            'education' => array(
-                'label' => _m('Education'),
-                'fields' => array(
-                    'education' => $this->getEducation()
-                ),
-            ),
-        );
-    }
-}
diff --git a/plugins/ExtendedProfile/extendedprofilewidget.php b/plugins/ExtendedProfile/extendedprofilewidget.php
deleted file mode 100644 (file)
index 53cb5d3..0000000
+++ /dev/null
@@ -1,657 +0,0 @@
-<?php
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-/**
- * Class for outputting a widget to display or edit
- * extended profiles
- */
-class ExtendedProfileWidget extends Form
-{
-    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);
-
-        $this->profile = $profile;
-        $this->ext = new ExtendedProfile($this->profile);
-
-        $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()
-    {
-        // 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();
-        foreach ($sections as $name => $section) {
-            $this->showExtendedProfileSection($name, $section);
-        }
-    }
-
-    /**
-     * 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']);
-        $this->out->elementStart('table', array('class' => 'extended-profile'));
-
-        foreach ($section['fields'] as $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');
-    }
-
-    /**
-     * 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');
-
-        $this->out->element('th', str_replace(' ','_',strtolower($field['label'])), $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 showMultiple($name, $fields) {
-        foreach ($fields as $field) {
-            $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'));
-        if (!empty($field['value'])) {
-            $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'));
-        if (!empty($field['school'])) {
-
-            $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->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)
-    {
-        $out = $this->out;
-
-        $type = strval(@$field['type']);
-        $id = "extprofile-" . $name;
-
-        $value = 'placeholder';
-
-        switch ($type) {
-        case '':
-        case 'text':
-            $out->input($id, null, $this->ext->getTextValue($name));
-            break;
-        case 'date':
-            $value = $this->ext->getDateValue($name);
-            $out->input(
-                $id,
-                null,
-                empty($value) ? null : date('j M Y', strtotime($value))
-            );
-            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');
-    }
-}
diff --git a/plugins/ExtendedProfile/lib/extendedprofile.php b/plugins/ExtendedProfile/lib/extendedprofile.php
new file mode 100644 (file)
index 0000000..fa632e5
--- /dev/null
@@ -0,0 +1,349 @@
+<?php
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Class to represent extended profile data
+ */
+class ExtendedProfile
+{
+    protected $fields;
+
+    /**
+     * Constructor
+     *
+     * @param Profile $profile
+     */
+    function __construct(Profile $profile)
+    {
+        $this->profile  = $profile;
+        $this->user     = $profile->getUser();
+        $this->fields   = $this->loadFields();
+        $this->sections = $this->getSections();
+        //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()
+    {
+        $detail = new Profile_detail();
+        $detail->profile_id = $this->profile->id;
+        $detail->find();
+
+        $fields = array();
+
+        while ($detail->fetch()) {
+            $fields[$detail->field_name][] = 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)
+    {
+        $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()
+    {
+        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'   => $this->getPhones(),
+                    'im'      => $this->getIms(),
+                    'website' => $this->getWebsites()
+                ),
+            ),
+            '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' => $this->getExperiences()
+                ),
+            ),
+            'education' => array(
+                'label' => _m('Education'),
+                'fields' => array(
+                    'education' => $this->getEducation()
+                ),
+            ),
+        );
+    }
+}
diff --git a/plugins/ExtendedProfile/lib/extendedprofilewidget.php b/plugins/ExtendedProfile/lib/extendedprofilewidget.php
new file mode 100644 (file)
index 0000000..53cb5d3
--- /dev/null
@@ -0,0 +1,657 @@
+<?php
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET')) {
+    exit(1);
+}
+
+/**
+ * Class for outputting a widget to display or edit
+ * extended profiles
+ */
+class ExtendedProfileWidget extends Form
+{
+    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);
+
+        $this->profile = $profile;
+        $this->ext = new ExtendedProfile($this->profile);
+
+        $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()
+    {
+        // 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();
+        foreach ($sections as $name => $section) {
+            $this->showExtendedProfileSection($name, $section);
+        }
+    }
+
+    /**
+     * 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']);
+        $this->out->elementStart('table', array('class' => 'extended-profile'));
+
+        foreach ($section['fields'] as $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');
+    }
+
+    /**
+     * 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');
+
+        $this->out->element('th', str_replace(' ','_',strtolower($field['label'])), $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 showMultiple($name, $fields) {
+        foreach ($fields as $field) {
+            $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'));
+        if (!empty($field['value'])) {
+            $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'));
+        if (!empty($field['school'])) {
+
+            $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->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)
+    {
+        $out = $this->out;
+
+        $type = strval(@$field['type']);
+        $id = "extprofile-" . $name;
+
+        $value = 'placeholder';
+
+        switch ($type) {
+        case '':
+        case 'text':
+            $out->input($id, null, $this->ext->getTextValue($name));
+            break;
+        case 'date':
+            $value = $this->ext->getDateValue($name);
+            $out->input(
+                $id,
+                null,
+                empty($value) ? null : date('j M Y', strtotime($value))
+            );
+            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');
+    }
+}
diff --git a/plugins/ExtendedProfile/profiledetailaction.php b/plugins/ExtendedProfile/profiledetailaction.php
deleted file mode 100644 (file)
index a777a28..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-class ProfileDetailAction extends ShowstreamAction
-{
-
-    function isReadOnly($args)
-    {
-        return true;
-    }
-
-    function title()
-    {
-        return $this->profile->getFancyName();
-    }
-
-    function showStylesheets() {
-        parent::showStylesheets();
-        $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css');
-        return true;
-    }
-
-    function showContent()
-    {
-        $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.
-                                      'title' => _m('Edit extended profile settings')),
-                           // TRANS: Link text for link on user profile.
-                           _m('Edit'));
-            $this->elementEnd('li');
-            $this->elementEnd('ul');
-            $this->elementEnd('div');
-        }
-
-        $widget = new ExtendedProfileWidget($this, $this->profile);
-        $widget->show();
-    }
-}
diff --git a/plugins/ExtendedProfile/profiledetailsettingsaction.php b/plugins/ExtendedProfile/profiledetailsettingsaction.php
deleted file mode 100644 (file)
index 4a2045c..0000000
+++ /dev/null
@@ -1,632 +0,0 @@
-<?php
-/*
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-class ProfileDetailSettingsAction extends ProfileSettingsAction
-{
-
-    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/css/profiledetail.css');
-        return true;
-    }
-
-    function  showScripts() {
-        parent::showScripts();
-        $this->script('plugins/ExtendedProfile/js/profiledetail.js');
-        return true;
-    }
-
-    function handlePost()
-    {
-        // 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()
-    {
-        $cur = common_current_user();
-        $profile = $cur->getProfile();
-
-        $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);
-
-            $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 (!empty($site['value']) && !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);
-        }
-    }
-
-}