used_in_override = [ 'placements' => $this->get_used_in_format( $placements ), 'groups' => $this->get_used_in_format( $groups ), ]; } /** * Get the ad type. * * @return string */ public function get_type(): string { return $this->get_meta( 'ad_type', '' ); } /** * Get the ad type object. * * @return AdTypeInterface|null */ public function get_type_object(): ?AdTypeInterface { return AdTypeRegistry::get()->get_by_id( $this->get_type() ); } /** * @return string */ public function get_content(): string { return $this->post->post_content ?? ''; } /** * Checks if the ad can be displayed. * * @return bool */ public function can_be_displayed(): bool { if ( ! $this->is_backend_valid ) { return false; } if ( $this->get_meta( 'display_only_once', false ) && apply_filters( 'adpresso_ad_display_limit_reached', false, $this->get_id() ) ) { $this->is_backend_valid = false; $this->skip_reason = __( 'Display limit reached (only once).', 'adpresso' ); return false; } return true; } /** * Notifies listeners (Pro's Ad_Rendered_Tracker) that the ad was rendered. * * @param bool $client_side True when only its JS config was shipped - the browser decides whether it shows. */ public function mark_as_rendered( bool $client_side = false ): void { if ( $this->get_meta( 'display_only_once', false ) ) { do_action( 'adpresso_ad_rendered', $this->get_id(), $client_side ); } } /** * Processes backend data for the ad. * * @param array $context_info * * @return void */ public function processBackendData( array $context_info = [] ): void { $this->reset_debug_log(); if ( $this->backend_processed ) { return; } $this->prepare_backend_data( $context_info ); // Conditions if ( ! $this->is_backend_valid ) { $this->backend_processed = true; $this->log_decision( $this->get_id(), 'rejected_condition', $this->skip_reason ?? 'Conditions failed' ); return; } // Scheduling needs JS rendering path since dates can be cached but only when a restriction is actually configured. $has_schedule_restriction = ScheduleValidator::get()->has_effective_schedule_restriction( $this ); if ( $has_schedule_restriction ) { $this->requires_frontend_logic = true; } $this->log_decision( $this->get_id(), 'valid', __( 'Ready for output', 'adpresso' ) ); $this->is_backend_valid = true; $ad_type_object = $this->get_type_object(); if ( $ad_type_object && $ad_type_object->requires_js_rendering() ) { $this->requires_frontend_logic = true; } if ( ConditionHelper::tree_contains_frontend_conditions( $this->conditions_json ) ) { $this->requires_frontend_logic = true; } if ( $ad_type_object ) { $type_config = $ad_type_object->get_frontend_config( $this ); if ( ! empty( $type_config ) ) { $this->frontend_config_data = array_merge( $this->frontend_config_data, $type_config ); } } $this->frontend_config_data['adId'] = $this->get_id(); $this->frontend_config_data['type'] = $this->get_type(); $this->frontend_config_data['name'] = $this->get_title() ?: __( '(No Name)', 'adpresso' ); $this->frontend_config_data['layout_styles'] = $this->get_layout_styles_string(); $this->frontend_config_data['custom_id'] = $this->get_meta( 'custom_id', '' ); $this->frontend_config_data['custom_classes'] = Util::sanitize_html_classes( (string) $this->get_meta( 'custom_classes', '' ) ); $this->frontend_config_data['inline_css'] = $this->get_meta( 'inline_css', '' ); $this->frontend_config_data['label_override'] = $this->get_meta( 'label_override', '' ); // The JS path (utils.js getStandardItemAttributes) only sets data-{prefix}-t, which click tracking needs, from this. $this->frontend_config_data['tracking_enabled'] = FrontendAttributes::is_item_tracked( $this ); $this->frontend_config_data['custom_ad_code'] = Util::get_custom_code( $this, 'custom_ad_code' ); if ( $has_schedule_restriction ) { $this->frontend_config_data['schedule'] = ScheduleValidator::get()->get_frontend_schedule_data( $this ); } if ( Util::is_pro_active() ) { $this->frontend_config_data['display_only_once'] = (bool) $this->get_meta( 'display_only_once', false ); $this->frontend_config_data['privacy_tcf_vendor'] = $this->get_meta( 'privacy_tcf_vendor', '0' ); $this->frontend_config_data['privacy_custom_service_id'] = $this->get_meta( 'privacy_custom_service_id', '' ); } /** * Filter to modify the frontend logic requirement for the ad. */ $this->requires_frontend_logic = apply_filters( 'adpresso_ad_requires_frontend_logic', $this->requires_frontend_logic, $this ); $this->backend_processed = true; } /** * Backfills 'scheduling_enabled' for the admin modal when it was never explicitly saved. * * @return array */ protected function get_all_meta_for_response(): array { $meta = parent::get_all_meta_for_response(); if ( ! isset( $meta['scheduling_enabled'] ) ) { $has_legacy_schedule = ! empty( $meta['start_date'] ) || ! empty( $meta['end_date'] ) || ! empty( Util::get_checked_keys( $meta['schedule_days'] ?? [] ) ) || ( $meta['schedule_time_mode'] ?? 'all_day' ) === 'scheduled'; if ( $has_legacy_schedule ) { $meta['scheduling_enabled'] = '1'; } } return $meta; } /** * Renders the ad for server-side rendering (SSR). * * @param array $context * * @return string|null */ public function render_ssr( array $context = [] ): ?string { if ( ! $this->backend_processed ) { $this->processBackendData(); } if ( ! $this->is_backend_valid || $this->requires_frontend_logic ) { return null; } $ad_type_object = $this->get_type_object(); if ( $ad_type_object && method_exists( $ad_type_object, 'output' ) ) { return $ad_type_object->output( $this, apply_filters( 'adpresso_ssr_output_ad_context', $context, $this ) ); } return null; } /** * Prepares the entity's data for a REST API response. * Extends the base method from the Entity class to add Ad-specific data. * * @param bool $include_edit_only_data * * @return array The prepared data array for the REST response. */ public function prepare_for_rest_response( bool $include_edit_only_data = true ): array { add_filter( 'adpresso_ad_metabox_classes', [ $this, 'remove_section_from_ad_type' ] ); $data = parent::prepare_for_rest_response( $include_edit_only_data ); $type_object = $this->get_type_object(); $id = $this->get_id(); $data['type'] = $type_object ? $type_object->get_title() : $this->get_type(); $data['image'] = $this->get_image(); $data['shortcode'] = '[adpresso_ad id="' . $id . '"]'; if ( Util::is_pro_active() ) { // Goes through get_final_url() so this preview also reflects the // adpresso_ad_link_masking_enabled filter forcing cloaking on/off, $final_url = LinkHandler::get_final_url( $this ); $raw_url = trim( (string) $this->get_meta( 'target_url', '' ) ); if ( $final_url !== '' && $final_url !== $raw_url ) { $data['masked_link'] = $final_url; } } $data['preview'] = ( $this->get_type() === 'image' ) ? wp_get_attachment_image_url( $this->get_meta( 'image_id' ) ) : null; if ( is_int( $id ) ) { if ( null !== $this->used_in_override ) { $data['used_in'] = $this->used_in_override; } else { $used_in_placements = adpresso_get_placement_repository()->get_by_using_item( $id, 'ad', [ 'any' ], false ); $used_in_groups = adpresso_get_group_repository()->find_by_contained_ad( $id, [ 'publish', 'draft' ], false ); $data['used_in'] = [ 'placements' => $this->get_used_in_format( $used_in_placements ), 'groups' => $this->get_used_in_format( $used_in_groups ), ]; } } return $data; } /** * Remove a specific section from the ad type. * * @param $sections * * @return array */ public function remove_section_from_ad_type( $sections ): array { if ( $this->get_type() === 'html5' ) { $sections = array_values( array_filter( $sections, static function( $box ) { return ! str_contains( $box, 'Ad_URL_Box' ); } ) ); } return $sections; } /** * Get used in format for REST response. * * @param array $entities Group|Placement * * @return array */ private function get_used_in_format( array $entities ): array { $raw_entities = []; foreach ( $entities as $entity ) { if ( ! $entity instanceof Group && ! $entity instanceof Placement ) { continue; } $raw_entities[] = [ 'id' => $entity->get_id(), 'title' => $entity->get_title(), 'edit_link' => Util::get_entity_edit_url( $entity instanceof Placement ? 'placements' : 'groups', $entity->get_id(), 'ads' ), ]; } return $raw_entities; } /** * Get the icon of the ad type as an SVG string. * * @return string */ public function get_image(): string { $type_object = $this->get_type_object(); $icon_path = $type_object ? $type_object->get_image() : 'admin/assets/img/ad-types/ap-ui-type-fallback.svg'; return SvgLoader::get( ADPRESSO_ABSPATH . $icon_path, [ 'class' => 'adpresso-icon-main' ] ); } }