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 ?>