@@ -542,4 +542,158 @@ public function test_get_local_recipients_with_malformed_urls() {
542542 $ result = $ method ->invoke ( $ this ->inbox_controller , $ activity );
543543 $ this ->assertEmpty ( $ result , 'Should handle malformed URLs gracefully ' );
544544 }
545+
546+ /**
547+ * Test get_local_recipients with public activity.
548+ *
549+ * @covers ::get_local_recipients
550+ */
551+ public function test_get_local_recipients_public_activity () {
552+ // Enable actor mode to allow user actors.
553+ \update_option ( 'activitypub_actor_mode ' , ACTIVITYPUB_ACTOR_MODE );
554+
555+ // Create additional test users (authors have activitypub capability by default).
556+ $ user_id_1 = self ::factory ()->user ->create ( array ( 'role ' => 'author ' ) );
557+ $ user_id_2 = self ::factory ()->user ->create ( array ( 'role ' => 'author ' ) );
558+ $ user_id_3 = self ::factory ()->user ->create ( array ( 'role ' => 'editor ' ) );
559+
560+ // Create a remote actor and make our users follow them.
561+ $ remote_actor_url = 'https://example.com/actor/1 ' ;
562+
563+ // Mock the remote actor fetch.
564+ \add_filter (
565+ 'activitypub_pre_http_get_remote_object ' ,
566+ function ( $ pre , $ url ) use ( $ remote_actor_url ) {
567+ if ( $ url === $ remote_actor_url ) {
568+ return array (
569+ '@context ' => 'https://www.w3.org/ns/activitystreams ' ,
570+ 'id ' => $ remote_actor_url ,
571+ 'type ' => 'Person ' ,
572+ 'preferredUsername ' => 'testactor ' ,
573+ 'name ' => 'Test Actor ' ,
574+ 'inbox ' => 'https://example.com/actor/1/inbox ' ,
575+ );
576+ }
577+ return $ pre ;
578+ },
579+ 10 ,
580+ 2
581+ );
582+
583+ $ remote_actor = \Activitypub \Collection \Remote_Actors::fetch_by_uri ( $ remote_actor_url );
584+
585+ // Make users follow the remote actor.
586+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , self ::$ user_id );
587+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , $ user_id_1 );
588+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , $ user_id_2 );
589+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , $ user_id_3 );
590+
591+ // Public activity with "to" containing the public collection.
592+ $ activity = array (
593+ 'type ' => 'Create ' ,
594+ 'actor ' => $ remote_actor_url ,
595+ 'to ' => array ( 'https://www.w3.org/ns/activitystreams#Public ' ),
596+ 'cc ' => array ( 'https://external.example.com/followers ' ),
597+ );
598+
599+ // Use reflection to test the private method.
600+ $ reflection = new \ReflectionClass ( $ this ->inbox_controller );
601+ $ method = $ reflection ->getMethod ( 'get_local_recipients ' );
602+ $ method ->setAccessible ( true );
603+
604+ $ result = $ method ->invoke ( $ this ->inbox_controller , $ activity );
605+
606+ // Should return users who follow the remote actor.
607+ $ this ->assertNotEmpty ( $ result , 'Should return users for public activity ' );
608+ $ this ->assertContains ( self ::$ user_id , $ result , 'Should contain test user ' );
609+ $ this ->assertContains ( $ user_id_1 , $ result , 'Should contain user 1 ' );
610+ $ this ->assertContains ( $ user_id_2 , $ result , 'Should contain user 2 ' );
611+ $ this ->assertContains ( $ user_id_3 , $ result , 'Should contain user 3 ' );
612+
613+ // Verify it returns exactly the followers we added.
614+ // Note: May include blog user (0) if blog mode is enabled.
615+ $ this ->assertGreaterThanOrEqual ( 4 , count ( $ result ), 'Should return at least 4 followers ' );
616+ $ this ->assertLessThanOrEqual ( 5 , count ( $ result ), 'Should return at most 5 followers (4 users + optional blog) ' );
617+
618+ // Clean up.
619+ \wp_delete_post ( $ remote_actor ->ID , true );
620+ \wp_delete_user ( $ user_id_1 );
621+ \wp_delete_user ( $ user_id_2 );
622+ \wp_delete_user ( $ user_id_3 );
623+ \delete_option ( 'activitypub_actor_mode ' );
624+ \remove_all_filters ( 'activitypub_pre_http_get_remote_object ' );
625+ }
626+
627+ /**
628+ * Test get_local_recipients with public activity using "cc" field.
629+ *
630+ * @covers ::get_local_recipients
631+ */
632+ public function test_get_local_recipients_public_activity_in_cc () {
633+ // Enable actor mode to allow user actors.
634+ \update_option ( 'activitypub_actor_mode ' , ACTIVITYPUB_ACTOR_MODE );
635+
636+ // Create a test user (authors have activitypub capability by default).
637+ $ user_id = self ::factory ()->user ->create ( array ( 'role ' => 'author ' ) );
638+
639+ // Create a remote actor and make our users follow them.
640+ $ remote_actor_url = 'https://example.com/actor/1 ' ;
641+
642+ // Mock the remote actor fetch.
643+ \add_filter (
644+ 'activitypub_pre_http_get_remote_object ' ,
645+ function ( $ pre , $ url ) use ( $ remote_actor_url ) {
646+ if ( $ url === $ remote_actor_url ) {
647+ return array (
648+ '@context ' => 'https://www.w3.org/ns/activitystreams ' ,
649+ 'id ' => $ remote_actor_url ,
650+ 'type ' => 'Person ' ,
651+ 'preferredUsername ' => 'testactor ' ,
652+ 'name ' => 'Test Actor ' ,
653+ 'inbox ' => 'https://example.com/actor/1/inbox ' ,
654+ );
655+ }
656+ return $ pre ;
657+ },
658+ 10 ,
659+ 2
660+ );
661+
662+ $ remote_actor = \Activitypub \Collection \Remote_Actors::fetch_by_uri ( $ remote_actor_url );
663+
664+ // Make users follow the remote actor.
665+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , self ::$ user_id );
666+ \add_post_meta ( $ remote_actor ->ID , '_activitypub_followers ' , $ user_id );
667+
668+ // Public activity with "cc" containing the public collection.
669+ $ activity = array (
670+ 'type ' => 'Create ' ,
671+ 'actor ' => $ remote_actor_url ,
672+ 'to ' => array ( 'https://external.example.com/user/specific ' ),
673+ 'cc ' => array ( 'https://www.w3.org/ns/activitystreams#Public ' ),
674+ );
675+
676+ // Use reflection to test the private method.
677+ $ reflection = new \ReflectionClass ( $ this ->inbox_controller );
678+ $ method = $ reflection ->getMethod ( 'get_local_recipients ' );
679+ $ method ->setAccessible ( true );
680+
681+ $ result = $ method ->invoke ( $ this ->inbox_controller , $ activity );
682+
683+ // Should return users who follow the remote actor because activity is public.
684+ $ this ->assertNotEmpty ( $ result , 'Should return users for public activity in cc ' );
685+ $ this ->assertContains ( self ::$ user_id , $ result , 'Should contain original test user ' );
686+ $ this ->assertContains ( $ user_id , $ result , 'Should contain new test user ' );
687+
688+ // Verify it returns exactly the followers we added.
689+ // Note: May include blog user (0) if blog mode is enabled.
690+ $ this ->assertGreaterThanOrEqual ( 2 , count ( $ result ), 'Should return at least 2 followers ' );
691+ $ this ->assertLessThanOrEqual ( 3 , count ( $ result ), 'Should return at most 3 followers (2 users + optional blog) ' );
692+
693+ // Clean up.
694+ \wp_delete_post ( $ remote_actor ->ID , true );
695+ \wp_delete_user ( $ user_id );
696+ \delete_option ( 'activitypub_actor_mode ' );
697+ \remove_all_filters ( 'activitypub_pre_http_get_remote_object ' );
698+ }
545699}
0 commit comments