Edit.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. <?php
  2. /**
  3. * This file is part of the ForkBB <https://github.com/forkbb>.
  4. *
  5. * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
  6. * @license The MIT License (MIT)
  7. */
  8. declare(strict_types=1);
  9. namespace ForkBB\Models\Pages\Profile;
  10. use ForkBB\Core\Image;
  11. use ForkBB\Core\Validator;
  12. use ForkBB\Core\Exceptions\MailException;
  13. use ForkBB\Models\Page;
  14. use ForkBB\Models\Pages\Profile;
  15. use ForkBB\Models\User\User;
  16. use function \ForkBB\__;
  17. use function \ForkBB\num;
  18. use function \ForkBB\size;
  19. class Edit extends Profile
  20. {
  21. /**
  22. * Паттерн для доступных к загрузке типов файлов
  23. * @var string
  24. */
  25. protected $accept = 'image/*';
  26. /**
  27. * Подготавливает данные для шаблона редактирования профиля
  28. */
  29. public function edit(array $args, string $method): Page
  30. {
  31. if (
  32. false === $this->initProfile($args['id'])
  33. || ! $this->rules->editProfile
  34. ) {
  35. return $this->c->Message->message('Bad request');
  36. }
  37. $this->c->Lang->load('validator');
  38. $this->c->Lang->load('profile_other');
  39. if ('POST' === $method) {
  40. if ($this->rules->rename) {
  41. $ruleUsername = 'required|string:trim|username|noURL:1';
  42. } else {
  43. $ruleUsername = 'absent';
  44. }
  45. if ($this->rules->setTitle) {
  46. $ruleTitle = 'exist|string:trim|max:50|noURL';
  47. } else {
  48. $ruleTitle = 'absent';
  49. }
  50. if ($this->rules->useAvatar) {
  51. $ruleAvatar = "image|max:{$this->c->Files->maxImgSize('K')}";
  52. $ruleDelAvatar = $this->curUser->avatar ? 'checkbox' : 'absent';
  53. } else {
  54. $ruleAvatar = 'absent';
  55. $ruleDelAvatar = 'absent';
  56. }
  57. if ($this->user->isAdmMod) {
  58. $ruleAdminNote = 'exist|string:trim|max:30';
  59. } else {
  60. $ruleAdminNote = 'absent';
  61. }
  62. if ($this->rules->editWebsite) {
  63. $ruleWebsite = 'exist|string:trim,empty|max:100|regex:%^(?:https?:)?//[^\x00-\x1F\s]+$%iu';
  64. } else {
  65. $ruleWebsite = 'absent';
  66. }
  67. if ($this->rules->useSignature) {
  68. $ruleSignature = "exist|string:trim|max:{$this->curUser->g_sig_length}|check_signature";
  69. } else {
  70. $ruleSignature = 'absent';
  71. }
  72. $v = $this->c->Validator->reset()
  73. ->addValidators([
  74. 'check_signature' => [$this, 'vCheckSignature'],
  75. ])->addRules([
  76. 'token' => 'token:EditUserProfile',
  77. 'username' => $ruleUsername,
  78. 'title' => $ruleTitle,
  79. 'upload_avatar' => $ruleAvatar,
  80. 'delete_avatar' => $ruleDelAvatar,
  81. 'admin_note' => $ruleAdminNote,
  82. 'realname' => 'exist|string:trim|max:40|noURL:1',
  83. 'gender' => 'required|integer|in:0,1,2',
  84. 'location' => 'exist|string:trim|max:30|noURL:1',
  85. 'email_setting' => 'required|integer|in:0,1,2',
  86. 'url' => $ruleWebsite,
  87. 'signature' => $ruleSignature,
  88. 'save' => 'required|string',
  89. ])->addAliases([
  90. 'username' => 'Username',
  91. 'title' => 'Title',
  92. 'upload_avatar' => 'New avatar',
  93. 'delete_avatar' => 'Delete avatar',
  94. 'admin_note' => 'Admin note',
  95. 'realname' => 'Realname',
  96. 'gender' => 'Gender',
  97. 'location' => 'Location',
  98. 'email_setting' => 'Email settings label',
  99. 'url' => 'Website',
  100. 'signature' => 'Signature',
  101. ])->addArguments([
  102. 'token' => $args,
  103. 'username.username' => $this->curUser,
  104. ])->addMessages([
  105. ]);
  106. $valid = $v->validation($_FILES + $_POST);
  107. $data = $v->getData();
  108. unset($data['token'], $data['upload_avatar'], $data['delete_avatar']);
  109. if ($valid) {
  110. if (
  111. $v->delete_avatar
  112. || $v->upload_avatar instanceof Image
  113. ) {
  114. $this->curUser->deleteAvatar();
  115. }
  116. if ($v->upload_avatar instanceof Image) {
  117. $name = $this->c->Secury->randomPass(8);
  118. $path = $this->c->DIR_PUBLIC . "{$this->c->config->o_avatars_dir}/{$name}.(jpg|png|gif|webp)";
  119. $result = $v->upload_avatar
  120. ->rename(true)
  121. ->rewrite(false)
  122. ->resize($this->c->config->i_avatars_width, $this->c->config->i_avatars_height)
  123. ->toFile($path, $this->c->config->i_avatars_size);
  124. if (true === $result) {
  125. $this->curUser->avatar = $v->upload_avatar->name() . '.' . $v->upload_avatar->ext();
  126. }
  127. }
  128. $this->curUser->replAttrs($data, true);
  129. $this->c->users->update($this->curUser);
  130. return $this->c->Redirect->page('EditUserProfile', $args)->message('Profile redirect');
  131. } else {
  132. $this->fIswev = $v->getErrors();
  133. $this->curUser->replAttrs($data);
  134. }
  135. }
  136. $this->crumbs = $this->crumbs(
  137. [
  138. $this->c->Router->link('EditUserProfile', $args),
  139. 'Editing profile',
  140. ]
  141. );
  142. $this->form = $this->form($args);
  143. $this->actionBtns = $this->btns('edit');
  144. return $this;
  145. }
  146. /**
  147. * Дополнительная проверка signature
  148. */
  149. public function vCheckSignature(Validator $v, string $signature): string
  150. {
  151. if ('' != $signature) {
  152. $prepare = null;
  153. // после цензуры текст сообщения пустой
  154. if ('' == $this->c->censorship->censor($signature)) {
  155. $v->addError('No signature after censoring');
  156. // количество строк
  157. } elseif (\substr_count($signature, "\n") >= $this->curUser->g_sig_lines) {
  158. $v->addError('Signature has too many lines');
  159. // проверка парсером
  160. } else {
  161. $prepare = true;
  162. $signature = $this->c->Parser->prepare($signature, true); //????
  163. foreach ($this->c->Parser->getErrors([], [], true) as $error) {
  164. $prepare = false;
  165. $v->addError($error);
  166. }
  167. }
  168. // текст сообщения только заглавными буквами
  169. if (
  170. true === $prepare
  171. && ! $this->user->isAdmin
  172. && 1 !== $this->c->config->b_sig_all_caps
  173. ) {
  174. $text = $this->c->Parser->getText();
  175. if (
  176. \preg_match('%\p{Lu}%u', $text)
  177. && ! \preg_match('%\p{Ll}%u', $text)
  178. ) {
  179. $v->addError('All caps signature');
  180. }
  181. }
  182. }
  183. return $signature;
  184. }
  185. /**
  186. * Создает массив данных для формы
  187. */
  188. protected function form(array $args): array
  189. {
  190. $form = [
  191. 'action' => $this->c->Router->link('EditUserProfile', $args),
  192. 'hidden' => [
  193. 'token' => $this->c->Csrf->create('EditUserProfile', $args),
  194. ],
  195. 'sets' => [],
  196. 'btns' => [
  197. 'save' => [
  198. 'type' => 'submit',
  199. 'value' => __('Save changes'),
  200. ],
  201. ],
  202. ];
  203. // имя, титул и аватара
  204. $fields = [];
  205. if ($this->rules->rename) {
  206. $fields['username'] = [
  207. 'type' => 'text',
  208. 'maxlength' => '25',
  209. 'caption' => 'Username',
  210. 'required' => true,
  211. 'pattern' => '^.{2,25}$',
  212. 'value' => $this->curUser->username,
  213. ];
  214. } else {
  215. $fields['username'] = [
  216. 'class' => ['pline'],
  217. 'type' => 'str',
  218. 'caption' => 'Username',
  219. 'value' => $this->curUser->username,
  220. ];
  221. }
  222. if ($this->rules->changeGroup) {
  223. $fields['group'] = [
  224. 'type' => 'link',
  225. 'caption' => 'Group',
  226. 'value' => $this->curUser->group_id ? $this->curUser->g_title : __('Change user group'),
  227. 'title' => __('Change user group'),
  228. 'href' => $this->linkChangeGroup(),
  229. ];
  230. } else {
  231. $fields['group'] = [
  232. 'class' => ['pline'],
  233. 'type' => 'str',
  234. 'caption' => 'Group',
  235. 'value' => $this->curUser->group_id ? $this->curUser->g_title : '-',
  236. ];
  237. }
  238. if ($this->rules->confModer) {
  239. $fields['configure-moderator'] = [
  240. 'type' => 'link',
  241. 'value' => __('Configure moderator rights'),
  242. 'title' => __('Configure moderator rights'),
  243. 'href' => $this->c->Router->link('EditUserModeration', $args),
  244. ];
  245. }
  246. if ($this->rules->setTitle) {
  247. $fields['title'] = [
  248. 'type' => 'text',
  249. 'maxlength' => '50',
  250. 'caption' => 'Title',
  251. 'value' => $this->curUser->title,
  252. 'help' => 'Leave blank',
  253. ];
  254. } else {
  255. $fields['title'] = [
  256. 'class' => ['pline'],
  257. 'type' => 'str',
  258. 'caption' => 'Title',
  259. 'value' => $this->curUser->title(),
  260. ];
  261. }
  262. if ($this->rules->editPass) {
  263. $fields['change_pass'] = [
  264. 'type' => 'link',
  265. 'value' => __('Change passphrase'),
  266. 'href' => $this->c->Router->link('EditUserPass', $args),
  267. ];
  268. }
  269. if ($this->rules->useAvatar) {
  270. if (! $this->curUser->avatar) {
  271. $fields['avatar'] = [
  272. 'class' => ['pline'],
  273. 'type' => 'str',
  274. 'caption' => 'Avatar',
  275. 'value' => __('Not uploaded'),
  276. ];
  277. } elseif ($this->curUser->avatar) {
  278. $fields['avatar'] = [
  279. 'type' => 'yield',
  280. 'caption' => 'Avatar',
  281. 'value' => 'avatar',
  282. ];
  283. }
  284. $form['enctype'] = 'multipart/form-data';
  285. $form['hidden']['MAX_FILE_SIZE'] = $this->c->Files->maxImgSize();
  286. if ($this->curUser->avatar) {
  287. $fields['delete_avatar'] = [
  288. 'type' => 'checkbox',
  289. 'label' => 'Delete avatar',
  290. 'checked' => false,
  291. ];
  292. }
  293. $fields['upload_avatar'] = [
  294. 'type' => 'file',
  295. 'caption' => 'New avatar',
  296. 'help' => ['New avatar info',
  297. num($this->c->config->i_avatars_width),
  298. num($this->c->config->i_avatars_height),
  299. num($this->c->config->i_avatars_size),
  300. size($this->c->config->i_avatars_size)
  301. ],
  302. 'accept' => $this->accept,
  303. ];
  304. }
  305. $form['sets']['header'] = [
  306. 'class' => ['header-edit'],
  307. # 'legend' => 'Options',
  308. 'fields' => $fields,
  309. ];
  310. // примечание администрации
  311. if ($this->user->isAdmMod) {
  312. $form['sets']['note'] = [
  313. 'class' => ['data-edit'],
  314. 'legend' => 'Admin note',
  315. 'fields' => [
  316. 'admin_note' => [
  317. 'type' => 'text',
  318. 'maxlength' => '30',
  319. 'caption' => 'Admin note',
  320. 'value' => $this->curUser->admin_note,
  321. ],
  322. ],
  323. ];
  324. }
  325. // личное
  326. $fields = [];
  327. $fields['realname'] = [
  328. 'type' => 'text',
  329. 'maxlength' => '40',
  330. 'caption' => 'Realname',
  331. 'value' => $this->curUser->realname,
  332. ];
  333. $genders = [
  334. 0 => __('Do not display'),
  335. 1 => __('Male'),
  336. 2 => __('Female'),
  337. ];
  338. $fields['gender'] = [
  339. 'class' => ['block'],
  340. 'type' => 'radio',
  341. 'value' => $this->curUser->gender,
  342. 'values' => $genders,
  343. 'caption' => 'Gender',
  344. ];
  345. $fields['location'] = [
  346. 'type' => 'text',
  347. 'maxlength' => 30,
  348. 'caption' => 'Location',
  349. 'value' => $this->curUser->location,
  350. ];
  351. $form['sets']['personal'] = [
  352. 'class' => ['data-edit'],
  353. 'legend' => 'Personal information',
  354. 'fields' => $fields,
  355. ];
  356. // контактная информация
  357. $fields = [];
  358. if ($this->rules->viewOEmail) {
  359. $fields['open-email'] = [
  360. 'class' => ['pline'],
  361. 'type' => 'str',
  362. 'caption' => 'Email info',
  363. 'value' => $this->curUser->censorEmail,
  364. ];
  365. }
  366. if ($this->rules->editEmail) {
  367. $fields['change_email'] = [
  368. 'type' => 'link',
  369. 'value' => __($this->rules->confirmEmail ? 'To confirm/change email' : 'To change email'),
  370. 'href' => $this->c->Router->link('EditUserEmail', $args),
  371. ];
  372. }
  373. $fields['email_setting'] = [
  374. 'class' => ['block'],
  375. 'type' => 'radio',
  376. 'value' => $this->curUser->email_setting,
  377. 'values' => [
  378. 0 => __('Display e-mail label'),
  379. 1 => __('Hide allow form label'),
  380. 2 => __('Hide both label'),
  381. ],
  382. 'caption' => 'Email settings label',
  383. ];
  384. if ($this->rules->editWebsite) {
  385. $fields['url'] = [
  386. 'id' => 'website',
  387. 'type' => 'text',
  388. 'maxlength' => '100',
  389. 'caption' => 'Website',
  390. 'value' => $this->curUser->url,
  391. ];
  392. } elseif (
  393. $this->rules->viewWebsite
  394. && $this->curUser->url
  395. ) {
  396. $fields['url'] = [
  397. 'id' => 'website',
  398. 'class' => ['pline'],
  399. 'type' => 'link',
  400. 'caption' => 'Website',
  401. 'value' => $this->curUser->censorUrl,
  402. 'href' => $this->curUser->censorUrl,
  403. ];
  404. }
  405. $form['sets']['contacts'] = [
  406. 'class' => ['data-edit'],
  407. 'legend' => 'Contact details',
  408. 'fields' => $fields,
  409. ];
  410. // подпись
  411. if ($this->rules->useSignature) {
  412. $fields = [];
  413. $fields['signature'] = [
  414. 'type' => 'textarea',
  415. 'value' => $this->curUser->signature,
  416. 'caption' => 'Signature',
  417. 'help' => ['Sig max size', num($this->curUser->g_sig_length), num($this->curUser->g_sig_lines)],
  418. ];
  419. $form['sets']['signature'] = [
  420. 'class' => ['data-edit'],
  421. 'legend' => 'Signature',
  422. 'fields' => $fields,
  423. ];
  424. }
  425. return $form;
  426. }
  427. }