Version: current

      1 <?php
      2 /**
      3  * This class collects all output that needs to be returned after an Form-Request to the client. It automatically determines
      4  * if it needs to send back javascript after an Ajax-Request or just redirect to another page (on a normal request).
      5  *
      6  * FormResponse is also responsible for keeping the client- and serverside in sync after an HTTP-Request
      7  * by collecting javascript-commands (which mostly trigger subsequent update-calls by Ajax.)
      8  * Use the output as a return-value for Ajax-based saving methods. Be sure to check if the call is acutally "ajaxy"
      9  * by checking Director::is_ajax(). It is the developers responsibility to include this into his custom form-methods.
     10  * Use the Request-Parameter 'htmlonly' to enforce a pure HTML-response from the client-side.
     11  *
     12  * Example: A {@TableField} is in an incorrect state after being saved, as it still has rows marked as "new"
     13  * which are already saved (and have an ID) in the database. By using AjaxSynchroniser we make sure that every instance
     14  * is refreshed by Ajax and reflects the correct state.
     15  *
     16  * Caution:
     17  * - FormResponse assumes that prototype.js is included on the client-side. (We can't put it into Requirements because it has to
     18  *   be included BEFORE an AjaxSynchroniser is called).
     19  * - Please DON'T escape literal parameters which are passed to FormResponse, they are escaped automatically.
     20  * - Some functions assume a {LeftAndMain}-based environment (e.g. load_form())
     21  *
     22  * @todo Force a specific execution order ($forceTop, $forceBottom)Ω
     23  * @todo Extension to return different formats, e.g. JSON or XML
     24  *
     25  * WARNING: This should only be used within the CMS context. Please use markup or JSON to transfer state to the client,
     26  * and react with javascript callbacks instead in other situations.
     27  *
     28  * @package forms
     29  * @subpackage core
     30  */
     31 class FormResponse {
     32
     33     /**
     34      * @var $rules array
     35      */
     36     static protected $rules = array();
     37
     38     /**
     39      * @var $behaviour_apply_rules array Separated from $rules because
     40      * we need to apply all behaviour at the very end of the evaluated script
     41      * to make sure we include all possible Behaviour.register()-calls.
     42      */
     43     static protected $behaviour_apply_rules = array();
     44
     45     /**
     46      * @var $non_ajax_content string
     47      */
     48     static protected $non_ajax_content;
     49
     50     /**
     51      * Status-messages are accumulated, and the "worst" is chosen
     52      *
     53      * @var $status_messages array
     54      */
     55     static protected $status_messages = array();
     56
     57     /**
     58      * @var $redirect_url string
     59      */
     60     static protected $redirect_url;
     61
     62
     63     /**
     64      * @var $redirect_url string
     65      */
     66     static protected $status_include_order = array('bad', 'good', 'unknown');
     67
     68     /**
     69      * Get all content as a javascript-compatible string (only if there is an Ajax-Request present).
     70      * Falls back to {non_ajax_content}, {redirect_url} or Director::redirectBack() (in this order).
     71      *
     72      * @return string
     73      */
     74     static function respond() {
     75         // we don't want non-ajax calls to receive javascript
     76         if(isset($_REQUEST['forcehtml'])) {
     77             return self::$non_ajax_content;
     78         } else if(isset($_REQUEST['forceajax']) || Director::is_ajax()) {
     79             // TODO figure out a way to stay backwards-compatible with Ajax.Evaluator and still use the automatic evaluating of Prototype
     80             //header("Content-type: text/javascript");
     81             return self::get_javascript();
     82         } elseif(!empty(self::$non_ajax_content)) {
     83             return self::$non_ajax_content;
     84         } elseif(!empty(self::$redirect_url)) {
     85             Director::redirect(self::$redirect_url);
     86             return null;
     87         } elseif(!Director::redirected_to()) {
     88             Director::redirectBack();
     89             return null;
     90         } else {
     91             return null;
     92         }
     93
     94     }
     95
     96     /**
     97      * Caution: Works only for forms which inherit methods from LeftAndMain.js
     98      */
     99     static function load_form($content, $id = 'Form_EditForm') {
    100         // make sure form-tags are stripped
    101         // loadNewPage() uses innerHTML to replace the form, which makes IE cry when replacing an element with itself
    102         $content = preg_replace(array('/<form[^>]*>/','/<\/form>/'), '', $content);
    103         $JS_content = Convert::raw2js($content);
    104         self::$rules[] = "\$('{$id}').loadNewPage('{$JS_content}');";
    105         self::$rules[] = "\$('{$id}').initialize();";
    106         self::$rules[] = "if(typeof onload_init_tabstrip != 'undefined') onload_init_tabstrip();";
    107     }
    108
    109     /**
    110      * Add custom scripts.
    111      * Caution: Not escaped for backwards-compatibility.
    112      *
    113      * @param $scriptContent string
    114      *
    115      * @todo Should this content be escaped?
    116      */
    117     static function add($scriptContent, $uniquenessID = null) {
    118         if(isset($uniquenessID)) {
    119             self::$rules[$uniquenessID] = $scriptContent;
    120         } else {
    121             self::$rules[] = $scriptContent;
    122         }
    123     }
    124
    125     static function clear() {
    126         self::$rules = array();
    127     }
    128
    129     /**
    130      * @param $id int
    131      */
    132     static function get_page($id, $form = 'Form_EditForm', $uniquenessID = null) {
    133         $JS_id = (int)$id;
    134         if($JS_id){
    135             if(isset($uniquenessID)) {
    136                 self::$rules[$uniquenessID] = "\$('$form').getPageFromServer($JS_id);";
    137             } else {
    138                 self::$rules[] = "\$('$form').getPageFromServer($JS_id);";
    139             }
    140         }
    141     }
    142
    143     /**
    144      * Sets the status-message (overlay-notification in the CMS).
    145      * You can call this method multiple times, it will default to the "worst" statusmessage.
    146      *
    147      * @param $message string
    148      * @param $status string
    149      */
    150     static function status_message($message = "", $status = null) {
    151         $JS_message = Convert::raw2js($message);
    152         $JS_status = Convert::raw2js($status);
    153         if(isset($JS_status)) {
    154             self::$status_messages[$JS_status] = "statusMessage('{$JS_message}', '{$JS_status}');";
    155         } else {
    156             self::$status_messages['unknown'] = "statusMessage('{$JS_message}');";
    157         }
    158     }
    159
    160     /**
    161      * Alias for status_message($messsage, 'bad')
    162      *
    163      * @param $message string
    164      */
    165     static function error($message = "") {
    166         $JS_message = Convert::raw2js($message);
    167         self::$status_messages['bad'] = $JS_message;
    168     }
    169
    170     /**
    171      * Update the status (upper right corner) of the given Form
    172      *
    173      * @param $status string
    174      * @param $form string
    175      */
    176     static function update_status($status, $form = "Form_EditForm") {
    177         $JS_form = Convert::raw2js($form);
    178         $JS_status = Convert::raw2js($status);
    179         self::$rules[] = "\$('$JS_form').updateStatus('$JS_status');";
    180     }
    181
    182     /**
    183      * Set the title of a single page in the pagetree
    184      *
    185      * @param $id int
    186      * @param $title string
    187      */
    188     static function set_node_title($id, $title = "") {
    189         $JS_id = Convert::raw2js($id);
    190         $JS_title = Convert::raw2js($title);
    191         self::$rules[] = "$('sitetree').setNodeTitle('$JS_id', '$JS_title');";
    192     }
    193
    194     /**
    195      * Fallback-method to supply normal HTML-response when not being called by ajax.
    196      *
    197      * @param $content string HTML-content
    198      */
    199     static function set_non_ajax_content($content) {
    200         self::$non_ajax_content = $content;
    201     }
    202
    203     /**
    204      * @param $url string
    205      */
    206     static function set_redirect_url($url) {
    207         self::$redirect_url = $url;
    208     }
    209
    210     /**
    211      * @return string
    212      */
    213     static function get_redirect_url() {
    214         return self::$redirect_url;
    215     }
    216
    217     /**
    218      * Replace a given DOM-element with the given content.
    219      * It automatically prefills {$non_ajax_content} with the passed content (as a fallback).
    220      *
    221      * @param $domID string The DOM-ID of an HTML-element that should be replaced
    222      * @param $domContent string The new HTML-content
    223      * @param $reapplyBehaviour boolean Applies behaviour to the given domID after refreshing it
    224      * @param $replaceMethod string Method for replacing - either 'replace' (=outerHTML) or 'update' (=innerHTML)
    225      *   (Caution: "outerHTML" might cause problems on the client-side, e.g. on table-tags)
    226      *
    227      * @todo More fancy replacing with loading-wheel etc.
    228      */
    229     static function update_dom_id($domID, $domContent, $reapplyBehaviour = true, $replaceMethod = 'replace', $uniquenessID = null) {
    230         //self::$non_ajax_content = $domContent;
    231         $JS_domID = Convert::raw2js($domID);
    232         $JS_domContent = Convert::raw2js($domContent);
    233         $JS_replaceMethod = Convert::raw2js($replaceMethod);
    234         if(isset($uniquenessID)) {
    235             self::$rules[$uniquenessID] = "Element.$JS_replaceMethod('{$JS_domID}','{$JS_domContent}');";
    236         } else {
    237             self::$rules[] = "Element.$JS_replaceMethod('{$JS_domID}','{$JS_domContent}');";
    238         }
    239         if($reapplyBehaviour) {
    240             if(isset($uniquenessID)) {
    241                 self::$behaviour_apply_rules[$uniquenessID] .= "Behaviour.apply('{$JS_domID}', true);";
    242             } else {
    243                 self::$behaviour_apply_rules[] = "Behaviour.apply('{$JS_domID}', true);";
    244             }
    245         }
    246     }
    247
    248     /**
    249      * @return string Compiled string of javascript-function-calls (needs to be evaluated on the client-side!)
    250      */
    251     protected static function get_javascript() {
    252         $js = "";
    253
    254         // select only one status message (with priority on "bad" messages)
    255         $msg = "";
    256         foreach(self::$status_include_order as $status) {
    257             if(isset(self::$status_messages[$status])) {
    258                 $msg = self::$status_messages[$status];
    259                 break;
    260             }
    261         }
    262         if(!empty($msg)) self::$rules[] = $msg;
    263
    264
    265         $js .= implode("\n", self::$rules);
    266         $js .= Requirements::get_custom_scripts();
    267
    268         // make sure behaviour is applied AFTER all registers are collected
    269         $js .= implode("\n", self::$behaviour_apply_rules);
    270
    271         return $js;
    272     }
    273 }
    274 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -