'render_form',
'fields/title' => 'render_title',
'fields/content' => 'render_content',
'fields/taxonomy' => 'render_taxonomy',
'fields/tags' => 'render_tags',
'fields/image' => 'render_image',
'fields/submit' => 'render_submit',
'fields/credit-notice' => 'render_credit_notice',
'dashboard' => 'render_dashboard',
'dashboard-parts/my-plans' => 'render_my_plans',
'dashboard-parts/my-posts' => 'render_my_posts',
'dashboard-parts/new-post' => 'render_new_post',
'plans' => 'render_plans',
'plan/name' => 'render_plan_name',
'plan/price' => 'render_plan_price',
'plan/description' => 'render_plan_description',
'plan/features' => 'render_plan_features',
'plan/buy' => 'render_plan_buy',
);
/**
* Blocks that decide for themselves whether and how often their inner
* blocks are rendered.
*/
const RENDERS_INNER_BLOCKS = array( 'dashboard', 'plans' );
/**
* The post being edited, null when there is none, false until looked up.
*
* @var \WP_Post|null|false
*/
private static $editing = false;
public static function init() {
add_action( 'init', array( __CLASS__, 'register' ) );
add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'editor_data' ) );
}
public static function register() {
wp_register_style( 'postfee', POSTFEE_URL . 'assets/css/postfee.css', array(), POSTFEE_VERSION );
wp_register_script( 'postfee-my-posts', POSTFEE_URL . 'assets/js/my-posts.js', array(), POSTFEE_VERSION, array( 'strategy' => 'defer' ) );
foreach ( self::BLOCKS as $dir => $method ) {
if ( file_exists( POSTFEE_DIR . 'build/' . $dir . '/block.json' ) ) {
register_block_type(
POSTFEE_DIR . 'build/' . $dir,
array(
'render_callback' => array( __CLASS__, $method ),
'skip_inner_blocks' => in_array( $dir, self::RENDERS_INNER_BLOCKS, true ),
)
);
}
}
}
/**
* Tells the editor which content can be submitted and which fields apply to it.
*/
public static function editor_data() {
$post_types = array();
foreach ( postfee_post_types() as $post_type ) {
$taxonomies = array();
foreach ( get_object_taxonomies( $post_type, 'objects' ) as $taxonomy ) {
if ( $taxonomy->public && $taxonomy->hierarchical ) {
$taxonomies[] = array(
'value' => $taxonomy->name,
'label' => $taxonomy->labels->singular_name,
);
}
}
$post_types[] = array(
'value' => $post_type,
'label' => get_post_type_object( $post_type )->labels->name,
'taxonomies' => $taxonomies,
'tags' => is_object_in_taxonomy( $post_type, 'post_tag' ),
'image' => post_type_supports( $post_type, 'thumbnail' ),
);
}
wp_add_inline_script( 'wp-blocks', 'window.postfeeBlocks = ' . wp_json_encode( array( 'postTypes' => $post_types ) ) . ';', 'before' );
}
/**
* Markup of a block holding its default inner blocks.
*/
private static function markup( $name, $inner ) {
$content = '';
foreach ( $inner as $inner_name ) {
$content .= get_comment_delimited_block_content( 'postfee/' . $inner_name, array(), '' ) . "\n";
}
return get_comment_delimited_block_content( 'postfee/' . $name, array(), "\n" . $content );
}
/**
* Markup of a form with every field, as inserted on the submit page.
*/
public static function default_form() {
return self::markup( 'form', array( 'credit-notice', 'field-title', 'field-content', 'field-taxonomy', 'field-tags', 'field-image', 'submit' ) );
}
/**
* Markup of a dashboard with every section, as inserted on the "My Posts" page.
*/
public static function default_dashboard() {
return self::markup( 'dashboard', array( 'my-plans', 'my-posts', 'new-post-button' ) );
}
private static function editing() {
if ( false === self::$editing ) {
self::$editing = empty( $_GET['postfee_edit'] ) ? null : Checkout::editable_post( absint( $_GET['postfee_edit'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
}
return self::$editing;
}
/**
* What to show in place of the form, or '' when the fields are shown.
*/
private static function view( $post_type ) {
if ( ! is_user_logged_in() ) {
return 'login';
}
if ( ! empty( $_GET['postfee_pay'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
return 'pay';
}
if ( ! empty( $_GET['postfee_edit'] ) && ! self::editing() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
return 'no_edit';
}
if ( ! in_array( self::editing() ? self::editing()->post_type : $post_type, postfee_post_types(), true ) ) {
return 'no_type';
}
return '';
}
/**
* The post type a field is being shown for, or '' when fields are not shown.
*/
private static function field_post_type( $block ) {
$post_type = isset( $block->context['postfee/postType'] ) ? sanitize_key( $block->context['postfee/postType'] ) : 'post';
if ( '' !== self::view( $post_type ) ) {
return '';
}
return self::editing() ? self::editing()->post_type : $post_type;
}
/**
* After a failed submit, what the user typed rather than the saved post.
* Only put back into the form it came from, and sanitized by each field.
*/
private static function posted( $key ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$posted = isset( $_POST['postfee_action'] ) && 'submit_post' === $_POST['postfee_action'];
return $posted && isset( $_POST[ $key ] ) && is_string( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : null;
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
/**
* Wraps a control in the block wrapper with its label and help text.
* $control must already be escaped.
*/
private static function field( $attributes, $id, $default_label, $control ) {
$label = isset( $attributes['label'] ) && '' !== $attributes['label'] ? $attributes['label'] : $default_label;
$help = empty( $attributes['help'] ) ? '' : '
' . esc_html( $attributes['help'] ) . '
';
return sprintf(
'%s %s%s
',
get_block_wrapper_attributes( array( 'class' => 'postfee-field' ) ),
esc_attr( $id ),
wp_kses_post( $label ),
$control,
$help
);
}
public static function render_form( $attributes, $content ) {
$post_type = sanitize_key( $attributes['postType'] );
$view = self::view( $post_type );
$post = self::editing();
ob_start();
if ( '' === $view ) {
printf( '';
return ob_get_clean();
}
printf( '', get_block_wrapper_attributes( array( 'class' => 'postfee' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by core.
Frontend::notices();
if ( 'login' === $view ) {
Frontend::login_prompt();
} elseif ( 'pay' === $view ) {
Frontend::pay_step( Checkout::payable_post( isset( $_GET['postfee_pay'] ) ? absint( $_GET['postfee_pay'] ) : 0 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
} elseif ( 'no_edit' === $view ) {
echo '
' . esc_html__( 'You cannot edit this post.', 'postfee' ) . '
';
} else {
echo '
' . esc_html__( 'This type of content cannot be submitted.', 'postfee' ) . '
';
}
echo '
';
return ob_get_clean();
}
public static function render_title( $attributes, $content, $block ) {
if ( ! self::field_post_type( $block ) ) {
return '';
}
$post = self::editing();
$title = self::posted( 'postfee_title' );
$title = null !== $title ? sanitize_text_field( $title ) : ( $post ? $post->post_title : '' );
$id = wp_unique_id( 'postfee_title_' );
return self::field(
$attributes,
$id,
__( 'Title', 'postfee' ),
sprintf(
' ',
esc_attr( $id ),
esc_attr( $title ),
esc_attr( $attributes['placeholder'] )
)
);
}
public static function render_content( $attributes, $content, $block ) {
if ( ! self::field_post_type( $block ) ) {
return '';
}
$post = self::editing();
$value = self::posted( 'postfee_content' );
$value = null !== $value ? wp_kses_post( $value ) : ( $post ? $post->post_content : '' );
$id = wp_unique_id( 'postfee_content_' );
$rows = max( 3, min( 30, (int) $attributes['rows'] ) );
if ( $attributes['richText'] ) {
ob_start();
wp_editor(
$value,
$id,
array(
'textarea_name' => 'postfee_content',
'media_buttons' => false,
'teeny' => true,
'textarea_rows' => $rows,
'quicktags' => false,
)
);
$control = ob_get_clean();
} else {
$control = sprintf( '', esc_attr( $id ), $rows, esc_textarea( $value ) );
}
return self::field( $attributes, $id, __( 'Content', 'postfee' ), $control );
}
public static function render_taxonomy( $attributes, $content, $block ) {
$post_type = self::field_post_type( $block );
if ( ! $post_type ) {
return '';
}
$taxonomy = sanitize_key( $attributes['taxonomy'] );
$taxonomies = get_object_taxonomies( $post_type, 'objects' );
if ( ! isset( $taxonomies[ $taxonomy ] ) || ! $taxonomies[ $taxonomy ]->public || ! $taxonomies[ $taxonomy ]->hierarchical ) {
return '';
}
$post = self::editing();
$term_ids = $post ? wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ) : array();
$id = wp_unique_id( 'postfee_term_' );
return self::field(
$attributes,
$id,
$taxonomies[ $taxonomy ]->labels->singular_name,
wp_dropdown_categories(
array(
'taxonomy' => $taxonomy,
'name' => 'postfee_terms[' . $taxonomy . ']',
'id' => $id,
'hide_empty' => false,
'hierarchical' => true,
'selected' => $term_ids && ! is_wp_error( $term_ids ) ? (int) $term_ids[0] : 0,
'show_option_none' => '' !== $attributes['placeholder'] ? $attributes['placeholder'] : __( 'Select…', 'postfee' ),
'option_none_value' => 0,
'echo' => 0,
)
)
);
}
public static function render_tags( $attributes, $content, $block ) {
$post_type = self::field_post_type( $block );
if ( ! $post_type || ! is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
return '';
}
$post = self::editing();
$tags = $post ? implode( ', ', wp_get_object_terms( $post->ID, 'post_tag', array( 'fields' => 'names' ) ) ) : '';
$id = wp_unique_id( 'postfee_tags_' );
return self::field(
$attributes,
$id,
__( 'Tags', 'postfee' ),
sprintf(
' ',
esc_attr( $id ),
esc_attr( $tags ),
esc_attr( '' !== $attributes['placeholder'] ? $attributes['placeholder'] : __( 'Separate tags with commas', 'postfee' ) )
)
);
}
public static function render_image( $attributes, $content, $block ) {
$post_type = self::field_post_type( $block );
if ( ! $post_type || ! post_type_supports( $post_type, 'thumbnail' ) ) {
return '';
}
$post = self::editing();
$id = wp_unique_id( 'postfee_image_' );
$control = $post && has_post_thumbnail( $post ) ? get_the_post_thumbnail( $post, 'thumbnail', array( 'class' => 'postfee-current-image' ) ) : '';
return self::field(
$attributes,
$id,
__( 'Featured image', 'postfee' ),
$control . sprintf( ' ', esc_attr( $id ) )
);
}
public static function render_submit( $attributes, $content, $block ) {
if ( ! self::field_post_type( $block ) ) {
return '';
}
if ( self::editing() ) {
$text = '' !== $attributes['editText'] ? esc_html( $attributes['editText'] ) : esc_html__( 'Save changes', 'postfee' );
} else {
$text = '' !== $attributes['text'] ? wp_kses_post( $attributes['text'] ) : esc_html__( 'Continue', 'postfee' );
}
return sprintf( '%s ', get_block_wrapper_attributes( array( 'class' => 'postfee-button' ) ), $text );
}
public static function render_credit_notice( $attributes, $content, $block ) {
$post_type = self::field_post_type( $block );
if ( ! $post_type || self::editing() ) {
return '';
}
$entitlement = Entitlements::find_usable( get_current_user_id(), $post_type );
if ( ! $entitlement ) {
return '';
}
$remaining = Entitlements::remaining( $entitlement );
return sprintf(
'%s
',
get_block_wrapper_attributes( array( 'class' => 'postfee-notice postfee-notice-info' ) ),
esc_html(
null === $remaining
? __( 'Your plan covers this post.', 'postfee' )
/* translators: %d: number of posts */
: sprintf( _n( 'You have %d prepaid post left. This one will use it.', 'You have %d prepaid posts left. This one will use one of them.', $remaining, 'postfee' ), $remaining )
)
);
}
public static function render_dashboard( $attributes, $content, $block ) {
ob_start();
printf( '', get_block_wrapper_attributes( array( 'class' => 'postfee' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by core.
Frontend::notices();
if ( ! is_user_logged_in() ) {
Frontend::login_prompt();
} elseif ( ! empty( $_GET['postfee_upgrade'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
Frontend::upgrade_step( Checkout::upgradable_post( absint( $_GET['postfee_upgrade'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only chooses what is displayed.
} else {
foreach ( $block->inner_blocks as $inner_block ) {
echo $inner_block->render(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Rendered blocks.
}
}
echo '
';
return ob_get_clean();
}
/**
* Wraps one of the dashboard tables, which print nothing when empty.
* $args are passed on to the table after its heading.
*/
private static function table( $attributes, $method, $default_heading, ...$args ) {
if ( ! is_user_logged_in() ) {
return '';
}
ob_start();
Frontend::$method( '' !== $attributes['heading'] ? $attributes['heading'] : $default_heading, ...$args );
$table = ob_get_clean();
return $table ? sprintf( '%s
', get_block_wrapper_attributes(), $table ) : '';
}
public static function render_my_plans( $attributes ) {
return self::table( $attributes, 'entitlements_table', __( 'My plans', 'postfee' ) );
}
public static function render_my_posts( $attributes ) {
return self::table( $attributes, 'posts_table', __( 'My posts', 'postfee' ), max( 1, min( 100, (int) $attributes['perPage'] ) ) );
}
public static function render_new_post( $attributes ) {
return sprintf(
'%s ',
esc_url( postfee_page_url( 'submit' ) ),
get_block_wrapper_attributes( array( 'class' => 'postfee-button' ) ),
'' !== $attributes['text'] ? wp_kses_post( $attributes['text'] ) : esc_html__( 'Submit a new post', 'postfee' )
);
}
public static function render_plans( $attributes, $content, $block ) {
// Single posts and upgrades are always bought for a specific post.
$types = array_values( array_intersect( (array) $attributes['types'], array( 'package', 'subscription' ) ) );
$plans = $types ? Plans::all( $types ) : array();
ob_start();
printf( '', get_block_wrapper_attributes( array( 'class' => 'postfee' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by core.
Frontend::notices();
if ( ! $plans ) {
echo '
' . esc_html__( 'There are no plans available yet.', 'postfee' ) . '
';
} else {
echo '
';
foreach ( $plans as $plan ) {
printf( '
', esc_attr( $plan['type'] ) );
// The inner blocks are the layout of one plan: render them once per plan.
foreach ( $block->parsed_block['innerBlocks'] as $inner_block ) {
echo ( new \WP_Block( $inner_block, array( 'postfee/planId' => $plan['id'] ) ) )->render(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Rendered blocks.
}
echo '
';
}
echo '
';
}
echo '
';
return ob_get_clean();
}
/**
* The plan a plan block is being rendered for.
*/
private static function plan( $block ) {
return empty( $block->context['postfee/planId'] ) ? null : Plans::get( absint( $block->context['postfee/planId'] ) );
}
public static function render_plan_name( $attributes, $content, $block ) {
$plan = self::plan( $block );
if ( ! $plan ) {
return '';
}
$tag = 'h' . max( 2, min( 6, (int) $attributes['level'] ) );
return sprintf( '<%1$s %2$s>%3$s%1$s>', $tag, get_block_wrapper_attributes( array( 'class' => 'postfee-plan-name' ) ), esc_html( $plan['name'] ) );
}
public static function render_plan_price( $attributes, $content, $block ) {
$plan = self::plan( $block );
return $plan ? sprintf( '%s
', get_block_wrapper_attributes( array( 'class' => 'postfee-plan-price' ) ), esc_html( Plans::price_label( $plan ) ) ) : '';
}
public static function render_plan_description( $attributes, $content, $block ) {
$plan = self::plan( $block );
return $plan && $plan['description'] ? sprintf( '%s
', get_block_wrapper_attributes( array( 'class' => 'postfee-plan-description' ) ), esc_html( $plan['description'] ) ) : '';
}
public static function render_plan_features( $attributes, $content, $block ) {
$plan = self::plan( $block );
if ( ! $plan ) {
return '';
}
$items = '';
foreach ( Plans::summary( $plan ) as $line ) {
$items .= '' . esc_html( $line ) . ' ';
}
return sprintf( '', get_block_wrapper_attributes( array( 'class' => 'postfee-plan-includes' ) ), $items );
}
public static function render_plan_buy( $attributes, $content, $block ) {
$plan = self::plan( $block );
if ( ! $plan ) {
return '';
}
ob_start();
Frontend::plan_buy( $plan, 0, wp_kses_post( $attributes['text'] ), esc_html( $attributes['freeText'] ) );
return sprintf( '%s
', get_block_wrapper_attributes(), ob_get_clean() );
}
}