2017-12-06 27 views
6

Tôi đã cập nhật biểu mẫu môi trường Symfony của tôi từ 3.3 đến 4.0. Sau khi cập nhật, tôi gặp vấn đề với thông tin đăng nhập (người dùng được cung cấp bởi cơ sở dữ liệu). Khi tôi gửi mẫu đăng nhập, tôi vừa trở lại mẫu đăng nhập mà không có bất kỳ thông báo lỗi nào. Khi tôi sử dụng thông tin đăng nhập không hợp lệ, tôi nhận được thông báo lỗi tương ứng. Đây là nhật ký sau khi cố đăng nhập. Thông tin đăng nhập với nhà cung cấp dịch vụ "in_memory" đang hoạt động. bạn có cần thêm thông tin không?Mã thông báo đã được xác thực sau khi cố gắng làm mới nó

Cảm ơn bạn!

[2017-12-06 13:57:05] security.INFO: User has been authenticated successfully. {"username":"***"} [] 
[2017-12-06 14:22:39] doctrine.DEBUG: "START TRANSACTION" [] [] 
[2017-12-06 13:57:05] security.DEBUG: Read existing security token from the session. {"key":"_security_secured_area","token_class":"Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken"} [] 
[2017-12-06 13:57:05] doctrine.DEBUG: SELECT t0.username AS username_1, t0.password AS password_2, t0.email AS email_3, t0.email_new AS email_new_4, t0.first_name AS first_name_5, t0.last_name AS last_name_6, t0.is_active AS is_active_7, t0.email_confirmed AS email_confirmed_8, t0.shibboleth_state AS shibboleth_state_9, t0.shibboleth_hash AS shibboleth_hash_10, t0.shibboleth_persistent_id AS shibboleth_persistent_id_11, t0.confirmation_email_send AS confirmation_email_send_12, t0.last_login AS last_login_13, t0.expires AS expires_14, t0.session_id AS session_id_15, t0.id AS id_16, t0.hidden AS hidden_17, t0.deleted AS deleted_18, t0.created AS created_19, t0.modified AS modified_20, t0.sorting AS sorting_21, t0.salutation_id AS salutation_id_22, t0.creator_id AS creator_id_23, t0.modifier_id AS modifier_id_24 FROM User t0 WHERE t0.id = ? AND ((t0.deleted = 0)) [2] [] 
[2017-12-06 13:57:05] security.DEBUG: Token was deauthenticated after trying to refresh it. {"username":"user","provider":"Symfony\\Component\\Security\\Core\\User\\ChainUserProvider"} [] 
[2017-12-06 13:57:05] security.INFO: Populated the TokenStorage with an anonymous Token. [] [] 
[2017-12-06 13:57:05] security.DEBUG: Access denied, the user is not fully authenticated; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException(code: 403): Access Denied. at /vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/AccessListener.php:68)"} [] 
[2017-12-06 13:57:05] security.DEBUG: Calling Authentication entry point. [] [] 

Entity \ User:

class User extends EntitySuperclass implements AdvancedUserInterface, \Serializable 
{ 
    /** 
    * @ORM\Column(type="string") 
    */ 
    private $username; 

    /** 
    * 
    * @Assert\Length(max=4096,groups={"account_complete","account_password","user"}) 
    * @Assert\Length(min = 8,groups={"account_complete","account_password","user"}, minMessage="user.password_length") 
    */ 
    private $plainPassword; 

    /** 
    * The below length depends on the "algorithm" you use for encoding 
    * the password, but this works well with bcrypt. 
    * 
    * @ORM\Column(type="string", length=64) 
    */ 
    private $password; 

    /** 
    * @ORM\Column(type="string", length=255) 
    * @Assert\NotBlank(groups={"account_register","user"}) 
    * @Assert\Email(
    *  groups = {"account_register", "account","user"}, 
    *  strict = true, 
    *  checkMX = true 
    *) 
    */ 
    private $email; 

    /** 
    * @ORM\Column(type="string", length=255) 
    */ 
    private $emailNew = ''; 

    /** 
    * @ORM\ManyToOne(targetEntity="Salutation") 
    * 
    */ 
    private $salutation; 

    /** 
    * @ORM\Column(type="string") 
    * @Assert\NotBlank(groups={"account_complete","user"}) 
    * @Assert\Regex(pattern = "/^[a-zA-ZäöüÄÖÜß0-9 ]+$/",groups={"account_complete","user"}, message="user.first_name.regex") 
    */ 
    private $firstName; 

    /** 
    * @ORM\Column(type="string") 
    * @Assert\NotBlank(groups={"account_complete","user"}) 
    * @Assert\Regex(pattern = "/^[a-zA-ZäöüÄÖÜß0-9 ]+$/",groups={"account_complete","user"}, message="user.last_name.regex") 
    */ 
    private $lastName; 

    /** 
    * @ORM\Column(name="is_active", type="boolean") 
    */ 
    private $isActive = false; 

    /** 
    * @ORM\Column(name="email_confirmed", type="boolean") 
    */ 
    private $emailConfirmed = false; 

    /** 
    * @ORM\Column(type="integer") 
    */ 
    private $shibbolethState = 0; 

    /** 
    * @ORM\Column(type="string") 
    */ 
    private $shibbolethHash = ''; 

    /** 
    * @ORM\Column(type="string") 
    */ 
    private $shibbolethPersistentId = ''; 

    /** 
    * @ORM\ManyToMany(targetEntity="UserGroup") 
    * @ORM\JoinTable(name="User_UserGroup", 
    *  joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="group_id", referencedColumnName="id")} 
    *  ) 
    */ 
    private $userGroups; 

    /** 
    * @ORM\Column(type="integer") 
    */ 
    private $confirmationEmailSend; 

    /** 
    * @ORM\Column(type="integer") 
    */ 
    private $lastLogin = 0; 

    /** 
    * @ORM\Column(type="integer") 
    */ 
    protected $expires = 0; 

    /** 
    * @ORM\Column(type="string", length=255) 
    */ 
    private $sessionId = ''; 

    /** 
    * @ORM\ManyToMany(targetEntity="BankDetails", cascade={"persist"}) 
    * @ORM\JoinTable(name="User_BankDetails", 
    *  joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="bank_details_id", referencedColumnName="id")} 
    *) 
    * @Assert\Valid 
    */ 
    private $bankDetails; 

    /** 
    * @ORM\ManyToMany(targetEntity="Address", cascade={"persist"}) 
    * @ORM\JoinTable(name="User_BillingAddress", 
    *  joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}, 
    *  inverseJoinColumns={@ORM\JoinColumn(name="billing_address_id", referencedColumnName="id")} 
    *) 
    * @Assert\Count(
    *  min = 1, 
    *  minMessage = "user.billing_addresses.min", 
    *) 
    * @Assert\Valid 
    */ 
    private $billingAddresses; 

    public function __construct() 
    { 
     parent::__construct(); 
     $this->isActive = true; 
     $this->confirmationEmailSend = 0; 
     $this->userGroups = new ArrayCollection(); 
     $this->bankDetails = new ArrayCollection(); 
     $this->billingAddresses = new ArrayCollection(); 
     // may not be needed, see section on salt below 
     // $this->salt = md5(uniqid(null, true)); 
    } 

    /** 
    * @ORM\PrePersist 
    */ 
    public function prePersist() 
    { 
     $currentTimestamp = time(); 

     if($this->getConfirmationEmailSend() == NULL) 
      $this->setConfirmationEmailSend(0); 

    } 

    public function getUsername() 
    { 
     //return $this->username; 
     return $this->email; 
    } 

    public function getSalt() 
    { 
     // The bcrypt algorithm doesn't require a separate salt. 
     return null; 
    } 

    public function getPassword() 
    { 
     return $this->password; 
    } 

    public function getRoles() 
    { 
     $roles = array(); 
     $userGroups = $this->getUserGroups(); 
     if(!empty($userGroups)) { 
      foreach($userGroups as $userGroup) { 
       $role = $userGroup->getRole(); 
       $roles[] = 'ROLE_'.strtoupper($role); 
      } 
     } 
     return $roles; 
    } 

    public function isGranted($role) 
    { 
     return in_array($role, $this->getRoles()); 
    } 

    public function eraseCredentials() 
    { 
    } 

    public function isAccountNonExpired() 
    { 
     return true; 
    } 

    public function isAccountNonLocked() 
    { 
     return true; 
    } 

    public function isCredentialsNonExpired() 
    { 
     return true; 
    } 

    public function isEnabled() 
    { 
     return $this->isActive; 
    } 

    /** @see \Serializable::serialize() */ 
    public function serialize() 
    { 
     return serialize(array(
      $this->id, 
      $this->username, 
      $this->password, 
      $this->isActive, 
      // see section on salt below 
      // $this->salt, 
     )); 
    } 

    /** @see \Serializable::unserialize() */ 
    public function unserialize($serialized) 
    { 
     list (
      $this->id, 
      $this->username, 
      $this->password, 
      $this->isActive, 
      // see section on salt below 
      // $this->salt 
     ) = unserialize($serialized); 
    } 

    /** 
    * Set username 
    * 
    * @param string $username 
    * 
    * @return User 
    */ 
    public function setUsername($username) 
    { 
     $this->username = $username; 

     return $this; 
    } 

    public function getPlainPassword() 
    { 
     return $this->plainPassword; 
    } 

    public function setPlainPassword($password) 
    { 
     $this->plainPassword = $password; 
    } 

    /** 
    * Set password 
    * 
    * @param string $password 
    * 
    * @return User 
    */ 
    public function setPassword($password) 
    { 
     $this->password = $password; 

     return $this; 
    } 

    /** 
    * Set email 
    * 
    * @param string $email 
    * 
    * @return User 
    */ 
    public function setEmail($email) 
    { 
     $this->email = $email; 
     $this->setUsername($email); 

     return $this; 
    } 

    /** 
    * Get email 
    * 
    * @return string 
    */ 
    public function getEmail() 
    { 
     return $this->email; 
    } 

    /** 
    * Set isActive 
    * 
    * @param boolean $isActive 
    * 
    * @return User 
    */ 
    public function setIsActive($isActive) 
    { 
     $this->isActive = $isActive; 

     return $this; 
    } 

    /** 
    * Get isActive 
    * 
    * @return boolean 
    */ 
    public function getIsActive() 
    { 
     return $this->isActive; 
    } 

    /** 
    * Add userGroup 
    * 
    * @param \AppBundle\Entity\UserGroup $userGroup 
    * 
    * @return User 
    */ 
    public function addUserGroup(\AppBundle\Entity\UserGroup $userGroup) 
    { 
     $this->userGroups[] = $userGroup; 

     return $this; 
    } 

    /** 
    * Remove userGroup 
    * 
    * @param \AppBundle\Entity\UserGroup $userGroup 
    */ 
    public function removeUserGroup(\AppBundle\Entity\UserGroup $userGroup) 
    { 
     $this->userGroups->removeElement($userGroup); 
    } 

    /** 
    * Get userGroups 
    * 
    * @return \Doctrine\Common\Collections\Collection 
    */ 
    public function getUserGroups() 
    { 
     return $this->userGroups; 
    } 

    /** 
    * Set shibbolethPersistentId 
    * 
    * @param string $shibbolethPersistentId 
    * 
    * @return User 
    */ 
    public function setShibbolethPersistentId($shibbolethPersistentId) 
    { 
     $this->shibbolethPersistentId = $shibbolethPersistentId; 

     return $this; 
    } 

    /** 
    * Get shibbolethPersistentId 
    * 
    * @return string 
    */ 
    public function getShibbolethPersistentId() 
    { 
     return $this->shibbolethPersistentId; 
    } 

    /** 
    * Set firstName 
    * 
    * @param string $firstName 
    * 
    * @return User 
    */ 
    public function setFirstName($firstName) 
    { 
     $this->firstName = $firstName; 

     return $this; 
    } 

    /** 
    * Get firstName 
    * 
    * @return string 
    */ 
    public function getFirstName() 
    { 
     return $this->firstName; 
    } 

    /** 
    * Set lastName 
    * 
    * @param string $lastName 
    * 
    * @return User 
    */ 
    public function setLastName($lastName) 
    { 
     $this->lastName = $lastName; 

     return $this; 
    } 

    /** 
    * Get lastName 
    * 
    * @return string 
    */ 
    public function getLastName() 
    { 
     return $this->lastName; 
    } 

    /** 
    * Set emailConfirmed 
    * 
    * @param boolean $emailConfirmed 
    * 
    * @return User 
    */ 
    public function setEmailConfirmed($emailConfirmed) 
    { 
     $this->emailConfirmed = $emailConfirmed; 

     return $this; 
    } 

    /** 
    * Get emailConfirmed 
    * 
    * @return boolean 
    */ 
    public function getEmailConfirmed() 
    { 
     return $this->emailConfirmed; 
    } 

    public function removeAllUserGroups() { 
     $userGroups = $this->getUserGroups(); 
     foreach($userGroups as $userGroup) { 
      $this->removeUserGroup($userGroup); 
     } 
    } 

    public function hasUserGroup($userGroupId) { 
     foreach($this->getUserGroups() as $userGroup) { 
      if($userGroup->getId() == $userGroupId) 
       return true; 
     } 
     return false; 
    } 

    /** 
    * Set lastLogin 
    * 
    * @param integer $lastLogin 
    * 
    * @return User 
    */ 
    public function setLastLogin($lastLogin) 
    { 
     $this->lastLogin = $lastLogin; 

     return $this; 
    } 

    /** 
    * Get lastLogin 
    * 
    * @return integer 
    */ 
    public function getLastLogin() 
    { 
     return $this->lastLogin; 
    } 

    /** 
    * Set confirmationEmailSend 
    * 
    * @param integer $confirmationEmailSend 
    * 
    * @return User 
    */ 
    public function setConfirmationEmailSend($confirmationEmailSend) 
    { 
     $this->confirmationEmailSend = $confirmationEmailSend; 

     return $this; 
    } 

    /** 
    * Get confirmationEmailSend 
    * 
    * @return integer 
    */ 
    public function getConfirmationEmailSend() 
    { 
     return $this->confirmationEmailSend; 
    } 

    /** 
    * Set validTill 
    * 
    * @param integer $validTill 
    * 
    * @return User 
    */ 
    public function setValidTill($validTill) 
    { 
     $this->validTill = $validTill; 

     return $this; 
    } 

    /** 
    * Get validTill 
    * 
    * @return integer 
    */ 
    public function getValidTill() 
    { 
     return $this->validTill; 
    } 

    /** 
    * Set shibbolethValid 
    * 
    * @param integer $shibbolethValid 
    * 
    * @return User 
    */ 
    public function setShibbolethValid($shibbolethValid) 
    { 
     $this->shibbolethValid = $shibbolethValid; 

     return $this; 
    } 

    /** 
    * Get shibbolethValid 
    * 
    * @return integer 
    */ 
    public function getShibbolethValid() 
    { 
     return $this->shibbolethValid; 
    } 

    /** 
    * Set shibbolethHash 
    * 
    * @param string $shibbolethHash 
    * 
    * @return User 
    */ 
    public function setShibbolethHash($shibbolethHash) 
    { 
     $this->shibbolethHash = $shibbolethHash; 

     return $this; 
    } 

    /** 
    * Get shibbolethHash 
    * 
    * @return string 
    */ 
    public function getShibbolethHash() 
    { 
     return $this->shibbolethHash; 
    } 

    /** 
    * Set shibbolethState 
    * 
    * @param integer $shibbolethState 
    * 
    * @return User 
    */ 
    public function setShibbolethState($shibbolethState) 
    { 
     $this->shibbolethState = $shibbolethState; 

     return $this; 
    } 

    /** 
    * Get shibbolethState 
    * 
    * @return integer 
    */ 
    public function getShibbolethState() 
    { 
     return $this->shibbolethState; 
    } 

    /** 
    * Set expires 
    * 
    * @param integer $expires 
    * 
    * @return User 
    */ 
    public function setExpires($expires) 
    { 
     $this->expires = $expires; 

     return $this; 
    } 

    /** 
    * Get expires 
    * 
    * @return integer 
    */ 
    public function getExpires() 
    { 
     return $this->expires; 
    } 

    /** 
    * Set emailNew 
    * 
    * @param string $emailNew 
    * 
    * @return User 
    */ 
    public function setEmailNew($emailNew) 
    { 
     $this->emailNew = $emailNew; 

     return $this; 
    } 

    /** 
    * Get emailNew 
    * 
    * @return string 
    */ 
    public function getEmailNew() 
    { 
     return $this->emailNew; 
    } 

    /** 
    * Set passwordHash 
    * 
    * @param string $passwordHash 
    * 
    * @return User 
    */ 
    public function setPasswordHash($passwordHash) 
    { 
     $this->passwordHash = $passwordHash; 

     return $this; 
    } 

    /** 
    * Get passwordHash 
    * 
    * @return string 
    */ 
    public function getPasswordHash() 
    { 
     return $this->passwordHash; 
    } 

    /** 
    * Set sessionId 
    * 
    * @param string $sessionId 
    * 
    * @return User 
    */ 
    public function setSessionId($sessionId) 
    { 
     $this->sessionId = $sessionId; 

     return $this; 
    } 

    /** 
    * Get sessionId 
    * 
    * @return string 
    */ 
    public function getSessionId() 
    { 
     return $this->sessionId; 
    } 

    /** 
    * Set salutation 
    * 
    * @param \AppBundle\Entity\Salutation $salutation 
    * 
    * @return User 
    */ 
    public function setSalutation(\AppBundle\Entity\Salutation $salutation = null) 
    { 
     $this->salutation = $salutation; 

     return $this; 
    } 

    /** 
    * Get salutation 
    * 
    * @return \AppBundle\Entity\Salutation 
    */ 
    public function getSalutation() 
    { 
     return $this->salutation; 
    } 

    /** 
    * Add bankDetail 
    * 
    * @param \AppBundle\Entity\BankDetails $bankDetail 
    * 
    * @return User 
    */ 
    public function addBankDetail(\AppBundle\Entity\BankDetails $bankDetail) 
    { 
     $this->bankDetails[] = $bankDetail; 

     return $this; 
    } 

    /** 
    * Remove bankDetail 
    * 
    * @param \AppBundle\Entity\BankDetails $bankDetail 
    */ 
    public function removeBankDetail(\AppBundle\Entity\BankDetails $bankDetail) 
    { 
     $this->bankDetails->removeElement($bankDetail); 
    } 

    /** 
    * Get bankDetails 
    * 
    * @return \Doctrine\Common\Collections\Collection 
    */ 
    public function getBankDetails() 
    { 
     return $this->bankDetails; 
    } 

    /** 
    * Add billingAddress 
    * 
    * @param \AppBundle\Entity\Address $billingAddress 
    * 
    * @return User 
    */ 
    public function addBillingAddress(\AppBundle\Entity\Address $billingAddress) 
    { 
     $this->billingAddresses[] = $billingAddress; 

     return $this; 
    } 

    /** 
    * Remove billingAddress 
    * 
    * @param \AppBundle\Entity\Address $billingAddress 
    */ 
    public function removeBillingAddress(\AppBundle\Entity\Address $billingAddress) 
    { 
     $this->billingAddresses->removeElement($billingAddress); 
    } 

    /** 
    * Set billingAddresses 
    * 
    * @param \AppBundle\Entity\Address $billingAddress 
    * 
    * @return User 
    * 
    */ 
    public function setBillingAddresses(\AppBundle\Entity\Address $billingAddress) 
    { 
     if($this->billingAddresses !== NULL and $this->billingAddresses->contains($billingAddress)){ 
      return false; 
     } 
     $this->addBillingAddress($billingAddress); 
     return $this; 
    } 

    /** 
    * Set one billingAddresses 
    * 
    * @param \AppBundle\Entity\Address $billingAddress 
    * 
    * @return User 
    * 
    */ 
    public function setOneBillingAddresses(\AppBundle\Entity\Address $billingAddress) 
    { 
     $this->billingAddresses = $billingAddress; 

     return $this; 
    } 

    /** 
    * Set one billingAddresses 
    * 
    * @param \AppBundle\Entity\Address $billingAddress 
    * 
    * @return User 
    * 
    */ 
    public function unsetBillingAddresses() 
    { 
     $this->billingAddresses = new ArrayCollection(); 

     return $this; 
    } 

    /** 
    * Get billingAddresses 
    * 
    * @return \Doctrine\Common\Collections\Collection 
    */ 
    public function getBillingAddresses() 
    { 
     return $this->billingAddresses; 
    } 
} 

config/security.yml

providers: 
     chain_provider: 
      chain: 
       providers: [in_memory, database_user] 
     in_memory: 
      memory: 
       users: 
        admin: 
         password: *** 
         roles: 'ROLE_ADMIN' 
     database_user: 
      entity: 
       class: AppBundle:User 

    firewalls: 
     # disables authentication for assets and the profiler, adapt it according to your needs 
     dev: 
      pattern: ^/(_(profiler|wdt)|css|images|js)/ 
      security: false 
     secured_area: 
      # pattern: match to pages 
      anonymous: ~ 
      pattern: ^/ 
      access_denied_handler: AppBundle\Security\AccessDeniedHandler 
      provider: chain_provider 
      form_login: 
       login_path: /login 
       check_path: /login_check 
       default_target_path: account 
       # Configuring CSRF protection 
       csrf_parameter: _csrf_security_token 
       csrf_token_id: a_private_string 
       success_handler: AppBundle\Handler\LoginSuccessHandler 
      logout: 
       path: /logout 
       target: /login 

    access_control: 
     ... 

    role_hierarchy: 
     ... 

    encoders: 
     AppBundle\Entity\User: 
      algorithm: bcrypt 
     Symfony\Component\Security\Core\User\User: 
      plaintext 

Trả lời

17

Tính đến Symfony 4.0, logout_on_user_change được thiết lập để true. Điều đó có nghĩa là người dùng sẽ bị đăng xuất nếu nó đã bị thay đổi.

Bạn nên thực hiện Symfony\Component\Security\Core\User\EquatableInterface và thêm isEqualTo phương pháp:

class User implements EquatableInterface 
{ 
    public function isEqualTo(UserInterface $user) 
    { 
     if ($this->password !== $user->getPassword()) { 
      return false; 
     } 

     if ($this->salt !== $user->getSalt()) { 
      return false; 
     } 

     if ($this->username !== $user->getUsername()) { 
      return false; 
     } 

     return true; 
    } 
} 

Changelog

https://github.com/symfony/security-bundle/blob/master/CHANGELOG.md

4.1.0

Các logout_on_user_change tường lửa Opti không được dùng nữa và sẽ bị xóa trong 5.0.

4.0.0

tùy chọn tường lửa logout_on_user_change tại là luôn luôn đúng, điều này sẽ kích hoạt một logout nếu người dùng thay đổi giữa các yêu cầu

3.4.0

Added logout_on_user_change đến tùy chọn tường lửa. Mục cấu hình này sẽ kích hoạt đăng xuất khi người dùng đã thay đổi. Nên được đặt thành true để tránh việc không dùng nữa trong cấu hình.

Các tùy chọn đã không được ghi nhận nào: https://github.com/symfony/symfony-docs/issues/8428

Side lưu ý về việc cập nhật một thông cáo chính mới

Nếu bạn muốn nâng cấp lên một phiên bản chính mới, luôn luôn cập nhật để trẻ vị thành niên mới nhất phiên bản đầu tiên. Điều đó có nghĩa là cập nhật lên 2.8 trước khi cập nhật lên 3.0 và cập nhật lên 3.4 trước khi lên 4.0. Xem Symfony 4: Compose your Applications bởi Fabien Potencier.

Symfony 3.0 = Symfony 2.8 - tính năng phản

(..)

Symfony 4.0 = Symfony 3.4 - bị phản đối các tính năng + một cách mới để phát triển các ứng dụng

Đang cập nhật một thông cáo chính mới là dễ dàng hơn nhiều nếu bạn đã có trên phiên bản nhỏ nhất, vì bạn có thể xem tất cả các thông báo deprecation.

+0

Cảm ơn rất nhiều! Tôi đã kiểm tra mã trong nhiều giờ nhưng không tìm thấy bất cứ điều gì một mình! Điều này không nên được thêm vào tài liệu của [nhà cung cấp thực thể] (https://symfony.com/doc/current/security/entity_provider.html)? – user2625247

+0

Bạn được chào đón! Tôi tìm kiếm hàng giờ, một vài ngày trước, rất vui khi chia sẻ giải pháp. Tôi không biết nó nên được ghi chép ở đâu, nhưng ít nhất nó cũng nên ở đâu đó. –

+0

Xin chào, tôi có chính xác cùng một vấn đề nhưng giải pháp này không hoạt động: (Bất kỳ ý tưởng? – ArGh

Các vấn đề liên quan