From 148af7a9ef164291a44262de6a07285b1ab152f6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 3 Jun 2011 10:27:16 +0200 Subject: [PATCH] Initial version of the PTA webservice. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- classes/profile.php | 27 +- classes/userfilter.php | 34 ++ classes/userfilter/conditions.inc.php | 66 +++ include/partnersharing.inc.php | 108 +++++ include/profilefields.inc.php | 41 ++ modules/ptawebservice.php | 115 ++++++ modules/ptawebservice/request.inc.php | 752 ++++++++++++++++++++++++++++++++++ upgrade/1.1.3/03_pta.sql | 37 ++ 8 files changed, 1177 insertions(+), 3 deletions(-) create mode 100644 include/partnersharing.inc.php create mode 100644 modules/ptawebservice.php create mode 100644 modules/ptawebservice/request.inc.php create mode 100644 upgrade/1.1.3/03_pta.sql diff --git a/classes/profile.php b/classes/profile.php index 6b06231..e9ff7b5 100644 --- a/classes/profile.php +++ b/classes/profile.php @@ -110,10 +110,11 @@ class Profile implements PlExportable const FETCH_JOB_TERMS = 0x000200; const FETCH_MENTOR_TERMS = 0x000400; const FETCH_DELTATEN = 0x000800; + const FETCH_PARTNER = 0x001000; const FETCH_MINIFICHES = 0x00012D; // FETCH_ADDRESSES | FETCH_EDU | FETCH_JOBS | FETCH_NETWORKING | FETCH_PHONES - const FETCH_ALL = 0x000FFF; // OR of FETCH_* + const FETCH_ALL = 0x001FFF; // OR of FETCH_* static public $descriptions = array( 'search_names' => 'Noms', @@ -458,7 +459,8 @@ class Profile implements PlExportable * Clears a profile. * *always deletes in: profile_addresses, profile_binets, profile_deltaten, * profile_job, profile_langskills, profile_mentor, profile_networking, - * profile_phones, profile_skills, watch_profile + * profile_partnersharing_settings, profile_phones, profile_skills, + * watch_profile * *always keeps in: profile_corps, profile_display, profile_education, * profile_medals, profile_*_names, profile_photos, search_name * *modifies: profiles @@ -469,7 +471,7 @@ class Profile implements PlExportable 'profile_job', 'profile_langskills', 'profile_mentor', 'profile_networking', 'profile_skills', 'watch_profile', 'profile_phones', 'profile_addresses', 'profile_binets', - 'profile_deltaten'); + 'profile_deltaten', 'profile_partnersharing_settings'); foreach ($tables as $t) { XDB::execute('DELETE FROM ' . $t . ' @@ -913,6 +915,25 @@ class Profile implements PlExportable return $this->medals->medals; } + /** Sharing data with partner websites + */ + private $partners_settings = null; + public function setPartnersSettings(ProfilePartnerSharing $partners_settings) + { + $this->partners_settings = $partners_settings; + } + + public function getPartnerSettings($partner_id) + { + if ($this->partners_settings == null && !$this->fetched(self::FETCH_PARTNER)) { + $this->setPartnersSettings($this->getProfileField(self::FETCH_PARTNER)); + } + if ($this->partners_settings == null) { + return PartnerSettings::getEmpty($partner_id); + } + return $this->partners_settings->get($partner_id); + } + public function compareNames($firstname, $lastname) { $_lastname = mb_strtoupper($this->lastName()); diff --git a/classes/userfilter.php b/classes/userfilter.php index 2314915..c46db70 100644 --- a/classes/userfilter.php +++ b/classes/userfilter.php @@ -1407,6 +1407,40 @@ class UserFilter extends PlFilter return array(); } } + + + /** PARTNER SHARING + */ + + // Lists partner shortnames in use, as a $partner_shortname => true map. + private $ppss = array(); + + /** Add a filter on user having settings for a given partner. + * @param $partner_id the ID of the partner + * @return the name of the table to use in joins (e.g ppss_$partner_id). + */ + public function addPartnerSharingFilter($partner_id) + { + $this->requireProfiles(); + $sub = "ppss_$partner_id"; + $this->ppss[$sub] = $partner_id; + return $sub; + } + + protected function partnerSharingJoins() + { + $joins = array(); + foreach ($this->ppss as $sub => $partner_id) { + $joins[$sub] = PlSqlJoin::left('profile_partnersharing_settings', '$ME.pid = $PID AND $ME.partner_id = {?} AND $ME.sharing_level != \'none\'', $partner_id); + } + return $joins; + } + + public function restrictVisibilityForPartner($partner_id) + { + $sub = $this->addPartnerSharingFilter($partner_id); + $this->visibility_field = $sub . '.sharing_level'; + } } // }}} // {{{ class ProfileFilter diff --git a/classes/userfilter/conditions.inc.php b/classes/userfilter/conditions.inc.php index 351ad2a..bf060ca 100644 --- a/classes/userfilter/conditions.inc.php +++ b/classes/userfilter/conditions.inc.php @@ -1616,6 +1616,72 @@ class UFC_MarketingHash extends UserFilterCondition } } // }}} +// {{{ class UFC_PartnerSharing +/** Filters users, keeping only those sharing data with a given partner. + */ +class UFC_PartnerSharing extends UserFilterCondition +{ + const PTA = 'pta'; + + private $partner; + + public function __construct($partner) + { + $this->partner = $partner; + } + + public function buildCondition(PlFilter $uf) + { + $sub = $uf->addPartnerSharingFilter($this->partner); + return XDB::format("$sub.exposed_uid IS NOT NULL"); + } +} +// }}} +// {{{ class UFC_PartnerSharingEmail +/** Filters users, keeping only those allowing emails to be sent by + * a given partner. + */ +class UFC_PartnerSharingEmail extends UserFilterCondition +{ + private $partner; + + public function __construct($partner) + { + $this->partner = $partner; + } + + public function buildCondition(PlFilter $uf) + { + $sub = $uf->addPartnerSharingFilter($this->partner); + return XDB::format("$sub.allow_email IN ('digest', 'direct')"); + } +} +// }}} +// {{{ class UFC_PartnerSharingID +/** Filters users according to a list of partner-known IDs + */ +class UFC_PartnerSharingID extends UserFilterCondition +{ + private $partner; + private $ids; + + public function __construct($partner) + { + $this->partner = $partner; + $ids = func_get_args(); + array_shift($ids); + $this->ids = pl_flatten($ids); + } + + public function buildCondition(PlFilter $uf) + { + $uf->requireProfiles(); + $ids = $this->ids; + $sub = $uf->addPartnerSharingFilter($this->partner); + return XDB::format("$sub.exposed_uid IN {?}", $ids); + } +} +// }}} // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: ?> diff --git a/include/partnersharing.inc.php b/include/partnersharing.inc.php new file mode 100644 index 0000000..7a7b6c7 --- /dev/null +++ b/include/partnersharing.inc.php @@ -0,0 +1,108 @@ + $val) { + $this->$key = $val; + } + } + + public function apiUser() + { + return User::getSilentWithUID($this->api_uid); + } + + public static function fetchByAPIUser(User $user) + { + $res = XDB::fetchOneAssoc('SELECT id, shortname, name, url, + has_directory, has_bulkmail, + default_sharing_level, api_uid + FROM profile_partnersharing_enum + WHERE api_uid = {?}', $user->uid); + if ($res == null) { + return null; + } else { + return new PartnerSharing($res); + } + } + + public static function fetchById($id) + { + $res = XDB::fetchOneAssoc('SELECT id, shortname, name, url, + has_directory, has_bulkmail, + default_sharing_level, api_uid + FROM profile_partnersharing_enum + WHERE id = {?}', $id); + if ($res == null) { + return null; + } else { + return new PartnerSharing($res); + } + } +} +// }}} +// {{{ class PartnerSettings +class PartnerSettings +{ + public $exposed_uid; + public $sharing_level; + public $allow_email = false; + protected $partner_id; + public $partner = null; + + public function __construct(array $data) + { + foreach ($data as $key => $val) { + $this->$key = $val; + } + $this->partner = PartnerSharing::fetchById($this->partner_id); + $this->sharing_visibility = Visibility::get($this->sharing_level); + } + + public static function getEmpty($partner_id) + { + $data = array( + 'partner_id' => $partner_id, + 'exposed_uid' => 0, + 'sharing_level' => Visibility::VIEW_NONE, + 'allow_email' => false, + ); + return new PartnerSettings($data); + } +} +// }}} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: +?> diff --git a/include/profilefields.inc.php b/include/profilefields.inc.php index 6bcf258..9500224 100644 --- a/include/profilefields.inc.php +++ b/include/profilefields.inc.php @@ -36,6 +36,7 @@ abstract class ProfileField Profile::FETCH_MENTOR_COUNTRY => 'ProfileMentoringCountries', Profile::FETCH_JOB_TERMS => 'ProfileJobTerms', Profile::FETCH_MENTOR_TERMS => 'ProfileMentoringTerms', + Profile::FETCH_PARTNER => 'ProfilePartnerSharing', ); /** The profile to which this field belongs @@ -719,6 +720,45 @@ class ProfileMentoringTerms extends ProfileJobTerms } } // }}} +// {{{ class ProfilePartnerSharing [ Field ] +class ProfilePartnerSharing extends ProfileField +{ + public function __construct(PlInnerSubIterator $it) + { + require_once 'partnersharing.inc.php'; + + $this->pid = $it->value(); + while ($partner_settings = $it->next()) { + $settings = new PartnerSettings($partner_settings); + $this->partners_settings[$settings->partner->id] = $settings; + } + } + + public static function fetchData(array $pids, Visibility $visibility) + { + $data = XDB::iterator('SELECT ppss.pid, ppss.exposed_uid, ppss.sharing_level, + ppss.allow_email, ppss.partner_id, + ppse.shortname AS partner_shortname, + ppse.name AS partner_name, + ppse.url AS partner_url + FROM profile_partnersharing_settings AS ppss + LEFT JOIN profile_partnersharing_enum AS ppse ON (ppss.partner_id = ppse.id) + WHERE ppss.pid IN {?} + ORDER BY ' . XDB::formatCustomOrder('ppss.pid', $pids), + $pids); + return PlIteratorUtils::subIterator($data, PlIteratorUtils::arrayValueCallback('pid')); + } + + public function get($partner_id) + { + if (isset($this->partners_settings[$partner_id])) { + return $this->partners_settings[$partner_id]; + } else { + return PartnerSettings::getEmpty($partner_id); + } + } +} +// }}} // {{{ class CompanyList class CompanyList { @@ -780,6 +820,7 @@ class CompanyList return null; } } +// }}} // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: ?> diff --git a/modules/ptawebservice.php b/modules/ptawebservice.php new file mode 100644 index 0000000..badc0dd --- /dev/null +++ b/modules/ptawebservice.php @@ -0,0 +1,115 @@ + $this->make_api_hook('search', AUTH_COOKIE, 'api_user_readonly'), + 'pta/ws/bulkmail/1/get_context' => $this->make_api_hook('bulkmail', AUTH_COOKIE, 'api_user_readonly'), + 'pta/picture' => $this->make_hook('picture_token', AUTH_PUBLIC), + ); + } + + function handler_search(PlPage $page, PlUser $authUser, $payload) + { + require_once 'partnersharing.inc.php'; + $partner = PartnerSharing::fetchByAPIUser($authUser); + if ($partner == null || !$partner->has_directory) { + return PL_FORBIDDEN; + } + + $this->load('request.inc.php'); + + $payload = new PlDict($payload); + + $errors = WSDirectoryRequest::validatePayload($payload); + + if (count($errors)) { + foreach ($errors as $error_code) { + $page->trigError(WSDirectoryRequest::$ERROR_MESSAGES[$error_code]); + } + return PL_BAD_REQUEST; + } + + // Processing + $request = new WSDirectoryRequest($partner, $payload); + $request->assignToPage($page); + return PL_JSON; + } + + function handler_bulkmail(PlPage $page, PlUser $authUser, $payload) + { + require_once 'partnersharing.inc.php'; + $partner = PartnerSharing::fetchByAPIUser($authUser); + if ($partner == null || !$partner->has_bulkmail) { + return PL_FORBIDDEN; + } + + if (!isset($payload['uids'])) { + $page->trigError('Malformed query.'); + return PL_BAD_REQUEST; + } + + $uids = $payload['uids']; + + $pf = new UserFilter( + new PFC_And( + new UFC_PartnerSharingID($partner->id, $uids), + new UFC_HasValidEmail(), + new UFC_PartnerSharingEmail($partner->id) + )); + + $contexts = array(); + foreach ($pf->iterUsers() as $user) { + $contexts[] = array( + 'name' => $user->fullName(), + 'email' => $user->bestEmail(), + 'gender' => $user->isFemale() ? 'woman' : 'man', + ); + } + $page->jsonAssign('contexts', $contexts); + return PL_JSON; + } + + function handler_picture_token(PlPage $page, $size, $token) + { + XDB::rawExecute('DELETE FROM profile_photo_tokens + WHERE expires <= NOW()'); + $pid = XDB::fetchOneCell('SELECT pid + FROM profile_photo_tokens + WHERE token = {?}', $token); + if ($pid != null) { + $res = XDB::fetchOneAssoc('SELECT attach, attachmime, x, y, last_update + FROM profile_photos + WHERE pid = {?}', $pid); + $photo = PlImage::fromData($res['attach'], 'image/' . $res['attachmime'], $res['x'], $res['y'], $res['last_update']); + $photo->send(); + } else { + return PL_NOT_FOUND; + } + } +} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: +?> diff --git a/modules/ptawebservice/request.inc.php b/modules/ptawebservice/request.inc.php new file mode 100644 index 0000000..a6c9d3a --- /dev/null +++ b/modules/ptawebservice/request.inc.php @@ -0,0 +1,752 @@ +partner = $partner; + global $globals; + + $this->fields = array_intersect($payload->v('fields'), WSRequestFields::$CHOICES); + $this->order = array_intersect($payload->v('order', array()), self::$ORDER_CHOICES); + + $this->criteria = array(); + $criteria = new PlDict($payload->v('criteria')); + foreach (WSRequestCriteria::$CHOICES_SIMPLE as $criterion) { + if ($criteria->has($criterion)) { + $this->criteria[$criterion] = $criteria->s($criterion); + } + } + foreach (WSRequestCriteria::$CHOICES_ENUM as $criterion) { + if ($criteria->has($criterion)) { + $this->criteria[$criterion] = $criteria->s($criterion); + } + } + foreach (WSRequestCriteria::$CHOICES_LIST as $criterion) { + if ($criteria->has($criterion)) { + $this->criteria[$criterion] = $criteria->v($criterion); + } + } + + // Amount may not exceed $globals->pta->max_result_per_query. + $amount = $payload->i('amount', self::DEFAULT_AMOUNT); + $this->amount = min($amount, $globals->pta->max_result_per_query); + } + + public function get() + { + $cond = $this->getCond(); + $cond->addChild(new UFC_PartnerSharing($this->partner->id)); + $pf = new ProfileFilter($cond, $this->getOrders()); + $pf->restrictVisibilityForPartner($this->partner->id); + $response = array(); + $matches = $pf->getTotalProfileCount(); + $response['matches'] = $matches; + + $profiles = array(); + if ($matches) { + // TODO : improve fetching by passing an adequate FETCH field + $iter = $pf->iterProfiles(new PlLimit($this->amount), 0x0000, Visibility::VIEW_PRIVATE); + while ($profile = $iter->next()) { + if ($profile->getPartnerSettings($this->partner->id)->exposed_uid !== 0) { + $profile_data = new WSRequestEntry($this->partner, $profile); + $profiles[] = $profile_data->getFields($this->fields); + } + } + } + $response['profiles'] = $profiles; + return $response; + } + + public function assignToPage(PlPage $page) + { + $response = $this->get(); + $page->jsonAssign('matches', $response['matches']); + $page->jsonAssign('profiles', $response['profiles']); + } + + /** Compute the orders to use for the current request. + * @return array of PlFilterOrder + */ + protected function getOrders() + { + $orders = array(); + foreach ($this->order as $order) + { + switch ($order) { + case self::ORDER_RAND: + $orders[] = new PFO_Random(); + break; + case self::ORDER_NAME: + $orders[] = new UFO_Name(Profile::DN_SORT); + break; + case self::ORDER_PROMOTION: + $orders[] = new UFO_Promo(); + break; + default: + break; + } + } + return $orders; + } + + /** Compute the conditions to use for the current request. + * @return A PlFilterCondition instance (actually a PFC_And) + */ + protected function getCond() + { + $cond = new PFC_And(); + foreach ($this->criteria as $criterion => $value) { + switch ($criterion) { + + // ENUM fields + case WSRequestCriteria::SCHOOL: + // Useless criterion: we don't need to check on origin school + if (WSRequestCriteria::$CHOICES_ENUM[$criterion][$value]) { + $cond->addChild(new PFC_True()); + } else { + $cond->addChild(new PFC_False()); + }; + break; + case WSRequestCriteria::DIPLOMA: + $diploma = WSRequestCriteria::$CHOICES_ENUM[$criterion][$value]; + $id_X = XDB::fetchOneCell('SELECT id + FROM profile_education_enum + WHERE abbreviation = {?}', 'X'); + $cond->addChildren(array( + new UFC_EducationSchool($id_X), + new UFC_EducationDegree($diploma), + )); + break; + + // TEXT fields + case WSRequestCriteria::FIRSTNAME: + case WSRequestCriteria::LASTNAME: + $cond->addChild(new UFC_NameTokens($value, UFC_NameTokens::FLAG_PUBLIC, false, false, $criterion)); + break; + case WSRequestCriteria::PROMOTION: + $cond->addChild(new PFC_Or( + new UFC_Promo(UserFilter::OP_EQUALS, + UserFilter::GRADE_ING, + $value), + new UFC_Promo(UserFilter::OP_EQUALS, + UserFilter::GRADE_MST, + $value), + new UFC_Promo(UserFilter::OP_EQUALS, + UserFilter::GRADE_PHD, + $value) + )); + break; + case WSRequestCriteria::ALT_DIPLOMA: + $cond->addChild( + new UFC_EducationDegree( + DirEnum::getIds(DirEnum::EDUDEGREES, $value))); + break; + case WSRequestCriteria::DIPLOMA_FIELD: + $cond->addChild( + new UFC_EducationField( + DirEnum::getIds(DirEnum::EDUFIELDS, $value))); + break; + case WSRequestCriteria::CITY: + $cond->addChild( + new UFC_AddressField($value, + UFC_AddressField::FIELD_LOCALITY, + UFC_Address::TYPE_HOME, + UFC_Address::FLAG_CURRENT)); + break; + case WSRequestCriteria::COUNTRY: + $cond->addChild( + new UFC_AddressField($value, + UFC_AddressField::FIELD_COUNTRY, + UFC_Address::TYPE_HOME, + UFC_Address::FLAG_CURRENT)); + break; + case WSRequestCriteria::ZIPCODE: + $cond->addChild( + new UFC_AddressField($value, + UFC_AddressField::FIELD_ZIPCODE, + UFC_Address::TYPE_HOME, + UFC_Address::FLAG_CURRENT)); + break; + case WSRequestCriteria::JOB_ANY_COUNTRY: + $cond->addChild( + new UFC_AddressField($value, + UFC_AddressField::FIELD_COUNTRY, + UFC_Address::TYPE_PRO, + UFC_Address::FLAG_ANY)); + break; + case WSRequestCriteria::JOB_CURRENT_CITY: + $cond->addChild( + new UFC_AddressField($value, + UFC_AddressField::FIELD_LOCALITY, + UFC_Address::TYPE_PRO, + UFC_Address::FLAG_ANY)); + break; + case WSRequestCriteria::JOB_ANY_COMPANY: + case WSRequestCriteria::JOB_CURRENT_COMPANY: + $cond->addChild( + new UFC_Job_Company(UFC_Job_Company::JOBNAME, + $value)); + break; + case WSRequestCriteria::JOB_ANY_SECTOR: + case WSRequestCriteria::JOB_CURRENT_SECTOR: + case WSRequestCriteria::JOB_CURRENT_TITLE: + $cond->addChild( + new UFC_Job_Terms(DirEnum::getIds(DirEnum::JOBTERMS, $value))); + break; + + // LIST fields + case WSRequestCriteria::HOBBIES: + $subcond = new PFC_Or(); + foreach ($value as $val) { + $subcond->addChild(new UFC_Comment($value)); + } + $cond->addChild($subcond); + break; + case WSRequestCriteria::JOB_COMPETENCIES: + case WSRequestCriteria::JOB_RESUME: + case WSRequestCriteria::PROFESSIONAL_PROJECT: + $subcond = new PFC_Or(); + foreach ($value as $val) { + $subcond->addChild( + new UFC_Job_Description($value, UserFilter::JOB_USERDEFINED)); + } + $cond->addChild($subcond); + break; + case WSRequestCriteria::NOT_UID: + $cond->addChild( + new PFC_Not( + new UFC_PartnerSharingID($this->partner->id, $value))); + break; + default: + break; + } + } + + return $cond; + } + + /** Input validation + */ + const ERROR_MISSING_FIELDS = 'missing_fields'; + const ERROR_MISSING_CRITERIA = 'missing_criteria'; + const ERROR_MALFORMED_AMOUNT = 'malformed_amount'; + const ERROR_MALFORMED_ORDER = 'malformed_order'; + + public static $ERROR_MESSAGES = array( + self::ERROR_MISSING_FIELDS => "The 'fields' field is mandatory.", + self::ERROR_MISSING_CRITERIA => "The 'criteria' field is mandatory.", + self::ERROR_MALFORMED_AMOUNT => "The 'amount' value is invalid (expected an int)", + self::ERROR_MALFORMED_ORDER => "The 'order' value is invalid (expected an array)", + ); + + /** Static method performing all input validation on the payload. + * @param PlDict $payload The payload to validate + * @return array Errors discovered when validating input + */ + public static function validatePayload(PlDict $payload) + { + $errors = array(); + if (!$payload->has('fields')) { + $errors[] = self::ERROR_MISSING_FIELDS; + } + if (!$payload->has('criteria')) { + $errors[] = self::ERROR_MISSING_CRITERIA; + } + + if ($payload->has('amount') && $payload->i('amount', -1) < 0) { + $errors[] = self::ERROR_MALFORMED_AMOUNT; + } + + if (!is_array($payload->v('order', array()))) { + $errors[] = self::ERROR_MALFORMED_ORDER; + } + + return $errors; + } +} + +// {{{ WSRequestEntry +/** Performs field retrieval for a profile. + */ +class WSRequestEntry +{ + private $profile = null; + private $partner = null; + private $settings = null; + + public function __construct($partner, $profile) + { + $this->partner = $partner; + $this->profile = $profile; + $this->settings = $this->profile->getPartnerSettings($this->partner->id); + } + + public function isVisible($level) + { + return $this->settings->sharing_visibility->isVisible($level); + } + + public function getFields($fields) + { + $data = array(); + foreach ($fields as $field) + { + $val = $this->getFieldValue($field); + if ($val !== null) { + $data[$field] = $val; + } + } + $data['uid'] = $this->settings->exposed_uid; + return $data; + } + + protected function getFieldValue($field) + { + // Shortcut + $p = $this->profile; + + switch ($field) { + case WSRequestFields::UID: + // UID is always included + return; + case WSRequestFields::BIRTHDATE: + case WSRequestFields::FAMILY_POSITION: + case WSRequestFields::HONORARY_TITLES: + case WSRequestFields::LANGS: + case WSRequestFields::JOB_COMPETENCIES: + case WSRequestFields::RESUME: + case WSRequestFields::PROFESSIONAL_PROJECT: + case WSRequestFields::HOBBIES: + // Ignored fields + return; + + // Public fields + case WSRequestFields::FIRSTNAME: + return $p->firstName(); + case WSRequestFields::LASTNAME: + return $p->lastName(); + case WSRequestFields::GENDER: + if ($p->isFemale()) { + return WSRequestFields::GENDER_WOMAN; + } else { + return WSRequestFields::GENDER_MAN; + } + case WSRequestFields::SCHOOL: + return WSRequestCriteria::SCHOOL_X; + case WSRequestFields::DIPLOMA: + $edu = $p->getEducations(Profile::EDUCATION_MAIN); + if (count($edu)) { + return WSRequestFields::profileDegreeToWSDiploma( + array_pop($edu)->degree); + } else { + return null; + } + case WSRequestFields::DIPLOMA_FIELD: + $edu = $p->getEducations(Profile::EDUCATION_MAIN); + if (count($edu)) { + return array_pop($edu)->field; + } else { + return null; + } + case WSRequestFields::PROMOTION: + return $p->yearpromo(); + case WSRequestFields::ALT_DIPLOMAS: + $diplomas = array(); + foreach ($p->getExtraEducations() as $edu) { + $diplomas[] = WSRequestFields::profileDegreeToWSDiploma( + $edu->degree); + } + return $diplomas; + + // Other generic profile fields + case WSRequestFields::EMAIL: + if ($this->settings->sharing_visibility->isVisible(Visibility::EXPORT_PRIVATE)) { + // If sharing "all" data, share best email. + return $p->displayEmail(); + } elseif ($this->settings->sharing_visibility->isVisible(Visibility::EXPORT_AX)) { + // If sharing "AX" level, share "AX" email. + return $p->email_directory; + } else { + // Otherwise, don't share. + return null; + } + case WSRequestFields::MOBILE_PHONE: + $phones = $p->getPhones(Phone::TYPE_MOBILE); + if (count($phones)) { + $phone = array_pop($phones); + if ($this->isVisible($phone->pub)) { + return $phone->display; + } + } + return null; + case WSRequestFields::PIC_SMALL: + case WSRequestFields::PIC_MEDIUM: + case WSRequestFields::PIC_LARGE: + if ($this->isVisible($p->photo_pub)) { + $token = sha1(uniqid(rand(), true)); + XDB::execute('DELETE FROM profile_photo_tokens + WHERE pid = {?}', $p->pid); + XDB::execute('INSERT INTO profile_photo_tokens + SET pid = {?}, token = {?}, + expires = ADDTIME(NOW(), \'0:05:00\'', + $p->pid, $token); + $size_mappings = array( + WSRequestFields::PIC_SMALL => 'small', + WSRequestFields::PIC_MEDIUM => 'medium', + WSRequestFields::PIC_LARGE => 'large', + ); + $size = $size_mappings[$field]; + return pl_url("pta/picture/$size/$token"); + } else { + return null; + } + + // Address related + case WSRequestFields::CURRENT_CITY: + $address = $p->getMainAddress(); + if ($address != null && $this->isVisible($address->pub)) { + return $address->localityName; + } else { + return null; + } + case WSRequestFields::CURRENT_COUNTRY: + $address = $p->getMainAddress(); + if ($address != null && $this->isVisible($address->pub)) { + return $address->countryId; + } else { + return null; + } + case WSRequestFields::ADDRESS: + $address = $p->getMainAddress(); + if ($address != null && $this->isVisible($address->pub)) { + return $this->addressToResponse($address); + } else { + return null; + } + + // Job related + case WSRequestFields::CURRENT_COMPANY: + $job = $p->getMainJob(); + if ($job != null && $this->isVisible($job->pub)) { + return $job->company->name; + } else { + return null; + } + case WSRequestFields::JOB: + $jobs = $p->getJobs(Profile::JOBS_ALL); + $res = array(); + foreach ($jobs as $job) { + if ($this->isVisible($job->pub)) { + $jobs[] = $this->jobToResponse($job); + } + } + return $jobs; + case WSRequestFields::MINI_RESUME: + if ($this->isVisible(Visibility::EXPORT_PRIVATE)) { + return $p->cv; + } else { + return null; + } + + // Community + case WSRequestFields::GROUPS: + $groups = array(); + if ($this->isVisible(Visibility::EXPORT_PRIVATE)) { + foreach ($p->owner()->groups(true, true) as $group) { + $groups[] = array('name' => $group['nom']); + } + } + return $groups; + case WSRequestFields::FRIENDS: + $friends = array(); + if ($this->isVisible(Visibility::EXPORT_PRIVATE)) { + while ($contact = $p->owner()->iterContacts()) { + $cps = $contact->getPartnerSettings(UFC_PartnerSharing::PTA); + if ($cps->sharing_visibility->isVisible(Visibility::EXPORT_PRIVATE)) { + $friends[] = $cps->exposed_uid; + } + } + } + return $friends; + case WSRequestFields::NETWORKING: + $networks = array(); + if ($this->isVisible(Visibility::EXPORT_PRIVATE)) { + foreach ($p->getNetworking(Profile::NETWORKING_ALL) as $nw) { + $networks[] = array( + 'network' => $nw['name'], + 'login' => $nw['address'], + ); + } + } + return $networks; + + default: + return null; + } + } + + protected function jobToResponse($job) + { + $data = array(); + $data['company'] = $job->company->name; + $data['title'] = $job->description; + $data['sector'] = array_pop($job->terms); + $data['entry'] = null; + $data['left'] = null; + foreach($job->phones() as $phone) { + if ($this->isVisible($phone->pub)) { + $data['phone'] = $phone->display; + break; + } + } + if ($job->address && $this->isVisible($job->address->pub)) { + $data['address'] = $this->addressToResponse($job->address); + } + return $data; + } + + protected function addressToResponse($address) + { + $data = array(); + $data['street'] = $address->postalText; + $data['zipcode'] = $address->postalCode; + $data['city'] = $address->localityName; + $data['country'] = $address->countryId; + $data['latitude'] = $address->latitude; + $data['longitude'] = $address->longitude; + return $data; + } +} +// }}} +// {{{ WSRequestCriteria +/** Holds all enums and related mappings for criterias. + */ +class WSRequestCriteria +{ + const FIRSTNAME = 'firstname'; + const LASTNAME = 'lastname'; + const SCHOOL = 'school'; + const DIPLOMA = 'diploma'; + const DIPLOMA_FIELD = 'diploma_field'; + const PROMOTION = 'promotion'; + const HOBBIES = 'hobbies'; + const ZIPCODE = 'zipcode'; + const CITY = 'city'; + const COUNTRY = 'country'; + const JOB_CURRENT_SECTOR = 'job_current_sector'; + const JOB_CURRENT_TITLE = 'job_current_title'; + const JOB_CURRENT_COMPANY = 'job_current_company'; + const JOB_CURRENT_CITY = 'job_current_city'; + const JOB_CURRENT_COUNTRY = 'job_current_country'; + const JOB_ANY_SECTOR = 'job_any_sector'; + const JOB_ANY_COMPANY = 'job_any_company'; + const JOB_ANY_COUNTRY = 'job_any_country'; + const JOB_RESUME = 'job_resume'; + const JOB_COMPETENCIES = 'job_competencies'; + const PROFESSIONAL_PROJECT = 'professional_project'; + const ALT_DIPLOMA = 'alt_diploma'; + const NOT_UID = 'not_uid'; + + public static $CHOICES_SIMPLE = array( + self::FIRSTNAME, + self::LASTNAME, + self::PROMOTION, + self::ALT_DIPLOMA, + self::DIPLOMA_FIELD, + self::CITY, + self::ZIPCODE, + self::COUNTRY, + self::JOB_ANY_COUNTRY, + self::JOB_CURRENT_CITY, + self::JOB_CURRENT_COUNTRY, + self::JOB_ANY_COMPANY, + self::JOB_ANY_SECTOR, + self::JOB_CURRENT_COMPANY, + self::JOB_CURRENT_SECTOR, + self::JOB_CURRENT_TITLE, + ); + + const SCHOOL_AGRO = 'agro'; + const SCHOOL_ENSAE = 'ensae'; + const SCHOOL_ENSCP = 'enscp'; + const SCHOOL_ENST = 'enst'; + const SCHOOL_ENSTA = 'ensta'; + const SCHOOL_ESPCI = 'espci'; + const SCHOOL_GADZ = 'gadz'; + const SCHOOL_HEC = 'hec'; + const SCHOOL_MINES = 'ensmp'; + const SCHOOL_PONTS = 'enpc'; + const SCHOOL_SUPELEC = 'supelec'; + const SCHOOL_SUPOP = 'supop'; + const SCHOOL_X = 'X'; + + const DIPLOMA_ING = 'ING'; + const DIPLOMA_MASTER = 'MASTER'; + const DIPLOMA_PHD = 'PHD'; + + public static $CHOICES_ENUM = array( + self::SCHOOL => array( + self::SCHOOL_AGRO => false, + self::SCHOOL_ENSAE => false, + self::SCHOOL_ENSCP => false, + self::SCHOOL_ENST => false, + self::SCHOOL_ENSTA => false, + self::SCHOOL_ESPCI => false, + self::SCHOOL_GADZ => false, + self::SCHOOL_HEC => false, + self::SCHOOL_MINES => false, + self::SCHOOL_PONTS => false, + self::SCHOOL_SUPELEC => false, + self::SCHOOL_SUPOP => false, + self::SCHOOL_X => true, + ), + self::DIPLOMA => array( + self::DIPLOMA_ING => UserFilter::GRADE_ING, + self::DIPLOMA_MASTER => UserFilter::GRADE_MST, + self::DIPLOMA_PHD => UserFilter::GRADE_PHD, + ), + ); + + public static $CHOICES_LIST = array( + self::HOBBIES, + self::JOB_COMPETENCIES, + self::JOB_RESUME, + self::NOT_UID, + self::PROFESSIONAL_PROJECT, + ); +} + +// }}} +// {{{ WSRequestFields +/** Holds all enums for fields. + */ +class WSRequestFields +{ + const UID = 'uid'; + const FIRSTNAME = 'firstname'; + const LASTNAME = 'lastname'; + const BIRTHDATE = 'birthdate'; + const GENDER = 'gender'; + const FAMILY_POSITION = 'family_position'; + const SCHOOL = 'school'; + const DIPLOMA = 'diploma'; + const DIPLOMA_FIELD = 'diploma_field'; + const PROMOTION = 'promotion'; + const ALT_DIPLOMAS = 'alt_diplomas'; + const CURRENT_COMPANY = 'current_company'; + const CURRENT_CITY = 'current_city'; + const CURRENT_COUNTRY = 'current_country'; + const MOBILE_PHONE = 'mobile_phone'; + const HONORARY_TITLES = 'honorary_titles'; + const EMAIL = 'email'; + const PIC_SMALL = 'pic_small'; + const PIC_MEDIUM = 'pic_medium'; + const PIC_LARGE = 'pic_large'; + const ADDRESS = 'address'; + const JOB = 'job'; + const GROUPS = 'groups'; + const LANGS = 'langs'; + const JOB_COMPETENCIES = 'job_competencies'; + const MINI_RESUME = 'mini_resume'; + const RESUME = 'resume'; + const PROFESSIONAL_PROJECT = 'professional_project'; + const HOBBIES = 'hobbies'; + const FRIENDS = 'friends'; + const NETWORKING = 'networking'; + + const GENDER_MAN = 'man'; + const GENDER_WOMAN = 'woman'; + + public static $CHOICES = array( + self::UID, + self::FIRSTNAME, + self::LASTNAME, + self::BIRTHDATE, + self::GENDER, + self::FAMILY_POSITION, + self::SCHOOL, + self::DIPLOMA, + self::DIPLOMA_FIELD, + self::PROMOTION, + self::ALT_DIPLOMAS, + self::CURRENT_COMPANY, + self::CURRENT_CITY, + self::CURRENT_COUNTRY, + self::MOBILE_PHONE, + self::HONORARY_TITLES, + self::EMAIL, + self::PIC_SMALL, + self::PIC_MEDIUM, + self::PIC_LARGE, + self::ADDRESS, + self::JOB, + self::GROUPS, + self::LANGS, + self::JOB_COMPETENCIES, + self::MINI_RESUME, + self::RESUME, + self::PROFESSIONAL_PROJECT, + self::HOBBIES, + self::FRIENDS, + self::NETWORKING, + ); + + public static function profileDegreeToWSDiploma($degree) + { + switch ($degree) { + case Profile::DEGREE_X: + return self::DIPLOMA_ING; + case Profile::DEGREE_M: + return self::DIPLOMA_MASTER; + case Profile::DEGREE_D: + return self::DIPLOMA_PHD; + default: + return null; + } + } + +} +// }}} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: +?> diff --git a/upgrade/1.1.3/03_pta.sql b/upgrade/1.1.3/03_pta.sql new file mode 100644 index 0000000..cb050e7 --- /dev/null +++ b/upgrade/1.1.3/03_pta.sql @@ -0,0 +1,37 @@ +CREATE TABLE IF NOT EXISTS profile_partnersharing_enum ( + id int(6) unsigned NOT NULL, + api_uid int(11) unsigned NULL, + shortname varchar(64) NOT NULL DEFAULT '', + name varchar(255) NOT NULL DEFAULT '', + url varchar(255) NOT NULL DEFAULT '', + default_sharing_level enum('admin', 'private', 'ax', 'public', 'none') DEFAULT 'none', + has_directory int(1) unsigned NOT NULL DEFAULT 0, + has_bulkmail int(1) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (id), + FOREIGN KEY (api_uid) REFERENCES accounts (uid) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO profile_partnersharing_enum + SET shortname = 'pta', name = 'ParisTech Alumni', url = 'http://www.paristech-alumni.org', default_sharing_level = 'public', has_directory = 1, has_bulkmail = 1; + +CREATE TABLE IF NOT EXISTS profile_partnersharing_settings ( + pid int(11) unsigned NOT NULL, + partner_id int(6) unsigned NOT NULL, + exposed_uid varchar(255) NOT NULL, + sharing_level enum('admin', 'private', 'ax', 'public', 'none') DEFAULT 'none', + allow_email enum('none', 'digest', 'direct') DEFAULT 'direct', + last_connection datetime NULL, + PRIMARY KEY (pid, partner_id), + KEY (partner_id, exposed_uid), + FOREIGN KEY (pid) REFERENCES profiles (pid) ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (partner_id) REFERENCES profile_partnersharing_enum (id) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS profile_photo_tokens ( + pid int(11) unsigned NOT NULL, + token varchar(255) NOT NULL, + expires datetime NOT NULL, + PRIMARY KEY (pid), + KEY (token), + FOREIGN KEY (pid) REFERENCES profiles (pid) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 2.1.4