Module

Class for the Evaluators module.

Description

Source

File: src/modules/evaluators/module.php

class Module extends Module_Base implements Submodule_Registry_Interface {
	use Submodule_Registry_Trait {
		Submodule_Registry_Trait::register_assets as protected _register_assets;
	}

	/**
	 * Bootstraps the module by setting properties.
	 *
	 * @since 1.0.0
	 */
	protected function bootstrap() {
		$this->slug        = 'evaluators';
		$this->title       = __( 'Evaluators', 'torro-forms' );
		$this->description = __( 'Evaluators allow evaluating form submissions, for example to generate charts and analytics.', 'torro-forms' );

		$this->submodule_base_class = Evaluator::class;
		$this->default_submodules = array(
			'participation'     => Participation::class,
			'element_responses' => Element_Responses::class,
		);
	}

	/**
	 * Evaluates a specific form submission.
	 *
	 * @since 1.0.0
	 *
	 * @param Submission $submission Submission to evaluate.
	 * @param Form       $form       Form the submission applies to.
	 */
	protected function evaluate( $submission, $form ) {
		foreach ( $this->submodules as $slug => $evaluator ) {
			if ( ! $evaluator->enabled( $form ) ) {
				continue;
			}

			$aggregate_results = $evaluator->get_stats( $form->id );
			$aggregate_results = $evaluator->evaluate_single( $aggregate_results, $submission, $form );

			$evaluator->update_stats( $form->id, $aggregate_results );
		}
	}

	/**
	 * Shows evaluation results if a form ID is provided as GET parameter.
	 *
	 * @since 1.0.0
	 *
	 * @param Submission_Manager $submissions Submission manager instance.
	 */
	protected function maybe_show_evaluation_results( $submissions ) {
		if ( empty( $_GET['form_id'] ) ) {
			return;
		}

		$form = $submissions->get_parent_manager( 'forms' )->get( (int) $_GET['form_id'] );
		if ( ! $form ) {
			return;
		}

		$tabs = array();

		foreach ( $this->submodules as $slug => $evaluator ) {
			if ( ! $evaluator->enabled( $form ) ) {
				continue;
			}

			$results = $evaluator->evaluate_all( $form );

			ob_start();
			$evaluator->show_results( $results, $form );

			$tabs[ $evaluator->get_slug() ] = array(
				'title'       => $evaluator->get_title(),
				'description' => $evaluator->get_description(),
				'content'     => ob_get_clean(),
			);
		}

		if ( empty( $tabs ) ) {
			return;
		}

		$screen = get_current_screen();
		$hidden = get_hidden_meta_boxes( $screen );
		$closed = get_user_option( 'closedpostboxes_' . $screen->id );

		if ( ! is_array( $closed ) ) {
			$closed = array();
		}

		$box_id    = 'torro-evaluations';
		$box_class = 'torro-evaluations-box postbox' . ( in_array( $box_id, $closed, true ) ? ' closed' : '' ) . ( in_array( $box_id, $hidden, true ) ? ' hide-if-js' : '' );

		$current_tab_slug = key( $tabs );

		echo '<input type="hidden" id="closedpostboxespage" value="' . esc_attr( $screen->id ) . '" />';
		wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
		?>
		<div id="<?php echo esc_attr( $box_id ); ?>" class="<?php echo esc_attr( $box_class ); ?>">
			<button type="button" class="handlediv" aria-expanded="true">
				<span class="screen-reader-text"><?php _e( 'Toggle panel: Evaluations', 'torro-forms' ); ?></span>
				<span class="toggle-indicator" aria-hidden="true"></span>
			</button>

			<h2 class="hndle">
				<span><?php _e( 'Evaluations', 'torro-forms' ); ?></span>
			</h2>

			<div class="inside">
				<div class="torro-evaluations-tabs" role="tablist">
					<?php foreach ( $tabs as $slug => $data ) : ?>
						<a id="<?php echo esc_attr( 'evaluations-tab-label-' . $slug ); ?>" class="torro-evaluations-tab" href="<?php echo esc_attr( '#evaluations-tab-' . $slug ); ?>" aria-controls="<?php echo esc_attr( 'evaluations-tab-' . $slug ); ?>" aria-selected="<?php echo $slug === $current_tab_slug ? 'true' : 'false'; ?>" role="tab">
							<?php echo esc_html( $data['title'] ); ?>
						</a>
					<?php endforeach; ?>
				</div>
				<div class="torro-evaluations-content">
					<?php foreach ( $tabs as $slug => $data ) : ?>
						<div id="<?php echo esc_attr( 'evaluations-tab-' . $slug ); ?>" class="torro-evaluations-tab-panel" aria-labelledby="<?php echo esc_attr( 'evaluations-tab-label-' . $slug ); ?>" aria-hidden="<?php echo $slug === $current_tab_slug ? 'false' : 'true'; ?>" role="tabpanel">
							<div class="torro-evaluations-description-wrap">
								<p class="description"><?php echo $data['description']; ?></p>
							</div>
							<div id="<?php echo esc_attr( 'torro-evaluations-results-' . $slug ); ?>" class="torro-evaluations-results">
								<?php echo $data['content']; ?>
							</div>
							<div class="torro-evaluations-shortcode">
								<label for="<?php echo esc_attr( 'torro-evaluations-shortcode-' . $slug ); ?>"><?php _e( 'Charts Shortcode:', 'torro-forms' ); ?></label>
								<input id="<?php echo esc_attr( 'torro-evaluations-shortcode-' . $slug ); ?>" class="clipboard-field regular-text" value="<?php echo esc_attr( sprintf( "[{$this->manager()->forms()->get_prefix()}form_charts id=&quot;%d&quot; mode=&quot;%s&quot;]", $form->id, $slug ) ); ?>" readonly="readonly" />
								<button type="button" class="clipboard-button button" data-clipboard-target="#<?php echo esc_attr( 'torro-evaluations-shortcode-' . $slug ); ?>">
									<?php $this->manager()->forms()->assets()->render_icon( 'torro-icon-clippy', __( 'Copy to clipboard', 'torro-forms' ) ); ?>
								</button>
							</div>
						</div>
					<?php endforeach; ?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Registers the default evaluators.
	 *
	 * The function also executes a hook that should be used by other developers to register their own evaluators.
	 *
	 * @since 1.0.0
	 */
	protected function register_defaults() {
		foreach ( $this->default_submodules as $slug => $class_name ) {
			$this->register( $slug, $class_name );
		}

		/**
		 * Fires when the default evaluators have been registered.
		 *
		 * This action should be used to register custom evaluators.
		 *
		 * @since 1.0.0
		 *
		 * @param Module $evaluators Form setting manager instance.
		 */
		do_action( "{$this->get_prefix()}register_evaluators", $this );
	}

	/**
	 * Registers the available module scripts and stylesheets.
	 *
	 * @since 1.0.0
	 *
	 * @param Assets $assets Assets API instance.
	 */
	protected function register_assets( $assets ) {
		$assets->register_script( 'admin-evaluations', 'assets/dist/js/admin-evaluations.js', array(
			'deps'      => array( 'jquery' ),
			'ver'       => torro()->version(),
			'in_footer' => true,
		) );

		$assets->register_style( 'admin-evaluations', 'assets/dist/css/admin-evaluations.css', array(
			'deps' => array(),
			'ver'  => torro()->version(),
		) );

		$this->_register_assets( $assets );
	}

	/**
	 * Enqueues assets to load in the submissions list table view if conditions are met.
	 *
	 * @since 1.0.0
	 *
	 * @param Submission_Manager $submissions Submission manager instance.
	 */
	protected function maybe_enqueue_submission_results_assets( $submissions ) {
		if ( empty( $_GET['form_id'] ) ) {
			return;
		}

		$form = $submissions->get_parent_manager( 'forms' )->get( (int) $_GET['form_id'] );
		if ( ! $form ) {
			return;
		}

		$assets = $this->manager()->assets();

		$has_enabled = false;
		foreach ( $this->submodules as $slug => $evaluator ) {
			if ( ! $evaluator->enabled( $form ) ) {
				continue;
			}

			$has_enabled = true;

			if ( ! is_a( $evaluator, Assets_Submodule_Interface::class ) ) {
				continue;
			}

			if ( ! is_callable( array( $evaluator, 'enqueue_submission_results_assets' ) ) ) {
				continue;
			}

			$evaluator->enqueue_submission_results_assets( $assets, $form );
		}

		if ( $has_enabled ) {
			$assets->enqueue_script( 'clipboard' );
			$assets->enqueue_style( 'clipboard' );
			$assets->enqueue_script( 'admin-evaluations' );
			$assets->enqueue_style( 'admin-evaluations' );
		}
	}

	/**
	 * Sets up all action and filter hooks for the service.
	 *
	 * @since 1.0.0
	 */
	protected function setup_hooks() {
		parent::setup_hooks();

		$this->actions[] = array(
			'name'     => "{$this->get_prefix()}complete_submission",
			'callback' => array( $this, 'evaluate' ),
			'priority' => 10,
			'num_args' => 2,
		);
		$this->actions[] = array(
			'name'     => "{$this->get_prefix()}before_submissions_list",
			'callback' => array( $this, 'maybe_show_evaluation_results' ),
			'priority' => 10,
			'num_args' => 1,
		);
		$this->actions[] = array(
			'name'     => "{$this->get_prefix()}list_submissions_enqueue_assets",
			'callback' => array( $this, 'maybe_enqueue_submission_results_assets' ),
			'priority' => 1,
			'num_args' => 1,
		);
		$this->actions[] = array(
			'name'     => 'init',
			'callback' => array( $this, 'register_defaults' ),
			'priority' => 100,
			'num_args' => 1,
		);
	}

	/**
	 * Handles the form charts shortcode.
	 *
	 * @since 1.0.0
	 *
	 * @param array $atts {
	 *     Array of shortcode attributes. In addition to the attributes specified here you may pass any arguments
	 *     that should be forwarded to the respective evaluator.
	 *
	 *     @type int    $id   Form ID. This must always be present.
	 *     @type string $mode Slug of the evaluator to use. The evaluator must exist and be enabled for the form.
	 *                        Default is 'element_responses'.
	 * }
	 * @return string Shortcode output.
	 */
	public function get_shortcode_content( $atts ) {
		$args = $atts;

		$atts = shortcode_atts( array(
			'id'   => '',
			'mode' => 'element_responses',
		), $atts, "{$this->get_prefix()}form_charts" );

		$args = array_diff_key( $args, $atts );

		$atts['id'] = absint( $atts['id'] );

		if ( empty( $atts['id'] ) ) {
			return __( 'Shortcode is missing a form ID!', 'torro-forms' );
		}

		if ( empty( $atts['mode'] ) ) {
			return __( 'Shortcode is missing a mode!', 'torro-forms' );
		}

		$form = torro()->forms()->get( $atts['id'] );
		if ( ! $form ) {
			return __( 'Shortcode is using an invalid form ID!', 'torro-forms' );
		}

		$evaluator = $this->get( $atts['mode'] );
		if ( is_wp_error( $evaluator ) ) {
			return __( 'Shortcode is using an invalid mode!', 'torro-forms' );
		}

		if ( ! $evaluator->enabled( $form ) ) {
			return __( 'Shortcode is using an invalid mode!', 'torro-forms' );
		}

		if ( is_a( $evaluator, Assets_Submodule_Interface::class ) && is_callable( array( $evaluator, 'enqueue_submission_results_assets' ) ) ) {
			$evaluator->enqueue_submission_results_assets( $this->manager()->assets(), $form );
		}

		$results = $evaluator->evaluate_all( $form );

		ob_start();

		echo '<div id="' . esc_attr( 'torro-evaluations-results-' . $evaluator->get_slug() ) . '" class="torro-evaluations-results">' . "\n";
		$evaluator->show_results( $results, $form, $args );
		echo "\n" . '</div>';

		$onclick = "var clickedLink=this;Array.from( clickedLink.parentElement.children ).forEach(function(link){clickedLink===link?link.setAttribute('aria-selected','true')||link.parentElement.parentElement.querySelector('#'+link.getAttribute('aria-controls')).setAttribute('aria-hidden','false'):link.setAttribute('aria-selected','false')||link.parentElement.parentElement.querySelector('#'+link.getAttribute('aria-controls')).setAttribute('aria-hidden','true')});return false";

		return str_replace( ' class="torro-evaluations-subtab"', ' class="torro-evaluations-subtab" onclick="' . esc_attr( $onclick ) . '"', ob_get_clean() );
	}

	/**
	 * Handles the deprecated form charts shortcode.
	 *
	 * @since 1.0.0
	 *
	 * @param array $atts {
	 *     Array of shortcode attributes.
	 *
	 *     @type int    $id   Form ID. This must always be present.
	 *     @type string $mode Slug of the evaluator to use. The evaluator must exist and be enabled for the form.
	 *                        Default is 'element_responses'.
	 * }
	 * @return string Shortcode output.
	 */
	public function get_deprecated_shortcode_content( $atts ) {
		$this->manager()->error_handler()->deprecated_shortcode( 'form_charts', '1.0.0-beta.9', "{$this->manager()->forms()->get_prefix()}form_charts" );

		$atts['mode'] = 'element_responses';

		return $this->get_shortcode_content( $atts );
	}

	/**
	 * Handles the deprecated element chart shortcode.
	 *
	 * Arguments are tweaked around inside it and passed on to the new shortcode to account for back-compat.
	 *
	 * @since 1.0.0
	 *
	 * @param array $atts {
	 *     Array of shortcode attributes.
	 *
	 *     @type int $id Element ID. This must always be present.
	 * }
	 * @return string Shortcode output.
	 */
	public function get_deprecated_element_chart_shortcode_content( $atts ) {
		$this->manager()->error_handler()->deprecated_shortcode( 'element_chart', '1.0.0-beta.9', "{$this->manager()->forms()->get_prefix()}form_charts" );

		$atts['mode'] = 'element_responses';

		if ( ! isset( $atts['id'] ) ) {
			return __( 'Shortcode is missing an element ID!', 'torro-forms' );
		}

		$atts['element_id'] = $atts['id'];
		unset( $atts['id'] );

		$element = $this->manager()->forms()->get_child_manager( 'containers' )->get_child_manager( 'elements' )->get( $atts['element_id'] );
		if ( ! $element ) {
			return __( 'Shortcode is using an invalid element ID!', 'torro-forms' );
		}

		$container = $element->get_container();
		if ( ! $container ) {
			return __( 'It looks like the container for this element has been removed. Please enter a different element ID into the shortcode.', 'torro-forms' );
		}

		$atts['id'] = $container->form_id;

		return $this->get_shortcode_content( $atts );
	}

	/**
	 * Adds the service hooks.
	 *
	 * @since 1.0.0
	 */
	public function add_hooks() {
		if ( ! $this->hooks_added ) {
			add_shortcode( "{$this->get_prefix()}form_charts", array( $this, 'get_shortcode_content' ) );
			add_shortcode( 'form_charts', array( $this, 'get_deprecated_shortcode_content' ) );
			add_shortcode( 'element_chart', array( $this, 'get_deprecated_element_chart_shortcode_content' ) );
		}

		return parent::add_hooks();
	}

	/**
	 * Removes the service hooks.
	 *
	 * @since 1.0.0
	 */
	public function remove_hooks() {
		if ( $this->hooks_added ) {
			remove_shortcode( "{$this->get_prefix()}form_charts" );
			remove_shortcode( 'form_charts' );
			remove_shortcode( 'element_chart' );
		}

		return parent::remove_hooks();
	}
}

Changelog

Changelog
Version Description
1.0.0 Introduced.

Methods

  • get — Returns a specific registered submodule.
  • get_all — Returns all registered submodules.
  • has — Checks whether a specific submodule is registered.
  • register — Registers a new submodule.
  • unregister — Unregisters a new submodule.