PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.7
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / inc / MCP / Domain / SectionTools.php

SectionTools.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.7, at inc/MCP/Domain/SectionTools.php

251 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\MCP\Domain;
4
5 use LearnPress\MCP\Mappers\ResponseMapper;
6 use LearnPress\MCP\Support\Errors;
7 use LearnPress\MCP\Support\Permissions;
8 use LearnPress\MCP\Support\Sanitizer;
9 use LearnPress\MCP\Support\Validator;
10 use LearnPress\Models\CourseModel;
11 use LearnPress\Models\CoursePostModel;
12 use LearnPress\Models\CourseSectionModel;
13 use Throwable;
14 use WP_Error;
15
16 defined( 'ABSPATH' ) || exit;
17
18 /**
19 * Section create/update/delete executors.
20 *
21 * Uses CoursePostModel + CourseSectionModel curriculum APIs. Delete is a
22 * relationship-safe removal that preserves contained lesson/quiz posts and
23 * returns recovery metadata.
24 */
25 class SectionTools {
26
27 /**
28 * Execute `learnpress/create-section`.
29 *
30 * @param mixed $input Ability input.
31 *
32 * @return array|WP_Error
33 */
34 public static function create_section( $input ) {
35 $args = is_array( $input ) ? $input : array();
36 $course_id = Validator::require_id( $args, 'course_id' );
37 if ( is_wp_error( $course_id ) ) {
38 return $course_id;
39 }
40
41 $name = Sanitizer::text( $args['name'] ?? '' );
42 if ( '' === $name ) {
43 return Errors::invalid( __( 'name is required.', 'learnpress' ) );
44 }
45
46 $course_post = CoursePostModel::find( $course_id, true );
47 if ( ! $course_post instanceof CoursePostModel ) {
48 return Errors::not_found( __( 'Course not found.', 'learnpress' ) );
49 }
50
51 if ( ! Permissions::can_edit_post( $course_id ) ) {
52 return Errors::forbidden();
53 }
54
55 try {
56 $section = $course_post->add_section(
57 array(
58 'section_name' => $name,
59 'section_description' => $args['description'] ?? '',
60 )
61 );
62
63 if ( array_key_exists( 'order', $args ) ) {
64 self::reorder_section( $course_post, $course_id, $section->get_section_id(), absint( $args['order'] ) );
65 }
66
67 $fresh = CourseSectionModel::find( $section->get_section_id(), $course_id, false );
68 $fresh = $fresh instanceof CourseSectionModel ? $fresh : $section;
69
70 return array( 'section' => ResponseMapper::section( $fresh ) );
71 } catch ( Throwable $e ) {
72 return Errors::from_throwable( $e );
73 }
74 }
75
76 /**
77 * Execute `learnpress/update-section`.
78 *
79 * @param mixed $input Ability input.
80 *
81 * @return array|WP_Error
82 */
83 public static function update_section( $input ) {
84 $args = is_array( $input ) ? $input : array();
85 $course_id = Validator::require_id( $args, 'course_id' );
86 if ( is_wp_error( $course_id ) ) {
87 return $course_id;
88 }
89 $section_id = Validator::require_id( $args, 'section_id' );
90 if ( is_wp_error( $section_id ) ) {
91 return $section_id;
92 }
93
94 $section = Validator::find_section( $section_id, $course_id );
95 if ( is_wp_error( $section ) ) {
96 return $section;
97 }
98
99 if ( ! Permissions::can_edit_post( $course_id ) ) {
100 return Errors::forbidden();
101 }
102
103 $course_post = CoursePostModel::find( $course_id, true );
104 if ( ! $course_post instanceof CoursePostModel ) {
105 return Errors::not_found( __( 'Course not found.', 'learnpress' ) );
106 }
107
108 try {
109 $data = array();
110 if ( array_key_exists( 'name', $args ) ) {
111 $data['section_name'] = Sanitizer::text( $args['name'] );
112 }
113 if ( array_key_exists( 'description', $args ) ) {
114 $data['section_description'] = Sanitizer::html( $args['description'] );
115 }
116
117 if ( ! empty( $data ) ) {
118 $course_post->update_section( $section, $data );
119 }
120
121 if ( array_key_exists( 'order', $args ) ) {
122 self::reorder_section( $course_post, $course_id, $section_id, absint( $args['order'] ) );
123 }
124
125 $fresh = CourseSectionModel::find( $section_id, $course_id, false );
126 $fresh = $fresh instanceof CourseSectionModel ? $fresh : $section;
127
128 return array( 'section' => ResponseMapper::section( $fresh ) );
129 } catch ( Throwable $e ) {
130 return Errors::from_throwable( $e );
131 }
132 }
133
134 /**
135 * Execute `learnpress/delete-section` (relationship-safe removal).
136 *
137 * @param mixed $input Ability input.
138 *
139 * @return array|WP_Error
140 */
141 public static function delete_section( $input ) {
142 $args = is_array( $input ) ? $input : array();
143 $course_id = Validator::require_id( $args, 'course_id' );
144 if ( is_wp_error( $course_id ) ) {
145 return $course_id;
146 }
147 $section_id = Validator::require_id( $args, 'section_id' );
148 if ( is_wp_error( $section_id ) ) {
149 return $section_id;
150 }
151
152 $section = Validator::find_section( $section_id, $course_id );
153 if ( is_wp_error( $section ) ) {
154 return $section;
155 }
156
157 if ( ! Permissions::can_edit_post( $course_id ) ) {
158 return Errors::forbidden();
159 }
160
161 try {
162 $affected_items = self::collect_section_items( $course_id, $section_id );
163
164 $recovery = array(
165 'course_id' => $course_id,
166 'section_id' => $section_id,
167 'name' => (string) $section->section_name,
168 'description' => (string) $section->section_description,
169 'previous_order' => (int) $section->section_order,
170 'affected_items' => $affected_items,
171 'note' => __( 'Section relationship removed. Lesson/quiz posts are preserved and can be re-added using the affected_items metadata.', 'learnpress' ),
172 );
173
174 $section->delete();
175
176 return array(
177 'removed' => true,
178 'section_id' => $section_id,
179 'course_id' => $course_id,
180 'affected_items' => $affected_items,
181 'recovery' => $recovery,
182 );
183 } catch ( Throwable $e ) {
184 return Errors::from_throwable( $e );
185 }
186 }
187
188 /**
189 * Move a section to a 1-based position within its course.
190 *
191 * @param CoursePostModel $course_post Course post model.
192 * @param int $course_id Course ID.
193 * @param int $section_id Section being moved.
194 * @param int $position Desired 1-based position.
195 *
196 * @return void
197 */
198 protected static function reorder_section( CoursePostModel $course_post, int $course_id, int $section_id, int $position ): void {
199 $course = CourseModel::find( $course_id, false );
200 if ( ! $course instanceof CourseModel ) {
201 return;
202 }
203 $course->sections_items = null; // Force a fresh read from the relationship tables, not the JSON snapshot.
204
205 $ids = array();
206 foreach ( $course->get_section_items() as $section ) {
207 $ids[] = absint( $section->section_id ?? 0 );
208 }
209 $ids = array_values( array_filter( $ids ) );
210
211 $ids = array_values( array_diff( $ids, array( $section_id ) ) );
212 $index = max( 0, min( count( $ids ), $position - 1 ) );
213 array_splice( $ids, $index, 0, array( $section_id ) );
214
215 $course_post->update_sections_position( array( 'new_position' => $ids ) );
216 }
217
218 /**
219 * Collect items belonging to a section, with previous order.
220 *
221 * @param int $course_id Course ID.
222 * @param int $section_id Section ID.
223 *
224 * @return array
225 */
226 protected static function collect_section_items( int $course_id, int $section_id ): array {
227 $course = CourseModel::find( $course_id, false );
228 if ( ! $course instanceof CourseModel ) {
229 return array();
230 }
231 $course->sections_items = null; // Force a fresh read from the relationship tables, not the JSON snapshot.
232
233 $items = array();
234 foreach ( $course->get_section_items() as $section ) {
235 if ( absint( $section->section_id ?? 0 ) !== $section_id ) {
236 continue;
237 }
238 $section_items = is_array( $section->items ?? null ) ? $section->items : array();
239 foreach ( $section_items as $item ) {
240 $items[] = array(
241 'item_id' => absint( $item->item_id ?? 0 ),
242 'item_type' => (string) ( $item->item_type ?? '' ),
243 'item_order' => absint( $item->item_order ?? 0 ),
244 );
245 }
246 }
247
248 return $items;
249 }
250 }
251