VYPR
High severity8.2GHSA Advisory· Published May 28, 2026· Updated May 28, 2026

CVE-2026-35676

CVE-2026-35676

Description

phpMyFAQ before 4.1.3 contains an unauthenticated password reset vulnerability in the user password update API endpoint that allows attackers to change account passwords without token validation. Attackers can enumerate valid username and email pairs and force immediate password changes by sending PUT requests to the /api/index.php/user/password/update endpoint, causing account disruption and invalidating legitimate user credentials.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

An unauthenticated password reset endpoint in phpMyFAQ before 4.1.3 allows attackers to enumerate valid user pairs and force immediate password changes without token validation.

Vulnerability

The vulnerability resides in the /api/index.php/user/password/update endpoint of phpMyFAQ versions before 4.1.3. The route is exposed without authentication and directly processes PUT requests containing a username and email pair [2]. The code in phpMyFAQ/src/phpMyFAQ/Controller/Frontend/Api/UnauthorizedUserController.php immediately validates the pair by checking if the username exists and the provided email matches the stored email; upon match, it generates a new password, writes it to the account, and only then sends the new password via email [2][3]. No reset token, confirmation link, or out-of-band step is required, making the endpoint an unauthenticated password change trigger rather than a safe password reset flow [4].

Exploitation

An attacker needs no authentication and only needs to know a valid username and email pair for a target account. The attacker sends a PUT request to /api/index.php/user/password/update with a JSON body containing username and email fields [2][3]. If the pair is valid, the server returns a 200 OK response with a success message; if invalid, it returns a 409 Conflict error. This response difference also enables user enumeration [1]. The password is changed immediately upon the request, before the email containing the new password is dispatched [3][4].

Impact

An attacker can force an immediate password change for any valid user account, invalidating the legitimate user's existing credentials and locking them out until they check the email or contact support. This causes account disruption and potential denial of access. Additionally, the response difference between valid and invalid username/email pairs allows the attacker to enumerate accounts on the system [2][4]. The attack does not grant the attacker direct access to the account (unless they also intercept the email), but the unauthorized password reset and user enumeration represent significant security issues.

Mitigation

The issue is fixed in phpMyFAQ version 4.1.3, which was released on an unspecified date prior to the CVE publication date (2026-05-28) [4]. Users should upgrade immediately to 4.1.3 or later. No workarounds have been published for versions prior to the fix. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities catalog at the time of writing. If upgrading is not immediately possible, network-level restrictions on the /api/index.php/user/password/update endpoint (e.g., requiring authentication via a reverse proxy) may be considered as a temporary measure.

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

Patches

10
84c095d6b39f

fix: prevent API authentication bypass via empty token

https://github.com/thorsten/phpmyfaqThorsten RinneMay 10, 2026Fixed in 4.1.3via llm-release-walk
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()]);
    
41d7436f5bb7

fix: replace plaintext password reset with signed token flow

https://github.com/thorsten/phpmyfaqThorsten RinneMay 8, 2026Fixed in 4.1.3via llm-release-walk
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&scaron;eg naloga je primljen.";
     $PMF_LANG["lostpwd_text_2"] = "Podesite novu lozinku u admin delu Va&scaron;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 &egrave; 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 &egrave; 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&scaron;eg naloga je primljen.";
     $PMF_LANG["lostpwd_text_2"] = "Podesite novu lozinku u admin delu Va&scaron;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"] = "最新版本於";
    
e3927a31b23e

fix: prevent IDOR privilege escalation in admin password overwrite

https://github.com/thorsten/phpmyfaqThorsten RinneMay 8, 2026Fixed in 4.1.3via llm-release-walk
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());
    +    }
    +}
    
7fe1ff0affd0

fix: minox fixes for correcting HTTP methods

https://github.com/thorsten/phpmyfaqThorsten RinneMay 14, 2026Fixed in 4.1.3via llm-release-walk
6 files changed · +7 8
  • docker-compose.yml+0 1 modified
    @@ -4,7 +4,6 @@ services:
     
       mariadb:
         image: mariadb:latest
    -    restart: always
         environment:
           - MYSQL_ROOT_PASSWORD=iop
           - MYSQL_DATABASE=phpmyfaq
    
  • phpmyfaq/admin/assets/src/api/stop-words.test.ts+1 1 modified
    @@ -59,7 +59,7 @@ describe('Stop Words API', () => {
     
         const result = await removeStopWord('csrfToken', 1, 'en');
         expect(fetch).toHaveBeenCalledWith('./api/stopword/delete', {
    -      method: 'POST',
    +      method: 'DELETE',
           headers: {
             Accept: 'application/json, text/plain, */*',
             'Content-Type': 'application/json',
    
  • phpmyfaq/admin/assets/src/api/stop-words.ts+1 1 modified
    @@ -50,7 +50,7 @@ export const postStopWord = async (
     
     export const removeStopWord = async (csrf: string, stopWordId: number, stopWordLanguage: string): Promise<unknown> => {
       const response = await fetch('./api/stopword/delete', {
    -    method: 'POST',
    +    method: 'DELETE',
         headers: {
           Accept: 'application/json, text/plain, */*',
           'Content-Type': 'application/json',
    
  • phpmyfaq/admin/assets/src/api/upgrade.test.ts+1 1 modified
    @@ -66,7 +66,7 @@ describe('Upgrade API', (): void => {
         const result = await checkForUpdates();
         expect(result).toEqual(mockResponse);
         expect(fetch).toHaveBeenCalledWith('./api/update-check', {
    -      method: 'POST',
    +      method: 'GET',
           headers: {
             Accept: 'application/json, text/plain, */*',
             'Content-Type': 'application/json',
    
  • phpmyfaq/admin/assets/src/api/upgrade.ts+1 1 modified
    @@ -51,7 +51,7 @@ export const activateMaintenanceMode = async (csrfToken: string): Promise<Respon
     
     export const checkForUpdates = async (): Promise<ResponseData> => {
       const response: Response = await fetch('./api/update-check', {
    -    method: 'POST',
    +    method: 'GET',
         headers: {
           Accept: 'application/json, text/plain, */*',
           'Content-Type': 'application/json',
    
  • phpmyfaq/src/admin-api-routes.php+3 3 modified
    @@ -206,7 +206,7 @@
         'admin.api.media.browser' => [
             'path' => '/media-browser',
             'controller' => [MediaBrowserController::class, 'index'],
    -        'methods' => 'GET'
    +        'methods' => 'POST'
         ],
         // Dashboard API
         'admin.api.dashboard.topten' => [
    @@ -416,13 +416,13 @@
         'admin.api.content.tags.id' => [
             'path' => '/content/tags/{tagId}',
             'controller' => [TagController::class, 'delete'],
    -        'methods' => 'GET'
    +        'methods' => 'DELETE'
         ],
         // Update API
         'admin.api.health-check' => [
             'path' => '/health-check',
             'controller' => [UpdateController::class, 'healthCheck'],
    -        'methods' => 'POST'
    +        'methods' => 'GET'
         ],
         'admin.api.versions' => [
             'path' => '/versions',
    
bc5dab558258

fix: corrected attachment handling for new FAQs

https://github.com/thorsten/phpmyfaqThorsten RinneMay 6, 2026Fixed in 4.1.3via llm-release-walk
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);
             }
     
    
787a8ea58c8f

fix: corrected HTTP method, closes #4230

https://github.com/thorsten/phpmyfaqThorsten RinneMay 6, 2026Fixed in 4.1.3via llm-release-walk
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',
    
cb5835f04260

fix: added missing array key, closes #4233

https://github.com/thorsten/phpmyfaqThorsten RinneMay 6, 2026Fixed in 4.1.3via llm-release-walk
1 file changed · +1 0
  • phpmyfaq/src/phpMyFAQ/Faq.php+1 0 modified
    @@ -1243,6 +1243,7 @@ public function getFaqBySolutionId(int $solutionId): void
                     'dateStart' => $row->date_start,
                     'dateEnd' => $row->date_end,
                     'notes' => $row->notes,
    +                'created' => $row->created,
                 ];
             } else {
                 $this->faqRecord = [];
    
d75ad98a75dd

fix: correct HTTP method matching in route builders, closes #4229

https://github.com/thorsten/phpmyfaqThorsten RinneMay 5, 2026Fixed in 4.1.3via llm-release-walk
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']],
             )
         );
     }
    
48c5ddc6af7d

fix: tightened the Apache rewrite rules

https://github.com/thorsten/phpmyfaqThorsten RinneMay 1, 2026Fixed in 4.1.3via llm-release-walk
1 file changed · +4 3
  • phpmyfaq/.htaccess+4 3 modified
    @@ -161,11 +161,12 @@ DirectoryIndex index.php
         # User pages
         RewriteRule ^user/(ucp|bookmarks|request-removal|logout|register) index.php?action=$1 [L,QSA]
         # Setup and update pages
    +    RewriteRule ^(.*/)?setup/index\.php$ - [L]
         RewriteCond %{REQUEST_URI} ^(.*/)setup/
    -    RewriteRule ^(.*/)?setup/(.*) $1setup/index.php [L,QSA]
    +    RewriteRule ^(.*/)?setup/?$ $1setup/index.php [L,QSA]
         RewriteRule ^update$ update/ [R=301,L]
    -    RewriteCond %{REQUEST_FILENAME} !-f
    -    RewriteRule ^update/(.*) update/index.php [L,QSA]
    +    RewriteRule ^update/index\.php$ - [L]
    +    RewriteRule ^update/$ update/index.php [L,QSA]
         # Administration API
         RewriteRule ^admin/api/(.*) admin/api/index.php [L,QSA]
         # Administration pages
    
f56c07b7bbf1

chore: v4.1.3 release

https://github.com/thorsten/phpmyfaqThorsten RinneMay 14, 2026Fixed in 4.1.3via release-tag
1 file changed · +2 1
  • CHANGELOG.md+2 1 modified
    @@ -6,8 +6,9 @@
     
     This is a log of major user-visible changes in each phpMyFAQ release.
     
    -### phpMyFAQ v4.1.3 – unreleased
    +### phpMyFAQ v4.1.3 – 2026-05-14
     
    +- fixed security vulnerabilities (Thorsten)
     - updated third party dependencies (Thorsten)
     - fixed bugs (Thorsten)
     
    

Vulnerability mechanics

Root cause

"Missing token-based confirmation in the password reset flow allows an unauthenticated caller to change a user's password immediately upon a simple username+email match."

Attack vector

An unauthenticated attacker sends a PUT request to `/api/index.php/user/password/update` with a JSON body containing a `username` and `email` pair [ref_id=1]. If the pair is valid, the application immediately generates a new password, writes it to the account, and returns HTTP 200; if invalid, it returns HTTP 409 [ref_id=1]. This response difference enables account enumeration, and the immediate password change forces a reset of the victim's account, invalidating the old password without requiring the attacker to control the victim's mailbox [CWE-640].

Affected code

The vulnerable code resides in `phpmyfaq/src/phpMyFAQ/Controller/Frontend/Api/UnauthorizedUserController.php` [ref_id=1]. The `updatePassword` method is exposed via the route `user/password/update` without authentication and immediately calls `$user->changePassword($newPassword)` upon a successful username+email match, before any out-of-band confirmation [ref_id=1].

What the fix does

No patch is included in the bundle. The advisory recommends a token-based password recovery flow: do not change the password inside the unauthenticated endpoint, generate a short-lived single-use reset token sent only by email, return the same generic response for both valid and invalid pairs, and add regression tests verifying the password hash does not change until a valid reset token is presented [ref_id=1][ref_id=2].

Preconditions

  • inputAttacker must know or guess a valid username and email pair for a target account.
  • authNo authentication or session is required; the endpoint is publicly accessible.
  • networkThe endpoint must be reachable over the network (e.g., exposed via a web server).

Reproduction

Send a PUT request to `/api/index.php/user/password/update` with `{"username":"user1","email":"user1@example.com"}` — a valid pair returns HTTP 200 with `{"success":"Email has been sent."}`. Send the same request with a mismatched email (e.g., `"wrong@example.com"`) — the response is HTTP 409 with `{"error":"Error: Username and email address not found."}` [ref_id=1]. To confirm the password actually changes, check the password hash in `faquserlogin` before and after the request; the old hash is replaced and the old password no longer authenticates [ref_id=1].

Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.