+ */
+ private function search_posts( string $search, string $post_type ): array {
+ $posts = get_posts(
+ array(
+ 'post_type' => $post_type,
+ 's' => $search,
+ 'posts_per_page' => self::MAX_RESULTS,
+ 'post_status' => 'publish',
+ 'orderby' => 'title',
+ 'order' => 'ASC',
+ )
+ );
+
+ if ( empty( $posts ) ) {
+ return array();
+ }
+
+ $results = array();
+ foreach ( $posts as $post ) {
+ // For is_page/is_single, we can use ID, slug, or title.
+ $results[] = array(
+ 'id' => (string) $post->ID,
+ 'text' => sprintf( '%s (ID: %d)', $post->post_title, $post->ID ),
+ );
+ }
+
+ return $results;
+ }
+}
diff --git a/src/class-acm-wp-list-table.php b/src/class-acm-wp-list-table.php
index 0d91ef9..4f0b4cf 100644
--- a/src/class-acm-wp-list-table.php
+++ b/src/class-acm-wp-list-table.php
@@ -273,26 +273,43 @@ function column_id( $item ) {
$output .= 'Remove';
}
}
- $output .= '';
+ $output .= '';
+ // Build the field for the logical operator (near conditionals)
+ $condition_count = ! empty( $item['conditionals'] ) ? count( $item['conditionals'] ) : 0;
+ $operator_style = $condition_count < 2 ? ' style="display:none;"' : '';
+ $output .= '';
+ $output .= '
' . __( 'Logical Operator', 'ad-code-manager' ) . '
';
+ $output .= '';
+ $output .= '';
$output .= '';
// Build the fields for the normal columns
$output .= '';
$output .= '
' . __( 'URL Variables', 'ad-code-manager' ) . '
';
foreach ( (array) $item['url_vars'] as $slug => $value ) {
$output .= '
';
- $column_id = 'acm-column[' . $slug . ']';
- $output .= '';
- // Support for select dropdowns
+ $column_id = 'acm-column-' . $slug;
+ // Get the proper label from provider's ad_code_args
$ad_code_args = wp_filter_object_list( $ad_code_manager->current_provider->ad_code_args, array( 'key' => $slug ) );
$ad_code_arg = array_shift( $ad_code_args );
+ $field_label = isset( $ad_code_arg['label'] ) ? $ad_code_arg['label'] : ucwords( str_replace( '_', ' ', $slug ) );
+ $output .= '';
+ // Support for select dropdowns
if ( isset( $ad_code_arg['type'] ) && 'select' == $ad_code_arg['type'] ) {
- $output .= '
';
}
@@ -300,20 +317,7 @@ function column_id( $item ) {
// Build the field for the priority
$output .= '
';
$output .= '
' . __( 'Priority', 'ad-code-manager' ) . '
';
- $output .= '';
- $output .= '';
- // Build the field for the logical operator
- $output .= '
';
- $output .= '
' . __( 'Logical Operator', 'ad-code-manager' ) . '
';
- $output .= '';
- $operators = array(
- 'OR' => __( 'OR', 'ad-code-manager' ),
- 'AND' => __( 'AND', 'ad-code-manager' ),
- );
- foreach ( $operators as $key => $label ) {
- $output .= '';
- }
- $output .= '';
+ $output .= '';
$output .= '';
$output .= '
';
@@ -387,9 +391,9 @@ function inline_edit() {
+
diff --git a/tests/Integration/UI/ConditionalAutocompleteTest.php b/tests/Integration/UI/ConditionalAutocompleteTest.php
new file mode 100644
index 0000000..7bfcbb6
--- /dev/null
+++ b/tests/Integration/UI/ConditionalAutocompleteTest.php
@@ -0,0 +1,245 @@
+autocomplete = new Conditional_Autocomplete();
+ }
+
+ /**
+ * Test that the run method registers the necessary hooks.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete::run
+ */
+ public function test_run_registers_hooks(): void {
+ $this->autocomplete->run();
+
+ self::assertNotFalse(
+ has_action( 'admin_enqueue_scripts', array( $this->autocomplete, 'enqueue_scripts' ) )
+ );
+ self::assertNotFalse(
+ has_action( 'wp_ajax_acm_search_terms', array( $this->autocomplete, 'ajax_search_terms' ) )
+ );
+ }
+
+ /**
+ * Test that scripts are not enqueued on non-ACM pages.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete::enqueue_scripts
+ */
+ public function test_scripts_not_enqueued_on_other_pages(): void {
+ $this->autocomplete->enqueue_scripts( 'edit.php' );
+
+ self::assertFalse( wp_script_is( 'acm-conditional-autocomplete', 'enqueued' ) );
+ }
+
+ /**
+ * Test that the acm_autocomplete_conditionals filter is applied.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_autocomplete_conditionals_filter(): void {
+ $filter_called = false;
+ $custom_conditionals = array(
+ 'custom_conditional' => array(
+ 'type' => 'taxonomy',
+ 'taxonomy' => 'custom_tax',
+ ),
+ );
+
+ add_filter(
+ 'acm_autocomplete_conditionals',
+ function ( $conditionals ) use ( &$filter_called, $custom_conditionals ) {
+ $filter_called = true;
+ return array_merge( $conditionals, $custom_conditionals );
+ }
+ );
+
+ // Trigger script enqueue to get the conditionals.
+ set_current_screen( 'settings_page_ad-code-manager' );
+ $this->autocomplete->enqueue_scripts( 'settings_page_ad-code-manager' );
+
+ self::assertTrue( $filter_called );
+ }
+
+ /**
+ * Test default autocomplete conditionals include expected entries.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_default_autocomplete_conditionals(): void {
+ // Use reflection to access the private method.
+ $reflection = new ReflectionClass( $this->autocomplete );
+ $method = $reflection->getMethod( 'get_autocomplete_conditionals' );
+ $method->setAccessible( true );
+
+ $conditionals = $method->invoke( $this->autocomplete );
+
+ // Verify expected conditionals are present.
+ self::assertArrayHasKey( 'is_category', $conditionals );
+ self::assertArrayHasKey( 'has_category', $conditionals );
+ self::assertArrayHasKey( 'is_tag', $conditionals );
+ self::assertArrayHasKey( 'has_tag', $conditionals );
+ self::assertArrayHasKey( 'is_page', $conditionals );
+ self::assertArrayHasKey( 'is_single', $conditionals );
+
+ // Verify structure of a taxonomy conditional.
+ self::assertEquals( 'taxonomy', $conditionals['is_category']['type'] );
+ self::assertEquals( 'category', $conditionals['is_category']['taxonomy'] );
+
+ // Verify structure of a post_type conditional.
+ self::assertEquals( 'post_type', $conditionals['is_page']['type'] );
+ self::assertEquals( 'page', $conditionals['is_page']['post_type'] );
+ }
+
+ /**
+ * Test taxonomy term search.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_search_taxonomy_terms(): void {
+ // Create test categories.
+ self::factory()->category->create( array( 'name' => 'Technology News' ) );
+ self::factory()->category->create( array( 'name' => 'Tech Reviews' ) );
+ self::factory()->category->create( array( 'name' => 'Sports' ) );
+
+ // Use reflection to access the private method.
+ $reflection = new ReflectionClass( $this->autocomplete );
+ $method = $reflection->getMethod( 'search_taxonomy_terms' );
+ $method->setAccessible( true );
+
+ $results = $method->invoke( $this->autocomplete, 'Tech', 'category' );
+
+ self::assertCount( 2, $results );
+ self::assertArrayHasKey( 'id', $results[0] );
+ self::assertArrayHasKey( 'text', $results[0] );
+ }
+
+ /**
+ * Test post search.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_search_posts(): void {
+ // Create test pages.
+ self::factory()->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_title' => 'About Us',
+ 'post_status' => 'publish',
+ )
+ );
+ self::factory()->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_title' => 'About Our Team',
+ 'post_status' => 'publish',
+ )
+ );
+ self::factory()->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_title' => 'Contact',
+ 'post_status' => 'publish',
+ )
+ );
+
+ // Use reflection to access the private method.
+ $reflection = new ReflectionClass( $this->autocomplete );
+ $method = $reflection->getMethod( 'search_posts' );
+ $method->setAccessible( true );
+
+ $results = $method->invoke( $this->autocomplete, 'About', 'page' );
+
+ self::assertCount( 2, $results );
+ self::assertArrayHasKey( 'id', $results[0] );
+ self::assertArrayHasKey( 'text', $results[0] );
+ }
+
+ /**
+ * Test empty search returns empty array.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_search_no_results(): void {
+ // Use reflection to access the private method.
+ $reflection = new ReflectionClass( $this->autocomplete );
+ $method = $reflection->getMethod( 'search_taxonomy_terms' );
+ $method->setAccessible( true );
+
+ $results = $method->invoke( $this->autocomplete, 'NonexistentTerm', 'category' );
+
+ self::assertIsArray( $results );
+ self::assertEmpty( $results );
+ }
+
+ /**
+ * Test search for tags returns correct structure.
+ *
+ * @covers \Automattic\AdCodeManager\UI\Conditional_Autocomplete
+ */
+ public function test_search_tags(): void {
+ // Create test tags.
+ self::factory()->tag->create( array( 'name' => 'WordPress Tips' ) );
+ self::factory()->tag->create( array( 'name' => 'WordPress Plugins' ) );
+
+ // Use reflection to access the private method.
+ $reflection = new ReflectionClass( $this->autocomplete );
+ $method = $reflection->getMethod( 'search_taxonomy_terms' );
+ $method->setAccessible( true );
+
+ $results = $method->invoke( $this->autocomplete, 'WordPress', 'post_tag' );
+
+ self::assertCount( 2, $results );
+ // Results should use slug as ID.
+ self::assertStringContainsString( 'wordpress', $results[0]['id'] );
+ }
+
+ /**
+ * Clean up after each test.
+ */
+ public function tear_down(): void {
+ unset( $_GET['search'], $_GET['conditional'], $_GET['type'], $_GET['taxonomy'], $_GET['post_type'], $_GET['nonce'] );
+
+ // Dequeue scripts to prevent test pollution.
+ wp_dequeue_script( 'acm-conditional-autocomplete' );
+ wp_dequeue_script( 'selectWoo' );
+ wp_dequeue_style( 'select2' );
+
+ parent::tear_down();
+ }
+}
diff --git a/views/ad-code-manager.tpl.php b/views/ad-code-manager.tpl.php
index 8c7d9b4..4b73889 100644
--- a/views/ad-code-manager.tpl.php
+++ b/views/ad-code-manager.tpl.php
@@ -41,39 +41,15 @@
}
?>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+