Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/classes/Course_Duplicator.php
Keine Baseline-Datei – Diff nur gegen leer.
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
+