add_action( 'save_post_academiccrew_course', $this, 'on_course_saved', 20, 3 ); $loader->add_action( 'save_post_page', $this, 'on_linked_page_saved', 20, 2 ); $loader->add_action( 'trashed_post', $this, 'on_cpt_trashed' ); $loader->add_action( 'untrashed_post', $this, 'on_cpt_untrashed' ); $loader->add_action( 'before_delete_post', $this, 'on_cpt_before_delete', 10, 2 ); $loader->add_action( 'before_delete_post', $this, 'on_page_before_delete',10, 2 ); } // --------------------------------------------------------------- // Save hooks // --------------------------------------------------------------- public function on_course_saved( int $post_id, \WP_Post $post, bool $update ): void { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if ( wp_is_post_revision( $post_id ) ) return; if ( 'publish' !== $post->post_status ) return; if ( self::$syncing ) return; $existing_page_id = (int) get_post_meta( $post_id, '_academiccrew_linked_page_id', true ); if ( $existing_page_id && get_post( $existing_page_id ) ) { $this->refresh_instructor_permissions( $post_id, $existing_page_id ); $this->sync_page_slug( $post_id, $existing_page_id ); $this->sync_to_page( $post->post_content, $existing_page_id ); } else { $this->generate_page( $post_id ); } } public function on_linked_page_saved( int $page_id, \WP_Post $page ): void { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if ( wp_is_post_revision( $page_id ) ) return; if ( self::$syncing ) return; if ( 'publish' !== $page->post_status && 'private' !== $page->post_status ) return; $course_id = (int) get_post_meta( $page_id, '_academiccrew_course_id', true ); if ( ! $course_id ) return; self::$syncing = true; wp_update_post( [ 'ID' => $course_id, 'post_content' => $page->post_content ] ); self::$syncing = false; } private function sync_to_page( string $content, int $page_id ): void { if ( self::$syncing ) return; self::$syncing = true; wp_update_post( [ 'ID' => $page_id, 'post_content' => $content ] ); self::$syncing = false; } // --------------------------------------------------------------- // Lifecycle hooks — trash / delete // --------------------------------------------------------------- public function on_cpt_trashed( int $post_id ): void { if ( 'academiccrew_course' !== get_post_type( $post_id ) ) return; $page_id = (int) get_post_meta( $post_id, '_academiccrew_linked_page_id', true ); if ( ! $page_id || 'publish' !== get_post_status( $page_id ) ) return; self::$syncing = true; wp_trash_post( $page_id ); self::$syncing = false; } public function on_cpt_untrashed( int $post_id ): void { if ( 'academiccrew_course' !== get_post_type( $post_id ) ) return; $page_id = (int) get_post_meta( $post_id, '_academiccrew_linked_page_id', true ); if ( ! $page_id || 'trash' !== get_post_status( $page_id ) ) return; self::$syncing = true; wp_untrash_post( $page_id ); if ( 'publish' !== get_post_status( $page_id ) ) { wp_update_post( [ 'ID' => $page_id, 'post_status' => 'publish' ] ); } self::$syncing = false; } public function on_cpt_before_delete( int $post_id, \WP_Post $post ): void { if ( 'academiccrew_course' !== $post->post_type ) return; $page_id = (int) get_post_meta( $post_id, '_academiccrew_linked_page_id', true ); if ( ! $page_id || ! get_post( $page_id ) ) return; self::$syncing = true; $this->remove_nav_menu_items_for_page( $page_id ); wp_delete_post( $page_id, true ); self::$syncing = false; } public function on_page_before_delete( int $post_id, \WP_Post $post ): void { if ( 'page' !== $post->post_type ) return; if ( self::$syncing ) return; $course_id = (int) get_post_meta( $post_id, '_academiccrew_course_id', true ); if ( ! $course_id ) return; delete_post_meta( $course_id, '_academiccrew_linked_page_id' ); } // --------------------------------------------------------------- // Page generation // --------------------------------------------------------------- public function generate_page( int $course_id ): int { $course = get_post( $course_id ); if ( ! $course ) { return 0; } $instructor_user_ids = $this->collect_instructor_user_ids( $course_id ); // Seed the editable page body with whatever the course already carries. // Without this the empty page immediately syncs back and wipes it. $content = (string) $course->post_content; $archive_page_id = (int) get_option( 'academiccrew_courses_archive_page_id', 0 ); if ( ! $archive_page_id ) { CoursesArchivePageGenerator::generate_if_needed(); $archive_page_id = (int) get_option( 'academiccrew_courses_archive_page_id', 0 ); } $primary_code = get_post_meta( $course_id, '_acc_course_code', true ); $slug = $primary_code ? strtolower( $primary_code ) : $course->post_name; $page_id = $this->create_page( [ 'post_title' => $course->post_title, 'post_content' => $content, 'post_status' => 'publish', 'post_type' => 'page', 'post_name' => $slug, 'post_author' => $instructor_user_ids[0] ?? 1, 'post_parent' => $archive_page_id ?: 0, 'meta_input' => [ '_academiccrew_is_course_page' => 1, '_academiccrew_course_id' => $course_id, '_academiccrew_instructor_user_ids' => $instructor_user_ids, 'pyre_page_title' => 'no', 'pyre_sidebar_position' => 'none', ], ] ); if ( ! $page_id ) { return 0; } update_post_meta( $course_id, '_academiccrew_linked_page_id', $page_id ); return $page_id; } /** * Re-sync instructor permissions and slug for an already-linked page. * Call after saving instructor meta during batch import/update operations. */ public function refresh_linked_page( int $course_id ): void { $page_id = (int) get_post_meta( $course_id, '_academiccrew_linked_page_id', true ); if ( ! $page_id || ! get_post( $page_id ) ) { $this->generate_page( $course_id ); return; } $this->refresh_instructor_permissions( $course_id, $page_id ); $this->sync_page_slug( $course_id, $page_id ); } private function sync_page_slug( int $course_id, int $page_id ): void { $primary_code = get_post_meta( $course_id, '_acc_course_code', true ); if ( ! $primary_code ) { return; } $desired_slug = strtolower( $primary_code ); $page = get_post( $page_id ); if ( $page && $page->post_name !== $desired_slug ) { // Wrap in the sync guard: without it the page save triggers // on_linked_page_saved, which copies the (possibly stale) page body // back onto the course and can clobber a just-made edit. self::$syncing = true; wp_update_post( [ 'ID' => $page_id, 'post_name' => $desired_slug, ] ); self::$syncing = false; } } private function refresh_instructor_permissions( int $course_id, int $page_id ): void { $user_ids = $this->collect_instructor_user_ids( $course_id ); update_post_meta( $page_id, '_academiccrew_instructor_user_ids', $user_ids ); } private function collect_instructor_user_ids( int $course_id ): array { $raw = get_post_meta( $course_id, '_acc_instructors', true ); $instructors = is_array( $raw ) ? $raw : ( $raw ? maybe_unserialize( $raw ) : [] ); if ( ! is_array( $instructors ) ) { return []; } $user_ids = []; foreach ( $instructors as $entry ) { $mid = (int) ( $entry['member_id'] ?? 0 ); $user_id = (int) get_post_meta( $mid, '_academiccrew_wp_user_id', true ); if ( $user_id > 0 ) { $user_ids[] = $user_id; } } return array_unique( $user_ids ); } }