/
model.llms.membership.php
444 lines (382 loc) · 13.9 KB
/
model.llms.membership.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<?php
/**
* LifterLMS Membership Model
*
* @package LifterLMS/Models/Classes
*
* @since 3.0.0
* @version 6.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* LLMS_Membership model class
*
* @since 3.0.0
* @since 3.30.0 Added optional argument to `add_auto_enroll_courses()` method.
* @since 3.32.0 Added `get_student_count()` method.
* @since 3.36.3 Added `get_categories()`, `get_tags()` and `toArrayAfter()` methods.
* @since 3.38.1 Added methods for retrieving posts associated with the membership.
* @since 4.0.0 Added MySQL 8.0 compatibility.
* @since 5.2.1 Check for an empty sales page URL or ID.
* @since 5.3.0 Move sales page methods to `LLMS_Trait_Sales_Page`.
*
* @property int[] $auto_enroll Array of course IDs that users will be autoenrolled in upon successful enrollment in this membership.
* @property array $instructors Course instructor user information.
* @property string $restriction_redirect_type What type of redirect action to take when content is restricted by this membership [none|membership|page|custom].
* @property int $redirect_page_id WP Post ID of a page to redirect users to when $restriction_redirect_type is 'page'.
* @property string $redirect_custom_url Arbitrary URL to redirect users to when $restriction_redirect_type is 'custom'.
* @property string $restriction_add_notice Whether or not to add an on screen message when content is restricted by this membership [yes|no].
* @property string $restriction_notice Notice to display when $restriction_add_notice is 'yes'.
* @property int $sales_page_content_page_id WP Post ID of the WP page to redirect to when $sales_page_content_type is 'page'.
* @property string $sales_page_content_type Sales page behavior [none,content,page,url].
* @property string $sales_page_content_url Redirect URL for a sales page, when $sales_page_content_type is 'url'.
*/
class LLMS_Membership extends LLMS_Post_Model implements LLMS_Interface_Post_Instructors {
use LLMS_Trait_Sales_Page;
/**
* Membership post meta.
*
* @var array
*/
protected $properties = array(
'auto_enroll' => 'array',
'instructors' => 'array',
'redirect_page_id' => 'absint',
'restriction_add_notice' => 'yesno',
'restriction_notice' => 'html',
'restriction_redirect_type' => 'text',
'redirect_custom_url' => 'text',
);
/**
* Database post type.
*
* @var string
*/
protected $db_post_type = 'llms_membership';
/**
* Model name.
*
* @var string
*/
protected $model_post_type = 'membership';
/**
* Constructor for this class and the traits it uses.
*
* @since 5.3.0
*
* @param string|int|LLMS_Post_Model|WP_Post $model 'new', WP post id, instance of an extending class, instance of WP_Post.
* @param array $args Args to create the post, only applies when $model is 'new'.
*/
public function __construct( $model, $args = array() ) {
$this->construct_sales_page();
parent::__construct( $model, $args );
}
/**
* Add courses to autoenrollment by id
*
* @since 3.0.0
* @since 3.30.0 Added optional `$replace` argument.
*
* @param array|int $course_ids Array of course id or course id as int.
* @param bool $replace Optional. When `true`, replaces all existing courses with `$course_ids`, when false merges `$course_ids` with existing courses. Default `false`.
* @return boolean Returns `true` on success, and `false` on error or if the value in the db is unchanged.
*/
public function add_auto_enroll_courses( $course_ids, $replace = false ) {
// allow a single course_id to be passed in.
if ( ! is_array( $course_ids ) ) {
$course_ids = array( $course_ids );
}
// add existing courses to the array if replace is false.
if ( ! $replace ) {
$course_ids = array_merge( $course_ids, $this->get_auto_enroll_courses() );
}
return $this->set( 'auto_enroll', array_unique( $course_ids ) );
}
/**
* Retrieve a list of posts associated with the membership
*
* An associated post is:
* + A post, page, or custom post type which supports `llms-membership-restrictions` and has restrictions enabled to this membership
* + A course that exists in the memberships list of auto-enroll courses
* + A course that has at least one access plan with members-only availability linked to this membership
*
* @since 3.38.1
* @since 4.15.0 Minor restructuring to only query post type data when it's needed.
*
* @param string $post_type If supplied, returns only associations of this post type, otherwise returns an associative array of all associations.
* @return array[]|int[] An array of arrays of post IDs. The array keys are the post type and the array values are arrays of integers.
* If `$post_type` is supplied returns an array of associated post ids as integers.
*/
public function get_associated_posts( $post_type = null ) {
// If we're querying only posts, we can skip these associations entirely because courses don't support them.
$post_types = 'course' !== $post_type ? get_post_types_by_support( 'llms-membership-restrictions' ) : array();
// If we're looking at a single post type we only have to query associations for that post type.
$post_types = $post_type ? array_intersect( $post_types, array( $post_type ) ) : $post_types;
// Our return array.
$posts = array();
// Retrieve all posts that are restricted to a membership via a LifterLMS Membership Restriction setting.
foreach ( $post_types as $type ) {
$posts[ $type ] = $this->query_associated_posts( $type, '_llms_is_restricted', 'yes', '_llms_restricted_levels' );
}
// Include courses if courses were requested or if no specific post type was requested.
if ( ! $post_type || 'course' === $post_type ) {
$posts['course'] = $this->query_associated_courses();
}
/**
* Filter the list of posts associated with the membership.
*
* @since 3.38.1
*
* @param array[] $posts An array of arrays of post IDs. The array keys are the post type and the array values are arrays of integers.
* @param string|null $post_type The requested post type if only a specific post type was requested, otherwise `null` to indicate all associated post types.
* @param LLMS_Membership $this Membership object.
*/
$posts = apply_filters( 'llms_membership_get_associated_posts', $posts, $post_type, $this );
// If a single post type was requested, return only that.
if ( $post_type ) {
// Return the request post type array and fallback to an empty array if that post type doesn't exist.
return isset( $posts[ $post_type ] ) ? $posts[ $post_type ] : array();
}
// Remove empty arrays and return the rest.
return array_filter( $posts );
}
/**
* Get an array of the auto enrollment course ids
*
* Uses a custom function due to the default "get_array" returning an array with an empty string
*
* @since 3.0.0
* @since 4.15.0 Exclude unpublished courses from the return array.
*
* @return array
*/
public function get_auto_enroll_courses() {
// Ensure an array when metadata is not set.
$courses = isset( $this->auto_enroll ) ? $this->get( 'auto_enroll' ) : array();
// Exclude unpublished courses.
$courses = array_values(
array_filter(
$courses,
function( $id ) {
return 'publish' === get_post_status( $id );
}
)
);
/**
* Filters the list of the membership's auto enroll courses
*
* @since 3.0.0
*
* @param int[] $courses List of LLMS_Course IDs.
* @param LLMS_Membership $membership Membership post object.
*/
return apply_filters( 'llms_membership_get_auto_enroll_courses', $courses, $this );
}
/**
* Retrieve membership categories.
*
* @since 3.36.3
*
* @param array $args Array of args passed to `wp_get_post_terms()`.
* @return array
*/
public function get_categories( $args = array() ) {
return wp_get_post_terms( $this->get( 'id' ), 'membership_cat', $args );
}
/**
* Retrieve course instructor information
*
* @since 3.13.0
*
* @param boolean $exclude_hidden If true, excludes hidden instructors from the return array.
* @return array
*/
public function get_instructors( $exclude_hidden = false ) {
return apply_filters(
'llms_membership_get_instructors',
$this->instructors()->get_instructors( $exclude_hidden ),
$this,
$exclude_hidden
);
}
/**
* Retrieve an instance of the LLMS_Product for this course
*
* @since 3.3.0
* @return LLMS_Product
*/
public function get_product() {
return new LLMS_Product( $this->get( 'id' ) );
}
/**
* Retrieve the number of enrolled students in the membership.
*
* @since 3.32.0
* @since 6.0.0 Don't access `LLMS_Student_Query` properties directly.
*
* @return int
*/
public function get_student_count() {
$query = new LLMS_Student_Query(
array(
'post_id' => $this->get( 'id' ),
'statuses' => array( 'enrolled' ),
'per_page' => 1,
)
);
return $query->get_found_results();
}
/**
* Get an array of student IDs based on enrollment status in the membership
*
* @since 3.0.0
*
* @param string|string[] $statuses Optional. List of enrollment statuses to query by status query is an OR relationship. Default is 'enrolled'.
* @param int $limit Optional. Number of results. Default is `50`.
* @param int $skip Optional. Number of results to skip (for pagination). Default is `0`.
* @return array
*/
public function get_students( $statuses = 'enrolled', $limit = 50, $skip = 0 ) {
return llms_get_enrolled_students( $this->get( 'id' ), $statuses, $limit, $skip );
}
/**
* Retrieve membership tags.
*
* @since 3.36.3
*
* @param array $args Array of args passed to `wp_get_post_terms()`.
* @return array
*/
public function get_tags( $args = array() ) {
return wp_get_post_terms( $this->get( 'id' ), 'membership_tag', $args );
}
/**
* Retrieve an instance of the Post Instructors model
*
* @since 3.13.0
*
* @return LLMS_Post_Instructors
*/
public function instructors() {
return new LLMS_Post_Instructors( $this );
}
/**
* Retrieve courses associated with the membership
*
* @since 3.38.1
* @since 4.15.0 Exclude unpublished courses.
*
* @see LLMS_Membership::get_associated_posts()
*
* @return int[]
*/
protected function query_associated_courses() {
// Start with autoenroll courses.
$courses = $this->get_auto_enroll_courses();
// Retrieve all access plans with a members-only availability restriction for this membership.
foreach ( $this->query_associated_posts( 'llms_access_plan', '_llms_availability', 'members', '_llms_availability_restrictions' ) as $plan_id ) {
$plan = llms_get_post( $plan_id );
if ( $plan ) {
$id = $plan->get( 'product_id' );
if ( 'publish' === get_post_status( $id ) ) {
$courses[] = $id;
}
}
}
return array_unique( $courses );
}
/**
* Performs a WPDB query to retrieve posts associated with the membership
*
* @since 3.38.1
* @since 4.0.0 Escape `{` character in SQL query to add MySQL 8.0 support.
*
* @see LLMS_Membesrhip::get_associated_posts()
*
* @param string $post_type Post type to query for an association with.
* @param string $enabled_key A meta key name, used to check if the association is enabled for the associated post. For example: "_llms_is_restricted"
* @param string $enabled_value The meta value of the `$enabled_key` when the association is enabled. For example "yes" when checking "_llms_is_restricted"..
* @param string $list_key The meta key name where associations are stored as a serialized array of WP_Post IDs. For example "_llms_restricted_levels".
* @return int[]
*/
protected function query_associated_posts( $post_type, $enabled_key, $enabled_value, $list_key ) {
global $wpdb;
// See if we have a cached result first.
$cache = sprintf( 'membership_%1$d_associated_%2$s', $this->get( 'id' ), $post_type );
$found = null;
$ids = wp_cache_get( $cache, '', false, $found );
// We don't, perform a query.
if ( ! $found ) {
$ids = $wpdb->get_col( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->prepare(
"SELECT metas.post_id
FROM {$wpdb->postmeta} AS metas
JOIN {$wpdb->postmeta} AS metas2 ON metas2.post_id = metas.post_id
JOIN {$wpdb->posts} AS posts ON posts.ID = metas.post_id
WHERE 1
AND posts.post_status = 'publish'
AND posts.post_type = %s
AND metas2.meta_key = %s
AND metas2.meta_value = %s
AND metas.meta_key = %s
AND metas.meta_value REGEXP %s;",
$post_type,
$enabled_key,
$enabled_value,
$list_key,
'a:[0-9][0-9]*:\{(i:[0-9][0-9]*;(i|s:[0-9][0-9]*):"?[0-9][0-9]*"?;)*(i:[0-9][0-9]*;(i|s:[0-9][0-9]*):"?' . $this->get( 'id' ) . '"?;)'
)
);
// Only return ints.
$ids = array_map( 'absint', $ids );
// Cache the result.
wp_cache_set( $cache, $ids );
}
return $ids;
}
/**
* Remove a course from auto enrollment
*
* @since 3.0.0
*
* @param int $course_id WP_Post ID of the course.
* @return bool
*/
public function remove_auto_enroll_course( $course_id ) {
return $this->set( 'auto_enroll', array_diff( $this->get_auto_enroll_courses(), array( $course_id ) ) );
}
/**
* Save instructor information
*
* @since 3.13.0
*
* @param array $instructors Array of course instructor information.
* @return array
*/
public function set_instructors( $instructors = array() ) {
return $this->instructors()->set_instructors( $instructors );
}
/**
* Add data to the membership model when converted to array.
*
* Called before data is sorted and returned by `$this->jsonSerialize()`.
*
* @since 3.36.3
*
* @param array $arr Data to be serialized.
* @return array
*/
public function toArrayAfter( $arr ) {
$arr['categories'] = $this->get_categories(
array(
'fields' => 'names',
)
);
$arr['tags'] = $this->get_tags(
array(
'fields' => 'names',
)
);
return $arr;
}
}