├── classes └── class.pmproseries.php ├── css ├── pmpro-series-admin.css ├── pmpro_series.css └── select2.css ├── email └── new_content.html ├── js ├── pmpro-series.js └── select2.js ├── languages ├── pmpro-series-en_EN.mo ├── pmpro-series-en_EN.po ├── pmpro-series-fr_FR.mo ├── pmpro-series-fr_FR.po ├── pmpro-series.mo ├── pmpro-series.po └── pmpro-series.pot ├── pmpro-series.php ├── readme.txt └── scheduled └── crons.php /classes/class.pmproseries.php: -------------------------------------------------------------------------------- 1 | getSeriesByID( $id ); 42 | } else { 43 | return true; 44 | } 45 | } 46 | 47 | /** 48 | * [getSeriesByID] Populate series data by post id passed. 49 | * 50 | * @param integer $id 51 | * @return integer 52 | */ 53 | function getSeriesByID( $id ) { 54 | $this->post = get_post( $id ); 55 | if ( ! empty( $this->post->ID ) ) { 56 | $this->id = $id; 57 | } else { 58 | $this->id = false; 59 | } 60 | 61 | return $this->id; 62 | } 63 | 64 | /** 65 | * [addPost] Add a post to this series. 66 | * 67 | * @param integer $post_id 68 | * @param integer $delay 69 | */ 70 | function addPost( $post_id, $delay ) { 71 | if ( empty( $post_id ) || ! isset( $delay ) ) { 72 | $this->error = esc_html__( 'Please enter a value for post and delay.', 'pmpro-series' ); 73 | return false; 74 | } 75 | 76 | $post = get_post( $post_id ); 77 | 78 | if ( empty( $post->ID ) ) { 79 | $this->error = esc_html__( 'A post with that id does not exist.', 'pmpro-series' ); 80 | return false; 81 | } 82 | 83 | $this->getPosts(); 84 | 85 | if ( empty( $this->posts ) ) { 86 | $this->posts = array(); 87 | } 88 | 89 | // remove any old post with this id 90 | if ( $this->hasPost( $post_id ) ) { 91 | $this->removePost( $post_id ); 92 | } 93 | 94 | // add post 95 | $temp = new stdClass(); 96 | $temp->id = $post_id; 97 | $temp->delay = $delay; 98 | $this->posts[] = $temp; 99 | 100 | // sort 101 | usort( $this->posts, array( 'PMProSeries', 'sortByDelay' ) ); 102 | 103 | // save 104 | update_post_meta( $this->id, '_series_posts', $this->posts ); 105 | 106 | // add series to post 107 | $post_series = get_post_meta( $post_id, '_post_series', true ); 108 | if ( ! is_array( $post_series ) ) { 109 | $post_series = array( $this->id ); 110 | } else { 111 | $post_series[] = $this->id; 112 | } 113 | 114 | // save 115 | update_post_meta( $post_id, '_post_series', $post_series ); 116 | } 117 | 118 | /** 119 | * [removePost] Remove a post from this series. 120 | * 121 | * @param integer $post_id 122 | * @param integer $delay 123 | */ 124 | function removePost( $post_id ) { 125 | if ( empty( $post_id ) ) { 126 | return false; 127 | } 128 | 129 | $this->getPosts(); 130 | 131 | if ( empty( $this->posts ) ) { 132 | return true; 133 | } 134 | 135 | // remove this post from the series 136 | foreach ( $this->posts as $i => $post ) { 137 | if ( $post->id == $post_id ) { 138 | unset( $this->posts[ $i ] ); 139 | $this->posts = array_values( $this->posts ); 140 | update_post_meta( $this->id, '_series_posts', $this->posts ); 141 | break; // assume there is only one 142 | } 143 | } 144 | 145 | // remove this series from the post 146 | $post_series = get_post_meta( $post_id, '_post_series', true ); 147 | if ( is_array( $post_series ) && ( $key = array_search( $this->id, $post_series ) ) !== false ) { 148 | unset( $post_series[ $key ] ); 149 | update_post_meta( $post_id, '_post_series', $post_series ); 150 | } 151 | 152 | return true; 153 | } 154 | 155 | /** 156 | * [getPosts] Get array of all posts in this series. 157 | * force = ignore cache and get data from DB. 158 | * 159 | * @param boolean $force update from database. 160 | * @param null|string $status of posts to return. 161 | * @return array of posts with status, if applicable. $this->posts will not be filtered by $status. 162 | */ 163 | function getPosts( $force = false, $status = null ) { 164 | if ( ! isset( $this->posts ) || $force ) { 165 | $this->posts = get_post_meta( $this->id, '_series_posts', true ); 166 | if ( ! is_array( $this->posts ) ) { 167 | $this->posts = array(); 168 | } 169 | } 170 | 171 | if ( ! empty( $status ) ) { 172 | $posts_with_status = array(); 173 | foreach ($this->posts as $key => $post) { 174 | if ( $status === get_post_status( $post->id ) ) { 175 | $posts_with_status[] = $post; 176 | } 177 | } 178 | return $posts_with_status; 179 | } 180 | return $this->posts; 181 | } 182 | 183 | /** 184 | * [hasPost] Does this series include post with id = post_id. 185 | * 186 | * @param [type] $post_id 187 | * @return boolean 188 | */ 189 | function hasPost( $post_id ) { 190 | $this->getPosts(); 191 | 192 | if ( empty( $this->posts ) ) { 193 | return false; 194 | } 195 | 196 | foreach ( $this->posts as $key => $post ) { 197 | if ( $post->id == $post_id ) { 198 | return true; 199 | } 200 | } 201 | 202 | return false; 203 | } 204 | 205 | /** 206 | * [getPostKey] Get key of post with id = $post_id. 207 | * 208 | * @param [type] $post_id 209 | * @return [type] 210 | */ 211 | function getPostKey( $post_id ) { 212 | $this->getPosts(); 213 | 214 | if ( empty( $this->posts ) ) { 215 | return false; 216 | } 217 | 218 | foreach ( $this->posts as $key => $post ) { 219 | if ( $post->id == $post_id ) { 220 | return $key; 221 | } 222 | } 223 | 224 | return false; 225 | } 226 | 227 | /** 228 | * [getDelayForPost] 229 | * 230 | * @param [type] $post_id 231 | * @return [type] 232 | */ 233 | function getDelayForPost( $post_id ) { 234 | $key = $this->getPostKey( $post_id ); 235 | 236 | if ( $key === false ) { 237 | return false; 238 | } else { 239 | return $this->posts[ $key ]->delay; 240 | } 241 | } 242 | 243 | /** 244 | * [getLongestPostDelay] 245 | * 246 | * @param null|string $status of posts to consider. 247 | * @return int longest post delay. 248 | */ 249 | function getLongestPostDelay( $status = null ) { 250 | $posts = $this->getPosts( false, $status ); 251 | $delay = 0; 252 | foreach ( $posts as $post ) { 253 | if ( ! empty( $post->delay ) && $post->delay > $delay ) { 254 | $delay = $post->delay; 255 | } 256 | } 257 | return $delay; 258 | } 259 | 260 | /** 261 | * [sortByDelay] Sort posts by delay. 262 | * 263 | * @param [type] $a 264 | * @param [type] $b 265 | * @return [type] 266 | */ 267 | function sortByDelay( $a, $b ) { 268 | if ( $a->delay == $b->delay ) { 269 | return 0; 270 | } 271 | return ( $a->delay < $b->delay ) ? -1 : 1; 272 | } 273 | 274 | /** 275 | * [sendEmail] Send an email RE new access to post_id to email of user_id. 276 | * 277 | * @param [type] $post_ids 278 | * @param [type] $user_id 279 | * @return [type] 280 | */ 281 | function sendEmail( $post_ids, $user_id ) { 282 | if ( ! class_exists( 'PMProEmail' ) ) { 283 | return; 284 | } 285 | 286 | $email = new PMProEmail(); 287 | 288 | $user = get_user_by( 'id', $user_id ); 289 | 290 | // build list of posts 291 | $post_list = "
' . esc_html__( 'An excerpt of the post is below.', 'pmpro-series' ) . '
' . esc_html( $post->post_excerpt ) . '
'; 326 | } else { 327 | $email->data['excerpt'] = ''; 328 | } 329 | 330 | $email->sendEmail(); 331 | } 332 | 333 | /** 334 | * [createCPT] Create the Custom Post Type for Series. 335 | * 336 | * @return [type] 337 | */ 338 | static function createCPT() { 339 | // don't want to do this when deactivating 340 | global $pmpros_deactivating; 341 | if ( ! empty( $pmpros_deactivating ) ) { 342 | return false; 343 | } 344 | 345 | $labels = apply_filters( 346 | 'pmpros_series_labels', 347 | array( 348 | 'name' => esc_html__( 'Series', 'pmpro-series' ), 349 | 'singular_name' => esc_html__( 'Series', 'pmpro-series' ), 350 | 'slug' => 'series', 351 | 'add_new' => esc_html__( 'New Series', 'pmpro-series' ), 352 | 'add_new_item' => esc_html__( 'New Series', 'pmpro-series' ), 353 | 'edit' => esc_html__( 'Edit Series', 'pmpro-series' ), 354 | 'edit_item' => esc_html__( 'Edit Series', 'pmpro-series' ), 355 | 'new_item' => esc_html__( 'Add New', 'pmpro-series' ), 356 | 'view' => esc_html__( 'View This Series', 'pmpro-series' ), 357 | 'view_item' => esc_html__( 'View This Series', 'pmpro-series' ), 358 | 'search_items' => esc_html__( 'Search Series', 'pmpro-series' ), 359 | 'not_found' => esc_html__( 'No Series Found', 'pmpro-series' ), 360 | 'not_found_in_trash' => esc_html__( 'No Series Found In Trash', 'pmpro-series' ), 361 | ) 362 | ); 363 | 364 | register_post_type( 365 | 'pmpro_series', 366 | apply_filters( 367 | 'pmpros_series_registration', 368 | array( 369 | 'labels' => $labels, 370 | 'public' => true, 371 | 'menu_icon' => 'dashicons-clock', 372 | 'show_ui' => true, 373 | 'show_in_menu' => true, 374 | 'show_in_rest' => true, 375 | 'publicly_queryable' => true, 376 | 'hierarchical' => true, 377 | 'supports' => array( 'title', 'editor', 'thumbnail', 'custom-fields', 'author' ), 378 | 'can_export' => true, 379 | 'show_in_nav_menus' => true, 380 | 'rewrite' => array( 381 | 'slug' => 'series', 382 | 'with_front' => false, 383 | ), 384 | 'has_archive' => 'series', 385 | ) 386 | ) 387 | ); 388 | } 389 | 390 | /** 391 | * [checkForMetaBoxes] Meta boxes 392 | * 393 | * @return [type] 394 | */ 395 | static function checkForMetaBoxes() { 396 | // add meta boxes 397 | if ( is_admin() ) { 398 | add_action( 'admin_menu', array( 'PMProSeries', 'defineMetaBoxes' ) ); 399 | } 400 | } 401 | 402 | /** 403 | * [defineMetaBoxes] Meta boxes 404 | * 405 | * @return [type] 406 | */ 407 | static function defineMetaBoxes() { 408 | // Add "Require Membership" meta box if Paid Memberships Pro is active. 409 | if ( defined( 'PMPRO_VERSION' ) ) { 410 | add_meta_box( 'pmpro_page_meta', esc_html__( 'Require Membership', 'pmpro-series' ), 'pmpro_page_meta', 'pmpro_series', 'side' ); 411 | } 412 | // series meta box 413 | add_meta_box( 'pmpro_series_meta', esc_html__( 'Manage Series', 'pmpro-series'), array( 'PMProSeries', 'seriesMetaBox' ), 'pmpro_series', 'normal' ); 414 | } 415 | 416 | /** 417 | * [seriesMetaBox] This is the actual series meta box. 418 | * 419 | * @return [type] 420 | */ 421 | static function seriesMetaBox() { 422 | global $post; 423 | $series = new PMProSeries( $post->ID ); 424 | ?> 425 |545 | | 546 | | 547 | | 548 | 549 | 550 | posts ) ) { 554 | ?> 555 | posts as $post ) { 558 | ?> 559 | |
---|---|---|---|
. | 561 |id ); ?> | 562 |delay; ?> | 563 |564 | 565 | 566 | | 567 |
580 | | 581 | | 582 | |
---|---|---|
587 | 607 | | 608 |609 | | 610 | |
New content is available at !!sitename!!.
2 | 3 | !!post_list!! -------------------------------------------------------------------------------- /js/pmpro-series.js: -------------------------------------------------------------------------------- 1 | 2 | function pmpros_editPost(post_id, delay){ 3 | jQuery('#pmpros_post').val(post_id).trigger("change"); 4 | jQuery('#pmpros_delay').val(delay); 5 | jQuery('#pmpros_save').html('Save'); 6 | location.href = "#pmpros_edit_post"; 7 | } 8 | 9 | function pmpros_removePost(post_id) { 10 | var seriesid = jQuery('#post_ID').val(); 11 | jQuery.ajax({ 12 | url: ajaxurl, 13 | type:'GET', 14 | timeout:2000, 15 | dataType: 'html', 16 | data: "pmpros_add_post=1&pmpros_series=" + seriesid + "&pmpros_remove="+post_id, 17 | error: function(xml){ 18 | alert('Error removing series post [1]'); 19 | //enable save button 20 | jQuery('#pmpros_save').removeAttr('disabled'); 21 | }, 22 | success: function(responseHTML){ 23 | if (responseHTML == 'error'){ 24 | alert('Error removing series post [2]'); 25 | //enable save button 26 | jQuery('#pmpros_save').removeAttr('disabled'); 27 | }else{ 28 | jQuery('#pmpros_series_posts').html(responseHTML); 29 | pmpros_Setup(); 30 | } 31 | } 32 | }); 33 | } 34 | 35 | function pmpros_updatePost() { 36 | var seriesid = jQuery('#post_ID').val(); 37 | jQuery.ajax({ 38 | url: ajaxurl, 39 | type: 'GET', 40 | dataType: 'html', 41 | data: "pmpros_add_post=1&pmpros_series=" + seriesid + "&pmpros_post=" + jQuery('#pmpros_post').val() + '&pmpros_delay=' + jQuery('#pmpros_delay').val(), 42 | error: function(xml){ 43 | alert('Error saving series post [1]'); 44 | //enable save button 45 | jQuery('#pmpros_save').html('Save'); 46 | }, 47 | success: function(responseHTML){ 48 | if (responseHTML == 'error'){ 49 | alert('Error saving series post [2]'); 50 | //enable save button 51 | jQuery('#pmpros_save').html('Save'); 52 | }else{ 53 | jQuery('#pmpros_series_posts').html(responseHTML); 54 | pmpros_Setup(); 55 | } 56 | } 57 | }); 58 | } 59 | 60 | function pmpros_Setup() { 61 | jQuery('#pmpros_post').select2({width: 'elements'}); 62 | 63 | jQuery('#pmpros_save').click(function() { 64 | if(jQuery(this).html() != 'Saving...'){ 65 | pmpros_updatePost(); 66 | } 67 | }); 68 | } 69 | 70 | jQuery(document).ready(function(jQuery) { 71 | pmpros_Setup(); 72 | }); 73 | -------------------------------------------------------------------------------- /languages/pmpro-series-en_EN.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strangerstudios/pmpro-series/a28e2eaa1b9fbda8ce5d97830e6c60b51fbc929c/languages/pmpro-series-en_EN.mo -------------------------------------------------------------------------------- /languages/pmpro-series-en_EN.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 Paid Memberships Pro 2 | # This file is distributed under the same license as the Paid Memberships Pro - Series plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Paid Memberships Pro - Series 0.7\n" 6 | "Report-Msgid-Bugs-To: info@paidmembershipspro.com\n" 7 | "Last-Translator: Paid Memberships Pro' . sprintf( esc_html__( 'This content is part of the %1$s series. You will gain access on %2$s.', 'pmpro-series' ), $series_link_text, esc_html( $series_date_text ) ) . '
'; 394 | 395 | $text = apply_filters( 'pmpros_days_left_message', $text, $member_days, $days_left, $current_user->ID ); 396 | } else { 397 | // user has to sign up for one of the series 398 | if ( count( $post_series ) == 1 ) { 399 | $series_link_text = '' . esc_html( get_the_title( $post_series[0] ) ) . ''; 400 | $text = '' . sprintf( esc_html__( 'This content is part of the %s series.', 'pmpro-series' ), $series_link_text ) . '
'; 401 | 402 | $text = apply_filters( 'pmpros_content_access_message_single_item', $text, $post_series ); 403 | } else { 404 | $series = array(); 405 | foreach ( $post_series as $series_id ) { 406 | $series[] = "" . esc_html( get_the_title( $series_id ) ) . ''; 407 | } 408 | $series_list_text = implode( ', ', $series ) . '.'; 409 | 410 | $text = '' . sprintf( esc_html__( 'This content is part of the following series: %s', 'pmpro-series' ), $series_list_text ) . '
'; 411 | 412 | $text = apply_filters( 'pmpros_content_access_message_many_items', $text, $post_series ); 413 | } 414 | } 415 | } 416 | } 417 | 418 | return $text; 419 | } 420 | add_filter( 'pmpro_no_access_message_body', 'pmpros_pmpro_text_filter' ); // PMPro v3.1+. 421 | add_filter( 'pmpro_non_member_text_filter', 'pmpros_pmpro_text_filter' ); // Pre-PMPro v3.1. 422 | add_filter( 'pmpro_not_logged_in_text_filter', 'pmpros_pmpro_text_filter' ); // Pre-PMPro v3.1. 423 | 424 | /* 425 | We need to flush rewrite rules on activation/etc for the CPTs. 426 | Register/unregister crons on activation/deactivation. 427 | */ 428 | /** 429 | * [pmpros_activation description] 430 | * 431 | * @return [type] 432 | */ 433 | function pmpros_activation() { 434 | // flush rewrite rules 435 | PMProSeries::createCPT(); 436 | flush_rewrite_rules(); 437 | 438 | // setup cron 439 | wp_schedule_event( current_time( 'timestamp' ), 'daily', 'pmpros_check_for_new_content' ); 440 | } 441 | register_activation_hook( __FILE__, 'pmpros_activation' ); 442 | function pmpros_deactivation() { 443 | // flush rewrite rules 444 | global $pmpros_deactivating; 445 | $pmpros_deactivating = true; 446 | flush_rewrite_rules(); 447 | 448 | // remove cron 449 | wp_clear_scheduled_hook( 'pmpros_check_for_new_content' ); 450 | } 451 | register_deactivation_hook( __FILE__, 'pmpros_deactivation' ); 452 | 453 | /* 454 | Add series post links to account page 455 | */ 456 | function pmpros_member_links_bottom() { 457 | global $wpdb, $current_user; 458 | 459 | // get all series 460 | $all_series = $wpdb->get_results( 461 | " 462 | SELECT * 463 | FROM $wpdb->posts 464 | WHERE post_type = 'pmpro_series' 465 | " 466 | ); 467 | 468 | if ( empty( $all_series ) ) { 469 | return; 470 | } 471 | 472 | // Build our list of series posts to show. 473 | $series_posts_list = array(); 474 | 475 | foreach ( $all_series as $s ) { 476 | $series = new PMProSeries( $s->ID ); 477 | $series_posts = $series->getPosts(); 478 | 479 | if ( ! empty( $series_posts ) ) { 480 | foreach ( $series_posts as $series_post ) { 481 | if ( pmpros_hasAccess( $current_user->ID, $series_post->id ) ) { 482 | $series_posts_list[$series_post->id] = array( 483 | 'title' => get_the_title( $series_post->id ), 484 | 'permalink' => get_permalink( $series_post->id ), 485 | ); 486 | } 487 | } 488 | } 489 | } 490 | 491 | if ( empty( $series_posts_list ) ) { 492 | return; 493 | } 494 | 495 | // Remove duplicate array keys. 496 | $series_posts_list = array_values( $series_posts_list ); 497 | 498 | // Show the list. 499 | foreach ( $series_posts_list as $series_post ) { 500 | ?> 501 |