Members

Class for an access control to restrict to members only.

Description

Source

File: src/modules/access-controls/members.php

class Members extends Access_Control implements Assets_Submodule_Interface {

	/**
	 * Template tag handler for email notifications.
	 *
	 * @since 1.0.0
	 * @var Template_Tag_Handler
	 */
	protected $template_tag_handler;

	/**
	 * Template tag handler for email address fields.
	 *
	 * @since 1.0.0
	 * @var Template_Tag_Handler
	 */
	protected $template_tag_handler_email_only;

	/**
	 * Temporary storage for email from name.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	protected $from_name = '';

	/**
	 * Temporary storage for email from email.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	protected $from_email = '';

	/**
	 * Bootstraps the submodule by setting properties.
	 *
	 * @since 1.0.0
	 */
	protected function bootstrap() {
		$this->slug        = 'members';
		$this->title       = __( 'Members', 'torro-forms' );
		$this->description = __( 'Allows you to make this form available to logged-in members only.', 'torro-forms' );

		Field_Manager::register_field_type( 'autocompletewithbutton', Autocomplete_With_Button::class );

		$this->register_template_tag_handlers();

		$this->module->manager()->ajax()->register_action( 'invite_member', array( $this, 'ajax_invite_member' ) );
	}

	/**
	 * Determines whether the current user can access a specific form or submission.
	 *
	 * @since 1.0.0
	 *
	 * @param Form            $form       Form object.
	 * @param Submission|null $submission Submission object, or null if no submission is set.
	 * @return bool|WP_Error True if the form or submission can be accessed, false or error object otherwise.
	 */
	public function can_access( $form, $submission = null ) {
		if ( ! is_user_logged_in() ) {
			$message = $this->get_form_option( $form->id, 'login_required_message' );
			if ( empty( $message ) ) {
				$message = $this->get_default_login_required_message();
			}

			return new WP_Error( 'must_be_logged_in', $message );
		}

		$allowed_roles    = $this->get_form_option( $form->id, 'allowed_roles', array() );
		$allowed_user_ids = $this->get_form_option( $form->id, 'allowed_users', array() );
		$allowed_user_ids = array_map( 'absint', array_unique( array_filter( $allowed_user_ids ) ) );

		if ( empty( $allowed_roles ) && empty( $allowed_user_ids ) ) {
			return true;
		}

		if ( ! empty( $allowed_roles ) ) {
			$user = wp_get_current_user();

			$intersected_roles = array_intersect( array_values( $user->roles ), $allowed_roles );
			if ( ! empty( $intersected_roles ) ) {
				return true;
			}
		}

		if ( ! empty( $allowed_user_ids ) ) {
			if ( in_array( get_current_user_id(), $allowed_user_ids, true ) ) {
				return true;
			}
		}

		$message = $this->get_form_option( $form->id, 'not_selected_message' );
		if ( empty( $message ) ) {
			$message = $this->get_default_not_selected_message();
		}

		return new WP_Error( 'not_selected', $message );
	}

	/**
	 * Returns the available meta fields for the submodule.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of `$field_slug => $field_args` pairs.
	 */
	public function get_meta_fields() {
		$meta_fields = parent::get_meta_fields();

		$role_choices = array();
		foreach ( wp_roles()->roles as $role => $details ) {
			$role_choices[ $role ] = translate_user_role( $details['name'] );
		}

		$meta_fields['allowed_roles'] = array(
			'type'        => count( $role_choices ) > 8 ? 'multiselect' : 'multibox',
			'label'       => __( 'Allowed Roles', 'torro-forms' ),
			'description' => __( 'If you select user roles here, only users with these roles are granted access to the form.', 'torro-forms' ),
			'choices'     => $role_choices,
		);

		$meta_fields['allowed_users'] = array(
			'type'          => 'autocompletewithbutton',
			'label'         => __( 'Allowed Users', 'torro-forms' ),
			'description'   => __( 'If you select users here, only these users are granted access to the form.', 'torro-forms' ),
			'repeatable'    => true,
			'autocomplete'  => array(
				'rest_placeholder_search_route' => 'wp/v2/users?search=%search%',
				'rest_placeholder_label_route'  => 'wp/v2/users/%value%',
				'value_generator'               => '%id%',
				/* translators: 1: user display name, 2: user ID */
				'label_generator'               => sprintf( __( '%1$s (User ID %2$s)', 'torro-forms' ), '%name%', '%id%' ),
			),
			'input_classes' => array( 'torro-member-invitation-input' ),
			'button_label'  => __( 'Send Invitation', 'torro-forms' ),
			'button_attrs'  => array(
				'class'    => 'button-link torro-send-invitation',
				'disabled' => true,
			),
		);

		$meta_fields['login_required_message'] = array(
			'type'          => 'text',
			'label'         => __( '“Login required” Message', 'torro-forms' ),
			'description'   => __( 'Enter the message to show to the user in case they are not logged in.', 'torro-forms' ),
			'default'       => $this->get_default_login_required_message(),
			'input_classes' => array( 'regular-text' ),
			'wrap_classes'  => array( 'has-torro-tooltip-description' ),
		);

		$meta_fields['not_selected_message'] = array(
			'type'          => 'text',
			'label'         => __( '“Not eligible” Message', 'torro-forms' ),
			'description'   => __( 'Enter the message to show to the user when they are logged in, but have not been selected to participate.', 'torro-forms' ),
			'default'       => $this->get_default_not_selected_message(),
			'input_classes' => array( 'regular-text' ),
			'wrap_classes'  => array( 'has-torro-tooltip-description' ),
		);

		return $meta_fields;
	}

	/**
	 * Returns the available settings sections for the submodule.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of `$section_slug => $section_args` pairs.
	 */
	public function get_settings_sections() {
		$settings_sections = parent::get_settings_sections();

		$settings_sections['invitation_email'] = array(
			'title'       => __( 'Invitation Email Template', 'torro-forms' ),
			'description' => __( 'Setup mail templates for inviting a user to a form.', 'torro-forms' ),
		);

		$settings_sections['reinvitation_email'] = array(
			'title'       => __( 'Renvitation Email Template', 'torro-forms' ),
			'description' => __( 'Setup mail templates for reinviting a user to a form.', 'torro-forms' ),
		);

		return $settings_sections;
	}

	/**
	 * Returns the available settings fields for the submodule.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of `$field_slug => $field_args` pairs.
	 */
	public function get_settings_fields() {
		$settings_fields = parent::get_settings_fields();

		$domain = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		if ( ! $domain ) {
			// Fall back to a random domain.
			$domain = 'yourwebsite.com';
		}

		$settings_fields['invitation_from_name'] = array(
			'section'              => 'invitation_email',
			'type'                 => 'templatetagtext',
			'label'                => __( 'From Name', 'torro-forms' ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => '{sitetitle}',
			'template_tag_handler' => $this->template_tag_handler,
		);

		$settings_fields['invitation_from_email'] = array(
			'section'              => 'invitation_email',
			'type'                 => 'templatetagemail',
			'label'                => __( 'From Email', 'torro-forms' ),
			/* translators: %s: email address */
			'description'          => sprintf( __( 'This email address should contain the same domain like your website (e.g. %s).', 'torro-forms' ), 'email@' . $domain ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => '{adminemail}',
			'template_tag_handler' => $this->template_tag_handler_email_only,
		);

		$settings_fields['invitation_subject'] = array(
			'section'              => 'invitation_email',
			'type'                 => 'templatetagtext',
			'label'                => __( 'Subject', 'torro-forms' ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => $this->get_default_invitation_subject(),
			'template_tag_handler' => $this->template_tag_handler,
		);

		$settings_fields['invitation_message'] = array(
			'section'              => 'invitation_email',
			'type'                 => 'templatetagwysiwyg',
			'label'                => __( 'Message', 'torro-forms' ),
			'rows'                 => 12,
			'media_buttons'        => true,
			'wpautop'              => true,
			'default'              => $this->get_default_invitation_message(),
			'template_tag_handler' => $this->template_tag_handler,
		);

		$settings_fields['reinvitation_from_name'] = array(
			'section'              => 'reinvitation_email',
			'type'                 => 'templatetagtext',
			'label'                => __( 'From Name', 'torro-forms' ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => '{sitetitle}',
			'template_tag_handler' => $this->template_tag_handler,
		);

		$settings_fields['reinvitation_from_email'] = array(
			'section'              => 'reinvitation_email',
			'type'                 => 'templatetagemail',
			'label'                => __( 'From Email', 'torro-forms' ),
			/* translators: %s: email address */
			'description'          => sprintf( __( 'This email address should contain the same domain like your website (e.g. %s).', 'torro-forms' ), 'email@' . $domain ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => '{adminemail}',
			'template_tag_handler' => $this->template_tag_handler_email_only,
		);

		$settings_fields['reinvitation_subject'] = array(
			'section'              => 'reinvitation_email',
			'type'                 => 'templatetagtext',
			'label'                => __( 'Subject', 'torro-forms' ),
			'input_classes'        => array( 'regular-text' ),
			'default'              => $this->get_default_reinvitation_subject(),
			'template_tag_handler' => $this->template_tag_handler,
		);

		$settings_fields['reinvitation_message'] = array(
			'section'              => 'reinvitation_email',
			'type'                 => 'templatetagwysiwyg',
			'label'                => __( 'Message', 'torro-forms' ),
			'rows'                 => 12,
			'media_buttons'        => true,
			'wpautop'              => true,
			'default'              => $this->get_default_reinvitation_message(),
			'template_tag_handler' => $this->template_tag_handler,
		);

		return $settings_fields;
	}

	/**
	 * Registers all assets the submodule provides.
	 *
	 * @since 1.0.0
	 *
	 * @param Assets $assets The plugin assets instance.
	 */
	public function register_assets( $assets ) {
		$assets->register_script( 'admin-member-invitations', 'assets/dist/js/admin-member-invitations.js', array(
			'deps'          => array( 'wp-util', 'jquery' ),
			'in_footer'     => true,
			'localize_name' => 'torroMemberInvitations',
			'localize_data' => array(
				'ajaxPrefix'            => $this->module->manager()->ajax()->get_prefix(),
				'ajaxInviteMemberNonce' => $this->module->manager()->ajax()->get_nonce( 'invite_member' ),
			),
		) );
	}

	/**
	 * Enqueues scripts and stylesheets on the form editing screen.
	 *
	 * @since 1.0.0
	 *
	 * @param Assets $assets The plugin assets instance.
	 */
	public function enqueue_form_builder_assets( $assets ) {
		$assets->enqueue_script( 'admin-member-invitations' );
	}

	/**
	 * Sends an email invitation to a member.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data AJAX data, including $userId and $formId arguments.
	 * @return array|WP_Error AJAX response array, or error object in case of failure.
	 */
	public function ajax_invite_member( $data ) {
		if ( empty( $data['userId'] ) || empty( $data['formId'] ) ) {
			return new WP_Error( 'missing_parameters', __( 'Missing request parameters.', 'torro-forms' ) );
		}

		$user = get_user_by( 'id', $data['userId'] );
		if ( ! $user || ! $user->exists() ) {
			return new WP_Error( 'cannot_find_user', __( 'Could not find user.', 'torro-forms' ) );
		}

		$form = $this->module->manager()->forms()->get( $data['formId'] );
		if ( ! $form ) {
			return new WP_Error( 'cannot_find_form', __( 'Could not find form.', 'torro-forms' ) );
		}

		$mode = $this->is_user_invited( (int) $user->ID, $form->id ) ? 'reinvitation' : 'invitation';

		$from_name  = $this->get_option( $mode . '_from_name', '{sitetitle}' );
		$from_email = $this->get_option( $mode . '_from_email', '{adminemail}' );
		$subject    = $this->get_option( $mode . '_subject', call_user_func( array( $this, 'get_default_' . $mode . '_subject' ) ) );
		$message    = $this->get_option( $mode . '_message', call_user_func( array( $this, 'get_default_' . $mode . '_message' ) ) );

		$from_name  = $this->template_tag_handler->process_content( $from_name, array( $form, $user ) );
		$from_email = $this->template_tag_handler_email_only->process_content( $from_email, array( $form, $user ) );
		$subject    = $this->template_tag_handler->process_content( $subject, array( $form, $user ) );
		$message    = $this->template_tag_handler->process_content( $message, array( $form, $user ) );

		$message = wpautop( $message );

		add_filter( 'wp_mail_content_type', array( $this, 'override_content_type' ) );
		add_filter( 'wp_mail_from_name', array( $this, 'override_from_name' ) );
		add_filter( 'wp_mail_from', array( $this, 'override_from_email' ) );

		$this->from_name  = $from_name;
		$this->from_email = $from_email;

		$result = wp_mail( $user->user_email, $subject, $message );

		$this->from_name = '';
		$this->from_email = '';

		remove_filter( 'wp_mail_content_type', array( $this, 'override_content_type' ) );
		remove_filter( 'wp_mail_from_name', array( $this, 'override_from_name' ) );
		remove_filter( 'wp_mail_from', array( $this, 'override_from_email' ) );

		if ( ! $result ) {
			if ( 'reinvitation' === $mode ) {
				/* translators: %s: user display name */
				return new WP_Error( 'cannot_reinvite_user', sprintf( __( 'User %s could not be reinvited.', 'torro-forms' ), $user->display_name ) );
			}

			/* translators: %s: user display name */
			return new WP_Error( 'cannot_invite_user', sprintf( __( 'User %s could not be invited.', 'torro-forms' ), $user->display_name ) );
		}

		$this->set_user_invited( (int) $user->ID, $form->id );

		if ( 'reinvitation' === $mode ) {
			return array(
				/* translators: %s: user display name */
				'message' => sprintf( __( 'User %s was successfully reinvited.', 'torro-forms' ), $user->display_name ),
			);
		}

		return array(
			/* translators: %s: user display name */
			'message' => sprintf( __( 'User %s was successfully invited.', 'torro-forms' ), $user->display_name ),
		);
	}

	/**
	 * Gets the email content type.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email content type.
	 */
	public function override_content_type() {
		return 'text/html';
	}

	/**
	 * Gets the email from name.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email from name.
	 */
	public function override_from_name() {
		return $this->from_name;
	}

	/**
	 * Gets the email from email.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email from email.
	 */
	public function override_from_email() {
		return $this->from_email;
	}

	/**
	 * Checks whether a user has been previously invited to a form.
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id User ID.
	 * @param int $form_id Form ID.
	 * @return bool True if the user has been invited, false otherwise.
	 */
	protected function is_user_invited( $user_id, $form_id ) {
		$meta_key = $this->module->manager()->get_prefix() . 'invited_users';

		$data = $this->module->manager()->forms()->get_meta( $form_id, $meta_key );
		if ( ! is_array( $data ) ) {
			return false;
		}

		$data = array_map( 'absint', $data );

		if ( ! in_array( $user_id, $data, true ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Sets a user as invited to a form.
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id User ID.
	 * @param int $form_id Form ID.
	 */
	protected function set_user_invited( $user_id, $form_id ) {
		if ( $this->is_user_invited( $user_id, $form_id ) ) {
			return;
		}

		$meta_key = $this->module->manager()->get_prefix() . 'invited_users';

		$this->module->manager()->forms()->add_meta( $form_id, $meta_key, $user_id );
	}

	/**
	 * Returns the default message to display when the user is not logged in.
	 *
	 * @since 1.0.0
	 *
	 * @return string Message to display.
	 */
	protected function get_default_login_required_message() {
		return __( 'You have to be logged in to participate.', 'torro-forms' );
	}

	/**
	 * Returns the default message to display when the user has not been selected.
	 *
	 * @since 1.0.0
	 *
	 * @return string Message to display.
	 */
	protected function get_default_not_selected_message() {
		return __( 'You have not been selected to participate.', 'torro-forms' );
	}

	/**
	 * Returns the default subject for an invitation email, including placeholders.
	 *
	 * @since 1.0.0
	 *
	 * @return string Invitation email subject.
	 */
	protected function get_default_invitation_subject() {
		/* translators: %s: form title */
		return sprintf( __( 'Invitation to the form “%s”', 'torro-forms' ), '{formtitle}' );
	}

	/**
	 * Returns the default subject for a reinvitation email, including placeholders.
	 *
	 * @since 1.0.0
	 *
	 * @return string Reinvitation email subject.
	 */
	protected function get_default_reinvitation_subject() {
		/* translators: %s: form title */
		return sprintf( __( 'Reinvitation to the form “%s”', 'torro-forms' ), '{formtitle}' );
	}

	/**
	 * Returns the default message for an invitation email, including placeholders.
	 *
	 * @since 1.0.0
	 *
	 * @return string Invitation email message.
	 */
	protected function get_default_invitation_message() {
		/* translators: %s: user display name */
		$message = sprintf( __( 'Dear %s,', 'torro-forms' ), '{userdisplayname}' ) . "\n\n";

		/* translators: %s: form title */
		$message .= sprintf( __( 'You have been invited to participate in the form “%s” which you can find at the following URL:', 'torro-forms' ), '{formtitle}' ) . "\n\n";
		$message .= '<a href="{formurl}">{formurl}</a>' . "\n\n";
		$message .= __( 'Thanks in advance for participating!', 'torro-forms' ) . "\n\n";
		$message .= '{sitetitle}';

		return $message;
	}

	/**
	 * Returns the default message for a reinvitation email, including placeholders.
	 *
	 * @since 1.0.0
	 *
	 * @return string Reinvitation email message.
	 */
	protected function get_default_reinvitation_message() {
		/* translators: %s: user display name */
		$message = sprintf( __( 'Dear %s,', 'torro-forms' ), '{userdisplayname}' ) . "\n\n";

		/* translators: %s: form title */
		$message .= sprintf( __( 'As a reminder, a while ago you have been invited to participate in the form &#8220;%s&#8221; which you can find at the following URL:', 'torro-forms' ), '{formtitle}' ) . "\n\n";
		$message .= '<a href="{formurl}">{formurl}</a>' . "\n\n";
		$message .= __( 'If you have already participated by now, please ignore this email. Thanks!', 'torro-forms' ) . "\n\n";
		$message .= '{sitetitle}';

		return $message;
	}

	/**
	 * Registers the template tag handler for member invitations.
	 *
	 * @since 1.0.0
	 */
	protected function register_template_tag_handlers() {
		$tags = array(
			'sitetitle'          => array(
				'group'       => 'global',
				'label'       => __( 'Site Title', 'torro-forms' ),
				'description' => __( 'Inserts the site title.', 'torro-forms' ),
				'callback'    => function() {
					return get_bloginfo( 'name' );
				},
			),
			'sitetagline'        => array(
				'group'       => 'global',
				'label'       => __( 'Site Tagline', 'torro-forms' ),
				'description' => __( 'Inserts the site tagline.', 'torro-forms' ),
				'callback'    => function() {
					return get_bloginfo( 'description' );
				},
			),
			'siteurl'            => array(
				'group'       => 'global',
				'label'       => __( 'Site URL', 'torro-forms' ),
				'description' => __( 'Inserts the site home URL.', 'torro-forms' ),
				'callback'    => function() {
					return home_url( '/' );
				},
			),
			'adminemail'         => array(
				'group'       => 'global',
				'label'       => __( 'Site Admin Email', 'torro-forms' ),
				'description' => __( 'Inserts the site admin email.', 'torro-forms' ),
				'callback'    => function() {
					return get_option( 'admin_email' );
				},
			),
			'userip'             => array(
				'group'       => 'global',
				'label'       => __( 'User IP', 'torro-forms' ),
				'description' => __( 'Inserts the current user IP address.', 'torro-forms' ),
				'callback'    => function() {
					$validated_ip = filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP );
					if ( empty( $validated_ip ) ) {
						return '0.0.0.0';
					}
					return $validated_ip;
				},
			),
			'refererurl'         => array(
				'group'       => 'global',
				'label'       => __( 'Referer URL', 'torro-forms' ),
				'description' => __( 'Inserts the current referer URL.', 'torro-forms' ),
				'callback'    => function() {
					return wp_get_referer();
				},
			),
			'formtitle'          => array(
				'group'       => 'form',
				'label'       => __( 'Form Title', 'torro-forms' ),
				'description' => __( 'Inserts the form title.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return $form->title;
				},
			),
			'formurl'            => array(
				'group'       => 'form',
				'label'       => __( 'Form URL', 'torro-forms' ),
				'description' => __( 'Inserts the URL to the form.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return get_permalink( $form->id );
				},
			),
			'formediturl'        => array(
				'group'       => 'form',
				'label'       => __( 'Form Edit URL', 'torro-forms' ),
				'description' => __( 'Inserts the edit URL for the form.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return get_edit_post_link( $form->id );
				},
			),
			'useremail'          => array(
				'group'       => 'user',
				'label'       => __( 'User Email', 'torro-forms' ),
				'description' => __( 'Inserts the email address for the user.', 'torro-forms' ),
				'callback'    => function( $form, $user ) {
					return $user->user_email;
				},
			),
			'username'           => array(
				'group'       => 'user',
				'label'       => __( 'Username', 'torro-forms' ),
				'description' => __( 'Inserts the username.', 'torro-forms' ),
				'callback'    => function( $form, $user ) {
					return $user->user_login;
				},
			),
			'userdisplayname'    => array(
				'group'       => 'user',
				'label'       => __( 'User Display Name', 'torro-forms' ),
				'description' => __( 'Inserts the full display name the user has chosen to be addressed with.', 'torro-forms' ),
				'callback'    => function( $form, $user ) {
					return $user->display_name;
				},
			),
		);

		$groups = array(
			'global' => _x( 'Global', 'template tag group', 'torro-forms' ),
			'form'   => _x( 'Form', 'template tag group', 'torro-forms' ),
			'user'   => _x( 'User', 'template tag group', 'torro-forms' ),
		);

		$this->template_tag_handler            = new Template_Tag_Handler( $this->slug, $tags, array( Form::class, WP_User::class ), $groups );
		$this->template_tag_handler_email_only = new Template_Tag_Handler( $this->slug . '_email_only', array(
			'adminemail' => $tags['adminemail'],
			'useremail'  => $tags['useremail'],
		), array( Form::class, WP_User::class ), array(
			'global' => $groups['global'],
			'user'   => $groups['user'],
		) );

		$this->module->manager()->template_tag_handlers()->register( $this->template_tag_handler );
		$this->module->manager()->template_tag_handlers()->register( $this->template_tag_handler_email_only );
	}
}

Changelog

Changelog
Version Description
1.0.0 Introduced.

Methods