1414namespace Silverback \ApiComponentsBundle \Security \Voter ;
1515
1616use ApiPlatform \Api \IriConverterInterface ;
17+ use Doctrine \Persistence \ManagerRegistry ;
18+ use Silverback \ApiComponentsBundle \AttributeReader \PublishableAttributeReader ;
1719use Silverback \ApiComponentsBundle \DataProvider \PageDataProvider ;
1820use Silverback \ApiComponentsBundle \Entity \Core \AbstractComponent ;
1921use Silverback \ApiComponentsBundle \Entity \Core \AbstractPageData ;
2022use Silverback \ApiComponentsBundle \Entity \Core \Route ;
23+ use Silverback \ApiComponentsBundle \Helper \Publishable \PublishableStatusChecker ;
24+ use Silverback \ApiComponentsBundle \Utility \ClassMetadataTrait ;
2125use Symfony \Component \HttpFoundation \Request ;
2226use Symfony \Component \HttpFoundation \RequestStack ;
2327use Symfony \Component \HttpFoundation \Response ;
3135 */
3236class ComponentVoter extends Voter
3337{
34- public const READ_COMPONENT = ' read_component ' ;
38+ use ClassMetadataTrait ;
3539
36- private PageDataProvider $ pageDataProvider ;
37- private IriConverterInterface $ iriConverter ;
38- private HttpKernelInterface $ httpKernel ;
39- private RequestStack $ requestStack ;
40+ public const READ_COMPONENT = 'read_component ' ;
4041
4142 public function __construct (
42- PageDataProvider $ pageDataProvider ,
43- IriConverterInterface $ iriConverter ,
44- HttpKernelInterface $ httpKernel ,
45- RequestStack $ requestStack
43+ private readonly PageDataProvider $ pageDataProvider ,
44+ private readonly IriConverterInterface $ iriConverter ,
45+ private readonly HttpKernelInterface $ httpKernel ,
46+ private readonly RequestStack $ requestStack ,
47+ private readonly PublishableStatusChecker $ publishableStatusChecker ,
48+ ManagerRegistry $ registry
4649 ) {
47- $ this ->pageDataProvider = $ pageDataProvider ;
48- $ this ->iriConverter = $ iriConverter ;
49- $ this ->httpKernel = $ httpKernel ;
50- $ this ->requestStack = $ requestStack ;
50+ $ this ->initRegistry ($ registry );
5151 }
5252
5353 protected function supports ($ attribute , $ subject ): bool
@@ -64,24 +64,47 @@ protected function voteOnAttribute($attribute, $subject, TokenInterface $token):
6464 if (!$ request ) {
6565 return true ;
6666 }
67- // TODO: if the subject is publishable, we should also check if there is a published version and the pages that one is in.
68- // The draft will not be in any locations.
67+
68+ $ subject = $ this -> getPublishedSubject ( $ subject );
6969
7070 $ pagesGenerator = $ this ->getComponentPages ($ subject );
7171 $ pages = iterator_to_array ($ pagesGenerator );
72- // Check if accessible via any route
73- $ routes = $ this ->getComponentRoutesFromPages ($ pages );
74- $ routeCount = 0 ;
75- foreach ($ routes as $ route ) {
76- ++$ routeCount ;
77- if ($ this ->isRouteReachableResource ($ route , $ request )) {
72+
73+ // 1. Check if accessible via any route
74+ $ routeVoteResult = $ this ->voteByRoute ($ pages , $ request );
75+ if ($ routeVoteResult ) {
76+ return true ;
77+ }
78+
79+ // 2. as a page data property
80+ $ pageDataResult = $ this ->voteByPageData ($ subject , $ request );
81+ if ($ pageDataResult ) {
82+ return true ;
83+ }
84+
85+ // 3. as a component in the page template being used by page data
86+ $ pageTemplateResult = $ this ->voteByPageTemplate ($ pages , $ request );
87+ if ($ pageTemplateResult ) {
88+ return true ;
89+ }
90+
91+ // vote is ok if all sub votes abstain
92+ return null === $ routeVoteResult && null === $ pageDataResult && null === $ pageTemplateResult ;
93+ }
94+
95+ private function voteByPageTemplate ($ pages , Request $ request ): ?bool
96+ {
97+ $ pageDataByPagesComponentUsedIn = $ this ->pageDataProvider ->findPageDataResourcesByPages ($ pages );
98+ foreach ($ pageDataByPagesComponentUsedIn as $ pageData ) {
99+ if ($ this ->isPageDataReachableResource ($ pageData , $ request )) {
78100 return true ;
79101 }
80102 }
103+ return \count ($ pageDataByPagesComponentUsedIn ) ? false : null ;
104+ }
81105
82- // check if accessible via any page data
83-
84- // 1. as a page data property
106+ private function voteByPageData ($ subject , Request $ request ): ?bool
107+ {
85108 $ pageData = $ this ->pageDataProvider ->findPageDataComponentMetadata ($ subject );
86109 $ pageDataCount = 0 ;
87110 foreach ($ pageData as $ pageDatum ) {
@@ -92,16 +115,37 @@ protected function voteOnAttribute($attribute, $subject, TokenInterface $token):
92115 }
93116 }
94117 }
118+ return $ pageDataCount ? false : null ;
119+ }
95120
96- // 2. as a component in the page template being used by page data
97- $ pageDataByPagesComponentUsedIn = $ this ->pageDataProvider ->findPageDataResourcesByPages ($ pages );
98- foreach ($ pageDataByPagesComponentUsedIn as $ pageData ) {
99- if ($ this ->isPageDataReachableResource ($ pageData , $ request )) {
121+ private function voteByRoute ($ pages , Request $ request ): ?bool
122+ {
123+ $ routes = $ this ->getComponentRoutesFromPages ($ pages );
124+ $ routeCount = 0 ;
125+ foreach ($ routes as $ route ) {
126+ ++$ routeCount ;
127+ if ($ this ->isRouteReachableResource ($ route , $ request )) {
100128 return true ;
101129 }
102130 }
131+ return $ routeCount ? false : null ;
132+ }
133+
103134
104- return !$ routeCount && !$ pageDataCount && !\count ($ pageDataByPagesComponentUsedIn );
135+ private function getPublishedSubject ($ subject )
136+ {
137+ // is a draft publishable. If a published version is available we should be checking the published version to see if it is in an accessible location
138+ $ publishableAttributeReader = $ this ->publishableStatusChecker ->getAttributeReader ();
139+ if ($ publishableAttributeReader ->isConfigured ($ subject ) && !$ this ->publishableStatusChecker ->isActivePublishedAt ($ subject )) {
140+ $ configuration = $ publishableAttributeReader ->getConfiguration ($ subject );
141+ $ classMetadata = $ this ->getClassMetadata ($ subject );
142+
143+ $ publishedResourceAssociation = $ classMetadata ->getFieldValue ($ subject , $ configuration ->associationName );
144+ if ($ publishedResourceAssociation ) {
145+ return $ publishedResourceAssociation ;
146+ }
147+ }
148+ return $ subject ;
105149 }
106150
107151 private function isRouteReachableResource (Route $ route , Request $ request ): bool
@@ -150,7 +194,7 @@ private function isPathReachable(string $path, Request $request): bool
150194 }
151195 }
152196
153- private function getComponentPages (AbstractComponent $ component ): iterable
197+ private function getComponentPages (AbstractComponent $ component ): \ Traversable
154198 {
155199 $ componentPositions = $ component ->getComponentPositions ();
156200 if (!\count ($ componentPositions )) {
0 commit comments