src/Security/InstallmentVoter.php line 25

Open in your IDE?
  1. <?php
  2. //------------------------------------------------------------------------------
  3. // src/Security/InstallmentVoter.php
  4. //------------------------------------------------------------------------------
  5. namespace App\Security;
  6. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  7. use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
  8. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  9. use Doctrine\Persistence\ManagerRegistry;
  10. use App\Entity\Access;
  11. use App\Entity\Config\Config;
  12. use App\Entity\Config\Module;
  13. use App\Entity\HR\AccessFunction;
  14. use App\Entity\Platform\Command\Command;
  15. use App\Entity\Platform\Devis\Devis;
  16. use App\Entity\Platform\Installment;
  17. use App\Entity\Platform\Invoice\Invoice;
  18. use App\Entity\Security\Acl;
  19. use App\Entity\Security\AclPermission;
  20. use App\Services\Config\ModuleTools;
  21. use App\Services\Config\OptionConfigTools;
  22. class InstallmentVoter extends Voter
  23. {
  24.     //--------------------------------------------------------------------------------
  25.     // is_granted constants
  26.     // This depends of the Devis Module, not the Payments,
  27.     // which design the Online payment option (Paypal, Stripe)
  28.     // It also depends on the Commands
  29.     const IS_ACTIVE "installment_is_active";
  30.     const LISTING "list_installments";
  31.     const LISTING_SOCIETY "list_installments_society";
  32.     const LISTING_ANY "list_installments_any";
  33.     const IMPORT "import_installments";
  34.     // Plan.io Task #4326
  35.     const ADD "add_installment";
  36.     const EDIT "edit_installment";
  37.     const DELETE "delete_installment";
  38.     // Plan.io Task #4653
  39.     const IS_ACTIVE_INSTALLMENT_AUTO_ACCOUNT "installment_auto_account_is_active";
  40.     const IS_GRANTED_CONSTANTS = array(
  41.         self::IS_ACTIVE,
  42.         self::LISTING,
  43.         self::LISTING_SOCIETY,
  44.         self::LISTING_ANY,
  45.         self::IMPORT,
  46.         self::ADD,
  47.         self::EDIT,
  48.         self::DELETE,
  49.         self::IS_ACTIVE_INSTALLMENT_AUTO_ACCOUNT,
  50.     );
  51.     //--------------------------------------------------------------------------------
  52.     // acl constants
  53.     const ACL_PERM_LISTING "installment_list";
  54.     const ACL_PERM_LISTING_SOCIETY "installment_list_society";
  55.     const ACL_PERM_IMPORT "installment_import";
  56.     //--------------------------------------------------------------------------------
  57.     public function __construct(AccessDecisionManagerInterface $accessDecisionManagerManagerRegistry $doctrine
  58.         ModuleTools $moduleToolsOptionConfigTools $optionConfigTools)
  59.     {
  60.         $this->accessDecisionManager $accessDecisionManager;
  61.         $this->em $doctrine->getManager();
  62.         $this->moduleTools $moduleTools;
  63.         $this->optionConfigTools $optionConfigTools;
  64.         $this->aclRepository $this->em->getRepository(Acl::class);
  65.         $this->aclPermissionRepository $this->em->getRepository(AclPermission::class);
  66.     }
  67.     // Plan.io Task #4453 [See AccessVoter for details]
  68.     public function supportsAttribute(string $attribute): bool
  69.     {
  70.         return in_array($attributeself::IS_GRANTED_CONSTANTStrue);
  71.     }
  72.     
  73.     protected function supports(string $attribute$subject null): bool
  74.     {
  75.         // if the attribute isn't one we support, return false
  76.         if (!in_array($attributeself::IS_GRANTED_CONSTANTS))
  77.         {
  78.             return false;
  79.         }
  80.         // only vote on Installment objects inside this voter
  81.         // ... and Invoice / Devis / Command
  82.         if ($subject !== null && !($subject instanceof Installment ||
  83.                                     $subject instanceof Devis ||
  84.                                     $subject instanceof Invoice ||
  85.                                     $subject instanceof Command
  86.         ))
  87.         {
  88.             return false;
  89.         }
  90.         return true;
  91.     }
  92.     protected function voteOnAttribute(string $attribute$subjectTokenInterface $token): bool
  93.     {
  94.         $user $token->getUser();
  95.         if (!$user instanceof Access)
  96.         {
  97.             // the user must be logged in; if not, deny access
  98.             return false;
  99.         }
  100.         // The user must have a function; if not deny access
  101.         $function $user->getFunction();
  102.         if ($function === null)        return false;
  103.         // Plan.io Task #3710 : Get current group
  104.         $currentGroup $user->getSocietyGroup();
  105.         if ($currentGroup === null)
  106.             return false;
  107.         // Module activated ?
  108.         // This depends of the Devis Module, not the Payments,
  109.         // which design the PayPal payment option
  110.         // It also depends on the Commands
  111.         if ($this->moduleTools->isInactiveByCode($currentGroupModule::MODULE_DEVIS) && $this->moduleTools->isInactiveByCode($currentGroupModule::MODULE_COMMAND))
  112.         {
  113.             return false;
  114.         }
  115.         // $subject can be Installment object,
  116.         // or Devis / Invoice / Command (Plan.io Task #4326)
  117.         $installment null;
  118.         $otherThanInstallment null;
  119.         if ($subject instanceof Installment)
  120.         {
  121.             $installment $subject;
  122.         }
  123.         if ($subject instanceof Devis || $subject instanceof Invoice ||    $subject instanceof Command)
  124.         {
  125.             $otherThanInstallment $subject;
  126.         }
  127.         // Check current group affectation
  128.         if ($subject !== null)
  129.         {
  130.             $subjectSociety $subject->getSociety();
  131.             if ($subjectSociety === null)
  132.                 return false;
  133.             $subjectGroup $subjectSociety->getGroup();
  134.             if ($subjectGroup === null)
  135.                 return false;
  136.             if (!$currentGroup->equals($subjectGroup))
  137.                 return false;
  138.         }
  139.         switch ($attribute)
  140.         {
  141.             case self::IS_ACTIVE:
  142.                 return true;
  143.             case self::IS_ACTIVE_INSTALLMENT_AUTO_ACCOUNT:
  144.             {
  145.                 return $this->optionConfigTools->isActive_InstallmentAutoAccount($currentGroup);
  146.             }
  147.             case self::IMPORT:
  148.                 return $this->canImport($user$function);
  149.             case self::LISTING:
  150.                 return $this->canList($user$function);
  151.             case self::LISTING_SOCIETY:
  152.                 return $this->canListSociety($user$function);
  153.             case self::LISTING_ANY:
  154.                 return $this->canListAny($user$function);
  155.             case self::ADD:
  156.                 return $this->canAdd($user$function$token$otherThanInstallment);
  157.             // For edit and delete subject can be both Installment or Object linked to installment
  158.             case self::EDIT:
  159.                 return $this->canEdit($user$function$token$subject);
  160.             case self::DELETE:
  161.                 return $this->canDelete($user$function$token$subject);
  162.         }
  163.         throw new \LogicException('This code should not be reached!');
  164.     }
  165.     private function canAdd(Access $userAccessFunction $function$token$subject)
  166.     {
  167.         if ($subject instanceof Command)
  168.         {
  169.             return $this->accessDecisionManager->decide($token, ['edit_command'], $subject);
  170.         }
  171.         if ($subject instanceof Devis)
  172.         {
  173.             return $this->accessDecisionManager->decide($token, ['edit_devis'], $subject);
  174.         }
  175.         if ($subject instanceof Invoice)
  176.         {
  177.             // Plan.io Task #4326
  178.             // Deny on Drafts
  179.             if ($subject->isDraft())
  180.             {
  181.                 return false;
  182.             }
  183.             // Allow on Invoices based on edit permissions
  184.             return $this->accessDecisionManager->decide($token, ['handle_installment_for_invoice'], $subject);
  185.         }
  186.         return false;
  187.     }
  188.     private function canEdit(Access $userAccessFunction $function$token$subject)
  189.     {
  190.         if ($subject instanceof Installment)
  191.         {
  192.             $installment $subject;
  193.             // Plan.io Task #4653
  194.             if ($installment->isEffectivelyAccounted())
  195.             {
  196.                 return false;
  197.             }
  198.             if ($installment->getCommand() !== null)
  199.             {
  200.                 return $this->accessDecisionManager->decide($token, ['edit_command'], $installment->getCommand());
  201.             }
  202.             if ($installment->getDevis() !== null)
  203.             {
  204.                 return $this->accessDecisionManager->decide($token, ['edit_devis'], $installment->getDevis());
  205.             }
  206.             if ($installment->getInvoice() instanceof Invoice)
  207.             {
  208.                 $invoice $installment->getInvoice();
  209.                 // Plan.io Task #4326
  210.                 // Deny on Drafts
  211.                 if ($invoice->isDraft())
  212.                 {
  213.                     return false;
  214.                 }
  215.                 // Allow on Invoices based on edit permissions
  216.                 return $this->accessDecisionManager->decide($token, ['handle_installment_for_invoice'], $invoice);
  217.             }
  218.         }
  219.         if ($subject instanceof Command)
  220.         {
  221.             return $this->accessDecisionManager->decide($token, ['edit_command'], $subject);
  222.         }
  223.         if ($subject instanceof Devis)
  224.         {
  225.             return $this->accessDecisionManager->decide($token, ['edit_devis'], $subject);
  226.         }
  227.         if ($subject instanceof Invoice)
  228.         {
  229.             $invoice $subject;
  230.             // Plan.io Task #4326
  231.             // Deny on Drafts
  232.             if ($invoice->isDraft())
  233.             {
  234.                 return false;
  235.             }
  236.             // Allow on Invoices based on edit permissions
  237.             return $this->accessDecisionManager->decide($token, ['handle_installment_for_invoice'], $invoice);
  238.         }
  239.         return false;
  240.     }
  241.     private function canDelete(Access $userAccessFunction $function$token$subject)
  242.     {
  243.         if ($subject instanceof Installment)
  244.         {
  245.             $installment $subject;
  246.             // Plan.io Task #4653
  247.             if ($installment->isEffectivelyAccounted())
  248.             {
  249.                 return false;
  250.             }
  251.             if ($installment->getCommand() !== null)
  252.             {
  253.                 return $this->accessDecisionManager->decide($token, ['edit_command'], $installment->getCommand());
  254.             }
  255.             if ($installment->getDevis() !== null)
  256.             {
  257.                 return $this->accessDecisionManager->decide($token, ['edit_devis'], $installment->getDevis());
  258.             }
  259.             if ($installment->getInvoice() instanceof Invoice)
  260.             {
  261.                 // Always allow deleting installments on invoices
  262.                 // Needed by Plan.io Task #2849
  263.                 return true;
  264.             }
  265.         }
  266.         if ($subject instanceof Command)
  267.         {
  268.             return $this->accessDecisionManager->decide($token, ['edit_command'], $subject);
  269.         }
  270.         if ($subject instanceof Devis)
  271.         {
  272.             return $this->accessDecisionManager->decide($token, ['edit_devis'], $subject);
  273.         }
  274.         if ($subject instanceof Invoice)
  275.         {
  276.             // Always allow deleting installments on invoices
  277.             // Needed by Plan.io Task #2849
  278.             return true;
  279.         }
  280.         return false;
  281.     }
  282.     private function canImport(Access $accessAccessFunction $function)
  283.     {
  284.         // Restrictions are also applied in the Controller
  285.         // But this helps speeding page loading if the access is not even allowed to load the page
  286.         // (ie. if it has no list privileges whatsoever)
  287.         // Get Acl_Permission
  288.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_IMPORT);
  289.         if ($aclPerm === null)        return false;
  290.         // Get Acl
  291.         $acl $this->aclRepository->findOneBy(array(
  292.             'function'        =>    $function,
  293.             'permission'    =>    $aclPerm
  294.         ));
  295.         if ($acl === null)        return false;
  296.         // Since only one list type can exist,
  297.         // we can return the result of the acl_permission
  298.         return $acl->getValue();
  299.     }
  300.     private function canList(Access $userAccessFunction $function)
  301.     {
  302.         // Get Acl_Permission
  303.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LISTING);
  304.         if ($aclPerm === null)        return false;
  305.         // Get Acl
  306.         $acl $this->aclRepository->findOneBy(array(
  307.             'function'        =>    $function,
  308.             'permission'    =>    $aclPerm
  309.         ));
  310.         if ($acl === null)        return false;
  311.         // Since only one acl type can exist
  312.         // we can return the result of the acl_permission
  313.         return $acl->getValue();
  314.     }
  315.     private function canListSociety(Access $userAccessFunction $function)
  316.     {
  317.         // Get Acl_Permission
  318.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LISTING_SOCIETY);
  319.         if ($aclPerm === null)        return false;
  320.         // Get Acl
  321.         $acl $this->aclRepository->findOneBy(array(
  322.             'function'        =>    $function,
  323.             'permission'    =>    $aclPerm
  324.         ));
  325.         if ($acl === null)        return false;
  326.         // Since only one acl type can exist
  327.         // we can return the result of the acl_permission
  328.         // Further filtering is done in the Controller
  329.         return $acl->getValue();
  330.     }
  331.     private function canListAny(Access $userAccessFunction $function)
  332.     {
  333.         // Two Acl_Permission may exist
  334.         $aclPerm $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LISTING);
  335.         $aclPermSociety $this->aclPermissionRepository->findOneByName(self::ACL_PERM_LISTING_SOCIETY);
  336.         // If all are null, exit
  337.         if ($aclPerm === null && $aclPermSociety === null)
  338.             return false;
  339.         // Get First one
  340.         if ($aclPerm !== null)
  341.         {
  342.             $acl $this->aclRepository->findOneBy(array(
  343.                 'function'        =>    $function,
  344.                 'permission'    =>    $aclPerm
  345.             ));
  346.             if ($acl !== null)
  347.             {
  348.                 if ($acl->getValue())
  349.                 {
  350.                     // A single positive answer is enough
  351.                     return true;
  352.                 }
  353.             }
  354.         }
  355.         // If we are here it means that nothing good has been found
  356.         // Load second permission
  357.         if ($aclPermSociety !== null)
  358.         {
  359.             $acl $this->aclRepository->findOneBy(array(
  360.                 'function'        =>    $function,
  361.                 'permission'    =>    $aclPermSociety
  362.             ));
  363.             if ($acl !== null)
  364.             {
  365.                 if ($acl->getValue())
  366.                 {
  367.                     // A single positive answer is enough
  368.                     return true;
  369.                 }
  370.             }
  371.         }
  372.         // If we are here, all hope is lost
  373.         return false;
  374.     }
  375. }