diff --git a/sites/all/modules/autosave/LICENSE.txt b/sites/all/modules/autosave/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/sites/all/modules/autosave/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/autosave/README.txt b/sites/all/modules/autosave/README.txt new file mode 100644 index 0000000..bc0bf49 --- /dev/null +++ b/sites/all/modules/autosave/README.txt @@ -0,0 +1,34 @@ +DESCRIPTION +=========== +The Autosave module automatically saves a node after a period of time. Content types that can be autosaved as well as the +period of time a node is autosaved, is configurable. + +Autosaved nodes are are saved as a snap shot of the form. + +NOTE: this version of autosave only works for single form (of selected node type) on a page. + + +DEPENDENCIES +============ +Includes the jQuery Form Plugin which was downloaded from here: http://dev.jquery.com/browser/trunk/plugins/form/jquery.form.js?format=txt + +NOTE: the plugin has been modified to work with Drupal field names + +INSTALLATION +============ +1. Place the "autosave" folder in your "modules" directory (i.e. modules/autosave). +2. Enable the Autosave module under Administer >> Site building >> Modules. +3. Under config for a node type select it to use Autosave. +5. Under Admin -> Site Config -> Autosave enter the period of time before each autosave (in milliseconds). + +AUTHOR +====== +original concept by Edmund Kwok (edmund.kwok [at] insyghtful.com) +Drupal 6 version and current maintainer: Peter Lindstrom of LiquidCMS + +CHANGE LOG +========== +- 6.x-2.0 version is a complete re-write to remove dependencies on TinyMCE. +- this version is now tied to the WYSIWYG module and currently is known to work with FCK, CK and TinyMCE 3.0 editors but requires +the 6.x-2.x-dev version of WYSIWYG with this patch: http://drupal.org/node/614146#comment-2193764; this patch should be commited soon and will +eventually be expanded to include other editors. \ No newline at end of file diff --git a/sites/all/modules/autosave/autosave.css b/sites/all/modules/autosave/autosave.css new file mode 100644 index 0000000..d50dba6 --- /dev/null +++ b/sites/all/modules/autosave/autosave.css @@ -0,0 +1,74 @@ +#autosave-status { + display: none; + text-align:left; + z-index:99; + color:#FFF; + background:#37a; + position:fixed; + width:100%; + height: 2em; + bottom:0px; right:0px; + overflow:hidden; +} + +#autosave-status img .left-arrow{ + position: absolute; + top: -6px; + margin-left: 50px; +} + +#autosave-status img .right-arrow{ + position: absolute; + top: -6px; + margin-left: 20px; +} + +#autosave-status .arrow { + background-color:#66A1CC; + border:1px solid #2E516B; + color:#2E516B; + display:inline; + height:15px; + margin-left:60px; + margin-top:4px; + position:absolute; + text-align:center; + width:15px; + line-height:1em; +} + +#autosave-status #right-arrow { + margin-left: 80px; +} + +#autosave-status .disabled { + background-color: #3174A5; +} + +#autosave-status .enabled:hover { + background-color: #B3D0E6; + text-decoration: none; + cursor: pointer; +} + +#autosave-status #status { + margin-left: 5px; +} + +#autosave-status #view a { + margin-left: 40px; + color: #FFF; + text-decoration: underline; +} + +#autosave-status #delete a { + margin-left: 20px; + color: #FFF; + text-decoration: underline; +} + +#autosave-status a { + margin-left: 20px; + color: #FFF; + text-decoration: underline; +} diff --git a/sites/all/modules/autosave/autosave.info b/sites/all/modules/autosave/autosave.info new file mode 100644 index 0000000..509e011 --- /dev/null +++ b/sites/all/modules/autosave/autosave.info @@ -0,0 +1,16 @@ +name = Autosave +description = Saves node edits in the background in case browser dies while editing. + +package = Other +project = autosave + +core = 6.x + + + +; Information added by drupal.org packaging script on 2012-08-06 +version = "6.x-2.11" +core = "6.x" +project = "autosave" +datestamp = "1344266211" + diff --git a/sites/all/modules/autosave/autosave.install b/sites/all/modules/autosave/autosave.install new file mode 100644 index 0000000..93c33b7 --- /dev/null +++ b/sites/all/modules/autosave/autosave.install @@ -0,0 +1,63 @@ +configuration settings.', array('@settings' => url('admin/settings/autosave')))); +} + +/** + * Implementation of hook_install(). + */ +function autosave_install() { + drupal_install_schema('autosave'); +} + +/** + * Implementation of hook_uninstall(). + */ +function autosave_uninstall() { + drupal_uninstall_schema('autosave'); +} + +/** + * Implementation of hook_schema() + */ +function autosave_schema() { + return array( + 'autosaved_forms' => array( + 'description' => 'Saves the input (POST) contents of partially filled forms for restoration by the autosave module.', + 'fields' => array( + 'form_id' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'path' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'uid' => array( + 'type' => 'int', + 'length' => 11, + 'not null' => TRUE, + 'default' => 0, + ), + 'timestamp' => array( + 'type' => 'int', + 'length' => 11, + 'not null' => TRUE, + 'default' => 0, + ), + 'serialized' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array('form_id', 'path', 'uid'), + ), + ); +} diff --git a/sites/all/modules/autosave/autosave.js b/sites/all/modules/autosave/autosave.js new file mode 100644 index 0000000..73a59a1 --- /dev/null +++ b/sites/all/modules/autosave/autosave.js @@ -0,0 +1,130 @@ +var autosaved_form; + +if (Drupal.jsEnabled) { + $(document).ready(function() { + $('body').append(''); + autosaved = Drupal.settings.autosave; + autosaved_form_id = 'node-form'; + + if (autosaved.serialized) { + $('#autosave-status #keep').css('display', 'none').css('visibility', 'hidden'); + $('#autosave-status #view a').click(function() { + if ($(this).attr('class') == 'view') { + $('#' + autosaved_form_id).formHash(autosaved.serialized); + if (Drupal.settings.autosave.wysiwyg && Drupal.wysiwyg) { + // need to loop through any WYSIWYG editor fields and update the visible iframe fields with hidden field content + for (var instance in Drupal.wysiwyg.instances) { + Drupal.wysiwyg.instances[instance].setContent($('#' + instance).val()); + } + } + + //CKEditor support + if (typeof(CKEDITOR) != 'undefined' ) { + for (var instance in CKEDITOR.instances) { + CKEDITOR.instances[instance].setData($('#' + instance).val()); + } + } + + $('#' + autosaved_form_id).focus(); + $(this).removeClass('view').addClass('reset'); + $(this).html(Drupal.t('Reset')); + $('#autosave-status #view a').attr('title', Drupal.t("Return to Last Saved Version")); + $('#autosave-status #keep').css('display', 'inline').css('visibility', 'visible'); + $('#autosave-status #keep a').html(Drupal.t('Keep')); + } + else if ($(this).attr('class') == 'reset') { + $(this).removeClass('reset').addClass('view'); + form = document.getElementById(autosaved_form_id); + form.reset(); + + //CKEditor support + if (typeof(CKEDITOR) != 'undefined' ) { + for (var instance in CKEDITOR.instances) { + CKEDITOR.instances[instance].setData($('#' + instance).val()); + } + } + + $('#autosave-status #keep').css('display', 'none').css('visibility', 'hidden'); + $(this).html(Drupal.t('View')); + } + return false; + }); + $('#autosave-status #ignore a').click(function() { + $('#autosave-status').fadeOut('slow'); + form = document.getElementById(autosaved_form_id); + form.reset(); + + //CKEditor support + if (typeof(CKEDITOR) != 'undefined' ) { + for (var instance in CKEDITOR.instances) { + CKEDITOR.instances[instance].setData($('#' + instance).val()); + } + } + + $('#autosave-status #operations').css('display', 'none').css('visibility', 'hidden'); + Drupal.attachAutosave(); + return false; + }); + $('#autosave-status #keep a').click(function() { + $('#autosave-status').fadeOut('slow'); + form = document.getElementById(autosaved_form_id); + $('#autosave-status #operations').css('display', 'none').css('visibility', 'hidden'); + Drupal.attachAutosave(); + return false; + }); + $('#autosave-status #status').html(Drupal.t('This form was autosaved on @date', {'@date' : autosaved.saved_date})); + $('#autosave-status').slideDown(); + } + // There are no autosaved forms, continue with autosave. + else { + Drupal.attachAutosave(); + } + }); +} + +Drupal.saveForm = function() { + if (Drupal.settings.autosave.wysiwyg && Drupal.wysiwyg) { + // need to loop through any WYSIWYG editor fields and update the real (hidden) text fields before saving + for (var instance in Drupal.wysiwyg.instances) { + if (Drupal.wysiwyg.instances[instance].editor != 'none') { + var content = Drupal.wysiwyg.instances[instance].getContent(); + $('#' + instance).val(content); + } + } + } + + //CKEditor support + if (typeof(CKEDITOR) != 'undefined') { + for (var instance in CKEDITOR.instances) { + CKEDITOR.instances[instance].updateElement(); + } + } + + var serialized = $('#node-form').formHash(); + serialized['autosave_path'] = Drupal.settings.autosave.autosave_path; + $.ajax({ + url: Drupal.settings.autosave.url, + type: "POST", + dataType: "xml/html/script/json", + data: serialized, + complete: function(XMLHttpRequest, textStatus) { + if (!Drupal.settings.autosave.hidden) Drupal.displaySaved(); + Drupal.attachAutosave(); + } + }); +} + +Drupal.attachAutosave = function() { + setTimeout('Drupal.saveForm()', Drupal.settings.autosave.period * 1000); +} + +Drupal.displaySaved = function() { + $('#autosave-status #status').html(Drupal.t('Form autosaved.')); + $('#autosave-status #operations').css('display', 'none').css('visibility', 'hidden'); + $('#autosave-status').slideDown(); + setTimeout("$('#autosave-status').fadeOut('slow')", 3000); +} + diff --git a/sites/all/modules/autosave/autosave.module b/sites/all/modules/autosave/autosave.module new file mode 100644 index 0000000..ab8bbae --- /dev/null +++ b/sites/all/modules/autosave/autosave.module @@ -0,0 +1,217 @@ +'. t('The autosave module automatically saves a form after a period of time.') .'

'; + break; + } + return $output; +} + +/** + * Implementation of hook_menu(). + */ +function autosave_menu() { + $items['autosave/handler/%'] = array( + 'title' => 'Autosave save', + 'page callback' => 'autosave_save', + 'access callback' => 'autosave_save_access', + 'type' => MENU_CALLBACK, + ); + + $items['admin/settings/autosave'] = array( + 'title' => 'Autosave', + 'description' => 'Configure autosave settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('autosave_admin_settings'), + 'access arguments' => array('administer nodes'), + ); + return $items; +} + +/** + * Access callback for the form save menu callback. + * + * + * For security reasons, we need to confirm that the user would have access + * to the page where the form lives in the first place. If they don't, they + * should not be able to access its saved version. We also check that the + * form's token is correct to avoid CSRF attacks. + * + * Because the form data is not available to us, the only way we can access + * the path is by checking $_POST directly. Sux. + * + * @return boolean + * True if this user should have access to save this form, false otherwise. +*/ +function autosave_save_access() { + $path = trim($_POST['autosave_path'], '/'); + $menu_item = menu_get_item($path); + + $token = isset($_POST['form_token'], $_POST['form_id']) && drupal_valid_token($_POST['form_token'], $_POST['form_id']); + $menu = isset($menu_item['access']) ? $menu_item['access'] : FALSE; + return $token && $menu; +} + +/** + * Menu callback; return the autosave module settings form. + */ +function autosave_admin_settings() { + $form['autosave_period'] = array( + '#type' => 'textfield', + '#title' => t('Autosave after this amount seconds has passed'), + '#default_value' => variable_get('autosave_period', 10), + ); + + $form['autosave_hidden'] = array( + '#prefix' => '
', + '#type' => 'checkbox', + '#title' => t('Run in stealth mode'), + '#description' => t('If this check box is selected no popup will appear notifying user that the form has been autosaved.'), + '#default_value' => variable_get('autosave_hidden', 0), + '#suffix' => "
", + ); + + return system_settings_form($form); +} + +/** + * Implementation of hook_form_alter() for node_type_form + */ +function autosave_form_node_type_form_alter(&$form, $form_state) { + $form['workflow']['autosave'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Autosave to add/edit forms for this node type'), + '#default_value' => variable_get('autosave_'. $form['#node_type']->type, 0), + '#description' => t('Check this box to enable Autosave for this node type.') + ); +} + +/** + * Implementation of hook_form_alter(). + */ +function autosave_form_alter(&$form, &$form_state, $form_id) { + global $user; + $path = $_GET['q']; + + if (stristr($form_id, '_node_form') && arg(0) != 'admin') { + + // check if this content_type has the autosave function enabled and make sure it's a node edit or add form + if ((variable_get('autosave_'. $form['type']['#value'], 0))) { + drupal_add_js(AUTOSAVE_PATH .'/autosave.js'); + drupal_add_js(AUTOSAVE_PATH .'/jquery.field.js'); + drupal_add_css(AUTOSAVE_PATH .'/autosave.css'); + + // if WYSIWYG module is enabled; lets let JS know this + if (module_exists('wysiwyg')) $settings['autosave']['wysiwyg'] = 1; + else $settings['autosave']['wysiwyg'] = 0; + + // add security token + $token = drupal_get_token($form_id); + + $settings['autosave']['url'] = url('autosave/handler/' . $token); + $settings['autosave']['period'] = variable_get('autosave_period', 10); + $settings['autosave']['autosave_path'] = $path; + $settings['autosave']['hidden'] = variable_get('autosave_hidden', 0); + + // If an autosaved version of the form exists, make it available via javascript. + if ($autosaved_form = autosave_get_autosaved_form($form_id, $path, $user->uid)) { + //$autosaved_form_id = $form['type']['#value'] ? $form['type']['#value'] .'_node_form' : 'node_form'; + $settings['autosave'] = array_merge($settings['autosave'], array( + 'serialized' => unserialize($autosaved_form['serialized']), + 'saved_date' => format_date($autosaved_form['timestamp'], 'medium'), + )); + } + drupal_add_js($settings, 'setting'); + } + } +} + +/** + * Menu callback; autosaves the node. + */ +function autosave_save() { + global $user; + $path = $_POST['autosave_path']; + $form_id = $_POST['form_id']; + + // Not all variables need to be serialized. + // - for Drupal 6 version need to remove op and form_build_id + unset($_POST['autosave_path'], $_POST['op'], $_POST['form_build_id']); + $serialized = serialize($_POST); + + // check if node has just been saved - if it has then it's because AS ajax fired off as user was submitting + // if it had just been submitted - no need to AS now + // - easy to figure out if we are submitting an edit to existing node + // - little harder if we have just added a node + $path_args = explode("/", $path); + + // update case + if (is_numeric($path_args[1])) { + $submitted = node_load($path_args[1]); + } + + // add case + else { + $submitted->changed = db_result(db_query("SELECT created FROM {node} WHERE uid = %d and type = '%s' ORDER BY created DESC LIMIT 1", $user->uid, str_replace("-", "_", $path_args[2]))); + } + + if (!$submitted || (time() - $submitted->changed) > 10) { + // Currently, each user can have only one autosave form at a particular path. + db_query("DELETE FROM {autosaved_forms} WHERE form_id = '%s' AND path = '%s' AND uid = %d", $form_id, $path, $user->uid); + db_query("INSERT INTO {autosaved_forms} (form_id, path, uid, timestamp, serialized) VALUES ('%s', '%s', %d, %d, '%s')", $form_id, $path, $user->uid, time(), $serialized); + } + + exit(); +} + +/** + * Get the autosaved form at a particular path for a user. + * + * @param string $form_id + * The form_id of the form. + * @param string $path + * The the internal Drupal path where the form is located + * @param string $uid + * Drupal UID of the user + * @return + * An array containing the serialized values of the autosaved form and the timestamp of when the form was autosaved. + */ +function autosave_get_autosaved_form($form_id, $path, $uid) { + $result = db_query("SELECT form_id, serialized, timestamp FROM {autosaved_forms} WHERE form_id = '%s' AND path = '%s' AND uid = %d", $form_id, $path, $uid); + + while ($data = db_fetch_object($result)) { + $form['serialized'] = $data->serialized; + $form['timestamp'] = $data->timestamp; + } + return $form; +} + +/** + * Implementation of hook_nodeapi(). + * + * Delete autosave table entry on successful submit (add or update) of node + * + */ +function autosave_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { + if ($op == 'presave') { + // we remove ALL edits for that page (not just the users) to avoid: + // - user1 asaves but doesnt submit + // - user2 edits same node and submits + // - user1 comes back to edit -> user1 SHOULD lose edits since user2 has precedence + db_query("DELETE FROM {autosaved_forms} WHERE form_id = '%s' AND path = '%s'", $node->form_id, $_GET['q']); + } +} diff --git a/sites/all/modules/autosave/jquery.field.js b/sites/all/modules/autosave/jquery.field.js new file mode 100644 index 0000000..1d20218 --- /dev/null +++ b/sites/all/modules/autosave/jquery.field.js @@ -0,0 +1,670 @@ + +/* + * jQuery Field Plug-in + * + * Copyright (c) 2007 Dan G. Switzer, II + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: 8 + * Version: 0.7-mod + * + * NOTES: The getValue() and setValue() methods are designed to be + * executed on single field (i.e. any field that would share the same + * "name" attribute--a single text box, a group of checkboxes or radio + * elements, etc.) + * + * Revision History + * + * v0.7a-mod + * - plindstrom: change all "@name" to just "name" to be compatible with jquery 1.3 (seems to still be compatible with jq 1.2 as well) + * + * v0.7-mod + * - Modified slightly by Edmund Kwok to work with Drupal's autosave.module. Refer to $.formHash(). + * + * v0.7 + * - Added tabIndex related function (getTabIndex, moveNext, movePrev, moveIndex) + * + * v0.6 + * - Fixed bug in the $.formHash() where the arrayed form elements would + * not correctly report their values. + * - Added the $.createCheckboxRange() which allow you to select multiple + * checkbox elements by doing a [SHIFT] + click. + * + * v0.5 + * - Added $.limitSelection() method for limiting the number of + * selection in a select-multiple of checkbox array. + * + * v0.4.1 + * - Moved $.type and $.isType into private functions + * - Rewrote $type() function to use instanceof operator + * + * v0.4 + * - Added the formHash() method + * + * v0.3 + * - First public release + * +*/ +(function($){ + + // set the defaults + var defaults = { + // use a comma as the string delimiter + delimiter: ",", + // for methods that could return either a string or array, decide default behavior + useArray: false + } + + // set default options + $.Field = { + version: "0.7", + setDefaults: function(options){ + $.extend(defaults, options); + } + } + + + /* + * jQuery.fn.fieldArray() + * + * returns either an array of values or a jQuery object + * + * NOTE: This *MAY* break the jQuery chain + * + * Examples: + * $("input[name='name']").fieldArray(); + * > Gets the current value of the name text element + * + * $("input[name='name']").fieldArray(["Dan G. Switzer, II"]); + * > Sets the value of the name text element to "Dan G. Switzer, II" + * + * $("select[name='state']").fieldArray(); + * > Gets the current value of the state text element + * + * $("select[name='state']").setValue(["OH","NY","CA"]); + * > Sets the selected value of the "state" select element to OH, NY and CA + * + */ + // this will set/get the values for a field based upon and array + $.fn.fieldArray = function(v){ + var t = $type(v); + + // if no value supplied, return an array of values + if( t == "undefined" ) return getValue(this); + + // convert the number/string into an array + if( t == "string" || t == "number" ){ + v = v.toString().split(defaults.delimiter); + t = "array"; + } + + // set the value -- doesn't break the chaing + if( t == "array" ) return setValue(this, v); + + // if we don't know what do to, don't break the chain + return this; + } + + /* + * jQuery.fn.getValue() + * + * returns String - a comma delimited list of values for the field + * + * NOTE: Breaks the jQuery chain, since it returns a string. + * + * Examples: + * $("input[name='name']").getValue(); + * > This would return the value of the name text element + * + * $("select[name='state']").getValue(); + * > This would return the currently selected value of the "state" select element + * + */ + // the getValue() method -- break the chain + $.fn.getValue = function(){ + // return the values as a comma-delimited string + return getValue(this).join(defaults.delimiter); + } + + /* + * getValue() + * + * returns Array - an array of values for the field + * + */ + // the getValue() method -- break the chain + var getValue = function(jq){ + var v = []; + + jq.each( + function (lc){ + // get the current type + var t = getType(this); + + switch( t ){ + case "checkbox": case "radio": + // if the checkbox or radio element is checked + if( this.checked ) v.push(this.value); + break; + + case "select": + if( this.type == "select-one" ){ + v.push( (this.selectedIndex == -1) ? "" : getOptionVal(this[this.selectedIndex]) ); + } else { + // loop through all element in the array for this field + for( var i=0; i < this.length; i++ ){ + // if the element is selected, get the selected values + if( this[i].selected ){ + // append the selected value, if the value property doesn't exist, use the text + v.push(getOptionVal(this[i])); + } + } + } + break; + + case "text": + v.push(this.value); + break; + } + } + ); + + // return the values as an array + return v; + } + + /* + * setValue() + * + * returns jQuery object + * + * NOTE: This does *NOT* break the jQuery chain + * + * Examples: + * $("input[name='name']").setValue("Dan G. Switzer, II"); + * > Sets the value of the name text element to "Dan G. Switzer, II" + * + * $("select[name='state']").setValue("OH"); + * > Sets the selected value of the "state" select element to "OH" + * + */ + // the setValue() method -- does *not* break the chain + $.fn.setValue = function(v){ + // f no value, set to empty string + return setValue(this, (!v ? [""] : v.toString().split(defaults.delimiter))); + } + + /* + * setValue() + * + * returns jQuery object + * + */ + // the setValue() method -- does *not* break the chain + var setValue = function(jq, v){ + + jq.each( + function (lc){ + var t = getType(this), x; + + switch( t ){ + case "checkbox": case "radio": + if( valueExists(v, this.value) ) this.checked = true; + else this.checked = false; + break; + + case "select": + var bSelectOne = (this.type == "select-one"); + var bKeepLooking = true; // if select-one type, then only select the first value found + // loop through all element in the array for this field + for( var i=0; i < this.length; i++ ){ + x = getOptionVal(this[i]); + bSelectItem = valueExists(v, x); + if( bSelectItem ){ + this[i].selected = true; + // if a select-one element + if( bSelectOne ){ + // no need to look farther + bKeepLooking = false; + // stop the loop + break; + } + } else if( !bSelectOne ) this[i].selected = false; + } + // if a select-one box and nothing selected, then try to select the default value + if( bSelectOne && bKeepLooking ){ + this[0].selected = true; + } + break; + + case "text": + this.value = v.join(defaults.delimiter); + break; + } + + } + ); + + return jq; + } + + /* + * jQuery.fn.formHash() + * + * returns either an hash table of form fields or a jQuery object + * + * NOTE: This *MAY* break the jQuery chain + * + * Examples: + * $("#formName").formHash(); + * > Returns a hash map of all the form fields and their values + * + * $("#formName").formHash({"name": "Dan G. Switzer, II", "state": "OH"}); + * > Returns the jQuery chain and sets the fields "name" and "state" with + * > the values "Dan G. Switzer, II" and "OH" respectively. + * + */ + // the formHash() method -- break the chain + $.fn.formHash = function(inHash){ + var bGetHash = (arguments.length == 0); + // create a hash to return + var stHash = {}; + + // run the code for each form + this.filter("form").each( + function (){ + // get all the form elements + var els = this.elements, el, n, stProcessed = {}, jel; + // loop through the elements and process + for( var i=0, elsMax = els.length; i < elsMax; i++ ){ + el = els[i], n = el.name; + + // Avoid submit buttons; Drupal forms with two submit buttons (eg Submit and Cancel) usually has the same name - op. + // These buttons will then have same names when value is set. + if (el.type == 'submit') { + continue; + } + + // if the element doesn't have a name, then skip it + if( !n || stProcessed[n] ) continue; + + // create a jquery object to the current named form elements + var jel = $(el.tagName.toLowerCase() + "[name='"+n+"']", this); + + // if we're getting the values, get them now + if( bGetHash ){ + stHash[n] = jel[defaults.useArray ? "fieldArray" : "getValue"](); + // if we're setting values, set them now + } else { + if(!!inHash[n] ){ + jel[defaults.useArray ? "fieldArray" : "setValue"](inHash[n]); + } + else { + // Deal with Drupal form elements with square brackets in their name (eg field_name[key]). + var value=null; + n = n.replace(/\[/g,','); n = n.replace(/\]/g,','); + keys = n.split(","); + if (keys.length > 1) { + for (var key in keys) { + if (keys[key]) { + if (!value) value = inHash[keys[key]]; + else value = value[keys[key]]; + } + } + } + jel[defaults.useArray ? "fieldArray" : "setValue"](value); + } + } + stProcessed[n] = true; + } + } + ); + + // if getting a hash map return it, otherwise return the jQuery object + return (bGetHash) ? stHash : this; + } + + /* + * jQuery.fn.autoAdvance() + * + * Finds all text-based input fields and makes them autoadvance to the next + * fields when they've met their maxlength property. + * + * + * Examples: + * $("#form").autoAdvance(); + * > When a field reaches it's maxlength attribute value, it'll advance to the + * > next field in the form's tabindex. + * + */ + // the autoAdvance() method + $.fn.autoAdvance = function(){ + return this.find(":text,:password,textarea").bind( + "keyup", + function (e){ + var + // get the field + $field = $(this), + // get the maxlength for the field + iMaxLength = parseInt($field.attr("maxlength"), 10); + + // if the user tabs to the field, exit event handler + // this will prevent movement if the field is already + // field in with the max number of characters + if( isNaN(iMaxLength) || ("|9|16|37|38|39|40|".indexOf("|" + e.keyCode + "|") > -1) ) return true; + + // if the value of the field is greater than maxlength attribute, + // then move the focus to the next field + if( $field.getValue().length >= $field.attr("maxlength") ){ + // move to the next field and select the existing value + $field.moveNext().select(); + } + } + ); + } + + /* + * jQuery.fn.moveNext() + * + * places the focus in the next form field. if the field element is + * the last in the form array, it'll return to the top. + * + * returns a jQuery object pointing to the next field element + * + * NOTE: if the selector returns multiple items, the first item is used. + * + * + * Examples: + * $("#firstName").moveNext(); + * > Moves the focus to the next form field found after firstName + * + */ + // the moveNext() method + $.fn.moveNext = function(){ + return this.moveIndex("next"); + } + + /* + * jQuery.fn.movePrev() + * + * places the focus in the previous form field. if the field element is + * the first in the form array, it'll return to the last element. + * + * returns a jQuery object pointing to the previos field element + * + * NOTE: if the selector returns multiple items, the first item is used + * + * Examples: + * $("#firstName").movePrev(); + * > Moves the focus to the next form field found after firstName + * + */ + // the movePrev() method + $.fn.movePrev = function(){ + return this.moveIndex("prev"); + } + + /* + * jQuery.fn.moveIndex() + * + * Places the tab index into the specified index position + * + * returns a jQuery object pointing to the previos field element + * + * NOTE: if the selector returns multiple items, the first item is used + * + * Examples: + * $("#firstName").movePrev(); + * > Moves the focus to the next form field found after firstName + * + */ + // the moveIndex() method + $.fn.moveIndex = function(i){ + // get the current position and elements + var aPos = getFieldPosition(this); + + // if a string option has been specified, calculate the position + if( i == "next" ) i = aPos[0] + 1; // get the next item + else if( i == "prev" ) i = aPos[0] - 1; // get the previous item + + // make sure the index position is within the bounds of the elements array + if( i < 0 ) i = aPos[1].length-1; + else if( i >= aPos[1].length ) i = 0; + + return $(aPos[1][i]).trigger("focus"); + } + + /* + * jQuery.fn.getTabIndex() + * + * gets the current tab index of the first element found in the selector + * + * NOTE: if the selector returns multiple items, the first item is used + * + * Examples: + * $("#firstName").getTabIndex(); + * > Gets the tabIndex for the firstName field + * + */ + // the getTabIndex() method + $.fn.getTabIndex = function(){ + // return the position of the form field + return getFieldPosition(this)[0]; + } + + var getFieldPosition = function (jq){ + var + // get the first matching field + $field = jq.filter("input select textarea").get(0), + // store items with a tabindex + aTabIndex = [], + // store items with no tabindex + aPosIndex = [] + ; + + // if there is no match, return 0 + if( !$field ) return [-1, []]; + + // make a single pass thru all form elements + $.each( + $field.form.elements, + function (i, o){ + if( o.tagName != "FIELDSET" && !o.disabled ){ + if( o.tabIndex > 0 ){ + aTabIndex.push(o); + } else { + aPosIndex.push(o); + } + } + } + ); + + // sort the fields that had tab indexes + aTabIndex.sort( + function (a, b){ + return a.tabIndex - b.tabIndex; + } + ); + + // merge the elements to create the correct tab position + aTabIndex = $.merge(aTabIndex, aPosIndex); + + for( var i=0; i < aTabIndex.length; i++ ){ + if( aTabIndex[i] == $field ) return [i, aTabIndex]; + } + + return [-1, aTabIndex]; + } + + /* + * jQuery.fn.limitSelection() + * + * limits the number of items that can be selected + * + * Examples: + * $("input:checkbox").limitSelection(3); + * > No more than 3 items can be selected + * + * $("input:checkbox").limitSelection(2, errorCallback, successCallback); + * > Limits the selection to 2 items and runs the callback function when + * > more than 2 items have been selected. + * + * NOTE: Current when a "select-multiple" option undoes the selection, + * it selects the first 3 options in the array--which isn't necessarily + * the first 3 options the user selected. This is not the most desired + * behavior. + * + */ + $.fn.limitSelection = function(n, _e, _s){ + var self = this; + // define the callback actions + var cb_onError = (!!_e) ? _e : function (n){ alert("You can only select a maximum a of " + n + " items."); return false; }; + var cb_onSuccess = (!!_s) ? _s : function (n){ return true; }; + + var getCount = function (el){ + if( el.type == "select-multiple" ) return $("option:selected", self).length; + else if( el.type == "checkbox" ) return self.filter(":checked").length; + return 0; + } + + var undoSelect = function (){ + // reduce selection to n items + setValue(self, getValue(self).slice(0, n)); + // do callback + return cb_onError(n, self); + } + + self.bind( + (!!self[0] && self[0].type == "select-multiple") ? "change" : "click", + function (){ + if( getCount(this) > n ){ + // run callback, it must return false to prevent action + return (this.type == "select-multiple") ? undoSelect() : cb_onError(n, self); + } + cb_onSuccess(n, self); + return true; + } + ); + return this; + } + + /* + * jQuery.fn.createCheckboxRange() + * + * limits the number of items that can be selected + * + * Examples: + * $("input:checkbox").createCheckboxRange(); + * > Allows a [SHIFT] + mouseclick to select all the items from the last + * > checked checkmark to the current checkbox. + * + */ + $.fn.createCheckboxRange = function(){ + var iLastSelection = 0, me = this; + + // this finds the position of the current element in the array + var findArrayPos = function (el){ + var pos = -1; + $("input[name='"+me[0].name+"']").each( + function (i){ + if( this == el ){ + pos = i; + return false; + } + } + ); + + return pos; + } + + this.each( + function (lc){ + // only perform this action on checkboxes + if( this.type != "checkbox" ) return false; + var self = this; + + var updateLastCheckbox = function (e){ + iLastSelection = findArrayPos(e.target); + } + + var checkboxClicked = function (e){ + var bSetChecked = this.checked, current = findArrayPos(e.target), iHigh, iLow; + // if we don't detect the keypress, exit function + if( !e.shiftKey ) return; + + // figure out which is the highest and which is the lowest value + if( iLastSelection > current ){ + iHigh = iLastSelection; + iLow = current-1; + } else { + iHigh = current; + iLow = iLastSelection-1; + } + + $("input[name='"+self.name+"']:gt("+iLow+"):lt("+iHigh+")").attr("checked", bSetChecked ? "checked" : ""); + } + + $(this) + // unbind the events so we can re-run the createCheckboxRange() plug-in for dynamicall created elements + .unbind("blur", updateLastCheckbox) + .unbind("click", checkboxClicked) + + // bind the functions + .bind("blur", updateLastCheckbox) + .bind("click", checkboxClicked) + ; + + return true; + } + ); + } + + // determines how to process a field + var getType = function (el){ + var t = el.type; + + switch( t ){ + case "select": case "select-one": case "select-multiple": + t = "select"; + break; + case "text": case "hidden": case "textarea": case "password": case "button": case "submit": case "submit": + t = "text"; + break; + case "checkbox": case "radio": + t = t; + break; + } + return t; + } + + // gets the value of a select element + var getOptionVal = function (el){ + return jQuery.browser.msie && !(el.attributes['value'].specified) ? el.text : el.value; + } + + // checks to see if a value exists in an array + var valueExists = function (a, v){ + return ($.inArray(v, a) > -1); + } + + // correctly gets the type of an object (including array/dates) + var $type = function (o){ + var t = (typeof o).toLowerCase(); + + if( t == "object" ){ + if( o instanceof Array ) t = "array"; + else if( o instanceof Date ) t = "date"; + } + return t; + } + + // checks to see if an object is the specified type + var $isType = function (o, v){ + return ($type(o) == String(v).toLowerCase()); + } + +})(jQuery); +