A mandatory agreement form to access another page?

I would use a shortcode for this. Wrap the content you want behind the “terms_required” shortcode, if the user hasn’t agreed to the terms, show them the form.

The form would submit to a custom rewrite rule (url endpoint) that validates the form and sets a cookie if everything checks out.

Let’s start by wrapping everything in a class — there are a few class constants here that we’ll use later.

<?php
class WPSE_52793
{
    /**
     * Query variable we'll use for rewrites and catching the form handler
     *
     */
    const Q_VAR = 'wpse52793_handler';

    /**
     * Form key field name.
     *
     */
    const F_KEY = 'wpse52793_accepted';

    /**
     * Form field nonce.
     *
     */
    const NONCE = 'wpse52793_fnonce';

    /**
     * Cookie key.
     *
     */
    const COOKIE = 'wpse52793_agreed';

    /********** Basic setup fluff **********/

    private static $ins = null;

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    public function _setup()
    {
         // we'll add actions here later
    }
}

We can add the shortcode: hook into init, call add shortcode and register the callback. The callback just checks for the cookie. If it’s there, display the content — if not display the terms and the form.

<?php
class WPSE_52793
{
    // snip snip

    public function _setup()
    {
        add_action('init', array($this, 'shortcode'));

    }

    public function shortcode()
    {
        add_shortcode('terms_required', array($this, 'shortcode_cb'));
    }

    public function shortcode_cb($opts, $content=null)
    {
        if(!empty($_COOKIE[self::COOKIE]))
            return $content;

        // if we're here, they haven't agreed. Show the terms.
        ob_start();
        // Your terms here!
        ?>
        <form method="post" action="<?php echo home_url('/terms-handler/'); ?>">
            <?php wp_nonce_field(self::NONCE, self::NONCE, false); ?>
            <input type="hidden" name="page_id" value="<?php echo get_queried_object_id(); ?>" />
            <label for="agree">
                <input type="checkbox" name="<?php echo esc_attr(self::F_KEY); ?>" value="agree" id="agree" />
                <?php esc_html_e('I agree to these terms and conditions.', 'wpse'); ?>
            </label>
            <p><input type="submit" value="<?php esc_attr_e('Submit', 'wpse'); ?>" /></p>
        </form>
        <?php
        return ob_get_clean();
    }
}

The rewrite rule is a bit more complex: hook into init, call add_rewrite_rule. We also need to filter query_vars and add a custom query var that we can use later.

<?php
class WPSE_52793
{
    // snip snip

    public function _setup()
    {
        add_action('init', array($this, 'rewrite'));
        add_filter('query_vars', array($this, 'add_var'));
        add_action('init', array($this, 'shortcode'));
    }

    public function rewrite()
    {
        add_rewrite_rule(
            '^terms-handler/?$',
            'index.php?' . self::Q_VAR . '=1',
            'top'
        );
    }

    public function add_var($v)
    {
        $v[] = self::Q_VAR;
        return $v;
    }

    // snip snip
}

The final piece of the puzzle handling the form submit. To do that, hook into template_redirect and check for our custom query variable. If it’s there, validate the form: check the nonces, make sure we have a page to redirect them to, and ensure that they actually agreed. If everything is okay, set a cookie and send them back to the original page.

<?php
class WPSE_52793
{
    // snip snip

    public function _setup()
    {
        add_action('init', array($this, 'rewrite'));
        add_filter('query_vars', array($this, 'add_var'));
        add_action('template_redirect', array($this, 'catch_handler'));
        add_action('init', array($this, 'shortcode'));
    }

    // snip snip

    public function catch_handler()
    {
        if(!get_query_var(self::Q_VAR))
            return;

        // only allow post request, check for empty post, and make sure we
        // have a page_id
        if(
            'POST' != $_SERVER['REQUEST_METHOD'] ||
            empty($_POST) ||
            empty($_POST['page_id'])
        ) {
            wp_redirect(home_url());
            exit;
        }

        // if we're here everything should be good.
        // fetch the permalink
        $r = get_permalink(absint($_POST['page_id']));

        if(!$r)
        {
            // bad permalink for some reason, bail
            wp_redirect(home_url());
            exit;
        }

        if(
            !isset($_POST[self::NONCE]) ||
            !wp_verify_nonce($_POST[self::NONCE], self::NONCE) ||
            empty($_POST[self::F_KEY])
        ) {
            // bad nonce or they didn't check the box, try again
            wp_redirect($r);
            exit;
        }

        // whew, they've agreed.  Set a cookie, and send them back to the page.
        setcookie(
            self::COOKIE,
            '1',
            strtotime('+30 Days'), // might want to change this?
            "https://wordpress.stackexchange.com/",
            COOKIE_DOMAIN, // WP constant
            false,
            true
        );

        wp_redirect($r);
        exit;
    }
}

The other improve that would be worth making is using the settings API to add a field in the admin area where you can the terms that end up displaying on the front end.

To do that, hook into admin_init, use register_setting along with add_settings_section and add_settings_field. We also need to modify the shortcode callback a bit to use the new option. The result is below, and all of this mess is in plugin form for you to checkout and modify to suit your needs.

<?php
class WPSE_52793
{
    // snip snip

    /**
     * Settings key.
     *
     */
    const SETTING = 'wpse52793_options';

    // snip snip

    public function _setup()
    {
        add_action('init', array($this, 'rewrite'));
        add_filter('query_vars', array($this, 'add_var'));
        add_action('template_redirect', array($this, 'catch_handler'));
        add_action('init', array($this, 'shortcode'));
        add_action('admin_init', array($this, 'settings'));
    }

    // snip snip

    public function shortcode_cb($opts, $content=null)
    {
        if(!empty($_COOKIE[self::COOKIE]))
            return $content;

        // if we're here, they haven't agreed. Show the terms.
        ob_start();
        ?>
        <div class="terms-and-conditions">
            <?php echo apply_filters('wpse52793_terms', get_option(self::SETTING, '')); ?>
        </div>
        <form method="post" action="<?php echo home_url('/terms-handler/'); ?>">
            <?php wp_nonce_field(self::NONCE, self::NONCE, false); ?>
            <input type="hidden" name="page_id" value="<?php echo get_queried_object_id(); ?>" />
            <label for="agree">
                <input type="checkbox" name="<?php echo esc_attr(self::F_KEY); ?>" value="agree" id="agree" />
                <?php esc_html_e('I agree to these terms and conditions.', 'wpse'); ?>
            </label>
            <p><input type="submit" value="<?php esc_attr_e('Submit', 'wpse'); ?>" /></p>
        </form>
        <?php
        return ob_get_clean();
    }

    /**
     * Hooked into `admin_init` -- registers the custom setting and adds a new
     * field and section to the Options > Reading page.
     *
     * @access  public
     * @uses    register_setting
     * @uses    add_settings_section
     * @uses    add_settings_field
     * @return  void
     */
    public function settings()
    {
        register_setting(
            'reading',
            self::SETTING,
            array($this, 'validate_cb')
        );

        add_settings_section(
            'terms-conditions',
            __('Terms and Conditions', 'wpse'),
            array($this, 'section_cb'),
            'reading'
        );

        add_settings_field(
            'terms-conditions',
            __('Terms & Conditions', 'wpse'),
            array($this, 'field_cb'),
            'reading',
            'terms-conditions',
            array('label_for' => self::SETTING)
        );
    }

    /**
     * Settings validation callback.  Checks to see if the user can post
     * unfiltered html and return the raw text or a kses filter string
     * where appropriate.
     *
     * @access  public
     * @uses    current_user_can
     * @uses    wp_filter_post_kses
     * @return  string
     */
    public function validate_cb($dirty)
    {
        return current_user_can('unfiltered_html') ?
            $dirty : wp_filter_post_kses($dirty);
    }

    /**
     * Callback for the settings section.  Displays some help text.
     *
     * @access  public
     * @uses    esc_html__
     * @return  void
     */
    public function section_cb()
    {
        echo '<p class="description">',
            esc_html__('The terms and conditions that get displayed when a user '.
                'visits a page protected by the `terms_required` shortcode.', 'wpse'),
            '</p>';
    }

    /**
     * Callback for the settings field.  Creates a new editor on the screen.
     *
     * @access  public
     * @uses    wp_editor
     * @uses    get_option
     * @return  void
     */
    public function field_cb($args)
    {
        wp_editor(
            get_option(self::SETTING, ''),
            self::SETTING,
            array(
                'wpautop'       => false,
                'textarea_rows' => 10,
            )
        );
    }
}