Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/classes/Course_Duplicator.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * Handle Course Duplicate
4 + *
5 + * @package TutorPro\Classes
6 + * @author Themeum <support@themeum.com>
7 + * @link https://themeum.com
8 + * @since 1.0.0
9 + */
10 +
11 + namespace TUTOR_PRO;
12 +
13 + use TUTOR\Input;
14 +
15 + if ( ! defined( 'ABSPATH' ) ) {
16 + exit;
17 + }
18 +
19 + /**
20 + * Class Course Duplicator
21 + */
22 + class Course_Duplicator {
23 + /**
24 + * Post columns
25 + *
26 + * @var array
27 + */
28 + private $necessary_post_columns = array(
29 + 'post_author',
30 + 'post_content',
31 + 'post_title',
32 + 'post_excerpt',
33 + 'post_status',
34 + 'comment_status',
35 + 'ping_status',
36 + 'post_password',
37 + 'post_name',
38 + 'to_ping',
39 + 'pinged',
40 + 'post_content_filtered',
41 + 'menu_order',
42 + 'post_type',
43 + 'post_mime_type',
44 + );
45 +
46 + /**
47 + * Child types
48 + *
49 + * @var array
50 + */
51 + private $necessary_child_types = array(
52 + 'topics',
53 + 'lesson',
54 + 'tutor_quiz',
55 + // 'tutor_announcements', // Announcment is probably not necessary in duplicated course since there is no student yet in the new one
56 + 'tutor_assignments',
57 + );
58 +
59 + /**
60 + * Allowed user role
61 + *
62 + * @var array
63 + */
64 + private $allowed_user_role = array(
65 + 'administrator',
66 + 'editor',
67 + 'tutor_instructor',
68 + );
69 +
70 + /**
71 + * Store duplicated IDs here to avoid accidental infinity recursion.
72 + *
73 + * @var array
74 + */
75 + private $duplicated_post_ids = array();
76 +
77 + /**
78 + * Register hooks
79 + *
80 + * @param boolean $register_hooks hook register or not.
81 + */
82 + public function __construct( $register_hooks = true ) {
83 + if ( $register_hooks ) {
84 + add_action( 'wp_loaded', array( $this, 'init_duplicator' ) );
85 + add_filter( 'post_row_actions', array( $this, 'register_duplicate_button' ), 10, 2 );
86 + add_action( 'tutor_course_dashboard_actions_after', array( $this, 'duplicate_button_in_dashboard' ) );
87 + }
88 + add_action( 'tutor_admin_middle_course_list_action', array( $this, 'add_course_duplicate_menu' ) );
89 + }
90 +
91 + /**
92 + * Get duplicator HTML
93 + *
94 + * @param int $course_id course id.
95 + * @param boolean $is_wp_admin is wp admin.
96 + * @param string $class class.
97 + *
98 + * @return string
99 + */
100 + private function get_duplicator_html( $course_id, bool $is_wp_admin, $class = '' ) {
101 + return '<a class="' . $class . '" href="?tutor_action=duplicate_course&is_wp_admin=' . ( $is_wp_admin ? 'yes' : 'no' ) . '&course_id=' . $course_id . '" aria-label="' . __( 'Duplicate', 'tutor-pro' ) . '">
102 + ' . __( 'Duplicate', 'tutor-pro' ) . '
103 + </a>';
104 + }
105 +
106 + /**
107 + * Get course edit link.
108 + *
109 + * @param int $course_id course id.
110 + * @param boolean $is_admin is admin or not.
111 + *
112 + * @return string
113 + */
114 + private function get_course_edit_link( $course_id, bool $is_admin ) {
115 + return $is_admin ? get_edit_post_link( $course_id, null ) : tutor_utils()->get_tutor_dashboard_page_permalink( 'create-course/?course_ID=' . $course_id );
116 + }
117 +
118 + /**
119 + * Duplicate button for dashboard.
120 + *
121 + * @param int $course_id course id.
122 + *
123 + * @return void
124 + */
125 + public function duplicate_button_in_dashboard( $course_id ) {
126 + echo wp_kses_post( $this->get_duplicator_html( $course_id, false, 'tutor-mycourse-edit' ) );
127 + }
128 +
129 + /**
130 + * Register duplicate button
131 + *
132 + * @param array $actions action list.
133 + * @param object $post post object.
134 + *
135 + * @return array
136 + */
137 + public function register_duplicate_button( $actions, $post ) {
138 + if ( tutor()->course_post_type === $post->post_type ) {
139 + $actions[] = $this->get_duplicator_html( $post->ID, true );
140 + }
141 +
142 + return $actions;
143 + }
144 +
145 + /**
146 + * Handle Course duplicate for WP Admin and Frontend Dashboard
147 + *
148 + * @return void
149 + *
150 + * @since 2.0.0
151 + */
152 + public function init_duplicator() {
153 + $action = Input::get( 'tutor_action' );
154 + $id = Input::get( 'course_id', 0, Input::TYPE_INT );
155 +
156 + if ( 'duplicate_course' !== $action || 0 === $id ) {
157 + return;
158 + }
159 +
160 + if ( $this->is_valid_user_role() ) {
161 + // Duplicate the post.
162 + $new_post_id = $this->duplicate_post( $id );
163 +
164 + if ( $new_post_id ) {
165 + $is_wp_admin = is_admin();
166 + $flash_message = __( 'Course Duplicated Successfully!', 'tutor-pro' );
167 + if ( $is_wp_admin ) {
168 + $link = admin_url( 'admin.php?page=tutor' );
169 + } else {
170 + $link = tutor_utils()->tutor_dashboard_url( 'my-courses/draft-courses' );
171 + }
172 +
173 + tutor_utils()->redirect_to( $link, $flash_message );
174 + exit;
175 + }
176 + }
177 +
178 + exit( 'You are not allowed for this action.' );
179 + }
180 +
181 + /**
182 + * Check is valid user role.
183 + *
184 + * @return boolean
185 + */
186 + private function is_valid_user_role() {
187 + $current_user = wp_get_current_user();
188 +
189 + if ( is_object( $current_user ) && property_exists( $current_user, 'roles' ) ) {
190 + $roles = (array) $current_user->roles;
191 + $different = array_diff( $this->allowed_user_role, $roles );
192 + $exist_in_allowed = count( $different ) < count( $this->allowed_user_role );
193 +
194 + return $exist_in_allowed;
195 + }
196 + }
197 +
198 + /**
199 + * Duplicate post by using recursive mechanism
200 + *
201 + * @param int $post_id post id that need to be duplicated.
202 + * @param int $absolute_course_id optional.
203 + * @param int $new_parent_id optional.
204 + * @param int $new_id optional.
205 + *
206 + * @return void
207 + */
208 + public function duplicate_post( $post_id, $absolute_course_id = null, $new_parent_id = 0, $new_id = null ) {
209 +
210 + if ( ! $post_id || ! is_numeric( $post_id ) ) {
211 + return;
212 + }
213 +
214 + $post = get_post( $post_id );
215 + $post = is_object( $post ) ? (array) $post : null;
216 +
217 + if ( ! $post ) {
218 + // Return right from here.
219 + return false;
220 + }
221 +
222 + // Create new post using the old values.
223 + $post = $this->strip_unnecessary_columns( $post );
224 + $post['post_author'] = get_current_user_id();
225 + $post['post_parent'] = $new_parent_id;
226 + $post['post_status'] = $new_parent_id > 0 ? 'publish' : 'draft';
227 + $post['post_name'] = tutor_utils()->get_unique_slug( sanitize_title( $post['post_name'], 'untitled-course' ) );
228 + ! $new_id ? $new_id = wp_insert_post( $post ) : 0;
229 +
230 + /**
231 + * Add meta flag for duplicate number
232 + * For ex: Copy 1 ,Copy 2, so that it can be identified how many
233 + * times a course has been duplicated
234 + *
235 + * @since v2.0.0
236 + */
237 + if ( $new_id ) {
238 + $has_duplicator_of_post = get_post_meta( $post_id, 'tutor-course-duplicate-' . $post_id, true );
239 + if ( $has_duplicator_of_post ) {
240 + update_post_meta( $post_id, 'tutor-course-duplicate-' . $post_id, (int) ++$has_duplicator_of_post );
241 + } else {
242 + update_post_meta( $post_id, 'tutor-course-duplicate-' . $post_id, 1 );
243 + }
244 +
245 + /**
246 + * Show copy text only for course
247 + *
248 + * @since 2.1.7
249 + */
250 + $copy_text = '';
251 + if ( tutor()->course_post_type === $post['post_type'] ) {
252 + $copy_text = ' (' . __( 'Copy ', 'tutor-pro' ) . get_post_meta( $post_id, 'tutor-course-duplicate-' . $post_id, 1 ) . ')';
253 + }
254 +
255 + self::update_course( array( 'ID' => $new_id ), array( 'post_title' => $post['post_title'] . $copy_text ) );
256 + }
257 +
258 + // Duplicate post meta.
259 + $this->duplicate_post_meta( $post_id, $new_id, $absolute_course_id );
260 +
261 + // Assign taxonomy.
262 + $this->assign_post_taxonomy( $post_id, $new_id, 'course-category' );
263 + $this->assign_post_taxonomy( $post_id, $new_id, 'course-tag' );
264 +
265 + // Duplicate quiz question if it is quiz post type.
266 + 'tutor_quiz' === $post['post_type'] ? $this->duplicate_quiz_dependency( $post_id, $new_id, false ) : 0;
267 +
268 + // Set it as done.
269 + $this->duplicated_post_ids[] = (int) $post_id;
270 +
271 + // Now duplicate childs like topic, lesson, etc.
272 + $childs = $this->get_child_post_ids( $post_id );
273 +
274 + foreach ( $childs as $child_id ) {
275 + if ( in_array( (int) $child_id, $this->duplicated_post_ids ) ) {
276 + // Avoid accidental infinity recursion.
277 + continue;
278 + }
279 +
280 + $this->duplicate_post( $child_id, ( $absolute_course_id ? $absolute_course_id : $new_id ), $new_id );
281 + }
282 +
283 + return $new_id;
284 + }
285 +
286 + /**
287 + * Duplicate post meta
288 + *
289 + * @param int $old_id main post id.
290 + * @param int $new_id new duplicated post id.
291 + * @param int $absolute_course_id course id.
292 + *
293 + * @return void
294 + */
295 + private function duplicate_post_meta( $old_id, $new_id, $absolute_course_id ) {
296 +
297 + // Get existing meta from old post.
298 + $meta_array = get_post_meta( $old_id );
299 + ! is_array( $meta_array ) ? $meta_array = array() : 0;
300 +
301 + // Add these meta to newly created post.
302 + foreach ( $meta_array as $name => $value ) {
303 +
304 + // Convert to singular value from second level array.
305 + $value = is_array( $value ) ? ( isset( $value[0] ) ? $value[0] : '' ) : '';
306 + $value = is_serialized( $value ) ? unserialize( $value ) : $value;
307 +
308 + // Replace old course ID meta with new one.
309 + '_tutor_course_price_type' === $name ? $value = 'free' : 0;
310 +
311 + if ( '_tutor_course_product_id' === $name ) {
312 + continue;
313 + }
314 +
315 + /**
316 + * Course assignment linked with new duplicated course id
317 + *
318 + * @since v2.0.7
319 + */
320 + if ( '_tutor_course_id_for_assignments' === $name ) {
321 + $value = $absolute_course_id;
322 + }
323 + update_post_meta( $new_id, $name, $value );
324 + }
325 + }
326 +
327 + /**
328 + * Assign post taxonomy.
329 + *
330 + * @param int $old_id old id.
331 + * @param int $new_id new id.
332 + * @param mixed $taxonomy taxonomy.
333 + *
334 + * @return void
335 + */
336 + private function assign_post_taxonomy( $old_id, $new_id, $taxonomy ) {
337 + $old_terms = get_the_terms( $old_id, $taxonomy );
338 + ! is_array( $old_terms ) ? $old_terms = array() : 0;
339 +
340 + // Extract terms IDs.
341 + $term_ids = array();
342 + foreach ( $old_terms as $term ) {
343 + $term_ids[] = $term->term_id;
344 + }
345 +
346 + // Assign the terms.
347 + count( $term_ids ) > 0 ? wp_set_post_terms( $new_id, $term_ids, $taxonomy ) : 0;
348 + }
349 +
350 + /**
351 + * Duplicate quiz dependency
352 + *
353 + * @param int $old_context_id old context id.
354 + * @param int $new_context_id new context id.
355 + * @param boolean $is_answer is answer or not.
356 + *
357 + * @return void
358 + */
359 + private function duplicate_quiz_dependency( $old_context_id, $new_context_id, bool $is_answer ) {
360 +
361 + $table_name = $is_answer ? 'tutor_quiz_question_answers' : 'tutor_quiz_questions';
362 + $rel_id_column = $is_answer ? 'belongs_question_id' : 'quiz_id';
363 + $base_id_column = $is_answer ? 'answer_id' : 'question_id';
364 +
365 + global $wpdb, $table_prefix;
366 + $context_table = $table_prefix . $table_name;
367 +
368 + // Get quiz quesions by quiz post ID.
369 + $query = 'SELECT * FROM ' . sanitize_text_field( $context_table ) . ' WHERE ' . sanitize_text_field( $rel_id_column ) . '=' . sanitize_text_field( $old_context_id );
370 + $result = $wpdb->get_results( $query );//phpcs:ignore
371 +
372 + if ( is_array( $result ) && ! empty( $result ) ) {
373 +
374 + // Loop through every question and duplicate.
375 + foreach ( $result as $context ) {
376 + $context = (array) $context;
377 +
378 + $old_stuff_id = $context[ $base_id_column ];
379 +
380 + unset( $context[ $base_id_column ] );
381 + $context[ $rel_id_column ] = $new_context_id;
382 +
383 + // Insert new row.
384 + $wpdb->insert( $context_table, $context );
385 +
386 + // Now copy quiz question answers.
387 + ! $is_answer ? $this->duplicate_quiz_dependency( $old_stuff_id, $wpdb->insert_id, true ) : 0;
388 + }
389 + }
390 + }
391 +
392 +
393 + /**
394 + * Get child post ids.
395 + *
396 + * @param int $parent_id parent id.
397 + *
398 + * @return array
399 + */
400 + private function get_child_post_ids( $parent_id ) {
401 +
402 + $children = get_children(
403 + array(
404 + 'post_parent' => $parent_id,
405 + 'post_type' => $this->necessary_child_types,
406 + 'posts_per_page' => -1,
407 + 'orderby' => 'ID',
408 + 'order' => 'ASC',
409 + )
410 + );
411 +
412 + ! is_array( $children ) ? $children = array() : 0;
413 +
414 + $child_ids = array();
415 + foreach ( $children as $child_post ) {
416 + is_object( $child_post ) ? $child_ids[] = (int) $child_post->ID : 0;
417 + }
418 +
419 + return $child_ids;
420 + }
421 +
422 + /**
423 + * Strip columns
424 + *
425 + * @param array $post post.
426 + *
427 + * @return array
428 + */
429 + private function strip_unnecessary_columns( array $post ) {
430 + $new_array = array();
431 +
432 + foreach ( $post as $column => $value ) {
433 + if ( in_array( $column, $this->necessary_post_columns ) ) {
434 + // Keep only if it exist in ncessary column list.
435 + $new_array[ $column ] = $value;
436 + }
437 + }
438 +
439 + return $new_array;
440 + }
441 +
442 + /**
443 + * Add course duplicate menu.
444 + *
445 + * @param int $id id.
446 + *
447 + * @return void
448 + */
449 + public function add_course_duplicate_menu( $id ) {
450 + $duplicate = '?tutor_action=duplicate_course&is_wp_admin=' . ( is_admin() ? 'yes' : 'no' ) . '&course_id=' . $id;
451 + ?>
452 + <a class="tutor-dropdown-item" href="<?php echo esc_url( $duplicate ); ?>">
453 + <i class="tutor-icon-copy tutor-mr-8" area-hidden="true"></i>
454 + <span><?php esc_html_e( 'Duplicate', 'tutor-pro' ); ?></span>
455 + </a>
456 + <?php
457 + }
458 +
459 + /**
460 + * Update course
461 + *
462 + * @param array $where | where condition required.
463 + * @param array $data | data that need to be updated required.
464 + *
465 + * @return bool | true on success, false on failure.
466 + *
467 + * @since v2.0.0
468 + */
469 + public static function update_course( array $where, array $data ) : bool {
470 + global $wpdb;
471 + $table = $wpdb->posts;
472 + $update = $wpdb->update(
473 + $table,
474 + $data,
475 + $where
476 + );
477 + return $update ? true : false;
478 + }
479 + }
480 +