1 /* 2 * Copyright (C) 2009 Mihai Şucan 3 * 4 * This file is part of PaintWeb. 5 * 6 * PaintWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * PaintWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * $URL: http://code.google.com/p/paintweb $ 20 * $Date: 2009-10-29 19:05:49 +0200 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Holds the integration code for PaintWeb inside <a 26 * href="http://www.moodle.org">Moodle</a>. 27 */ 28 29 /** 30 * @class The Moodle extension for PaintWeb. This extension handles the Moodle 31 * integration inside the PaintWeb code. 32 * 33 * <p><strong>Note:</strong> This extension is supposed to work with Moodle 1.9 34 * and Moodle 2.0. 35 * 36 * @param {PaintWeb} app Reference to the main paint application object. 37 */ 38 pwlib.extensions.moodle = function (app) { 39 var _self = this, 40 appEvent = pwlib.appEvent, 41 config = app.config, 42 gui = app.gui, 43 lang = app.lang.moodle, 44 moodleServer = config.moodleServer, 45 tinymceEditor = null, 46 qfErrorShown = false; 47 48 // Holds information related to Moodle. 49 var moodleInfo = { 50 // Holds the URL of the image the user is saving. 51 imageURL: null, 52 53 // The class name for the element which holds the textarea buttons (toggle 54 // on/off). 55 // This element exists only in Moodle 1.9. 56 textareaButtons: 'textareaicons', 57 58 // The image save handler script on the server-side. The path is relative to 59 // the PaintWeb base folder. 60 // This used with Moodle 2.0. 61 imageSaveHandler20: '../ext/moodle/imagesave20.php', 62 63 // The image save handler script for Moodle 1.9. 64 imageSaveHandler19: '../ext/moodle/imagesave19.php', 65 66 // This holds the release version of Moodle being used. This should be 1.9 67 // or 2.0. 68 release: 0, 69 70 // Moodle 2.0 draft item ID used for file storage. 71 draftitemid: null 72 }; 73 74 /** 75 * The <code>extensionRegister</code> event handler. Setup event listeners, 76 * determine Moodle version, and more. 77 * 78 * @returns {Boolean} True if the extension initialized successfully, or false 79 * if not. 80 */ 81 this.extensionRegister = function () { 82 // Register application events. 83 app.events.add('guiShow', this.guiShow); 84 app.events.add('guiHide', this.guiHide); 85 app.events.add('imageSave', this.imageSave); 86 87 if (moodleServer && moodleServer.release) { 88 var matches = moodleServer.release.match(/^\s*(\d+\.\d+)/); 89 if (matches && matches[1]) { 90 moodleInfo.release = parseFloat(matches[1]); 91 } 92 } 93 94 if (typeof window.qf_errorHandler === 'function' && config.tinymce && 95 !config.tinymce.onSubmitUnsaved) { 96 config.tinymce.onSubmitUnsaved = this.onSubmitUnsaved; 97 } 98 99 return true; 100 }; 101 102 /** 103 * The <code>submit</code> event handler for the form to which the PaintWeb 104 * instance is attached to. This method is invoked by the TinyMCE plugin when 105 * the form is submitted while the user edits an image with unsaved changes. 106 * @private 107 */ 108 this.onSubmitUnsaved = function () { 109 var tmce = config.tinymceEditor, 110 textarea = tmce ? tmce.getElement() : null, 111 guiPlaceholder = config.guiPlaceholder, 112 prevSibling = guiPlaceholder.previousSibling; 113 114 if (tmce && textarea && window.qf_errorHandler) { 115 try { 116 qf_errorHandler(textarea, "\n - " + lang.errorSubmitUnsaved); 117 } catch (err) { 118 return; 119 } 120 121 qfErrorShown = true; 122 123 // Due to the styling of the error shown by Moodle, PaintWeb must have 124 // clear:right. 125 if (prevSibling && prevSibling.className && 126 prevSibling.className.indexOf('paintweb_tinymce_status') !== -1) { 127 prevSibling.style.clear = 'right'; 128 } else { 129 guiPlaceholder.style.clear = 'right'; 130 } 131 } 132 }; 133 134 /** 135 * The <code>imageSave</code> application event handler. When the user 136 * attempts to save an image, this extension handles the event by sending the 137 * image data to the Moodle server, to perform the actual save operation. 138 * 139 * @private 140 * @param {pwlib.appEvent.imageSave} ev The application event object. 141 */ 142 this.imageSave = function (ev) { 143 if (!ev.dataURL) { 144 return; 145 } 146 147 ev.preventDefault(); 148 149 moodleInfo.imageURL = config.imageLoad.src; 150 if (!moodleInfo.imageURL || moodleInfo.imageURL.substr(0, 5) === 'data:') { 151 moodleInfo.imageURL = '-'; 152 } 153 154 if (config.moodleSaveMethod === 'dataURL') { 155 app.events.dispatch(new appEvent.imageSaveResult(true, 156 moodleInfo.imageURL, ev.dataURL)); 157 158 } else { 159 var handlerURL = PaintWeb.baseFolder, 160 send = 'url=' + encodeURIComponent(moodleInfo.imageURL) + 161 '&dataURL=' + encodeURIComponent(ev.dataURL), 162 headers = {'Content-Type': 'application/x-www-form-urlencoded'}; 163 164 // In Moodle 2.0 we include the context ID and the draft item ID, such 165 // that the image save script can properly save the new image inside the 166 // current draft area of the current textarea element. 167 if (moodleInfo.release >= 2) { 168 handlerURL += moodleInfo.imageSaveHandler20; 169 if (moodleServer.contextid) { 170 send += '&contextid=' + encodeURIComponent(moodleServer.contextid); 171 } 172 if (moodleInfo.draftitemid) { 173 send += '&draftitemid=' + encodeURIComponent(moodleInfo.draftitemid); 174 } 175 176 } else { 177 handlerURL += moodleInfo.imageSaveHandler19; 178 } 179 180 pwlib.xhrLoad(handlerURL, imageSaveReady, 'POST', send, headers); 181 } 182 }; 183 184 /** 185 * The image save <code>onreadystatechange</code> event handler for the 186 * <code>XMLHttpRequest</code> which performs the image save. This function 187 * uses the reply to determine if the image save operation is successful or 188 * not. 189 * 190 * <p>The {@link pwlib.appEvent.imageSaveResult} application event is 191 * dispatched. 192 * 193 * <p>The server-side script must reply with a JSON object with the following 194 * properties: 195 * 196 * <ul> 197 * <li><var>successful</var> which tells if the image save operation was 198 * successful or not; 199 * 200 * <li><var>url</var> which must tell the same URL as the image we just 201 * saved (sanity/security check); 202 * 203 * <li><var>urlNew</var> is optional. This allows the server-side script to 204 * change the image URL; 205 * 206 * <li><var>errorMessage</var> is optional. When the image save was not 207 * successful, an error message can be displayed. 208 * </ul> 209 * 210 * @private 211 * @param {XMLHttpRequest} xhr The XMLHttpRequest object. 212 */ 213 function imageSaveReady (xhr) { 214 if (!xhr || xhr.readyState !== 4) { 215 return; 216 } 217 218 var result = {successful: false, url: moodleInfo.imageURL}; 219 220 if ((xhr.status !== 304 && xhr.status !== 200) || !xhr.responseText) { 221 alert(lang.xhrRequestFailed); 222 223 app.events.dispatch(new appEvent.imageSaveResult(false, result.url, null, 224 lang.xhrRequestFailed)); 225 226 return; 227 } 228 229 try { 230 result = JSON.parse(xhr.responseText); 231 } catch (err) { 232 result.errorMessage = lang.jsonParseFailed + "\n" + err; 233 alert(result.errorMessage); 234 } 235 236 if (result.successful) { 237 if (result.url !== moodleInfo.imageURL) { 238 alert(pwlib.strf(lang.urlMismatch, { 239 url: moodleInfo.imageURL, 240 urlServer: result.url || 'null'})); 241 } 242 } else { 243 if (result.errorMessage) { 244 alert(lang.imageSaveFailed + "\n" + result.errorMessage); 245 } else { 246 alert(lang.imageSaveFailed); 247 } 248 } 249 250 app.events.dispatch(new appEvent.imageSaveResult(result.successful, 251 result.url, result.urlNew, result.errorMessage)); 252 }; 253 254 /** 255 * The <code>guiShow</code> application event handler. When the PaintWeb GUI 256 * is shown, we must hide the textarea icons for the current textarea element, 257 * inside a Moodle page. 258 * @private 259 */ 260 this.guiShow = function () { 261 var pNode = config.guiPlaceholder.parentNode, 262 textareaButtons 263 = pNode.getElementsByClassName(moodleInfo.textareaButtons)[0]; 264 265 // These show in Moodle 1.9. 266 if (textareaButtons) { 267 textareaButtons.style.display = 'none'; 268 } 269 270 qfErrorShown = false; 271 272 // For Moodle 2.0 we must determine the draft item ID in order to properly 273 // perform the image save operation into the current draft area. 274 if (moodleInfo.release < 2) { 275 return; 276 } 277 278 // Typically the TinyMCE editor instance is attached to a textarea element 279 // which has a name=whatever[text] or similar form. In the same form as the 280 // textarea, there must be a hidden input element with the 281 // name=whatever[itemid]. The value of that input holds the draft item ID. 282 var tmce = config.tinymceEditor, 283 textarea = tmce ? tmce.getElement() : null, 284 frm = textarea ? textarea.form : null; 285 286 if (!tmce || !textarea || !textarea.name || !frm) { 287 return; 288 } 289 290 var fieldname = textarea.name.replace(/\[text\]$/, ''); 291 if (!fieldname) { 292 return; 293 } 294 295 var draftitemid = frm.elements.namedItem(fieldname + '[itemid]'), 296 format = frm.elements.namedItem(fieldname + '[format]'); 297 298 if (draftitemid) { 299 moodleInfo.draftitemid = draftitemid.value; 300 } 301 302 if (format) { 303 format.style.display = 'none'; 304 } 305 }; 306 307 /** 308 * The <code>guiHide</code> application event handler. When the PaintWeb GUI 309 * is hidden, we must show again the textarea icons for the current textarea 310 * element, inside a Moodle page. 311 * @private 312 */ 313 this.guiHide = function () { 314 var guiPlaceholder = config.guiPlaceholder, 315 prevSibling = guiPlaceholder.previousSibling; 316 pNode = guiPlaceholder.parentNode, 317 textareaButtons 318 = pNode.getElementsByClassName(moodleInfo.textareaButtons)[0]; 319 320 // These show in Moodle 1.9. 321 if (textareaButtons) { 322 textareaButtons.style.display = ''; 323 } 324 325 var tmce = config.tinymceEditor, 326 textarea = tmce ? tmce.getElement() : null, 327 frm = textarea ? textarea.form : null; 328 329 if (!tmce || !textarea || !textarea.name || !frm) { 330 return; 331 } 332 333 if (qfErrorShown) { 334 if (window.qf_errorHandler) { 335 qf_errorHandler(textarea, ''); 336 } 337 338 if (prevSibling && prevSibling.className && 339 prevSibling.className.indexOf('paintweb_tinymce_status') !== -1) { 340 prevSibling.style.clear = ''; 341 } else { 342 guiPlaceholder.style.clear = ''; 343 } 344 } 345 346 // The format input element only shows in Moodle 2.0. 347 if (moodleInfo.release >= 2) { 348 var fieldname = textarea.name.replace(/\[text\]$/, ''); 349 if (!fieldname) { 350 return; 351 } 352 353 var format = frm.elements.namedItem(fieldname + '[format]'); 354 355 if (format) { 356 format.style.display = ''; 357 } 358 } 359 }; 360 }; 361 362 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 363 364