diff --git a/css-presets/Inge Peda.css b/css-presets/Inge Peda.css new file mode 100644 index 0000000..4aa3080 --- /dev/null +++ b/css-presets/Inge Peda.css @@ -0,0 +1,43 @@ +:root { + --primary-color: #80276c; + --secondary-color-1: #ffa300; + --secondary-color-2: #d78958; + --neutral-color: #4e5056; + --neutral-soft-color: #87898e; + --neutral-light-color: #f2f2f2; + --main-text-fontsize: 17px; + --main-text-fontfamily: "Nunito", sans-serif; + --main-title-fontfamily: 'Nunito', sans-serif; +} + + + +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: url('../../custom/fonts/nunito/nunito-normal-400-.eot'); + src: local(''), + url('../../custom/fonts/nunito/nunito-normal-400-.woff2') format('woff2'), + url('../../custom/fonts/nunito/nunito-normal-400-.woff') format('woff'), + url('../../custom/fonts/nunito/nunito-normal-400-.ttf') format('truetype'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* latin */ +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: local(''), + url('../../custom/fonts/nunito/nunito-normal-400-latin.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Nunito'; + font-style: normal; + font-weight: 400; + src: local(''), + url('../../custom/fonts/nunito/nunito-normal-400-latin-ext.woff2') format('woff2'); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} diff --git a/fields/YunohostUserField.php b/fields/YunohostUserField.php new file mode 100644 index 0000000..ea53440 --- /dev/null +++ b/fields/YunohostUserField.php @@ -0,0 +1,478 @@ +yunohostOptions = $this->getValuesFromConfig(); + $this->nameField = $values[self::FIELD_NAME_FIELD] ?: 'bf_titre'; + $this->emailField = $values[self::FIELD_EMAIL_FIELD] ?: 'bf_mail'; + $this->usernameField = $values[self::FIELD_USERNAME_FIELD] ?: ''; // empty usernameField will use nameField as username + $this->userMailDomain = $values[self::FIELD_USER_MAIL_DOMAIN_FIELD] ?: $this->yunohostOptions['mail_domain']; + $this->mailingList = $values[self::FIELD_MAILING_LIST]; + $this->autoUpdateMail = in_array($values[self::FIELD_AUTO_UPDATE_MAIL], [true,"1",1], true); + $this->autoAddToGroup = trim(strval($values[self::FIELD_AUTO_ADD_TO_GROUP])); + $this->cmdPrefix = $this->yunohostOptions['cmd_prefix'] ?? 'sudo'; + + // We have no default value + $this->default = null; + // not searchable + $this->searchable = null; + + $this->propertyName = 'yunohost_username'; + $this->label = _t('BAZ_USER_FIELD_LABEL'); + $this->maxChars = 60; + } + + protected function getValuesFromConfig() + { + return $this->getWiki()->config['yunohost'] ?? []; + } + protected function getYunohostUsernames() + { + $output = $retval = null; + $cmd = $this->cmdPrefix.' yunohost user list --fields username 2>&1'; + exec($cmd, $output, $retval); + $users = []; + foreach ($output as $outputline) { + preg_match('/^\s*username:\s*(.*)$/s', $outputline, $matches, PREG_OFFSET_CAPTURE, 0); + if (!empty($matches[1][0])) { + $users[] = $matches[1][0]; + } + } + return $users; + } + protected function addYunohostUser($userValues) + { + $users = $this->getYunohostUsernames(); + if (in_array($userValues['name'], $users)) { + throw new UserNameAlreadyUsedException(); + } + $output = $retval = null; + // To be able to sudo use yunohost with your php user without password, + // you need to add in /etc/sudoers (with visudo) something like + // ALL=(root) NOPASSWD: /usr/bin/yunohost + // utiliser proc_open avec des arguments plutot que de echaper / concatener + $cmd = $this->cmdPrefix.' yunohost user create '.$userValues['name'] + .' -F \''.$userValues['fullname'].'\'' + .' -d '.$this->userMailDomain + .' -p \''.$userValues['password'].'\'' + .' 2>&1'; + exec($cmd, $output, $retval); + // handle errors + if ($retval == 1) { + // TODO : LC_ALL=fr_FR.UTF-8 + if (preg_match('/^This password is among the most used passwords in the world.*$/s', $output[0], $matches, PREG_OFFSET_CAPTURE, 0)) { + throw new Exception('Le mot de passe utilisé est trop courant, il fait parti de la liste des mots de passe les plus utilisés, et pose des problème de sécurité, veuillez le changer.'); + } elseif (preg_match('/^The password needs to be at least 8 characters long.*$/s', $output[0], $matches, PREG_OFFSET_CAPTURE, 0)) { + throw new Exception('Le mot de passe utilisé est trop court, il doit faire au moins 8 caractères.'); + } else { + throw new Exception($output[0]); + } + } + $cmd = $this->cmdPrefix.' yunohost user update '.$userValues['name'] + .' --add-mailforward '.$userValues[$this->emailField] + .' 2>&1'; + exec($cmd, $output, $retval); + //dump($output); + // handle errors + if ($retval == 1) { + throw new Exception($output[0]); + } + } + + protected function modifyYunohostUser($userId, $userValues) + { + #yunohost user update -F '' -d $this->userMailDomain -p '' --add-mailforward [MAIL ...]] + } + + protected function deleteYunohostUser($userId) + { + #yunohost user delete + } + + protected function renderInput($entry) + { + $value = $this->getValue($entry); + + $authController = $this->getService(AuthController::class); + $userManager = $this->getService(UserManager::class); + $loggedUser = $authController->getLoggedUser(); + if (!empty($loggedUser)) { + $associatedUser = $userManager->getOneByName($loggedUser['name']); + if (!empty($associatedUser['name'])) { + if (empty($value) || !$this->isUserByName($value)) { + $value = $associatedUser['name']; + $message = str_replace( + ['{wikiname}','{email}'], + [$value,$associatedUser['email']], + _t('BAZ_USER_FIELD_ALREADY_CONNECTED') + ); + } + if ($value !== $loggedUser['name'] && $this->getWiki()->UserIsAdmin()) { + $associatedUser = $userManager->getOneByName($value); + } + if ($value === $loggedUser['name'] || ($this->getWiki()->UserIsAdmin() && !empty($associatedUser['email']))) { + $message = (!empty($message) ? $message."\n" : '').($this->autoUpdateMail ? str_replace( + '{email}', + $associatedUser['email'], + _t('BAZ_USER_FIELD_ALREADY_CONNECTED_AUTOUPDATE') + ): ''); + } + } + } + return $this->render("@bazar/inputs/user.twig", [ + 'value' => $value, + 'creationMode' => empty($entry[$this->getPropertyName()]), + 'message' => $message ?? null, + 'userIsAdmin' => $this->getWiki()->UserIsAdmin(), + 'userName' => $loggedUser['name'] ?? null, + 'userEmail' => $loggedUser['email'] ?? null, + 'forceLabel' => $this->propertyName.self::FORCE_LABEL, + 'forceLabelChecked' => $_POST[$this->propertyName.self::FORCE_LABEL] ?? false, + ]); + } + + public function formatValuesBeforeSave($entry) + { + $userController = $this->getService(UserController::class); + $userManager = $this->getService(UserManager::class); + $mailer = $this->getService(Mailer::class); + + $value = $this->getValue($entry); + $isImport = isset($GLOBALS['_BAZAR_']['provenance']) && $GLOBALS['_BAZAR_']['provenance'] === 'import'; + + $wiki = $this->getWiki(); + + if ($this->getWiki()->UserIsAdmin() + && in_array($_POST[$this->propertyName.self::FORCE_LABEL] ?? false, [true,"true",1,"1"], true)) { + // force entry creation but do not create user if existing for this email + $userManager = $this->getService(UserManager::class); + $existingUser = $userManager->getOneByEmail($entry[$this->emailField]); + if (!empty($existingUser)) { + $value = $existingUser['name']; + } else { + $value = null; + } + } + + if ($value && $this->isUserByName($value)) { + $wikiName = $value; + // add in groups + $this->addUserToGroups($wikiName, $entry); + + $this->updateEmailIfNeeded($wikiName, $entry[$this->emailField] ?? null); + } else { + $wikiName = strtolower($entry[$this->nameField]); + + if (!$wiki->IsWikiName($wikiName)) { + // create a UserName from value that is a wikiname + // so the User could have a chance to have the same name as the created entry + // if the name is based on `bf_titre` + $wikiName = strtolower(genere_nom_wiki($wikiName, 0)); + } + if ($this->isUserByName($wikiName)) { + $currentWikiName = strtolower($wikiName); + $wikiName = $this->findANewNotExistingUserName($currentWikiName); + if (!$isImport + && ( + !isset($_POST[$this->propertyName.self::CONFIRM_NAME_SUFFIX]) + || !in_array($_POST[$this->propertyName.self::CONFIRM_NAME_SUFFIX], [true,1,"1"], true) + ) + ) { + throw new UserFieldException( + $this->render("@bazar/inputs/user-confirm.twig", [ + 'confirmName' => $this->propertyName.self::CONFIRM_NAME_SUFFIX, + 'wikiName' => $currentWikiName, + 'newWikiName' => $wikiName, + ]) + ); + } + } + if (!isset($entry[$this->emailField])) { + throw new Exception("Yunohost user : email not set"); + } + if (!$isImport) { + if (!isset($entry['mot_de_passe_repete_wikini'])) { + throw new Exception("Yunohost user : password not confirmed second time"); + } + if ($entry['mot_de_passe_wikini'] !== $entry['mot_de_passe_repete_wikini']) { + throw new UserFieldException(_t('USER_PASSWORDS_NOT_IDENTICAL')); + } + } + + try { + $this->addYunohostUser([ + 'name' => $wikiName, + 'fullname' => $entry[$this->nameField], + 'password' => $entry['mot_de_passe_wikini'] + ]); + try { + $userController->create([ + 'name' => $wikiName, + 'email' => $entry[$this->emailField], + 'password' => $entry['mot_de_passe_wikini'] + ]); + } catch (UserNameAlreadyUsedException $ex) { + throw new UserFieldException('yeswiki : '._t('BAZ_USER_FIELD_EXISTING_USER_BY_EMAIL')); + } catch (Exception $ex) { + throw new UserFieldException('yeswiki : '.$ex->getMessage(), $ex->getCode(), $ex); + } + } catch (UserNameAlreadyUsedException $ex) { + throw new UserFieldException(_t('L\'utilisateur.ice '.$wikiName.' est déjà utilisé, veuillez vous connecter si c\'est vous, ou utiliser un autre nom d\'utilisateur.ice.')); + } catch (Exception $ex) { + throw new UserFieldException($ex->getMessage(), $ex->getCode(), $ex); + } + + // add in groups + $this->addUserToGroups($wikiName, $entry); + + // Do not send mails if we are importing + // TODO improve import detection + if (!$isImport) { + $mailer->notifyNewUser($wikiName, $entry[$this->emailField]); + + // Check if we need to subscribe the user to a mailing list + if (isset($this->mailingList) && $this->mailingList != '') { + $mailer->subscribeToMailingList($entry[$this->emailField], $this->mailingList); + } + } + } + + // indicateur pour la gestion des droits associee a la fiche. + $GLOBALS['utilisateur_wikini'] = $wikiName; + + return [ + $this->propertyName => $wikiName, + 'fields-to-remove' => [ + 'mot_de_passe_wikini', + 'mot_de_passe_repete_wikini', + $this->propertyName.self::CONFIRM_NAME_SUFFIX, + $this->propertyName.self::FORCE_LABEL, + ] + ]; + } + + protected function renderStatic($entry) + { + $value = $this->getValue($entry); + $authController = $this->getService(AuthController::class); + if ($value) { + return $this->render("@bazar/fields/user.twig", [ + 'value' => $value, + 'isLoggedUser' => $authController->getLoggedUser() && $authController->getLoggedUserName() === $value, + 'editUrl' => $this->getWiki()->href('edit', $value), + 'settingsUrl' => $this->getWiki()->href('', 'ParametresUtilisateur') + ]); + } + + return ""; + } + + // GETTERS. Needed to use them in the Twig syntax + + public function getNameField() + { + return $this->nameField; + } + + public function getEmailField() + { + return $this->emailField; + } + + public function getMailingList() + { + return $this->mailingList; + } + + public function getAutoUpdateMail() + { + return $this->autoUpdateMail; + } + + public function getAutoAddToGroup() + { + return $this->autoAddToGroup; + } + + // change return of this method to keep compatible with php 7.3 (mixed is not managed) + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array_merge( + parent::jsonSerialize(), + [ + 'nameField' => $this->getNameField(), + 'emailField' => $this->getEmailField(), + 'mailingList' => $this->getMailingList(), + 'autoUpdateMail' => $this->getAutoUpdateMail(), + 'autoAddToGroup' => $this->getAutoAddToGroup(), + ] + ); + } + + private function isUserByName(string $userName): bool + { + $userManager = $this->getService(UserManager::class); + return !empty($userManager->getOneByName($userName)); + } + + private function isUserByEmail(string $email): bool + { + $userManager = $this->getService(UserManager::class); + return !empty($userManager->getOneByEmail($email)); + } + + private function updateEmailIfNeeded(string $userName, string $email) + { + if ($this->getAutoUpdateMail() && !empty($userName) && !empty($email)) { + $authController = $this->getService(AuthController::class); + $userController = $this->getService(UserController::class); + $userManager = $this->getService(UserManager::class); + $user = $userManager->getOneByName($userName); + $loggedUser = $authController->getLoggedUser(); + if (!empty($user) + && ( + $this->getWiki()->UserIsAdmin() + || ( + !empty($loggedUser) + && $user['name'] === $loggedUser['name'] + ) + ) + && $user['email'] !== $email + ) { + try { + $userController->update($user, ['email'=>$email]); + } catch (UserNameAlreadyUsedException $ex) { + throw new UserFieldException(_t('BAZ_USER_FIELD_EXISTING_USER_BY_EMAIL')); + } catch (Exception $ex) { + throw new UserFieldException($ex->getMessage(), $ex->getCode(), $ex); + } + } + } + } + + private function addUserToGroups(string $wikiName, ?array $entry) + { + if (!empty($this->autoAddToGroup)) { + $groups = explode(',', $this->autoAddToGroup); + $groupsNames = []; + $wiki = $this->getWiki(); + $existingsGroups = $wiki->GetGroupsList(); + $formManager = $this->getService(FormManager::class); + $userManager = $this->getService(UserManager::class); + foreach ($groups as $group) { + $group = trim($group); + $forceGroupCreation = (substr($group, 0, 1) === '+'); + $groupName = substr($group, ($forceGroupCreation ? 1 : 0)); + if (substr($groupName, 0, 1) !== '@') { + // field name + $field = $formManager->findFieldFromNameOrPropertyName($groupName, $entry['id_typeannonce']); + if (!empty($field) && !empty($entry[$field->getPropertyName()])) { + $groupsNamesFromField = explode(',', $entry[$field->getPropertyName()]); + foreach ($groupsNamesFromField as $groupNameFromField) { + if ($this->userMustBeAddedToGroup($wikiName, $groupNameFromField, $forceGroupCreation, $userManager, $existingsGroups)) { + $groupsNames[] = $groupNameFromField; + } + } + } + } else { + $groupName = substr($groupName, 1); + if ($this->userMustBeAddedToGroup($wikiName, $groupName, $forceGroupCreation, $userManager, $existingsGroups)) { + $groupsNames[] = $groupName; + } + } + } + + $groupsNames = array_unique($groupsNames); + + foreach ($groupsNames as $groupName) { + $previousACL = !in_array($groupName, $existingsGroups, true) + ? '' + : $wiki->GetGroupACL($groupName)."\n"; + $wiki->SetGroupACL($groupName, $previousACL.$wikiName); + } + } + } + + private function userMustBeAddedToGroup( + string $wikiName, + string $groupName, + bool $forceGroupCreation, + UserManager $userManager, + array $existingsGroups + ) { + if (!preg_match('/^[A-Za-z0-9]+$/m', $groupName)) { + return false; + } + + if (in_array($groupName, $existingsGroups, true)) { + if (!$userManager->isInGroup($groupName, $wikiName, false)) { + return true; + } + } elseif ($forceGroupCreation) { + return true; + } + return false; + } + + private function findANewNotExistingUserName(string $firstWikiName): string + { + // remove last numbers + $baseWikiName = preg_replace("/[0-9]*$/", "", $firstWikiName); + + // a loop 1000 should be enough + for ($i=1; $i < 1000; $i++) { + $newName = "$baseWikiName$i"; + if (!$this->isUserByName($newName)) { + return $newName; + } + } + + // if here, this is because all new usernames are existing + // it could be an error + throw new UserFieldException('Impossible to find a new user name !'); + } +} + diff --git a/fonts/nunito/nunito-normal-400-.eot b/fonts/nunito/nunito-normal-400-.eot new file mode 100644 index 0000000..55f909b Binary files /dev/null and b/fonts/nunito/nunito-normal-400-.eot differ diff --git a/fonts/nunito/nunito-normal-400-.ttf b/fonts/nunito/nunito-normal-400-.ttf new file mode 100644 index 0000000..d682bb9 Binary files /dev/null and b/fonts/nunito/nunito-normal-400-.ttf differ diff --git a/fonts/nunito/nunito-normal-400-.woff b/fonts/nunito/nunito-normal-400-.woff new file mode 100644 index 0000000..7ea0bcc Binary files /dev/null and b/fonts/nunito/nunito-normal-400-.woff differ diff --git a/fonts/nunito/nunito-normal-400-.woff2 b/fonts/nunito/nunito-normal-400-.woff2 new file mode 100644 index 0000000..2ffa453 Binary files /dev/null and b/fonts/nunito/nunito-normal-400-.woff2 differ diff --git a/fonts/nunito/nunito-normal-400-latin-ext.woff2 b/fonts/nunito/nunito-normal-400-latin-ext.woff2 new file mode 100644 index 0000000..67d545d Binary files /dev/null and b/fonts/nunito/nunito-normal-400-latin-ext.woff2 differ diff --git a/fonts/nunito/nunito-normal-400-latin.woff2 b/fonts/nunito/nunito-normal-400-latin.woff2 new file mode 100644 index 0000000..99439c1 Binary files /dev/null and b/fonts/nunito/nunito-normal-400-latin.woff2 differ