Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cocoa/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 在会话和后退按钮中存储表单_Php_Back Button - Fatal编程技术网

Php 在会话和后退按钮中存储表单

Php 在会话和后退按钮中存储表单,php,back-button,Php,Back Button,我正在尝试实现以下场景: 1. user display the page addBook.php 2. user starts filling the form 3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author 5.

我正在尝试实现以下场景:

1. user display the page addBook.php
2. user starts filling the form
3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author
5. user is redirected to addAuthor.php
6. the user fill the form and when he submits it, he goes back to addBook.php with all the previous data already present and the new Author selected.
问题是:我有不止一个递归级别的场景。示例:添加书籍=>添加作者=>添加国家/地区

我该怎么做

At step #3, the link submit the form so that I can save it in session.
To handle recursion, I can use a Stack and push the current from on the Stack each time I click a link. And pop the last form of the Stack when the user completes the action correctly or click a cancel button.
我的问题是:

如何处理浏览器的“后退”按钮? 如果用户没有单击“取消”按钮,而是单击“上一步”按钮,我怎么知道需要弹出最后一个元素


您知道实现这一点的一些常见模式吗?

您必须在客户机上使用javascript并钩住窗口卸载事件,序列化表单并将答案发送到服务器,服务器将其保存在会话中

$(window).unload(function() {
     $.ajax({
          url   : 'autosave.php',
          data : $('#my_form').serialize()
     });
});
在服务器上

// autosave.php

$_SESSION['autosave_data'] = $_POST['autosave_data'];

// addbook.php

if (isset($_SESSION['autosave_data'])) {
    // populate the fields
}

这是我为解决我的问题而开发的解决方案

因为问题不是客户端的问题,而是服务器端的问题。以下是我在项目中使用的php类:

首先是堆栈功能的主要类。由于对象将存储在会话中,因此需要在会话_开始之前完成包含

class Stack {

    private $stack;
    private $currentPosition;
    private $comeFromCancelledAction = false;

    public function __construct() {
        $this->clear();
    }

    /* ----------------------------------------------------- */
    /* PUBLICS METHODS                                       */
    /* ----------------------------------------------------- */

    /**
     * Clear the stack history
     */
    public function clear() {
        $this->stack = array();
        $this->currentPosition = -1;
    }

    /**
     * get the current position of the stack
     */
    public function getCurrentPosition() {
        return $this->currentPosition;
    }

    /**
     * Add a new element on the stack
     * Increment the current position
     *
     * @param $url the url to add on the stack
     * @param $data optionnal, the data that could be stored with this $url
     */
    public function add($url, &$data = array()) {
        if (count($this->stack) != $this->currentPosition) {
            // the currentPosition is not the top of the stack
            // need to slice the array to discard dirty urls
            $this->stack = array_slice($this->stack, 0, $this->currentPosition+1);
        }

        $this->currentPosition++;
        $this->stack[] = array('url' => $url, 'data' => $data, 'previousData' => null, 'linked_data' => null);
    }

    /**
     * Add the stack position parameter in the URL and do a redirect
     * Exit the current script.
     */
    public function redirect() {
        header('location:'.$this->addStackParam($this->getUrl($this->currentPosition)), 301);
        exit;
    }

    /**
     * get the URL of a given position
     * return null if the position is not valid
     */
    public function getUrl($position) {
        if (isset($this->stack[$position])) {
            return $this->stack[$position]['url'];
        } else {
            return null;
        }
    }

    /**
     * get the Data of a given position
     * return a reference of the data
     */
    public function &getData($position) {
        if (isset($this->stack[$position])) {
            return $this->stack[$position]['data'];
        } else {
            return null;
        }
    }

    /**
     * Update the context of the current position
     */
    public function storeCurrentData(&$data) {
        $this->stack[$this->currentPosition]['data'] = $data;
    }

    /**
     * store some data that need to be fixed in sub flow
     * (for example the id of the parent object)
     */
    public function storeLinkedData($data) {
        $this->stack[$this->currentPosition]['linked_data'] = $data;
    }

    /**
     * Update the context of the current position
     */
    public function storePreviousData(&$data) {
        $this->stack[$this->currentPosition]['previousData'] = $data;
    }

    /**
     * Compute all linked data for every positions before the current one and return an array
     * containing all keys / values
     * Should be called in sub flow to fixed some data.
     *
     * Example: if you have tree pages: dad.php, mum.php and child.php
     * when creating a "child" object from a "dad", the dad_id should be fixed
     * but when creating a "child" object from a "mum", the mum_id should be fixed and a combo for choosing a dad should be displayed
     */
    public function getLinkedData() {
        $totalLinkedData = array();
        for($i = 0; $i < $this->currentPosition; $i++) {
            $linkedData = $this->stack[$i]['linked_data'];
            if ($linkedData != null && count($linkedData) > 0) {
                foreach($linkedData as $key => $value) {
                    $totalLinkedData[$key] = $value;
                }
            }
        }
        return $totalLinkedData;
    }

    /**
     * Main method of the Stack class.
     * Should be called on each page before any output as this method should do redirects.
     *
     * @param $handler StackHandler object that will be called at each step of the stack process
     *                 Let the caller to be notified when something appens.
     * @return the data 
     */
    public function initialise(StackHandler $handler) {
        if (!isset($_GET['stack']) || !ctype_digit($_GET['stack'])) {
            // no stack info, acces the page directly
            $this->clear();
            $this->add($this->getCurrentUrl()); //add the ?stack=<position number>
            $this->storeLinkedData($handler->getLinkedData());
            $this->redirect(); //do a redirect to the same page
        } else {
            // $_GET['stack'] is set and is a number
            $position = $_GET['stack'];

            if ($this->currentPosition == $position) {
                // ok the user stay on the same page
                // or just comme from the redirection

                if (!empty($_POST['action'])) {
                    // user submit a form and need to do an action

                    if ($_POST['action'] == 'cancel') {
                        $currentData = array_pop($this->stack);
                        $this->currentPosition--;

                        $handler->onCancel($currentData);

                        // redirect to the next page with ?stack=<current position + 1>
                        $this->redirect();
                    } else {
                        // store the action for future use
                        $this->stack[$this->currentPosition]['action'] = $_POST['action'];

                        $currentData = $this->getData($this->currentPosition);
                        list($currentData, $nextUrl) = $handler->onAction($currentData, $_POST['action']);

                        // store current form for future use
                        $this->storeCurrentData($currentData);

                        // add the new page on the stack
                        $this->add($nextUrl);

                        // redirect to the next page with ?stack=<current position + 1>
                        $this->redirect();
                    }
                } else if (isset($this->stack[$this->currentPosition]['action'])) {
                    // no action, and an action exists for this position

                    $currentData = $this->getData($this->currentPosition);
                    $action = $this->stack[$this->currentPosition]['action'];

                    if ($this->comeFromCancelledAction) {
                        //we return from a cancelled action
                        $currentData = $handler->onReturningFromCancelledAction($action, $currentData);
                        $this->comeFromCancelledAction = false;
                    } else {
                        $previousData = $this->getPreviousData();
                        if ($previousData != null) {
                            //we return from a sucessful action
                            $currentData = $handler->onReturningFromSuccesAction($action, $currentData, $previousData);
                            $this->resetPreviousData();
                        }
                    }
                    $this->storeCurrentData( $currentData );
                }

                $currentData = $this->getData($this->currentPosition);
                if ($currentData == null) {
                    $currentData = $handler->getInitialData();
                    $this->storeCurrentData( $currentData );
                }

                return $currentData;

            } else if ($this->getUrl($position) == $this->getCurrentUrl()) {
                // seems that the user pressed the back or next button of the browser

                // set the current position
                $this->currentPosition = $position;

                return $this->getData($position);

            } else {
                // the position does not exist or the url is incorrect

                // redirect to the last known position
                $this->redirect();
            }
        }
    }

    /**
     * call this method after completing an action and need to redirect to the previous page.
     * If you need to give some data to the previous action, use $dataForPreviousAction
     */
    public function finishAction($dataForPreviousAction = null) {
        $pop = array_pop($this->stack);
        $this->currentPosition--;

        $this->storePreviousData($dataForPreviousAction);

        $this->redirect();
    }

    /* ----------------------------------------------------- */
    /* PRIVATE METHODS                                       */
    /* ----------------------------------------------------- */

    /**
     * get the previous data for the current position
     * used when a sub flow finish an action to give some data to the parent flow
     */
    private function &getPreviousData() {
        if (isset($this->stack[$this->currentPosition])) {
            return $this->stack[$this->currentPosition]['previousData'];
        } else {
            return null;
        }
    }

    /**
     * get the current url without the stack parameter
     * 
     * Attention: this method calls "basename" on PHP_SELF do strip the folder structure
     * and assume that every pages are in the same directory.
     *
     * The "stack" parameter is removed from the query string
     *
     * Example: for the page "http://myserver.com/path/to/a.php?id=1&stack=2"
     * PHP_SELF will be: /path/to/a.php
     * QUERY_STRING wille be: id=1&stack=2
     * This method will return: "a.php?id=1"
     */
    private function getCurrentUrl() {
        $basename = basename($_SERVER['PHP_SELF']);
        if ($_SERVER['QUERY_STRING'] != '') {
            return $basename.$this->removeQueryStringKey('?'.$_SERVER['QUERY_STRING'], 'stack');
        } else {
            return $basename;
        }
    }

    /**
     * add the "stack" parameter in an url
     */
    private function addStackParam($url) {
        return $url . (strpos($url, '?') === false ? '?' : '&') . 'stack=' . $this->currentPosition;

    }

    /**
     * Usefull private method to remove a key=value from a query string.
     */
    private function removeQueryStringKey($url, $key) {
        $url = preg_replace('/(?:&|(\?))'.$key.'=[^&]*(?(1)&|)?/i', "$1", $url);
        return $url != '?' ? $url : '';
    }

    /**
     * reset the previous data so that the data are not used twice
     */
    private function resetPreviousData() {
        $this->stack[$this->currentPosition]['previousData'] = null;
    }
}
然后在页面顶部添加以下行。调整处理程序,使其适合您的需要

// be sure that a stack object exist in the session
if (!isset($_SESSION['stack'])) {
    $_SESSION['stack'] = new Stack();
}

$myDad = $_SESSION['stack']->initialise(new DadStackHandler());

class DadStackHandler extends StackHandler {

    /**
     * return the initial data to store for this current page
     */
    public function &getInitialData() {
        if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
            // update
            $myDad = new Dad($_GET['id_dad']);
        } else {
            // creation
            $myDad = new Dad();
        }
        return $myDad;
    }

    /**
     * return an array containing the key/values that need to be fixed in sub flows
     */
    public function getLinkedData() {
        $linkedData = array();
        if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
            $linkedData['id_dad'] = $_GET['id_dad'];
        }
        return $linkedData;
    }

    /**
     * user ask to go to a sub page
     */
    public function onAction(&$myDad, $action) {
        //in order not to loose user inputs, save them in the current data
        $myDad->name = $_POST['name'];

        $nextUrl = null;

        // find the next url based on the action name
        if ($action == 'child') {
            $nextUrl = 'child.php';
        }

        return array($myDad, $nextUrl);
    }

    public function onCancel(&$myDad) {
        // probably nothing to do, leave the current data untouched
        // or update current data
        return $myDad;
    }

    public function onReturningFromCancelledAction($action, &$myDad) {
        // probably nothing to do, leave the current data untouched
        // called when returning from child.php
        return $myDad;
    }

    public function onReturningFromSuccesAction($action, &$myDad, $newId) {
        // update the id of the foreign field if needed
        // or update the current data

        // not a good example as in real life child should be a list and not a foreign key
        // $myDad->childId = $newId; 

        $myDad->numberOfChildren++;

        return $myDad;
    }
}


...
if (user submit form and all input are correct) {
    if ($myDad->save()) {
        // the user finish an action, so we should redirect him to the previous one
        if ($_SESSION['stack']->getCurrentPosition() > 0) {
            $_SESSION['stack']->finishAction($myDad->idDad);
        } else {
            // default redirect, redirect to the same page in view more or redirect to a list page
        }
    }
}

我希望这能帮助其他人。

为什么不使用ajax呢?这是个不错的主意,我会仔细研究一下。作为一个补充说明,对于其他阅读器来说,如果您在稍后再次向用户显示时不转义/清理该值,则直接在会话中存储$\u POST数据可能会触发一个大的XSS问题。我建议在将$\u POST数据存储到会话中之前对其进行清理。
// be sure that a stack object exist in the session
if (!isset($_SESSION['stack'])) {
    $_SESSION['stack'] = new Stack();
}

$myDad = $_SESSION['stack']->initialise(new DadStackHandler());

class DadStackHandler extends StackHandler {

    /**
     * return the initial data to store for this current page
     */
    public function &getInitialData() {
        if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
            // update
            $myDad = new Dad($_GET['id_dad']);
        } else {
            // creation
            $myDad = new Dad();
        }
        return $myDad;
    }

    /**
     * return an array containing the key/values that need to be fixed in sub flows
     */
    public function getLinkedData() {
        $linkedData = array();
        if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
            $linkedData['id_dad'] = $_GET['id_dad'];
        }
        return $linkedData;
    }

    /**
     * user ask to go to a sub page
     */
    public function onAction(&$myDad, $action) {
        //in order not to loose user inputs, save them in the current data
        $myDad->name = $_POST['name'];

        $nextUrl = null;

        // find the next url based on the action name
        if ($action == 'child') {
            $nextUrl = 'child.php';
        }

        return array($myDad, $nextUrl);
    }

    public function onCancel(&$myDad) {
        // probably nothing to do, leave the current data untouched
        // or update current data
        return $myDad;
    }

    public function onReturningFromCancelledAction($action, &$myDad) {
        // probably nothing to do, leave the current data untouched
        // called when returning from child.php
        return $myDad;
    }

    public function onReturningFromSuccesAction($action, &$myDad, $newId) {
        // update the id of the foreign field if needed
        // or update the current data

        // not a good example as in real life child should be a list and not a foreign key
        // $myDad->childId = $newId; 

        $myDad->numberOfChildren++;

        return $myDad;
    }
}


...
if (user submit form and all input are correct) {
    if ($myDad->save()) {
        // the user finish an action, so we should redirect him to the previous one
        if ($_SESSION['stack']->getCurrentPosition() > 0) {
            $_SESSION['stack']->finishAction($myDad->idDad);
        } else {
            // default redirect, redirect to the same page in view more or redirect to a list page
        }
    }
}