profiledetailsettings.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. <?php
  2. /*
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2011, StatusNet, Inc.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. if (!defined('GNUSOCIAL')) {
  20. exit(1);
  21. }
  22. class ProfileDetailSettingsAction extends ProfileSettingsAction
  23. {
  24. /**
  25. * Title of the page
  26. *
  27. * @return string Title of the page
  28. */
  29. public function title()
  30. {
  31. // TRANS: Title for extended profile settings.
  32. // TRANS: %%site.name%% is the name of the site.
  33. return _m('Extended profile settings');
  34. }
  35. protected function doPost()
  36. {
  37. if ($this->arg('save')) {
  38. return $this->saveDetails();
  39. }
  40. // TRANS: Message given submitting a form with an unknown action.
  41. throw new ClientException(_m('Unexpected form submission.'));
  42. }
  43. public function showContent()
  44. {
  45. $widget = new ExtendedProfileWidget(
  46. $this,
  47. $this->scoped,
  48. ExtendedProfileWidget::EDITABLE
  49. );
  50. $widget->show();
  51. }
  52. public function saveDetails()
  53. {
  54. common_debug(var_export($_POST, true));
  55. $this->saveStandardProfileDetails();
  56. $simpleFieldNames = ['title', 'spouse', 'kids', 'manager'];
  57. $dateFieldNames = ['birthday'];
  58. foreach ($simpleFieldNames as $name) {
  59. $value = $this->trimmed('extprofile-' . $name);
  60. $this->saveField($name, $value);
  61. }
  62. foreach ($dateFieldNames as $name) {
  63. $value = $this->trimmed('extprofile-' . $name);
  64. $date_val = $this->parseDate($name, $value);
  65. $this->saveField($name, null, null, null, $date_val);
  66. }
  67. $this->savePhoneNumbers();
  68. $this->saveIms();
  69. $this->saveWebsites();
  70. $this->saveExperiences();
  71. $this->saveEducations();
  72. $this->saveCustomFields($this);
  73. // TRANS: Success message after saving extended profile details.
  74. return _m('Details saved.');
  75. }
  76. public function parseDate($fieldname, $datestr, $required = false)
  77. {
  78. if (empty($datestr)) {
  79. if ($required) {
  80. $msg = sprintf(
  81. // TRANS: Exception thrown when no date was entered in a required date field.
  82. // TRANS: %s is the field name.
  83. _m('You must supply a date for "%s".'),
  84. $fieldname
  85. );
  86. throw new Exception($msg);
  87. }
  88. } else {
  89. $ts = strtotime($datestr);
  90. if ($ts === false) {
  91. throw new Exception(
  92. sprintf(
  93. // TRANS: Exception thrown on incorrect data input.
  94. // TRANS: %1$s is a field name, %2$s is the incorrect input.
  95. _m('Invalid date entered for "%1$s": %2$s.'),
  96. $fieldname,
  97. $ts
  98. )
  99. );
  100. }
  101. return common_sql_date($ts);
  102. }
  103. return null;
  104. }
  105. public function savePhoneNumbers()
  106. {
  107. common_debug('save phone numbers');
  108. $phones = $this->findPhoneNumbers();
  109. $this->removeAll('phone');
  110. $i = 0;
  111. foreach ($phones as $phone) {
  112. if (!empty($phone['value'])) {
  113. ++$i;
  114. $this->saveField(
  115. 'phone',
  116. $phone['value'],
  117. $phone['rel'],
  118. $i
  119. );
  120. }
  121. }
  122. }
  123. public function findPhoneNumbers()
  124. {
  125. // Form vals look like this:
  126. // 'extprofile-phone-1' => '11332',
  127. // 'extprofile-phone-1-rel' => 'mobile',
  128. $phones = $this->sliceParams('phone', 2);
  129. $phone_array = [];
  130. foreach ($phones as $phone) {
  131. list($number, $rel) = array_values($phone);
  132. $phone_array[] = [
  133. 'value' => $number,
  134. 'rel' => $rel
  135. ];
  136. }
  137. return $phone_array;
  138. }
  139. public function findIms()
  140. {
  141. // Form vals look like this:
  142. // 'extprofile-im-0' => 'jed',
  143. // 'extprofile-im-0-rel' => 'yahoo',
  144. $ims = $this->sliceParams('im', 2);
  145. $imArray = array();
  146. foreach ($ims as $im) {
  147. list($id, $rel) = array_values($im);
  148. $imArray[] = [
  149. 'value' => $id,
  150. 'rel' => $rel
  151. ];
  152. }
  153. return $imArray;
  154. }
  155. public function saveIms()
  156. {
  157. common_debug('save ims');
  158. $ims = $this->findIms();
  159. $this->removeAll('im');
  160. $i = 0;
  161. foreach ($ims as $im) {
  162. if (!empty($im['value'])) {
  163. ++$i;
  164. $this->saveField(
  165. 'im',
  166. $im['value'],
  167. $im['rel'],
  168. $i
  169. );
  170. }
  171. }
  172. }
  173. public function findWebsites()
  174. {
  175. // Form vals look like this:
  176. $sites = $this->sliceParams('website', 2);
  177. $wsArray = array();
  178. foreach ($sites as $site) {
  179. list($id, $rel) = array_values($site);
  180. $wsArray[] = array(
  181. 'value' => $id,
  182. 'rel' => $rel
  183. );
  184. }
  185. return $wsArray;
  186. }
  187. public function saveWebsites()
  188. {
  189. common_debug('save websites');
  190. $sites = $this->findWebsites();
  191. $this->removeAll('website');
  192. $i = 0;
  193. foreach ($sites as $site) {
  194. if (!empty($site['value']) && !common_valid_http_url($site['value'])) {
  195. // TRANS: Exception thrown when entering an invalid URL.
  196. // TRANS: %s is the invalid URL.
  197. throw new Exception(sprintf(_m('Invalid URL: %s.'), $site['value']));
  198. }
  199. if (!empty($site['value'])) {
  200. ++$i;
  201. $this->saveField(
  202. 'website',
  203. $site['value'],
  204. $site['rel'],
  205. $i
  206. );
  207. }
  208. }
  209. }
  210. public function findExperiences()
  211. {
  212. // Form vals look like this:
  213. // 'extprofile-experience-0' => 'Bozotronix',
  214. // 'extprofile-experience-0-current' => 'true'
  215. // 'extprofile-experience-0-start' => '1/5/10',
  216. // 'extprofile-experience-0-end' => '2/3/11',
  217. $experiences = $this->sliceParams('experience', 4);
  218. $expArray = array();
  219. foreach ($experiences as $exp) {
  220. if (count($exp) === 4) {
  221. [$company, $current, $end, $start] = array_values($exp);
  222. } else {
  223. $end = null;
  224. [$company, $current, $start] = array_values($exp);
  225. }
  226. if (!empty($company)) {
  227. $expArray[] = array(
  228. 'company' => $company,
  229. 'start' => $this->parseDate('Start', $start, true),
  230. 'end' => ($current == 'false') ? $this->parseDate('End', $end, true) : null,
  231. 'current' => ($current == 'false') ? false : true
  232. );
  233. }
  234. }
  235. return $expArray;
  236. }
  237. public function saveExperiences()
  238. {
  239. common_debug('save experiences');
  240. $experiences = $this->findExperiences();
  241. $this->removeAll('company');
  242. $this->removeAll('start');
  243. $this->removeAll('end'); // also stores 'current'
  244. $i = 0;
  245. foreach ($experiences as $experience) {
  246. if (!empty($experience['company'])) {
  247. ++$i;
  248. $this->saveField(
  249. 'company',
  250. $experience['company'],
  251. null,
  252. $i
  253. );
  254. $this->saveField(
  255. 'start',
  256. null,
  257. null,
  258. $i,
  259. $experience['start']
  260. );
  261. // Save "current" employer indicator in rel
  262. if ($experience['current']) {
  263. $this->saveField(
  264. 'end',
  265. null,
  266. 'current', // rel
  267. $i
  268. );
  269. } else {
  270. $this->saveField(
  271. 'end',
  272. null,
  273. null,
  274. $i,
  275. $experience['end']
  276. );
  277. }
  278. }
  279. }
  280. }
  281. public function findEducations()
  282. {
  283. // Form vals look like this:
  284. // 'extprofile-education-0-school' => 'Pigdog',
  285. // 'extprofile-education-0-degree' => 'BA',
  286. // 'extprofile-education-0-description' => 'Blar',
  287. // 'extprofile-education-0-start' => '05/22/99',
  288. // 'extprofile-education-0-end' => '05/22/05',
  289. $edus = $this->sliceParams('education', 5);
  290. $eduArray = array();
  291. foreach ($edus as $edu) {
  292. list($school, $degree, $description, $end, $start) = array_values($edu);
  293. if (!empty($school)) {
  294. $eduArray[] = array(
  295. 'school' => $school,
  296. 'degree' => $degree,
  297. 'description' => $description,
  298. 'start' => $this->parseDate('Start', $start, true),
  299. 'end' => $this->parseDate('End', $end, true)
  300. );
  301. }
  302. }
  303. return $eduArray;
  304. }
  305. public function saveEducations()
  306. {
  307. common_debug('save educations');
  308. $edus = $this->findEducations();
  309. common_debug(var_export($edus, true));
  310. $this->removeAll('school');
  311. $this->removeAll('degree');
  312. $this->removeAll('degree_descr');
  313. $this->removeAll('school_start');
  314. $this->removeAll('school_end');
  315. $i = 0;
  316. foreach ($edus as $edu) {
  317. if (!empty($edu['school'])) {
  318. ++$i;
  319. $this->saveField(
  320. 'school',
  321. $edu['school'],
  322. null,
  323. $i
  324. );
  325. $this->saveField(
  326. 'degree',
  327. $edu['degree'],
  328. null,
  329. $i
  330. );
  331. $this->saveField(
  332. 'degree_descr',
  333. $edu['description'],
  334. null,
  335. $i
  336. );
  337. $this->saveField(
  338. 'school_start',
  339. null,
  340. null,
  341. $i,
  342. $edu['start']
  343. );
  344. $this->saveField(
  345. 'school_end',
  346. null,
  347. null,
  348. $i,
  349. $edu['end']
  350. );
  351. }
  352. }
  353. }
  354. public function arraySplit($array, $pieces)
  355. {
  356. if ($pieces < 2) {
  357. return array($array);
  358. }
  359. $newCount = ceil(count($array) / $pieces);
  360. $a = array_slice($array, 0, $newCount);
  361. $b = $this->arraySplit(array_slice($array, $newCount), $pieces - 1);
  362. return array_merge(array($a), $b);
  363. }
  364. public function findMultiParams($type)
  365. {
  366. $formVals = array();
  367. $target = $type;
  368. foreach ($_POST as $key => $val) {
  369. if (strrpos('extprofile-' . $key, $target) !== false) {
  370. $formVals[$key] = $val;
  371. }
  372. }
  373. return $formVals;
  374. }
  375. public function sliceParams($key, $size)
  376. {
  377. $slice = array();
  378. $params = $this->findMultiParams($key);
  379. ksort($params);
  380. $slice = $this->arraySplit($params, sizeof($params) / $size);
  381. return $slice;
  382. }
  383. /**
  384. * Save an extended profile field as a Profile_detail
  385. *
  386. * @param string $name field name
  387. * @param string $value field value
  388. * @param string|null $rel field rel (type)
  389. * @param int|null $index index (fields can have multiple values)
  390. * @param string|null $date date related date
  391. * @throws ServerException
  392. */
  393. public function saveField(string $name, ?string $value, ?string $rel = null, ?int $index = null, ?string $date = null)
  394. {
  395. common_debug('save ' . $name);
  396. $detail = new Profile_detail();
  397. $detail->profile_id = $this->scoped->getID();
  398. $detail->field_name = $name;
  399. $detail->value_index = $index;
  400. if ($detail->find(true)) {
  401. // Found an existing Profile Detail, let's update it!
  402. $orig = clone($detail);
  403. $detail->field_value = $value ?? $detail->sqlValue('NULL');
  404. $detail->rel = $rel ?? $detail->sqlValue('NULL');
  405. $detail->date = $date ?? $detail->sqlValue('NULL');
  406. if (!$detail->update($orig)) {
  407. common_log_db_error($detail, 'UPDATE', __FILE__);
  408. // TRANS: Server error displayed when a field could not be saved in the database.
  409. throw new ServerException(_m('Could not save profile details.'));
  410. }
  411. } elseif (!empty($rel) || !empty($value) || !empty($date)) {
  412. // It's the first time, let's insert, if there is anything to insert.
  413. $detail->rel = $rel;
  414. $detail->field_value = $value;
  415. $detail->date = $date;
  416. $detail->created = common_sql_now();
  417. if (!$detail->insert()) {
  418. common_log_db_error($detail, 'INSERT', __FILE__);
  419. // TRANS: Server error displayed when a field could not be saved in the database.
  420. throw new ServerException(_m('Could not save profile details.'));
  421. }
  422. }
  423. $detail->free();
  424. }
  425. public function removeAll($name)
  426. {
  427. $detail = new Profile_detail();
  428. $detail->profile_id = $this->scoped->getID();
  429. $detail->field_name = $name;
  430. $detail->delete();
  431. $detail->free();
  432. }
  433. /**
  434. * Save fields that should be stored in the main profile object
  435. *
  436. * XXX: There's a lot of dupe code here from ProfileSettingsAction.
  437. * Do not want.
  438. */
  439. public function saveStandardProfileDetails()
  440. {
  441. $fullname = $this->trimmed('extprofile-fullname');
  442. $location = $this->trimmed('extprofile-location');
  443. $tagstring = $this->trimmed('extprofile-tags');
  444. $bio = $this->trimmed('extprofile-bio');
  445. if ($tagstring) {
  446. $tags = array_map(
  447. 'common_canonical_tag',
  448. preg_split('/[\s,]+/', $tagstring)
  449. );
  450. } else {
  451. $tags = [];
  452. }
  453. foreach ($tags as $tag) {
  454. if (!common_valid_profile_tag($tag)) {
  455. // TRANS: Validation error in form for profile settings.
  456. // TRANS: %s is an invalid tag.
  457. throw new Exception(sprintf(_m('Invalid tag: "%s".'), $tag));
  458. }
  459. }
  460. $oldTags = Profile_tag::getSelfTagsArray($this->scoped);
  461. $newTags = array_diff($tags, $oldTags);
  462. if ($fullname != $this->scoped->getFullname()
  463. || $location != $this->scoped->location
  464. || !empty($newTags)
  465. || $bio != $this->scoped->getDescription()) {
  466. $orig = clone($this->scoped);
  467. // Skipping nickname change here until we add logic for when the site allows it or not
  468. // old Profilesettings will still let us do that.
  469. $this->scoped->fullname = $fullname;
  470. $this->scoped->bio = $bio;
  471. $this->scoped->location = $location;
  472. $loc = Location::fromName($location);
  473. if (empty($loc)) {
  474. $this->scoped->lat = null;
  475. $this->scoped->lon = null;
  476. $this->scoped->location_id = null;
  477. $this->scoped->location_ns = null;
  478. } else {
  479. $this->scoped->lat = $loc->lat;
  480. $this->scoped->lon = $loc->lon;
  481. $this->scoped->location_id = $loc->location_id;
  482. $this->scoped->location_ns = $loc->location_ns;
  483. }
  484. $result = $this->scoped->update($orig);
  485. if ($result === false) {
  486. common_log_db_error($this->scoped, 'UPDATE', __FILE__);
  487. // TRANS: Server error thrown when user profile settings could not be saved.
  488. throw new ServerException(_m('Could not save profile.'));
  489. }
  490. // Set the user tags
  491. $result = Profile_tag::setSelfTags($this->scoped, $tags);
  492. Event::handle('EndProfileSaveForm', array($this));
  493. }
  494. }
  495. private function saveCustomFields($action)
  496. {
  497. common_debug('save custom fields');
  498. $fields = GNUsocialProfileExtensionField::allFields();
  499. $user = common_current_user();
  500. $profile = $user->getProfile();
  501. foreach ($fields as $field) {
  502. $val = $action->trimmed('extprofile-' . $field->systemname);
  503. if (empty($val)) {
  504. continue;
  505. }
  506. $response = new GNUsocialProfileExtensionResponse();
  507. $response->profile_id = $profile->id;
  508. $response->extension_id = $field->id;
  509. if ($response->find()) {
  510. $response->fetch();
  511. $response->value = $val;
  512. if ($response->validate()) {
  513. if (empty($val)) {
  514. $response->delete();
  515. } else {
  516. $response->update();
  517. }
  518. }
  519. } else {
  520. $response->value = $val;
  521. $response->insert();
  522. }
  523. }
  524. }
  525. }