├── .gitignore ├── README.md ├── dist ├── glz_custom_fields_v2.0.6_zip.txt ├── plugins.glz_custom_fields.zip └── plugins │ └── glz_custom_fields │ ├── glz_custom_fields.css │ ├── glz_custom_fields.js │ ├── glz_custom_fields.min.css │ ├── glz_custom_fields.min.js │ ├── glz_jqueryui.sortable.js │ ├── glz_jqueryui.sortable.min.js │ ├── jquery.datePicker │ ├── date.js │ ├── date.min.js │ ├── datePicker.css │ ├── datePicker.js │ ├── datePicker.min.css │ └── datePicker.min.js │ ├── jquery.timePicker │ ├── timePicker.css │ ├── timePicker.js │ ├── timePicker.min.css │ └── timePicker.min.js │ └── my_images.php ├── docs └── help.textile ├── legacy └── frontend.php ├── manifest.json ├── src └── glz │ ├── callbacks.php │ ├── css-js.php │ ├── db.php │ ├── field-types.php │ ├── helpers.php │ ├── main.php │ ├── prefspane.php │ ├── public-tags.php │ └── steps.php └── textpacks ├── de-de.textpack ├── en-gb.textpack └── fr-fr.textpack /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nova -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GLZ CUSTOM FIELDS for Textpattern CMS 2 | 3 | ### v2.0.6 4 | 5 | Compatible with PHP8 and Textpattern 4.9 6 | 7 | 8 | ### Update for Textpattern 4.7 – v2.0.1 9 | 10 | **IMPORTANT: Backup your database before experimenting with this version!** 11 | 12 | **Note:** This plugin has been extensively refactored and is therefore **not backwards-compatible** to earlier versions of TXP. 13 | 14 | ### Unlimited custom fields for Textpattern 15 | 16 | This plugin sits under the **Extensions** tab in the back-end and gives your custom fields new life. You can have custom fields that are selects, multi-selects, checkboxes, radios and textareas - besides the default input fields. You can predefine values for custom fields and you can select a single default value (selects, multi-selects, checkboxes and radios only). Rather then constantly typing in the same thing over and over again, just select or check it and you're set. 17 | 18 | ### Installation 19 | 20 | 1. Make a backup of your existing database for safety's sake. 21 | 2. Copy the `plugins` folder including its contents to your textpattern folder. 22 | 3. Copy the contents of `glz_custom_fields_v2.0 beta_zip.txt` and paste it into the `Admin › Plugins` installation box. Install and activate the plugin. 23 | 4. Visit the `Admin › Preferences » Custom fields preferences` panel to verify your plugin preferences. Correct if needed. 24 | 5. Visit the `Extensions › Custom Fields` panel to begin editing and adding new custom fields. 25 | 26 | **Other locations:** You can move the files from the plugins folder if you so wish, e.g. to your `assets/js/` and `assets/css/` folders but adjust the new js/css url pref accordingly in the plugin preferences. The Datepicker and Timepicker folders may also be moved but its contents should remain together. 27 | 28 | **Multi-site installations:** copy the `plugins` folder including its contents to the `admin` folder of your multi-site. If necessary, adjust the custom_fields prefs to match your admin-side path and subdomain URL. 29 | 30 | 31 | ### Changelog 32 | 33 | v2.0.6 – May 2025 34 | 35 | - Prevent `{`default`}` braces from showing in radio/checkbox/select choices 36 | - HTML5-compliant void tag endings for radios and checkboxes 37 | 38 | v2.0.5 – July 2024 39 | 40 | - PHP 8+ patches / deprecation notices 41 | - CSP nonce support for script and style blocks 42 | - CSS tweaks 43 | 44 | v2.0.1 – March 2021 45 | 46 | - PHP 7.4 deprecation notice / PHP 8 error patched 47 | 48 | v2.0 – March 2018 49 | 50 | - Refactored for Textpattern v4.7 51 | - Plugin preferences now handled in Admin › Preferences 52 | - Plugin preferences not overwritten during installation 53 | - Support for custom field title labels and supporting instruction hints (also per UI-language) 54 | - Custom field titles are accessible in page templates and forms by adding the attribute `title="1"` to [txp:custom_field](https://docs.textpattern.io/tags/custom_field). 55 | - Change order of custom fields per drag and drop (deactivatable via a hidden pref) 56 | - Translatable: UI now uses textpacks throughout, custom field labels also translatable by switching the UI language 57 | - Under the hood: makes use of Textpattern’s in-built functions for UI creation, error messages/notices, language strings, field labels 58 | - New prefs for js/css URL location including support for relative URLs: relocate your js/css files where you want them 59 | - Compatibility with multi-site Textpattern installations 60 | - Updated + minified js/css and datepicker/timepicker labels 61 | - Adding/deleting/changing a custom field updates site-wide last modified date (for cache renewal) 62 | 63 | 64 | ### Credits 65 | 66 | This plugin was originally written by [Gerhard Lazu](https://github.com/gerhard) and dates back to [2007](https://forum.textpattern.io/viewtopic.php?pid=157983#p157983). It has been repaired, expanded and adopted by numerous forum members over the years (credited in the code), most recently by [Bloke](https://github.com/Bloke/glz_custom_fields). This significantly refactored version adds compatibility with Textpattern v4.7 and is currently looked after by [jools-r](https://github.com/jools-r/glz_custom_fields). Its concept and core workings are still those of Gerhard's original plugin. The Textpattern core will ultimately support unlimited custom fields, making this plugin obsolete at some point in the future. 67 | 68 | This plugin uses **jQuery DatePicker** ([homepage](http://2008.kelvinluck.com/assets/jquery/datePicker/v2/demo/) / [github](https://github.com/vitch/jQuery-datepicker)) by Kelvin Luck, **jQuery Timepicker** ([github](https://github.com/perifer/timePicker)) by Anders Fajerson and **jQuery sortElements** ([github](https://github.com/padolsey-archive/jquery.fn/tree/master/sortElements)) by James Padolsey. 69 | 70 | 71 | ### License 72 | 73 | Copyright (c) 2012 Gerhard Lazu 74 | 75 | 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. 76 | 77 | 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. 78 | 79 | You should have received a copy of the GNU General Public License along with this program. If not, see . 80 | -------------------------------------------------------------------------------- /dist/plugins.glz_custom_fields.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jools-r/glz_custom_fields/3882adacb5bf27f448c8d61c3fe132e6ef9882f5/dist/plugins.glz_custom_fields.zip -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_custom_fields.css: -------------------------------------------------------------------------------- 1 | /* - - - - - - - - - - - - - - - - - - - - - 2 | 3 | ### TEXTPATTERN CUSTOM FIELDS ### 4 | 5 | Title : glz_custom_fields stylesheet 6 | Author : Gerhard Lazu + many contributors 7 | 8 | Last modified: 7 April 2018 9 | 10 | This file serves as a basis for glz_custom_fields_head_css() 11 | and is not loaded by the plugin unless: 12 | 13 | $debug = true; 14 | $use_minified = false; 15 | 16 | is set at the top of the plugin. Use these settings to debug. 17 | 18 | - - - - - - - - - - - - - - - - - - - - - */ 19 | 20 | /* CUSTOM FIELDS PANEL 21 | -------------------------------------------------------------- */ 22 | 23 | .glz-cf-setup-switch { 24 | float: right; 25 | } 26 | [dir="rtl"] .glz-cf-setup-switch { 27 | float: left; 28 | } 29 | /* define only first and last (narrow) column widths */ 30 | #glz_custom_fields_container .txp-list-col-id { 31 | width: 3em; 32 | text-align: center; 33 | } 34 | #glz_custom_fields_container .txp-list-col-position, 35 | #glz_custom_fields_container .txp-list-col-options { 36 | width: 5em; 37 | } 38 | #glz_custom_fields_container .txp-list-col-title .cf-instructions.ui-icon { 39 | width: 2em; 40 | height: 17px; 41 | float: right; 42 | background-repeat: no-repeat; 43 | background-position: center 2px; 44 | opacity: .33; 45 | cursor: pointer; 46 | } 47 | /* instructions on 'undefined' entries: reverse effect of double opacity */ 48 | #glz_custom_fields_container .txp-list-col-title.disabled .cf-instructions { 49 | opacity: 1 !important; 50 | pointer-events: auto; 51 | } 52 | #glz_custom_fields_container .txp-list-col-options { 53 | text-align: center; 54 | } 55 | #glz_custom_fields_container .txp-list-col-options .ui-icon { 56 | /* larger click target */ 57 | width: 4em; 58 | background-repeat: no-repeat; 59 | background-position: center; 60 | } 61 | /* change color of svg background */ 62 | #glz_custom_fields_container .txp-list-col-options .ui-icon:hover { 63 | /* #00c4bf calculated with https://codepen.io/sosuke/pen/Pjoqqp prepended 64 | with brightness(0) saturate(100%) as original icons not black */ 65 | -webkit-filter: brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%); 66 | filter: brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%); 67 | } 68 | 69 | /* only attached while dragging */ 70 | #glz_custom_fields_container table.fixed-width { 71 | table-layout: fixed; 72 | } 73 | #glz_custom_fields_container table.sortable .txp-list-col-sort { 74 | width: 3em; 75 | text-align: center; 76 | } 77 | #glz_custom_fields_container table.sortable .ui-sortable-handle { 78 | cursor: row-resize; 79 | text-align: center; 80 | opacity: 0.66; 81 | } 82 | #glz_custom_fields_container table.sortable .txp-list-col-position { 83 | display: none; 84 | } 85 | #glz_custom_fields_container .ui-sortable-helper, 86 | #glz_custom_fields_container .ui-sortable-placeholder { 87 | display: table; 88 | } 89 | /* hide language string spans to avoid FOUT */ 90 | #add_edit_custom_field .hidden { 91 | display: none; 92 | } 93 | 94 | 95 | /* CUSTOM FIELDS EDIT PANEL + PREFS PANE 96 | -------------------------------------------------------------- */ 97 | 98 | /* Instructions text styling */ 99 | @media screen and (min-width: 47em) { 100 | .txp-edit .txp-form-field .txp-form-field-instructions, 101 | .txp-tabs-vertical-group .txp-form-field-instructions { 102 | max-width: 50%; /* prevent horizontal scrollbars */ 103 | padding-left: 50%; /* range right beneath fields */ 104 | } 105 | } 106 | /* "Check paths" button */ 107 | .check-path { 108 | float: right; 109 | font-size: .7em; /* match normal font-size */ 110 | font-weight: 400; 111 | } 112 | [dir=rtl] .check-path { float: left; } 113 | .ui-tabs-nav .check-path { 114 | display: none; /* hide in panel chooser */ 115 | } 116 | #prefs-glz_cf_css_asset_url, #prefs-glz_cf_js_asset_url { display: none; } 117 | 118 | /* ARTICLE / WRITE PANE 119 | -------------------------------------------------------------- */ 120 | 121 | /* reset button, make disabled state appear non-responsive */ 122 | .glz-custom-field-reset.disabled:hover { 123 | text-decoration: none; 124 | } 125 | .glz-custom-field-reset.disabled { 126 | cursor: default; 127 | } 128 | .glz-custom-radio .txp-form-field-value label, 129 | .glz-custom-checkbox .txp-form-field-value label { 130 | cursor: pointer; 131 | } 132 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_custom_fields.js: -------------------------------------------------------------------------------- 1 | /* - - - - - - - - - - - - - - - - - - - - - 2 | 3 | ### TEXTPATTERN CUSTOM FIELDS ### 4 | 5 | Title : glz_custom_fields javascript 6 | Author : Gerhard Lazu, jcr + many contributors 7 | 8 | Last modified: 7 April 2018 9 | 10 | This file serves as a basis for glz_custom_fields_head_js() 11 | and is not loaded by the plugin unless: 12 | 13 | $debug = true; 14 | $use_minified = false; 15 | 16 | is set at the top of the plugin. Use these settings to debug. 17 | 18 | - - - - - - - - - - - - - - - - - - - - - */ 19 | 20 | $(function() { 21 | // Ensure glzResetRadio() function persists after article asynchronous save 22 | textpattern.Relay.register('txpAsyncForm.success', glzResetRadio); 23 | 24 | // Init: global object to store variables 25 | var GLZ_CF; 26 | GLZ_CF = {}; 27 | GLZ_CF.special_custom_types = ["date-picker", "time-picker"]; 28 | GLZ_CF.no_value_custom_types = ["text_input", "textarea"]; 29 | 30 | // Init: re-used jQuery objects 31 | $glz_value_field = $(".edit-custom-set-value"); 32 | $glz_value_instructions = $glz_value_field.find(".txp-form-field-instructions"); 33 | $glz_select_instructions = $('.edit-custom-set-type').find(".txp-form-field-instructions"); 34 | 35 | // Init: get and store instruction strings, clear up message holders 36 | GLZ_CF.messages = { 37 | 'textarea' : $('.glz-custom-textarea-msg').html(), 38 | 'configure' : $glz_select_instructions.text(), 39 | 'customscriptpath' : $('.glz-custom-script-msg').text() 40 | } 41 | $('.glz-custom-script-msg').remove(); 42 | $('.glz-custom-textarea-msg').remove(); 43 | 44 | 45 | // CUSTOM FIELD EDIT PANE 46 | // ----------------------- 47 | 48 | // Toggle 'hint' for "select" custom field type dropdown 49 | glzToggleTypeLink(); 50 | 51 | // Change custom field "value" field state 52 | // – Disabled for special custom types / types that have no preset values 53 | // – Textarea for custom types with multiple values (default at start) 54 | // – Path input field for custom scripts name 55 | if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CF.special_custom_types, GLZ_CF.no_value_custom_types)) != -1 ) { 56 | glzCustomFieldValueOff(); 57 | } else if ( $("select#custom_set_type :selected").attr("value") == "custom-script" ) { 58 | glzCustomFieldValuePath(); 59 | } 60 | 61 | // Update custom field "value" field if type drop-down is changed 62 | $("select#custom_set_type").change( function() { 63 | glzToggleTypeLink(); 64 | if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CF.special_custom_types, GLZ_CF.no_value_custom_types)) != -1 ) { 65 | glzCustomFieldValueOff(); 66 | } else if ( $("select#custom_set_type :selected").attr("value") == "custom-script" ) { 67 | glzCustomFieldValuePath(); 68 | } else { 69 | glzCustomFieldValueOn(); 70 | } 71 | }); 72 | 73 | 74 | // WRITE TAB: 75 | // --------- 76 | 77 | // Add a reset link to all radio custom fields 78 | glzResetRadio(); 79 | 80 | // reset all radio buttons if "reset" is clicked (also after async save) 81 | $(".txp-layout").on("click", ".glz-custom-field-reset", function() { 82 | // Abort if disabled (= previously clicked) 83 | if ($(this).hasClass('disabled')) return false; 84 | // Get this radio button group name 85 | var custom_field_to_reset = $(this).attr("name"); 86 | 87 | // Reset radio input(s) 88 | $("input[name=" + custom_field_to_reset + "]").prop("checked", false); 89 | // If default exists, reset to default value 90 | $("input[name=" + custom_field_to_reset + "].default").prop("checked", true); 91 | // Set "reset" button to disabled 92 | $(this).addClass("disabled"); 93 | 94 | // If none checked, add hidden input with empty value and same ID to save as empty to the db (only if not already there) 95 | if ( ($(this).siblings(".txp-form-radio-reset").length === 0) && ($("input[name=" + custom_field_to_reset + "]:checked").length === 0) ) { 96 | $(this).after(""); 97 | } 98 | return false; 99 | }); 100 | 101 | // Revert reset state if radio button subsequently clicked (also after async save) 102 | $(".txp-layout").on("click",".glz-custom-radio .radio", function() { 103 | var custom_field_to_reanimate = $(this).attr("name"); 104 | $this_reset_button = $(".glz-custom-field-reset[name=" + custom_field_to_reanimate + "]"); 105 | // If "reset" button currently disabled 106 | if ($this_reset_button.hasClass("disabled")) { 107 | // Remove input with empty value 108 | $("input[type=hidden][name=" + custom_field_to_reanimate + "]").remove(); 109 | // Revert disabled status of "reset" button 110 | $this_reset_button.removeClass("disabled"); 111 | } 112 | }); 113 | 114 | 115 | // RE-USABLE FUNCTIONS 116 | // ------------------- 117 | 118 | // Add reset button to radio fields 119 | function glzResetRadio() { 120 | // if there are radio fields 121 | if ($(".glz-custom-radio").length > 0) { 122 | // loop over each set 123 | $(".glz-custom-radio").each(function() { 124 | var custom_field_to_reset = $(this).find("input:first").attr("name"); 125 | $(this).find("label:first").after(" Reset"); 126 | // if none of the radio buttons are checked on load, set "reset" button to disabled 127 | if(!$("input:radio[name=" + custom_field_to_reset + "]").is(":checked")) { 128 | $(".glz-custom-field-reset[name=" + custom_field_to_reset + "]").addClass("disabled"); 129 | } 130 | }); 131 | } 132 | } 133 | 134 | // Hide or disable custom field "value" input/textarea 135 | function glzCustomFieldValueOff() { 136 | // Save any textarea contents for future restoring, then remove textarea 137 | if ($glz_value_field.find("textarea#value").length) { 138 | GLZ_CF.textarea_value = $glz_value_field.find("textarea#value").html(); 139 | $glz_value_field.find("textarea#value").remove(); 140 | } 141 | 142 | // If input exists and is not disabled, save 'custom scripts path' for future restoring 143 | if ($glz_value_field.find("input#value").length) { 144 | if ($glz_value_field.find("input#value").prop('disabled') == false) { 145 | GLZ_CF.path_value = $glz_value_field.find("input#value").attr('value'); 146 | } 147 | } else { 148 | // No input field? Then add a new empty input 149 | $glz_value_field.find(".txp-form-field-value").prepend(''); 150 | } 151 | 152 | // Blank input field and set to disabled 153 | $glz_value_field.find("input#value").attr('value', "-----").prop('disabled', true); 154 | // Remove any 'hint' instructions 155 | $glz_value_instructions.html(''); 156 | } 157 | 158 | // Show custom field "value" input + messages 159 | function glzCustomFieldValueOn() { 160 | // If input exists and is not disabled, save 'custom scripts path' for future restoring, 161 | // then remove input 162 | if ( $glz_value_field.find("input#value").length ) { 163 | if ($glz_value_field.find("input#value").prop('disabled') == false) { 164 | GLZ_CF.path_value = $glz_value_field.find("input#value").attr('value'); 165 | } 166 | $glz_value_field.find("input#value").remove(); 167 | $glz_value_instructions.html(''); 168 | } 169 | // No textarea? Then add one for inserting multiple values 170 | if ( !$glz_value_field.find("textarea#value").length ) { 171 | $(".edit-custom-set-value .txp-form-field-value").prepend(''); 172 | } 173 | // If textarea contents were previously saved, restore them 174 | if ( GLZ_CF.textarea_value ) { 175 | $glz_value_field.find("textarea#value").html(GLZ_CF.textarea_value); 176 | } 177 | // Update 'hint' instructions for textarea 178 | $glz_value_instructions.html(GLZ_CF.messages['textarea']); 179 | } 180 | 181 | // Custom script path input 182 | function glzCustomFieldValuePath() { 183 | // Save any textarea contents for future restoring, then remove textarea 184 | if ($glz_value_field.find("textarea#value").length) { 185 | GLZ_CF.textarea_value = $glz_value_field.find("textarea#value").html(); 186 | $glz_value_field.find("textarea#value").remove(); 187 | $glz_value_instructions.html(''); 188 | } 189 | // If no input field exists, insert one 190 | if (!$glz_value_field.find("input#value").length) { 191 | $glz_value_field.find(".txp-form-field-value").prepend(''); 192 | } 193 | // Clear any blanked values and restore visibility 194 | 195 | // If input is placeholder "-----" value, reset to empty 196 | // otherwise let through any other incoming value 197 | if ($glz_value_field.find("input#value").attr('value') == "-----") { 198 | $glz_value_field.find("input#value").attr('value', ''); 199 | } 200 | // Make sure field is visible 201 | $glz_value_field.find("input#value").prop('disabled', false); 202 | 203 | // Update 'hint' instructions for custom scripts path 204 | $glz_value_instructions.html(GLZ_CF.messages['customscriptpath']); 205 | // If the path value was previously saved, restore it 206 | if ( GLZ_CF.path_value ) { 207 | $glz_value_field.find("input#value").attr("value", GLZ_CF.path_value); 208 | } 209 | 210 | } 211 | 212 | // Custom field type dropdown: show/hide 'see settings' messages based on type 213 | function glzToggleTypeLink() { 214 | if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CF.special_custom_types, ["multi-select", "custom-script"])) != -1 ) { 215 | $glz_select_instructions.html("" + GLZ_CF.messages['configure'] + ""); 216 | } else { 217 | $glz_select_instructions.html(''); 218 | } 219 | } 220 | 221 | }); 222 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_custom_fields.min.css: -------------------------------------------------------------------------------- 1 | .glz-cf-setup-switch{float:right}[dir=rtl] .glz-cf-setup-switch{float:left}#glz_custom_fields_container .txp-list-col-id{width:3em;text-align:center}#glz_custom_fields_container .txp-list-col-options,#glz_custom_fields_container .txp-list-col-position{width:5em}#glz_custom_fields_container .txp-list-col-title .cf-instructions.ui-icon{width:2em;height:17px;float:right;background-repeat:no-repeat;background-position:center 2px;opacity:.33;cursor:pointer}#glz_custom_fields_container .txp-list-col-title.disabled .cf-instructions{opacity:1!important;pointer-events:auto}#glz_custom_fields_container .txp-list-col-options{text-align:center}#glz_custom_fields_container .txp-list-col-options .ui-icon{width:4em;background-repeat:no-repeat;background-position:50%}#glz_custom_fields_container .txp-list-col-options .ui-icon:hover{-webkit-filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%);filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%)}#glz_custom_fields_container table.fixed-width{table-layout:fixed}#glz_custom_fields_container table.sortable .txp-list-col-sort{width:3em;text-align:center}#glz_custom_fields_container table.sortable .ui-sortable-handle{cursor:row-resize;text-align:center;opacity:.66}#glz_custom_fields_container table.sortable .txp-list-col-position{display:none}#glz_custom_fields_container .ui-sortable-helper,#glz_custom_fields_container .ui-sortable-placeholder{display:table}#add_edit_custom_field .hidden{display:none}@media screen and (min-width:47em){.txp-edit .txp-form-field .txp-form-field-instructions,.txp-tabs-vertical-group .txp-form-field-instructions{max-width:50%;padding-left:50%}}.check-path{float:right;font-size:.7em;font-weight:400}[dir=rtl] .check-path{float:left}.ui-tabs-nav .check-path{display:none}#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:none}.glz-custom-field-reset.disabled:hover{text-decoration:none}.glz-custom-field-reset.disabled{cursor:default}.glz-custom-checkbox .txp-form-field-value label,.glz-custom-radio .txp-form-field-value label{cursor:pointer} 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_custom_fields.min.js: -------------------------------------------------------------------------------- 1 | $(function(){function e(){$(".glz-custom-radio").length>0&&$(".glz-custom-radio").each(function(){var e=$(this).find("input:first").attr("name");$(this).find("label:first").after(' Reset'),$("input:radio[name="+e+"]").is(":checked")||$(".glz-custom-field-reset[name="+e+"]").addClass("disabled")})}function t(){$glz_value_field.find("textarea#value").length&&(s.textarea_value=$glz_value_field.find("textarea#value").html(),$glz_value_field.find("textarea#value").remove()),$glz_value_field.find("input#value").length?0==$glz_value_field.find("input#value").prop("disabled")&&(s.path_value=$glz_value_field.find("input#value").attr("value")):$glz_value_field.find(".txp-form-field-value").prepend(''),$glz_value_field.find("input#value").attr("value","-----").prop("disabled",!0),$glz_value_instructions.html("")}function l(){$glz_value_field.find("input#value").length&&(0==$glz_value_field.find("input#value").prop("disabled")&&(s.path_value=$glz_value_field.find("input#value").attr("value")),$glz_value_field.find("input#value").remove(),$glz_value_instructions.html("")),$glz_value_field.find("textarea#value").length||$(".edit-custom-set-value .txp-form-field-value").prepend(''),s.textarea_value&&$glz_value_field.find("textarea#value").html(s.textarea_value),$glz_value_instructions.html(s.messages.textarea)}function a(){$glz_value_field.find("textarea#value").length&&(s.textarea_value=$glz_value_field.find("textarea#value").html(),$glz_value_field.find("textarea#value").remove(),$glz_value_instructions.html("")),$glz_value_field.find("input#value").length||$glz_value_field.find(".txp-form-field-value").prepend(''),"-----"==$glz_value_field.find("input#value").attr("value")&&$glz_value_field.find("input#value").attr("value",""),$glz_value_field.find("input#value").prop("disabled",!1),$glz_value_instructions.html(s.messages.customscriptpath),s.path_value&&$glz_value_field.find("input#value").attr("value",s.path_value)}function i(){-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,["multi-select","custom-script"]))?$glz_select_instructions.html(''+s.messages.configure+""):$glz_select_instructions.html("")}textpattern.Relay.register("txpAsyncForm.success",e);var s;s={},s.special_custom_types=["date-picker","time-picker"],s.no_value_custom_types=["text_input","textarea"],$glz_value_field=$(".edit-custom-set-value"),$glz_value_instructions=$glz_value_field.find(".txp-form-field-instructions"),$glz_select_instructions=$(".edit-custom-set-type").find(".txp-form-field-instructions"),s.messages={textarea:$(".glz-custom-textarea-msg").html(),configure:$glz_select_instructions.text(),customscriptpath:$(".glz-custom-script-msg").text()},$(".glz-custom-script-msg").remove(),$(".glz-custom-textarea-msg").remove(),i(),-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():"custom-script"==$("select#custom_set_type :selected").attr("value")&&a(),$("select#custom_set_type").change(function(){i(),-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():"custom-script"==$("select#custom_set_type :selected").attr("value")?a():l()}),e(),$(".txp-layout").on("click",".glz-custom-field-reset",function(){if($(this).hasClass("disabled"))return!1;var e=$(this).attr("name");return $("input[name="+e+"]").prop("checked",!1),$("input[name="+e+"].default").prop("checked",!0),$(this).addClass("disabled"),0===$(this).siblings(".txp-form-radio-reset").length&&0===$("input[name="+e+"]:checked").length&&$(this).after(''),!1}),$(".txp-layout").on("click",".glz-custom-radio .radio",function(){var e=$(this).attr("name");$this_reset_button=$(".glz-custom-field-reset[name="+e+"]"),$this_reset_button.hasClass("disabled")&&($("input[type=hidden][name="+e+"]").remove(),$this_reset_button.removeClass("disabled"))})}); 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_jqueryui.sortable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery.fn.sortElements 3 | * -------------- 4 | * @author James Padolsey (http://james.padolsey.com) 5 | * @version 0.11 6 | * @updated 18-MAR-2010 7 | * -------------- 8 | * @param Function comparator: 9 | * Exactly the same behaviour as [1,2,3].sort(comparator) 10 | * 11 | * @param Function getSortable 12 | * A function that should return the element that is 13 | * to be sorted. The comparator will run on the 14 | * current collection, but you may want the actual 15 | * resulting sort to occur on a parent or another 16 | * associated element. 17 | * 18 | * E.g. $('td').sortElements(comparator, function(){ 19 | * return this.parentNode; 20 | * }) 21 | * 22 | * The 's parent () will be sorted instead 23 | * of the itself. 24 | */ 25 | 26 | jQuery.fn.sortElements = (function() { 27 | var sort = [].sort; 28 | return function(comparator, getSortable) { 29 | getSortable = getSortable || function() { 30 | return this; 31 | }; 32 | var placements = this.map(function() { 33 | var sortElement = getSortable.call(this), 34 | parentNode = sortElement.parentNode, 35 | // Since the element itself will change position, we have 36 | // to have some way of storing it's original position in 37 | // the DOM. The easiest way is to have a 'flag' node: 38 | nextSibling = parentNode.insertBefore( 39 | document.createTextNode(''), 40 | sortElement.nextSibling 41 | ); 42 | return function() { 43 | if (parentNode === this) { 44 | throw new Error( 45 | "You can't sort elements if any one is a descendant of another." 46 | ); 47 | } 48 | // Insert before flag: 49 | parentNode.insertBefore(this, nextSibling); 50 | // Remove flag: 51 | parentNode.removeChild(nextSibling); 52 | }; 53 | }); 54 | return sort.call(this, comparator).each(function(i) { 55 | placements[i].call(getSortable.call(this)); 56 | }); 57 | }; 58 | })(); 59 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/glz_jqueryui.sortable.min.js: -------------------------------------------------------------------------------- 1 | jQuery.fn.sortElements=function(){var t=[].sort;return function(e,n){n=n||function(){return this};var r=this.map(function(){var t=n.call(this),e=t.parentNode,r=e.insertBefore(document.createTextNode(""),t.nextSibling);return function(){if(e===this)throw new Error("You can't sort elements if any one is a descendant of another.");e.insertBefore(this,r),e.removeChild(r)}});return t.call(this,e).each(function(t){r[t].call(n.call(this))})}}(); 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.datePicker/date.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Date prototype extensions. Doesn't depend on any 3 | * other code. Doens't overwrite existing methods. 4 | * 5 | * Adds dayNames, abbrDayNames, monthNames and abbrMonthNames static properties and isLeapYear, 6 | * isWeekend, isWeekDay, getDaysInMonth, getDayName, getMonthName, getDayOfYear, getWeekOfYear, 7 | * setDayOfYear, addYears, addMonths, addDays, addHours, addMinutes, addSeconds methods 8 | * 9 | * Copyright (c) 2006 Jörn Zaefferer and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net) 10 | * 11 | * Additional methods and properties added by Kelvin Luck: firstDayOfWeek, dateFormat, zeroTime, asString, fromString - 12 | * I've added my name to these methods so you know who to blame if they are broken! 13 | * 14 | * Dual licensed under the MIT and GPL licenses: 15 | * http://www.opensource.org/licenses/mit-license.php 16 | * http://www.gnu.org/licenses/gpl.html 17 | * 18 | */ 19 | 20 | /** 21 | * An Array of day names starting with Sunday. 22 | * 23 | * @example dayNames[0] 24 | * @result 'Sunday' 25 | * 26 | * @name dayNames 27 | * @type Array 28 | * @cat Plugins/Methods/Date 29 | */ 30 | Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 31 | 32 | /** 33 | * An Array of abbreviated day names starting with Sun. 34 | * 35 | * @example abbrDayNames[0] 36 | * @result 'Sun' 37 | * 38 | * @name abbrDayNames 39 | * @type Array 40 | * @cat Plugins/Methods/Date 41 | */ 42 | Date.abbrDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; 43 | 44 | /** 45 | * An Array of month names starting with Janurary. 46 | * 47 | * @example monthNames[0] 48 | * @result 'January' 49 | * 50 | * @name monthNames 51 | * @type Array 52 | * @cat Plugins/Methods/Date 53 | */ 54 | Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 55 | 56 | /** 57 | * An Array of abbreviated month names starting with Jan. 58 | * 59 | * @example abbrMonthNames[0] 60 | * @result 'Jan' 61 | * 62 | * @name monthNames 63 | * @type Array 64 | * @cat Plugins/Methods/Date 65 | */ 66 | Date.abbrMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 67 | 68 | /** 69 | * The first day of the week for this locale. 70 | * 71 | * @name firstDayOfWeek 72 | * @type Number 73 | * @cat Plugins/Methods/Date 74 | * @author Kelvin Luck 75 | */ 76 | Date.firstDayOfWeek = 1; 77 | 78 | /** 79 | * The format that string dates should be represented as (e.g. 'dd/mm/yyyy' for UK, 'mm/dd/yyyy' for US, 'yyyy-mm-dd' for Unicode etc). 80 | * 81 | * @name format 82 | * @type String 83 | * @cat Plugins/Methods/Date 84 | * @author Kelvin Luck 85 | */ 86 | Date.format = 'dd/mm/yyyy'; 87 | //Date.format = 'mm/dd/yyyy'; 88 | //Date.format = 'yyyy-mm-dd'; 89 | //Date.format = 'dd mmm yy'; 90 | 91 | /** 92 | * The first two numbers in the century to be used when decoding a two digit year. Since a two digit year is ambiguous (and date.setYear 93 | * only works with numbers < 99 and so doesn't allow you to set years after 2000) we need to use this to disambiguate the two digit year codes. 94 | * 95 | * @name format 96 | * @type String 97 | * @cat Plugins/Methods/Date 98 | * @author Kelvin Luck 99 | */ 100 | Date.fullYearStart = '20'; 101 | 102 | (function() { 103 | 104 | /** 105 | * Adds a given method under the given name 106 | * to the Date prototype if it doesn't 107 | * currently exist. 108 | * 109 | * @private 110 | */ 111 | function add(name, method) { 112 | if( !Date.prototype[name] ) { 113 | Date.prototype[name] = method; 114 | } 115 | }; 116 | 117 | /** 118 | * Checks if the year is a leap year. 119 | * 120 | * @example var dtm = new Date("01/12/2008"); 121 | * dtm.isLeapYear(); 122 | * @result true 123 | * 124 | * @name isLeapYear 125 | * @type Boolean 126 | * @cat Plugins/Methods/Date 127 | */ 128 | add("isLeapYear", function() { 129 | var y = this.getFullYear(); 130 | return (y%4==0 && y%100!=0) || y%400==0; 131 | }); 132 | 133 | /** 134 | * Checks if the day is a weekend day (Sat or Sun). 135 | * 136 | * @example var dtm = new Date("01/12/2008"); 137 | * dtm.isWeekend(); 138 | * @result false 139 | * 140 | * @name isWeekend 141 | * @type Boolean 142 | * @cat Plugins/Methods/Date 143 | */ 144 | add("isWeekend", function() { 145 | return this.getDay()==0 || this.getDay()==6; 146 | }); 147 | 148 | /** 149 | * Check if the day is a day of the week (Mon-Fri) 150 | * 151 | * @example var dtm = new Date("01/12/2008"); 152 | * dtm.isWeekDay(); 153 | * @result false 154 | * 155 | * @name isWeekDay 156 | * @type Boolean 157 | * @cat Plugins/Methods/Date 158 | */ 159 | add("isWeekDay", function() { 160 | return !this.isWeekend(); 161 | }); 162 | 163 | /** 164 | * Gets the number of days in the month. 165 | * 166 | * @example var dtm = new Date("01/12/2008"); 167 | * dtm.getDaysInMonth(); 168 | * @result 31 169 | * 170 | * @name getDaysInMonth 171 | * @type Number 172 | * @cat Plugins/Methods/Date 173 | */ 174 | add("getDaysInMonth", function() { 175 | return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][this.getMonth()]; 176 | }); 177 | 178 | /** 179 | * Gets the name of the day. 180 | * 181 | * @example var dtm = new Date("01/12/2008"); 182 | * dtm.getDayName(); 183 | * @result 'Saturday' 184 | * 185 | * @example var dtm = new Date("01/12/2008"); 186 | * dtm.getDayName(true); 187 | * @result 'Sat' 188 | * 189 | * @param abbreviated Boolean When set to true the name will be abbreviated. 190 | * @name getDayName 191 | * @type String 192 | * @cat Plugins/Methods/Date 193 | */ 194 | add("getDayName", function(abbreviated) { 195 | return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()]; 196 | }); 197 | 198 | /** 199 | * Gets the name of the month. 200 | * 201 | * @example var dtm = new Date("01/12/2008"); 202 | * dtm.getMonthName(); 203 | * @result 'Janurary' 204 | * 205 | * @example var dtm = new Date("01/12/2008"); 206 | * dtm.getMonthName(true); 207 | * @result 'Jan' 208 | * 209 | * @param abbreviated Boolean When set to true the name will be abbreviated. 210 | * @name getDayName 211 | * @type String 212 | * @cat Plugins/Methods/Date 213 | */ 214 | add("getMonthName", function(abbreviated) { 215 | return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()]; 216 | }); 217 | 218 | /** 219 | * Get the number of the day of the year. 220 | * 221 | * @example var dtm = new Date("01/12/2008"); 222 | * dtm.getDayOfYear(); 223 | * @result 11 224 | * 225 | * @name getDayOfYear 226 | * @type Number 227 | * @cat Plugins/Methods/Date 228 | */ 229 | add("getDayOfYear", function() { 230 | var tmpdtm = new Date("1/1/" + this.getFullYear()); 231 | return Math.floor((this.getTime() - tmpdtm.getTime()) / 86400000); 232 | }); 233 | 234 | /** 235 | * Get the number of the week of the year. 236 | * 237 | * @example var dtm = new Date("01/12/2008"); 238 | * dtm.getWeekOfYear(); 239 | * @result 2 240 | * 241 | * @name getWeekOfYear 242 | * @type Number 243 | * @cat Plugins/Methods/Date 244 | */ 245 | add("getWeekOfYear", function() { 246 | return Math.ceil(this.getDayOfYear() / 7); 247 | }); 248 | 249 | /** 250 | * Set the day of the year. 251 | * 252 | * @example var dtm = new Date("01/12/2008"); 253 | * dtm.setDayOfYear(1); 254 | * dtm.toString(); 255 | * @result 'Tue Jan 01 2008 00:00:00' 256 | * 257 | * @name setDayOfYear 258 | * @type Date 259 | * @cat Plugins/Methods/Date 260 | */ 261 | add("setDayOfYear", function(day) { 262 | this.setMonth(0); 263 | this.setDate(day); 264 | return this; 265 | }); 266 | 267 | /** 268 | * Add a number of years to the date object. 269 | * 270 | * @example var dtm = new Date("01/12/2008"); 271 | * dtm.addYears(1); 272 | * dtm.toString(); 273 | * @result 'Mon Jan 12 2009 00:00:00' 274 | * 275 | * @name addYears 276 | * @type Date 277 | * @cat Plugins/Methods/Date 278 | */ 279 | add("addYears", function(num) { 280 | this.setFullYear(this.getFullYear() + num); 281 | return this; 282 | }); 283 | 284 | /** 285 | * Add a number of months to the date object. 286 | * 287 | * @example var dtm = new Date("01/12/2008"); 288 | * dtm.addMonths(1); 289 | * dtm.toString(); 290 | * @result 'Tue Feb 12 2008 00:00:00' 291 | * 292 | * @name addMonths 293 | * @type Date 294 | * @cat Plugins/Methods/Date 295 | */ 296 | add("addMonths", function(num) { 297 | var tmpdtm = this.getDate(); 298 | 299 | this.setMonth(this.getMonth() + num); 300 | 301 | if (tmpdtm > this.getDate()) 302 | this.addDays(-this.getDate()); 303 | 304 | return this; 305 | }); 306 | 307 | /** 308 | * Add a number of days to the date object. 309 | * 310 | * @example var dtm = new Date("01/12/2008"); 311 | * dtm.addDays(1); 312 | * dtm.toString(); 313 | * @result 'Sun Jan 13 2008 00:00:00' 314 | * 315 | * @name addDays 316 | * @type Date 317 | * @cat Plugins/Methods/Date 318 | */ 319 | add("addDays", function(num) { 320 | var timezoneOffsetBefore = this.getTimezoneOffset(), 321 | timezoneOffsetAfter; 322 | this.setTime(this.getTime() + (num*86400000) ); 323 | timezoneOffsetAfter = this.getTimezoneOffset(); 324 | 325 | // If the timezone has changed between days then adjust the time to reflect this 326 | if(timezoneOffsetAfter !== timezoneOffsetBefore){ 327 | this.setTime(this.getTime() + ((timezoneOffsetAfter-timezoneOffsetBefore) * 60 * 1000)); 328 | } 329 | return this; 330 | }); 331 | 332 | /** 333 | * Add a number of hours to the date object. 334 | * 335 | * @example var dtm = new Date("01/12/2008"); 336 | * dtm.addHours(24); 337 | * dtm.toString(); 338 | * @result 'Sun Jan 13 2008 00:00:00' 339 | * 340 | * @name addHours 341 | * @type Date 342 | * @cat Plugins/Methods/Date 343 | */ 344 | add("addHours", function(num) { 345 | this.setHours(this.getHours() + num); 346 | return this; 347 | }); 348 | 349 | /** 350 | * Add a number of minutes to the date object. 351 | * 352 | * @example var dtm = new Date("01/12/2008"); 353 | * dtm.addMinutes(60); 354 | * dtm.toString(); 355 | * @result 'Sat Jan 12 2008 01:00:00' 356 | * 357 | * @name addMinutes 358 | * @type Date 359 | * @cat Plugins/Methods/Date 360 | */ 361 | add("addMinutes", function(num) { 362 | this.setMinutes(this.getMinutes() + num); 363 | return this; 364 | }); 365 | 366 | /** 367 | * Add a number of seconds to the date object. 368 | * 369 | * @example var dtm = new Date("01/12/2008"); 370 | * dtm.addSeconds(60); 371 | * dtm.toString(); 372 | * @result 'Sat Jan 12 2008 00:01:00' 373 | * 374 | * @name addSeconds 375 | * @type Date 376 | * @cat Plugins/Methods/Date 377 | */ 378 | add("addSeconds", function(num) { 379 | this.setSeconds(this.getSeconds() + num); 380 | return this; 381 | }); 382 | 383 | /** 384 | * Sets the time component of this Date to zero for cleaner, easier comparison of dates where time is not relevant. 385 | * 386 | * @example var dtm = new Date(); 387 | * dtm.zeroTime(); 388 | * dtm.toString(); 389 | * @result 'Sat Jan 12 2008 00:01:00' 390 | * 391 | * @name zeroTime 392 | * @type Date 393 | * @cat Plugins/Methods/Date 394 | * @author Kelvin Luck 395 | */ 396 | add("zeroTime", function() { 397 | this.setMilliseconds(0); 398 | this.setSeconds(0); 399 | this.setMinutes(0); 400 | this.setHours(0); 401 | return this; 402 | }); 403 | 404 | /** 405 | * Returns a string representation of the date object according to Date.format. 406 | * (Date.toString may be used in other places so I purposefully didn't overwrite it) 407 | * 408 | * @example var dtm = new Date("01/12/2008"); 409 | * dtm.asString(); 410 | * @result '12/01/2008' // (where Date.format == 'dd/mm/yyyy' 411 | * 412 | * @name asString 413 | * @type Date 414 | * @cat Plugins/Methods/Date 415 | * @author Kelvin Luck 416 | */ 417 | add("asString", function(format) { 418 | var r = format || Date.format; 419 | return r 420 | .split('yyyy').join(this.getFullYear()) 421 | .split('yy').join((this.getFullYear() + '').substring(2)) 422 | .split('dd').join(_zeroPad(this.getDate())) 423 | .split('d').join(this.getDate()) 424 | .split('DD').join(this.getDayName(false)) 425 | .split('D').join(this.getDayName(true)) 426 | .split('mmmm').join(this.getMonthName(false)) 427 | .split('mmm').join(this.getMonthName(true)) 428 | .split('mm').join(_zeroPad(this.getMonth()+1)) 429 | .split('hh').join(_zeroPad(this.getHours())) 430 | .split('min').join(_zeroPad(this.getMinutes())) 431 | .split('ss').join(_zeroPad(this.getSeconds())); 432 | }); 433 | 434 | /** 435 | * Returns a new date object created from the passed String according to Date.format or false if the attempt to do this results in an invalid date object 436 | * (We can't simple use Date.parse as it's not aware of locale and I chose not to overwrite it incase it's functionality is being relied on elsewhere) 437 | * 438 | * @example var dtm = Date.fromString("12/01/2008"); 439 | * dtm.toString(); 440 | * @result 'Sat Jan 12 2008 00:00:00' // (where Date.format == 'dd/mm/yyyy' 441 | * 442 | * @name fromString 443 | * @type Date 444 | * @cat Plugins/Methods/Date 445 | * @author Kelvin Luck 446 | */ 447 | Date.fromString = function(s, format) 448 | { 449 | var f = format || Date.format, 450 | d = new Date('01/01/1977'), 451 | mLength = 0, 452 | iM, iD, iY, 453 | i, mStr; 454 | 455 | iM = f.indexOf('mmmm'); 456 | if (iM > -1) { 457 | for (i=0; i -1) { 468 | mStr = s.substr(iM, 3); 469 | for (i=0; i -1) { 481 | if (iM < iY) 482 | { 483 | iY += mLength; 484 | } 485 | d.setFullYear(Number(s.substr(iY, 4))); 486 | } else { 487 | if (iM < iY) 488 | { 489 | iY += mLength; 490 | } 491 | // TODO - this doesn't work very well - are there any rules for what is meant by a two digit year? 492 | d.setFullYear(Number(Date.fullYearStart + s.substr(f.indexOf('yy'), 2))); 493 | } 494 | iD = f.indexOf('dd'); 495 | if (iM < iD) 496 | { 497 | iD += mLength; 498 | } 499 | d.setDate(Number(s.substr(iD, 2))); 500 | if (isNaN(d.getTime())) { 501 | return false; 502 | } 503 | return d; 504 | }; 505 | 506 | // utility method 507 | var _zeroPad = function(num) { 508 | var s = '0'+num; 509 | return s.substring(s.length-2) 510 | //return ('0'+num).substring(-2); // doesn't work on IE :( 511 | }; 512 | 513 | })(); 514 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.datePicker/date.min.js: -------------------------------------------------------------------------------- 1 | Date.dayNames=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],Date.abbrDayNames=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],Date.monthNames=["January","February","March","April","May","June","July","August","September","October","November","December"],Date.abbrMonthNames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],Date.firstDayOfWeek=1,Date.format="dd/mm/yyyy",Date.fullYearStart="20",function(){function t(t,e){Date.prototype[t]||(Date.prototype[t]=e)}t("isLeapYear",function(){var t=this.getFullYear();return t%4==0&&t%100!=0||t%400==0}),t("isWeekend",function(){return 0==this.getDay()||6==this.getDay()}),t("isWeekDay",function(){return!this.isWeekend()}),t("getDaysInMonth",function(){return[31,this.isLeapYear()?29:28,31,30,31,30,31,31,30,31,30,31][this.getMonth()]}),t("getDayName",function(t){return t?Date.abbrDayNames[this.getDay()]:Date.dayNames[this.getDay()]}),t("getMonthName",function(t){return t?Date.abbrMonthNames[this.getMonth()]:Date.monthNames[this.getMonth()]}),t("getDayOfYear",function(){var t=new Date("1/1/"+this.getFullYear());return Math.floor((this.getTime()-t.getTime())/864e5)}),t("getWeekOfYear",function(){return Math.ceil(this.getDayOfYear()/7)}),t("setDayOfYear",function(t){return this.setMonth(0),this.setDate(t),this}),t("addYears",function(t){return this.setFullYear(this.getFullYear()+t),this}),t("addMonths",function(t){var e=this.getDate();return this.setMonth(this.getMonth()+t),e>this.getDate()&&this.addDays(-this.getDate()),this}),t("addDays",function(t){var e,s=this.getTimezoneOffset();return this.setTime(this.getTime()+864e5*t),(e=this.getTimezoneOffset())!==s&&this.setTime(this.getTime()+60*(e-s)*1e3),this}),t("addHours",function(t){return this.setHours(this.getHours()+t),this}),t("addMinutes",function(t){return this.setMinutes(this.getMinutes()+t),this}),t("addSeconds",function(t){return this.setSeconds(this.getSeconds()+t),this}),t("zeroTime",function(){return this.setMilliseconds(0),this.setSeconds(0),this.setMinutes(0),this.setHours(0),this}),t("asString",function(t){return(t||Date.format).split("yyyy").join(this.getFullYear()).split("yy").join((this.getFullYear()+"").substring(2)).split("dd").join(e(this.getDate())).split("d").join(this.getDate()).split("DD").join(this.getDayName(!1)).split("D").join(this.getDayName(!0)).split("mmmm").join(this.getMonthName(!1)).split("mmm").join(this.getMonthName(!0)).split("mm").join(e(this.getMonth()+1)).split("hh").join(e(this.getHours())).split("min").join(e(this.getMinutes())).split("ss").join(e(this.getSeconds()))}),Date.fromString=function(t,e){var s,i,n,a,r,h=e||Date.format,o=new Date("01/01/1977"),u=0;if((s=h.indexOf("mmmm"))>-1){for(a=0;a-1){for(r=t.substr(s,3),a=0;a-1?(s1&&(u-=7);var f=Math.ceil((-1*u+1+p.getDaysInMonth())/7);p.addDays(u-1);for(var v=function(a){return function(){if(t.hoverClass){var s=e(this);t.selectWeek?a&&!s.is(".disabled")&&s.parent().addClass("activeWeekHover"):s.addClass(t.hoverClass)}}},D=function(){if(t.hoverClass){var a=e(this);a.removeClass(t.hoverClass),a.parent().removeClass("activeWeekHover")}},m=0;m++t.dpController.startDate;for(i=0;i<7;i++){var g=p.getMonth()==h,b=e(a("td")).text(p.getDate()+"").addClass((g?"current-month ":"other-month ")+(p.isWeekend()?"weekend ":"weekday ")+(g&&p.getTime()==o.getTime()?"today ":"")).data("datePickerDate",p.asString()).hover(v(y),D);C.append(b),t.renderCallback&&t.renderCallback(b,p,h,c),p=new Date(p.getFullYear(),p.getMonth(),p.getDate()+1,12,0,0)}r.append(C)}return d.append(r),this.each(function(){e(this).empty().append(d)})},datePicker:function(t){return e.event._dpCache||(e.event._dpCache=[]),t=e.extend({},e.fn.datePicker.defaults,t),this.each(function(){var s=e(this),i=!0;this._dpId||(this._dpId=e.guid++,e.event._dpCache[this._dpId]=new a(this),i=!1),t.inline&&(t.createButton=!1,t.displayClose=!1,t.closeOnSelect=!1,s.empty());var n=e.event._dpCache[this._dpId];if(n.init(t),!i&&t.createButton&&(n.button=e(''+e.dpText.TEXT_CHOOSE_DATE+"").bind("click",function(){return s.dpDisplay(this),this.blur(),!1}),s.after(n.button)),!i&&s.is(":text")){s.bind("dateSelected",function(e,t,a){this.value=t.asString()}).bind("change",function(){if(""==this.value)n.clearSelected();else{var e=Date.fromString(this.value);e&&n.setSelected(e,!0,!0)}}),t.clickInput&&s.bind("click",function(){s.trigger("change"),s.dpDisplay()});var l=Date.fromString(this.value);""!=this.value&&l&&n.setSelected(l,!0,!0)}s.addClass("dp-applied")})},dpSetDisabled:function(e){return t.call(this,"setDisabled",e)},dpSetStartDate:function(e){return t.call(this,"setStartDate",e)},dpSetEndDate:function(e){return t.call(this,"setEndDate",e)},dpGetSelected:function(){var e=s(this[0]);return e?e.getSelected():null},dpSetSelected:function(e,a,s,i){return null==a&&(a=!0),null==s&&(s=!0),null==i&&(i=!0),t.call(this,"setSelected",Date.fromString(e),a,s,i)},dpClearSelected:function(){return t.call(this,"clearSelected")},dpSetDisplayedMonth:function(e,a){return t.call(this,"setDisplayedMonth",Number(e),Number(a),!0)},dpDisplay:function(e){return t.call(this,"display",e)},dpSetRenderCallback:function(e){return t.call(this,"setRenderCallback",e)},dpSetPosition:function(e,a){return t.call(this,"setPosition",e,a)},dpSetOffset:function(e,a){return t.call(this,"setOffset",e,a)},dpClose:function(){return t.call(this,"_closeCalendar",!1,this[0])},dpRerenderCalendar:function(){return t.call(this,"_rerenderCalendar")},_dpDestroy:function(){}});var t=function(e,t,a,i,n){return this.each(function(){var l=s(this);l&&l[e](t,a,i,n)})};function a(e){this.ele=e,this.displayedMonth=null,this.displayedYear=null,this.startDate=null,this.endDate=null,this.showYearNavigation=null,this.closeOnSelect=null,this.displayClose=null,this.rememberViewedMonth=null,this.selectMultiple=null,this.numSelectable=null,this.numSelected=null,this.verticalPosition=null,this.horizontalPosition=null,this.verticalOffset=null,this.horizontalOffset=null,this.button=null,this.renderCallback=[],this.selectedDates={},this.inline=null,this.context="#dp-popup",this.settings={}}function s(t){return!!t._dpId&&e.event._dpCache[t._dpId]}e.extend(a.prototype,{init:function(e){this.setStartDate(e.startDate),this.setEndDate(e.endDate),this.setDisplayedMonth(Number(e.month),Number(e.year)),this.setRenderCallback(e.renderCallback),this.showYearNavigation=e.showYearNavigation,this.closeOnSelect=e.closeOnSelect,this.displayClose=e.displayClose,this.rememberViewedMonth=e.rememberViewedMonth,this.selectMultiple=e.selectMultiple,this.numSelectable=e.selectMultiple?e.numSelectable:1,this.numSelected=0,this.verticalPosition=e.verticalPosition,this.horizontalPosition=e.horizontalPosition,this.hoverClass=e.hoverClass,this.setOffset(e.verticalOffset,e.horizontalOffset),this.inline=e.inline,this.settings=e,this.inline&&(this.context=this.ele,this.display())},setStartDate:function(e){e&&(e instanceof Date?this.startDate=e:this.startDate=Date.fromString(e)),this.startDate||(this.startDate=(new Date).zeroTime()),this.setDisplayedMonth(this.displayedMonth,this.displayedYear)},setEndDate:function(e){e&&(e instanceof Date?this.endDate=e:this.endDate=Date.fromString(e)),this.endDate||(this.endDate=new Date("12/31/2999")),this.endDate.getTime()l.getTime()&&(n=l);var d=this.displayedMonth,r=this.displayedYear;this.displayedMonth=n.getMonth(),this.displayedYear=n.getFullYear(),!s||this.displayedMonth==d&&this.displayedYear==r||(this._rerenderCalendar(),e(this.ele).trigger("dpMonthChanged",[this.displayedMonth,this.displayedYear]))}},setSelected:function(t,a,s,i){if(!(tthis.endDate.zeroTime())&&(!((d=this.settings).selectWeek&&(t=t.addDays(-(t.getDay()-Date.firstDayOfWeek+7)%7))").attr(s).css(i).append(e("

"),e('
').append(e('<<').bind("click",function(){return n._displayNewMonth.call(n,this,0,-1)}),e('<').bind("click",function(){return n._displayNewMonth.call(n,this,-1,0)})),e('
').append(e('>>').bind("click",function(){return n._displayNewMonth.call(n,this,0,1)}),e('>').bind("click",function(){return n._displayNewMonth.call(n,this,1,0)})),e('
')).bgIframe());var o=this.inline?e(".dp-popup",this.context):e("#dp-popup");0==this.showYearNavigation&&e(".dp-nav-prev-year, .dp-nav-next-year",n.context).css("display","none"),this.displayClose&&o.append(e(''+e.dpText.TEXT_CLOSE+"").bind("click",function(){return n._closeCalendar(),!1})),n._renderCalendar(),e(this.ele).trigger("dpDisplayed",o),n.inline||(this.verticalPosition==e.dpConst.POS_BOTTOM&&o.css("top",d.top+l.height()-o.height()+n.verticalOffset),this.horizontalPosition==e.dpConst.POS_RIGHT&&o.css("left",d.left+l.width()-o.width()+n.horizontalOffset),e(document).bind("mousedown.datepicker",this._checkMouse))}},setRenderCallback:function(e){null!=e&&(e&&"function"==typeof e&&(e=[e]),this.renderCallback=this.renderCallback.concat(e))},cellRender:function(t,a,s,i){var n=this.dpController,l=new Date(a.getTime());t.bind("click",function(){var t=e(this);if(!t.is(".disabled")&&(n.setSelected(l,!t.is(".selected")||!n.selectMultiple,!1,!0),n.closeOnSelect)){if(n.settings.autoFocusNextInput){var a=n.ele,s=!1;e(":input",a.form).each(function(){if(s)return e(this).focus(),!1;this==a&&(s=!0)})}else try{n.ele.focus()}catch(e){}n._closeCalendar()}}),n.isSelected(l)?(t.addClass("selected"),n.settings.selectWeek&&t.parent().addClass("selectedWeek")):n.selectMultiple&&n.numSelected==n.numSelectable&&t.addClass("unselectable")},_applyRenderCallbacks:function(){var t=this;e("td",this.context).each(function(){for(var a=0;a20&&t.addClass("disabled")});var t=this.startDate.getDate();e(".dp-calendar td.current-month",this.context).each(function(){var a=e(this);Number(a.text())20){var a=this.startDate.getTime(),s=new Date(a);s.addMonths(1),this.displayedYear==s.getFullYear()&&this.displayedMonth==s.getMonth()&&e(".dp-calendar td.other-month",this.context).each(function(){var t=e(this);Date.fromString(t.data("datePickerDate")).getTime()t&&a.addClass("disabled")})}else{if(e(".dp-nav-next-year",this.context).removeClass("disabled"),e(".dp-nav-next-month",this.context).removeClass("disabled"),(t=this.endDate.getDate())<13){var i=new Date(this.endDate.getTime());i.addMonths(-1),this.displayedYear==i.getFullYear()&&this.displayedMonth==i.getMonth()&&e(".dp-calendar td.other-month",this.context).each(function(){var a=e(this),s=Number(a.text());s<13&&s>t&&a.addClass("disabled")})}}this._applyRenderCallbacks()},_closeCalendar:function(t,a){a&&a!=this.ele||(e(document).unbind("mousedown.datepicker"),e(document).unbind("keydown.datepicker"),this._clearCalendar(),e("#dp-popup a").unbind(),e("#dp-popup").empty().remove(),t||e(this.ele).trigger("dpClosed",[this.getSelected()]))},_clearCalendar:function(){e(".dp-calendar td",this.context).unbind(),e(".dp-calendar",this.context).empty()}}),e.dpConst={SHOW_HEADER_NONE:0,SHOW_HEADER_SHORT:1,SHOW_HEADER_LONG:2,POS_TOP:0,POS_BOTTOM:1,POS_LEFT:0,POS_RIGHT:1,DP_INTERNAL_FOCUS:"dpInternalFocusTrigger"},e.dpText={TEXT_PREV_YEAR:"Previous year",TEXT_PREV_MONTH:"Previous month",TEXT_NEXT_YEAR:"Next year",TEXT_NEXT_MONTH:"Next month",TEXT_CLOSE:"Close",TEXT_CHOOSE_DATE:"Choose date",HEADER_FORMAT:"mmmm yyyy"},e.dpVersion="$Id$",e.fn.datePicker.defaults={month:void 0,year:void 0,showHeader:e.dpConst.SHOW_HEADER_SHORT,startDate:void 0,endDate:void 0,inline:!1,renderCallback:null,createButton:!0,showYearNavigation:!0,closeOnSelect:!0,displayClose:!1,selectMultiple:!1,numSelectable:Number.MAX_VALUE,clickInput:!1,rememberViewedMonth:!0,selectWeek:!1,verticalPosition:e.dpConst.POS_TOP,horizontalPosition:e.dpConst.POS_LEFT,verticalOffset:0,horizontalOffset:0,hoverClass:"dp-hover",autoFocusNextInput:!1},null==e.fn.bgIframe&&(e.fn.bgIframe=function(){return this}),e(window).bind("unload",function(){var t=e.event._dpCache||[];for(var a in t)e(t[a].ele)._dpDestroy()})}(jQuery); 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.timePicker/timePicker.css: -------------------------------------------------------------------------------- 1 | /* - - - - - - - - - - - - - - - - - - - - - 2 | 3 | ### TEXTPATTERN CUSTOM FIELDS ### 4 | 5 | Title : glz_custom_fields stylesheet 6 | Author : Gerhard Lazu + many contributors 7 | 8 | Last modified: 27 February 2018 (jcr) 9 | 10 | - - - - - - - - - - - - - - - - - - - - - */ 11 | 12 | /* TIME PICKER STYLES 13 | -------------------------------------------------------------- */ 14 | 15 | div.time-picker { 16 | position: absolute; 17 | height: 140px; 18 | width:5em; /* needed for IE */ 19 | overflow: auto; 20 | background: #fff; 21 | border: 1px solid #ccc; 22 | z-index: 99; 23 | margin: 0; 24 | } 25 | div.time-picker-12hours { 26 | width:7em; /* needed for IE */ 27 | } 28 | 29 | div.time-picker ul { 30 | list-style-type: none; 31 | margin: 0; 32 | padding: 0; 33 | } 34 | div.time-picker li { 35 | cursor: pointer; 36 | color: #777; 37 | padding: 4px 4px; 38 | } 39 | div.time-picker li.selected { 40 | background: #fea; 41 | color: #000; 42 | } 43 | 44 | /* button */ 45 | 46 | .glz-custom-timepicker .txp-form-field-value { 47 | position: relative; 48 | } 49 | input.time-picker { 50 | text-indent: 2em; 51 | } 52 | .glz-custom-timepicker .txp-form-field-value::before { 53 | position: absolute; 54 | top: 1px; 55 | left: 1px; 56 | width: 16px; 57 | height: 16px; 58 | padding: 6px; 59 | margin: 0; 60 | display: block; 61 | content: ""; 62 | text-indent: -2000px; 63 | overflow: hidden; 64 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg fill='%23333'%3E%3Cpath d='M7 8V4h2v5l-4 1.5L4.5 9z'/%3E%3Cpath d='M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8zm7 5c2.762 0 5-2.238 5-5s-2.238-5-5-5-5 2.238-5 5 2.238 5 5 5z'/%3E%3C/g%3E%3C/svg%3E"); 65 | background-position: 7px 7px; 66 | background-size: 15px 15px; 67 | background-repeat: no-repeat; 68 | filter: opacity(45%); 69 | } 70 | .glz-custom-timepicker .txp-form-field-value:hover::before { 71 | filter: opacity(100%); 72 | cursor: pointer; 73 | } 74 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.timePicker/timePicker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A time picker for jQuery 3 | * 4 | * Dual licensed under the MIT and GPL licenses. 5 | * Copyright (c) 2009-2011 Anders Fajerson 6 | * @name timePicker 7 | * @author Anders Fajerson (http://perifer.se) 8 | * @example $("#mytime").timePicker(); 9 | * @example $("#mytime").timePicker({step:30, startTime:new Date(0, 0, 0, 15, 00, 0), endTime:"18:00", timeFormat:"hh:mm tt"}); 10 | * 11 | * Based on timePicker by Sam Collet (http://www.texotela.co.uk) 12 | * 13 | */ 14 | 15 | (function($){ 16 | $.fn.timePicker = function(options) { 17 | // Handle deprecated options. 18 | if (options !== undefined && (options.separator !== undefined || options.show24Hours !== undefined) && !options.timeFormat) { 19 | var show24Hours = (options.show24Hours === undefined || options.show24Hours); 20 | options.timeFormat = (show24Hours ? 'HH': 'hh') + (options.separator !== undefined ? options.separator : ':') + 'mm' + (show24Hours ? '': ' tt'); 21 | }; 22 | 23 | var settings = $.extend({}, $.fn.timePicker.defaults, options); 24 | return this.each(function() { 25 | $.timePicker(this, settings); 26 | }); 27 | }; 28 | 29 | $.timePicker = function (elm, settings) { 30 | var e = $(elm)[0]; 31 | return e.timePicker || (e.timePicker = new jQuery._timePicker(e, settings)); 32 | }; 33 | 34 | $.timePicker.version = '0.4'; 35 | 36 | // Public funtions. 37 | $.timePicker.formatTime = function(format, date, settings) { 38 | settings = settings || $.fn.timePicker.defaults; 39 | settings.timeFormat = format; 40 | return formatTime(date, settings); 41 | }; 42 | $.timePicker.parseTime = function(format, input, settings) { 43 | settings = settings || $.fn.timePicker.defaults; 44 | settings.timeFormat = format; 45 | return parseTime(input, settings); 46 | }; 47 | 48 | $._timePicker = function(elm, settings) { 49 | 50 | var tpOver = false; 51 | var keyDown = false; 52 | var startTime = parseTime(settings.startTime, settings); 53 | var endTime = parseTime(settings.endTime, settings); 54 | var selectedClass = "selected"; 55 | var selectedSelector = "li." + selectedClass; 56 | 57 | $(elm).attr('autocomplete', 'OFF'); // Disable browser autocomplete 58 | 59 | var times = []; 60 | var time = new Date(startTime); // Create a new date object. 61 | while(time <= endTime) { 62 | times[times.length] = formatTime(time, settings); 63 | time = new Date(time.setMinutes(time.getMinutes() + settings.step)); 64 | } 65 | 66 | var $tpDiv = $('
'); 67 | var $tpList = $('
    '); 68 | 69 | // Build the list. 70 | for(var i = 0; i < times.length; i++) { 71 | $tpList.append("
  • " + times[i] + "
  • "); 72 | } 73 | $tpDiv.append($tpList); 74 | // Append the timPicker to the body and position it. 75 | $tpDiv.appendTo('body').hide(); 76 | 77 | // Store the mouse state, used by the blur event. Use mouseover instead of 78 | // mousedown since Opera fires blur before mousedown. 79 | $tpDiv.mouseover(function() { 80 | tpOver = true; 81 | }).mouseout(function() { 82 | tpOver = false; 83 | }); 84 | 85 | $("li", $tpList).mouseover(function() { 86 | if (!keyDown) { 87 | $(selectedSelector, $tpDiv).removeClass(selectedClass); 88 | $(this).addClass(selectedClass); 89 | } 90 | }).mousedown(function() { 91 | tpOver = true; 92 | }).click(function() { 93 | setTimeVal(elm, this, $tpDiv, settings); 94 | tpOver = false; 95 | }); 96 | 97 | var showPicker = function() { 98 | if ($tpDiv.is(":visible")) { 99 | return false; 100 | } 101 | $("li", $tpDiv).removeClass(selectedClass); 102 | 103 | // Position 104 | var elmOffset = $(elm).offset(); 105 | $tpDiv.css({'top':elmOffset.top + elm.offsetHeight, 'left':elmOffset.left}); 106 | 107 | // Show picker. This has to be done before scrollTop is set since that 108 | // can't be done on hidden elements. 109 | $tpDiv.show(); 110 | 111 | // Select startTime as default. 112 | var time = startTime; 113 | if (elm.value) { 114 | try { 115 | // Try to find a time in the list that matches the entered time. 116 | time = parseTime(elm.value, settings); 117 | var startMin = startTime.getHours() * 60 + startTime.getMinutes(); 118 | var min = (time.getHours() * 60 + time.getMinutes()) - startMin; 119 | var steps = Math.round(min / settings.step); 120 | var roundTime = new Date(2001, 0, 0, 0, (steps * settings.step + startMin), 0); 121 | time = (startTime < roundTime && roundTime <= endTime) ? roundTime : startTime; 122 | 123 | } 124 | catch(e) { 125 | // Ignore parse errors. 126 | } 127 | }; 128 | var $matchedTime = $("li:contains(" + formatTime(time, settings) + ")", $tpDiv); 129 | if ($matchedTime.length) { 130 | $matchedTime.addClass(selectedClass); 131 | // Scroll to matched time. 132 | $tpDiv[0].scrollTop = $matchedTime[0].offsetTop; 133 | } 134 | 135 | return true; 136 | }; 137 | // Attach to click as well as focus so timePicker can be shown again when 138 | // clicking on the input when it already has focus. 139 | $(elm).focus(showPicker).click(showPicker); 140 | // Hide timepicker on blur 141 | $(elm).blur(function() { 142 | if (!tpOver) { 143 | $tpDiv.hide(); 144 | } 145 | }); 146 | // Keypress doesn't repeat on Safari for non-text keys. 147 | // Keydown doesn't repeat on Firefox and Opera on Mac. 148 | // Using kepress for Opera and Firefox and keydown for the rest seems to 149 | // work with up/down/enter/esc. 150 | // var event = ($.browser.opera || $.browser.mozilla) ? 'keypress' : 'keydown'; 151 | // $(elm)[event](function(e) { 152 | // Removed $.browser as removed since jquery 1.9, using “keydown” 153 | $(elm).keydown(function(e) { 154 | var $selected; 155 | keyDown = true; 156 | var top = $tpDiv[0].scrollTop; 157 | switch (e.keyCode) { 158 | case 38: // Up arrow. 159 | // Just show picker if it's hidden. 160 | if (showPicker()) { 161 | return false; 162 | }; 163 | $selected = $(selectedSelector, $tpList); 164 | var prev = $selected.prev().addClass(selectedClass)[0]; 165 | if (prev) { 166 | $selected.removeClass(selectedClass); 167 | // Scroll item into view. 168 | if (prev.offsetTop < top) { 169 | $tpDiv[0].scrollTop = top - prev.offsetHeight; 170 | } 171 | } 172 | else { 173 | // Loop to next item. 174 | $selected.removeClass(selectedClass); 175 | prev = $("li:last", $tpList).addClass(selectedClass)[0]; 176 | $tpDiv[0].scrollTop = prev.offsetTop - prev.offsetHeight; 177 | } 178 | return false; 179 | break; 180 | case 40: // Down arrow, similar in behaviour to up arrow. 181 | if (showPicker()) { 182 | return false; 183 | }; 184 | $selected = $(selectedSelector, $tpList); 185 | var next = $selected.next().addClass(selectedClass)[0]; 186 | if (next) { 187 | $selected.removeClass(selectedClass); 188 | if (next.offsetTop + next.offsetHeight > top + $tpDiv[0].offsetHeight) { 189 | $tpDiv[0].scrollTop = top + next.offsetHeight; 190 | } 191 | } 192 | else { 193 | $selected.removeClass(selectedClass); 194 | next = $("li:first", $tpList).addClass(selectedClass)[0]; 195 | $tpDiv[0].scrollTop = 0; 196 | } 197 | return false; 198 | break; 199 | case 13: // Enter 200 | if ($tpDiv.is(":visible")) { 201 | var sel = $(selectedSelector, $tpList)[0]; 202 | setTimeVal(elm, sel, $tpDiv, settings); 203 | } 204 | return false; 205 | break; 206 | case 27: // Esc 207 | $tpDiv.hide(); 208 | return false; 209 | break; 210 | } 211 | return true; 212 | }); 213 | $(elm).keyup(function(e) { 214 | keyDown = false; 215 | }); 216 | // Helper function to get an inputs current time as Date object. 217 | // Returns a Date object. 218 | this.getTime = function() { 219 | return parseTime(elm.value, settings); 220 | }; 221 | // Helper function to set a time input. 222 | // Takes a Date object or string. 223 | this.setTime = function(time) { 224 | elm.value = formatTime(parseTime(time, settings), settings); 225 | // Trigger element's change events. 226 | $(elm).change(); 227 | }; 228 | 229 | }; // End fn; 230 | 231 | // Plugin defaults. 232 | 233 | $.fn.timePicker.defaults = { 234 | step:30, 235 | startTime: new Date(0, 0, 0, 0, 0, 0), 236 | endTime: new Date(0, 0, 0, 23, 30, 0), 237 | timeFormat: 'HH:mm', 238 | amDesignator: 'AM', 239 | pmDesignator: 'PM' 240 | }; 241 | 242 | // Private functions. 243 | 244 | function setTimeVal(elm, sel, $tpDiv, settings) { 245 | // Update input field 246 | elm.value = $(sel).text(); 247 | // Trigger element's change events. 248 | $(elm).change(); 249 | 250 | // Removed $.browser as removed since jquery 1.9 251 | elm.focus(); 252 | 253 | // Hide picker 254 | $tpDiv.hide(); 255 | } 256 | 257 | function formatTime(date, settings) { 258 | if (date) return parseFormat(settings, date); 259 | }; 260 | 261 | function formatNumber(value) { 262 | return (value < 10 ? '0' : '') + value; 263 | } 264 | 265 | function parseTime(string, settings) { 266 | if (typeof string == 'object') { 267 | return normalizeTime(string); 268 | } 269 | 270 | var formatParts = settings.timeFormat.match(/(hh?|HH?|mm?|ss?|tt?)/g); 271 | var regexPattern = parseFormat(settings, false); 272 | var re = new RegExp('^' + regexPattern + '$'); 273 | var stringParts = string.match(re); 274 | var ampm; 275 | var hours = -1; 276 | var minutes = -1; 277 | var seconds = 0; 278 | var am = settings.amDesignator; 279 | var pm = settings.pmDesignator; 280 | var date; 281 | for (var i=0; i < formatParts.length; i++) { 282 | if (stringParts && stringParts[i+1]) { 283 | switch (formatParts[i]) { 284 | case "hh": 285 | case "h": 286 | hours = parseInt(stringParts[i+1], 10); 287 | break; 288 | case "HH": 289 | case "H": 290 | hours = parseInt(stringParts[i+1], 10); 291 | break; 292 | case "mm": 293 | case "m": 294 | minutes = parseInt(stringParts[i+1], 10); 295 | break; 296 | case "ss": 297 | case "s": 298 | seconds = parseInt(stringParts[i+1], 10); 299 | break; 300 | case "t": 301 | am = am.substring(0, 1); 302 | pm = pm.substring(0, 1); 303 | // break intentionally left out. 304 | case "tt": 305 | ampm = stringParts[i+1]; 306 | break; 307 | } 308 | }; 309 | }; 310 | if (hours === 12 && ampm === am) { 311 | hours = 0; 312 | } 313 | else if (hours !== 12 && ampm === pm) { 314 | hours += 12; 315 | } 316 | 317 | date = new Date(2001,0,0,hours,minutes,seconds); 318 | if (date.getHours() != hours || date.getMinutes() != minutes || date.getSeconds() != seconds) 319 | throw 'Invalid time'; 320 | return date; 321 | } 322 | 323 | /* Normalise date object to a common year, month and day. */ 324 | function normalizeTime(date) { 325 | date.setFullYear(2001); 326 | date.setMonth(0); 327 | date.setDate(0); 328 | return date; 329 | } 330 | 331 | function parseFormat(settings, date) { 332 | var p = function p(s) { 333 | return (s < 10) ? "0" + s : s; 334 | }; 335 | return settings.timeFormat.replace(/hh?|HH?|mm?|ss?|tt?/g, function(format) { 336 | var am = settings.amDesignator; 337 | var pm = settings.pmDesignator; 338 | switch (format) { 339 | case "hh": 340 | return date ? p(((date.getHours() + 11) % 12) + 1) : '([0-1][0-9])'; 341 | case "h": 342 | return date ? ((date.getHours() + 11) % 12) + 1 : '([0-1]?[0-9])'; 343 | case "HH": 344 | return date ? p(date.getHours()) : '([0-2][0-9])'; 345 | case "H": 346 | return date ? date.getHours() : '([0-2]?[0-9])'; 347 | case "mm": 348 | return date ? p(date.getMinutes()) : '([0-6][0-9])'; 349 | case "m": 350 | return date ? date.getMinutes() : '([0-6]?[0-9])'; 351 | case "ss": 352 | return date ? p(date.getSeconds()) : '([0-6][0-9])'; 353 | case "s": 354 | return date ? date.getSeconds() : '([0-6]?[0-9])'; 355 | case "t": 356 | return date ? date.getHours() < 12 ? am.substring(0, 1) : pm.substring(0, 1) : '(' + am.substring(0, 1) + '|' + pm.substring(0, 1) + ')'; 357 | case "tt": 358 | return date ? date.getHours() < 12 ? am : pm : '(' + am + '|' + pm + ')'; 359 | } 360 | return ''; 361 | }); 362 | } 363 | 364 | })(jQuery); 365 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.timePicker/timePicker.min.css: -------------------------------------------------------------------------------- 1 | div.time-picker{position:absolute;height:140px;width:5em;overflow:auto;background:#fff;border:1px solid #ccc;z-index:99;margin:0}div.time-picker-12hours{width:7em}div.time-picker ul{list-style-type:none;margin:0;padding:0}div.time-picker li{cursor:pointer;color:#777;padding:4px}div.time-picker li.selected{background:#fea;color:#000}.glz-custom-timepicker .txp-form-field-value{position:relative}input.time-picker{text-indent:2em}.glz-custom-timepicker .txp-form-field-value::before{position:absolute;top:1px;left:1px;width:16px;height:16px;padding:6px;margin:0;display:block;content:"";text-indent:-2000px;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg fill='%23333'%3E%3Cpath d='M7 8V4h2v5l-4 1.5L4.5 9z'/%3E%3Cpath d='M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8zm7 5c2.762 0 5-2.238 5-5s-2.238-5-5-5-5 2.238-5 5 2.238 5 5 5z'/%3E%3C/g%3E%3C/svg%3E");background-position:7px 7px;background-size:15px 15px;background-repeat:no-repeat;filter:opacity(45%)}.glz-custom-timepicker .txp-form-field-value:hover::before{filter:opacity(100%);cursor:pointer} 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/jquery.timePicker/timePicker.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t,s,r,i){t.value=e(s).text(),e(t).change(),t.focus(),r.hide()}function s(e,t){if(e)return i(t,e)}function r(e,t){if("object"==typeof e)return function(e){return e.setFullYear(2001),e.setMonth(0),e.setDate(0),e}(e);for(var s,r,n=t.timeFormat.match(/(hh?|HH?|mm?|ss?|tt?)/g),o=i(t,!1),a=new RegExp("^"+o+"$"),c=e.match(a),u=-1,l=-1,f=0,m=t.amDesignator,d=t.pmDesignator,h=0;h'),d=e("
      "),h=0;h"+l[h]+"");m.append(d),m.appendTo("body").hide(),m.mouseover(function(){o=!0}).mouseout(function(){o=!1}),e("li",d).mouseover(function(){a||(e("li.selected",m).removeClass("selected"),e(this).addClass("selected"))}).mousedown(function(){o=!0}).click(function(){t(i,this,m,n),o=!1});var g=function(){if(m.is(":visible"))return!1;e("li",m).removeClass("selected");var t=e(i).offset();m.css({top:t.top+i.offsetHeight,left:t.left}),m.show();var o=c;if(i.value)try{o=r(i.value,n);var a=60*c.getHours()+c.getMinutes(),l=60*o.getHours()+o.getMinutes()-a,f=Math.round(l/n.step),d=new Date(2001,0,0,0,f*n.step+a,0);o=co+m[0].offsetHeight&&(m[0].scrollTop=o+u.offsetHeight)):(r.removeClass("selected"),u=e("li:first",d).addClass("selected")[0],m[0].scrollTop=0),!1;case 13:if(m.is(":visible")){var l=e("li.selected",d)[0];t(i,l,m,n)}return!1;case 27:return m.hide(),!1}return!0}),e(i).keyup(function(e){a=!1}),this.getTime=function(){return r(i.value,n)},this.setTime=function(t){i.value=s(r(t,n),n),e(i).change()}},e.fn.timePicker.defaults={step:30,startTime:new Date(0,0,0,0,0,0),endTime:new Date(0,0,0,23,30,0),timeFormat:"HH:mm",amDesignator:"AM",pmDesignator:"PM"}}(jQuery); 2 | -------------------------------------------------------------------------------- /dist/plugins/glz_custom_fields/my_images.php: -------------------------------------------------------------------------------- 1 | Custom Fields, we're hard-coding it here 92 | return glz_selectInput($custom_field, $custom_id, $images, $custom_value, "2"); 93 | } 94 | 95 | ?> 96 | -------------------------------------------------------------------------------- /docs/help.textile: -------------------------------------------------------------------------------- 1 | h1. Unlimited Custom Fields for Textpattern v4.7+ 2 | 3 | This plugin removes the limit of 10 custom fields in Textpattern and also allows you to create a range of different custom field types: 4 | 5 | * Text inputs (default) 6 | * Checkboxes 7 | * Radio buttons 8 | * Select dropdowns 9 | * Multiselect boxes 10 | * Textareas 11 | 12 | You can specify a set of predefined choices for selects, multi-selects, checkboxes and radio button custom fields, one of which may also be set as the @{default value}@ by placing it in curly brackets. 13 | 14 | Additionally, there are three special custom field types: date pickers, time pickers and custom fields powered by custom scripts of your own making. 15 | 16 | You can provide field label titles for the "Write panel":https://docs.textpattern.io/administration/write-panel along with short instruction texts as input hints for admin users (also per UI languages), and define the order in which the custom fields appear on the Write panel. 17 | 18 | h2(#installation). Installation 19 | 20 | # Open the plugin text file, copy its entire contents to the clipboard and paste into the 'Install plugin' textarea of the "Plugins panel":https://docs.textpattern.io/administration/plugins-panel#uploading-plugins. Click 'Upload' and then 'Install'. 21 | # Upload the "plugins/glz_custom_fields":https://github.com/jools-r/glz_custom_fields/tree/master/dist/ folder to your Textpattern installation directory. 22 | # Activate the plugin. 23 | # Switch to 'Admin › Preferences › Custom fields preferences' and click 'Check paths' at the top right to verify that the corresponding paths in the plugin preferences are correct. The plugin will indicate if there are potential problems. Correct as necessary to match your server setup. 24 | 25 | h3(#upgrading). Upgrading 26 | 27 | Follow the same process as installation. The installer should detect and convert your existing preferences to new names in the background. Replace all the files in the "plugins/glz_custom_fields":https://github.com/jools-r/glz_custom_fields/tree/master/dist/ folder as all files have changed to work with the most recent admin layout. The timepicker and datepicker have likewise been updated and minified. 28 | 29 | _Note: You may relocate the css and js files to locations of your own choosing. Change the paths in the plugin preferences panel accordingly. Only the contents of the jquery.datePicker or jquery.timePicker folders must stay together. You may rename the folders but not the filenames._ 30 | 31 | h3(#deinstallation). Deinstallation 32 | 33 | Deinstalling the plugin does not remove the custom fields settings or the data input into the articles. This is intentional to avoid inadvertent data loss. The fields cannot be edited in the meantime, but reinstalling the plugin restores access to them. To remove data input in the custom fields, delete / reset the custom fields on the “Extensions › Custom Fields” page first before deinstalling the plugin. 34 | 35 | h2(#special-custom-fields). Special custom fields 36 | 37 | There are three special custom field types: 38 | 39 | h3. Time and date pickers 40 | 41 | Time and date pickers offer an easy way for users to specify dates and times in a consistent format. The *date format*, *first day of the week* and *start date* for the calendar can be set in the plugin preferences. 42 | 43 | Similarly, the *time format* (24 or 12 hours), *start and end times* (e.g. @08:00@ and @19:00@) and *time step* (intervals, e.g. @30@ minutes) can be set in the plugin preferences. 44 | 45 | _IMPORTANT: For the datepicker and timepicker to function correctly, you must copy the "plugins/glz_custom_fields":https://github.com/jools-r/glz_custom_fields/tree/master/dist folder to your Textpattern installation directory and set the corresponding paths in the plugin preferences._ 46 | 47 | h3. Custom scripts 48 | 49 | Custom scripts make it possible to build-in custom functionality, for example to pull in information from elsewhere in your site, by writing a corresponding php script to create the custom input field. Please refer to "my_image.php":https://github.com/jools-r/glz_custom_fields/blob/master/dist/plugins/glz_custom_fields/my_images.php file as an example with inline explanations or ask on the forum for tips and examples. 50 | 51 | 52 | h2(#custom-field-names). Custom field names 53 | 54 | *Custom field names* should adhere to the following naming rules: 55 | 56 | * only lowercase letters and numbers 57 | * underscores as separators (no spaces or dashes) 58 | * should not start with a number 59 | 60 | This version of glz_custom_fields will convert the names of _newly created_ custom fields to this pattern and notify you when saving. In the interests of backwards compatibility, it will _not_ change existing custom fields but will issue a warning. 61 | 62 | Previous versions of glz_custom_fields permitted custom names with spaces, dashes, capital letters and numbers. These can, however, cause problems when using custom field names as an attribute for "txp:article_custom":https://docs.textpattern.io/tags/article_custom#attributes. 63 | 64 | Future versions of Textpattern will be stricter about custom field naming and it is advisable to adapt your custom field names and code accordingly. 65 | 66 | _*Important:* You can rename custom field names with this plugin but remember you will also need to alter all instances of "txp:custom_field":https://docs.textpattern.io/tags/custom_field and "txp:if_custom_field":https://docs.textpattern.io/tags/if_custom_field as well as all customfieldname attributes in "txp:article_custom":https://docs.textpattern.io/tags/article_custom in your page templates and forms to match._ 67 | 68 | h3. Custom field titles 69 | 70 | *Custom field titles* have no strict naming restrictions and can also be specified per language. Switch the "User language" in "Admin › Languages" and enter a new title. 71 | 72 | If no custom field title is specified, the custom_field_name will be displayed as the field label as in previous versions. 73 | 74 | The custom field title can be accessed from @page templates@ and @forms@ for displaying to the front-end if required as follows: 75 | 76 | bc. 77 | 78 | This outputs the custom field's title string in the current front-side language. If no title has been defined for the custom field, the tag outputs nothing. 79 | 80 | _Note: The @title="1"@ attribute does not work when this plugin is not installed._ 81 | 82 | h2(#upgrading). Version compatibility 83 | 84 | This version is compatible with Version 4.7+ of Textpattern. It uses functionality not available in earlier versions of Textpattern and adds new functionality not previously available in earlier versions of glz_custom_fields. It is therefore not for use with earlier versions of Textpattern. 85 | 86 | On installation, the plugin upgrades your settings and preferences to avoid potential naming collisions. Downgrading to an earlier version of Textpattern is therefore inadvisable and requires manually renaming preference names to their old naming scheme in the database. 87 | 88 | h3(#changelog). Changelog 89 | 90 | v2.0.6 – May 2025 91 | 92 | * Prevent {default} braces from showing in radio/checkbox/select choices 93 | * HTML5-compliant void tag endings for radios and checkboxes 94 | 95 | v2.0.5 – July 2024 96 | 97 | * PHP 8+ patches / deprecation notices 98 | * CSP nonce support for script and style blocks 99 | * CSS tweaks 100 | 101 | v2.0.1 – March 2021 102 | 103 | * PHP 7.4 deprecation notice / PHP 8 error patched 104 | 105 | v2.0 – March 2018 106 | 107 | * Refactored for Textpattern v4.7 108 | * Plugin preferences now handled in Admin › Preferences 109 | * Plugin preferences not overwritten during installation 110 | * Support for custom field title labels and supporting instruction hints (also per UI-language) 111 | * Custom field titles are accessible in page templates and forms by adding the attribute @title="1"@ to "txp:custom_field":https://docs.textpattern.io/tags/custom_field. 112 | * Change order of custom fields per drag and drop (deactivatable via a hidden pref) 113 | * Translatable: UI now uses textpacks throughout, custom field labels also translatable by switching the UI language 114 | * Under the hood: makes use of Textpattern’s in-built functions for UI creation, error messages/notices, language strings, field labels 115 | * New prefs for js/css URL location including support for relative URLs: relocate your js/css files where you want them 116 | * Compatibility with multi-site Textpattern installations 117 | * Updated + minified js/css and datepicker/timepicker labels 118 | * Adding/deleting/changing a custom field updates site-wide last modified date (for cache renewal) 119 | 120 | h2(#credits). Help and Credits 121 | 122 | h3. Troubleshooting 123 | 124 | Things to check if the plugin does not seem to work as described: 125 | 126 | * Have you copied the "plugins folder":https://github.com/jools-r/glz_custom_fields/tree/master/dist to your textpattern installation? Without the corresponding css and js files, the plugin cannot work. 127 | * Check you have copied _all_ the necessary files. If you have updated from an earlier version, make sure you have the most recent versions. The plugin repository has a "plugins.glz_custom_fields.zip file":https://github.com/jools-r/glz_custom_fields/tree/master/dist with all the necessary files included. 128 | * Switch to 'Admin › Preferences › Custom fields preferences' and click 'Check paths' at the top right to verify that the corresponding paths in the plugin preferences are correct. The plugin will indicate if there are potential problems. Correct as necessary to match your server setup. 129 | * If you are using your own custom locations, check the paths are set correctly. Note that the css and js files of the Datepicker should be kept in the same folder. The same applies for the TimePicker. 130 | * If you are on a windows server avoid a mix of backslashes and forward slashes in your paths. Use just forward slashes and php will understand and convert them automatically. 131 | * If you see a message in the Write pane indicating a problem with the DatePicker or TimePicker, click the error message to check the paths in the settings pane. See above. 132 | * If you see a message in the Write pane that a custom script could not be found or could not be read: 133 | ** Check the custom scripts path is correct as described above. Use the 'Check paths' link at the top right to verify the path can be found. 134 | ** Check you have a .php file named in the folder you specified above. Check that the file has read permissions ("0644":http://www.filepermissions.com/file-permission/0644). Write permissions are _not_ necessary. 135 | ** Check that the name of the .php file matches the name you have specified in the corresponding custom field. 136 | 137 | h3. Help 138 | 139 | Help is always available from the friendly people at the "Textpattern forum":https://forum.textpattern.io/viewtopic.php?id=48511. 140 | 141 | Improvements and problem notifications are always welcome. Please "raise an issue":https://github.com/jools-r/glz_custom_fields/issues or "submit a pull request":https://github.com/jools-r/glz_custom_fields/pulls on GitHub. 142 | 143 | h3. Credits 144 | 145 | This plugin was originally written by "Gerhard Lazu":https://github.com/gerhard and dates back to "2007":https://forum.textpattern.io/viewtopic.php?pid=157983#p157983. It has been repaired, expanded and adopted by numerous forum members over the years (credited in the code), most recently by "Bloke":https://github.com/Bloke/glz_custom_fields. This significantly refactored version adds compatibility with Textpattern v4.7 and is currently looked after by "jools-r":https://github.com/jools-r/glz_custom_fields. Its concept and core workings are still those of Gerhard's original plugin. The Textpattern core will ultimately support unlimited custom fields, making this plugin obsolete at some point in the future. 146 | 147 | This plugin uses *jQuery DatePicker* ("homepage":http://2008.kelvinluck.com/assets/jquery/datePicker/v2/demo/ / "github":https://github.com/vitch/jQuery-datepicker) by Kelvin Luck, *jQuery Timepicker* ("github":https://github.com/perifer/timePicker) by Anders Fajerson and *jQuery sortElements* ("github":https://github.com/padolsey-archive/jquery.fn/tree/master/sortElements) by James Padolsey. 148 | -------------------------------------------------------------------------------- /legacy/frontend.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | function glz_custom_fields_search_form($atts) { 9 | global $all_custom_sets, $s, $glz_search_options; 10 | 11 | $glz_search_options = array(); 12 | 13 | // DEBUG 14 | // dmp($all_custom_sets); 15 | 16 | // we have $_POST, let's see if it comes from glz_custom_fields_search 17 | // DEBUG 18 | // dmp($_POST); 19 | if ( $_POST AND in_array("glz_custom_fields_search", $_POST) ) { 20 | // stripPost() doesn't know how to handle arrays 21 | foreach ($_POST as $key => $value) 22 | $glz_search_options[$key] = (is_array($value)) ? stripslashes(join("|", $value)) : stripslashes($value); 23 | } 24 | 25 | extract(lAtts(array( 26 | 'results_page' => "search", 27 | 'searchby' => "", 28 | 'section' => "", 29 | 'category' => "", 30 | 'labels' => 1 31 | ), $atts)); 32 | 33 | if ( !isset($glz_search_options['section']) ) { 34 | if ( $section != "" ) 35 | $glz_search_options['section'] = $section; 36 | } 37 | 38 | if ( !isset($glz_search_options['category']) && $category != "" && $category != "all" ) 39 | $glz_search_options['category'] = $category; 40 | 41 | // DEBUG 42 | // dmp($glz_search_options); 43 | 44 | if ( !empty($searchby) ) { 45 | // initialize our custom search array 46 | $arr_query_custom = array(); 47 | 48 | // see which custom sets are searchby values associated to - if any 49 | if ( strstr($searchby, ",") ) { 50 | // go through values 1 by 1 and add them to the above array 51 | foreach ( do_list($searchby) as $key => $query_custom ) { 52 | // now we have types for our search fields 53 | // they are separated by : 54 | if ( strstr($query_custom, ":") ) 55 | list($query_custom, $query_custom_type) = explode(":", $query_custom); 56 | else 57 | // if we don't unset this, it will use the one from the previous searchby value 58 | unset($query_custom_type); 59 | 60 | // if this is a section or category, we just need to set the type 61 | if ( in_array($query_custom, array("section", "category")) ) { 62 | $query_custom_type = "checkbox"; 63 | $custom = $key; 64 | } 65 | // otherwise we need to check for the type of this custom field 66 | else { 67 | // get the values we have in our Prefs for this custom set - we might not get anything back 68 | $custom = array_search($query_custom, $GLOBALS['prefs']); 69 | 70 | if ( $custom ) { 71 | if ( !isset($query_custom_type) ) 72 | list($query_custom, $query_custom_type) = array_values($all_custom_sets[$custom]); 73 | } 74 | // if we don't get a custom back, lets default to search input 75 | else 76 | $query_custom_type = "text_input"; 77 | } 78 | // add this custom set to our search array 79 | $arr_query_custom[$custom] = array( 80 | 'name' => $query_custom, 81 | 'type' => $query_custom_type); 82 | } 83 | } 84 | else 85 | // we are searching for a single custom set, add it to our custom sets search array 86 | $arr_query_custom[$searchby] = array_search($query_custom_set, $GLOBALS['prefs']); 87 | 88 | // DEBUG 89 | // dmp($arr_query_custom); 90 | 91 | // start our form 92 | $out[] = '".n 151 | ."".n.n; 152 | // DEBUG 153 | // dmp(join($out)); 154 | 155 | return join($out); 156 | } 157 | else 158 | return trigger_error(glz_custom_fields_gTxt('searchby_not_set')); 159 | } 160 | 161 | 162 | ?> 163 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "glz_custom_fields", 3 | "description" : "Unlimited special custom fields", 4 | "version" : "2.0.6", 5 | "type" : 5, 6 | "author" : "Gerhard Lazu", 7 | "author_uri" : "http://github.com/jools-r/glz_custom_fields", 8 | "contributors" : "Randy Levine, Sam Weiss, Luca Botti, Manfre, Vladimir Siljkovic, Julian Reisenberger, Steve Dickinson, Stef Dawson, Jean-Pol Dupont", 9 | "compatibility" : "4.7", 10 | "order" : 9, 11 | "flags" : 3, 12 | "help" : {"file" : [ 13 | "./docs/help.textile" 14 | ]}, 15 | "code" : {"file" : [ 16 | "./src/glz/db.php", 17 | "./src/glz/helpers.php", 18 | "./src/glz/public-tags.php", 19 | "./src/glz/prefspane.php", 20 | "./src/glz/field-types.php", 21 | "./src/glz/css-js.php", 22 | "./src/glz/callbacks.php", 23 | "./src/glz/steps.php", 24 | "./src/glz/main.php" 25 | ]} 26 | } 27 | -------------------------------------------------------------------------------- /src/glz/callbacks.php: -------------------------------------------------------------------------------- 1 | $custom_set) { 46 | // Get all possible/default value(s) for this custom set from custom_fields table 47 | $arr_custom_field_values = glz_db_get_custom_field_values($custom, array('custom_set_name' => $custom_set['name'])); 48 | 49 | // DEBUG 50 | // dmp($arr_custom_field_values); 51 | 52 | // Custom_set formatted for id e.g. custom_1_set => custom-1 - don't ask... 53 | $custom_id = glz_custom_number($custom, "-"); 54 | // custom_set without "_set" e.g. custom_1_set => custom_1 55 | $custom = glz_custom_number($custom); 56 | 57 | // If current article holds no value for this custom field and we have no default value, make it empty 58 | // (not using empty() as it also eradicates values of '0') 59 | $custom_value = ((isset($$custom) && trim($$custom) <> '') ? $$custom : ''); 60 | // DEBUG 61 | // dmp("custom_value: {$custom_value}"); 62 | 63 | // Check if there is a default value 64 | // if there is, strip the { } 65 | $default_value = glz_clean_default(glz_default_value($arr_custom_field_values)); 66 | // DEBUG 67 | // dmp("default_value: {$default_value}"); 68 | 69 | // Now that we've found our default, we need to clean our custom_field values 70 | if (is_array($arr_custom_field_values)) { 71 | array_walk($arr_custom_field_values, "glz_clean_default_array_values"); 72 | } 73 | 74 | // DEBUG 75 | // dmp($arr_custom_field_values); 76 | 77 | // The way our custom field value is going to look like 78 | list($custom_set_value, $custom_class) = glz_format_custom_set_by_type($custom, $custom_id, $custom_set['type'], $arr_custom_field_values, $custom_value, $default_value); 79 | 80 | // DEBUG 81 | // dmp($custom_set_value); 82 | 83 | // cf_lang string (define this in your language to create a field label) 84 | $cf_lang = glz_cf_langname($custom_set["name"]); 85 | // Get the (localised) label if one exists, otherwise the regular name (as before) 86 | $cf_label = (gTxt($cf_lang) != $cf_lang) ? gTxt($cf_lang) : $custom_set["name"]; 87 | 88 | $out .= inputLabel( 89 | $custom_id, 90 | $custom_set_value, 91 | $cf_label, 92 | array('', 'instructions_'.$custom), 93 | array('class' => 'txp-form-field custom-field glz-cf '.$custom_class.' '.$custom_id.' cf-'.glz_cf_idname(str_replace('_', '-', $custom_set["name"]))) 94 | ); 95 | } 96 | } 97 | 98 | // DEBUG 99 | // dmp($out); 100 | 101 | // If we're writing textarea custom fields, we need to include the excerpt as well 102 | if ($step == "body") { 103 | $out = $data.$out; 104 | } 105 | 106 | return $out; 107 | } 108 | 109 | 110 | // ------------------------------------------------------------- 111 | // Prep custom fields values for db (convert multiple values into a string e.g. multi-selects, checkboxes & radios) 112 | function glz_custom_fields_before_save() 113 | { 114 | // Iterate over POST vars 115 | foreach ($_POST as $key => $value) { 116 | // Extract custom_{} keys with multiple values as arrays 117 | if (strstr($key, 'custom_') && is_array($value)) { 118 | // Convert to delimited string … 119 | $value = implode('|', $value); 120 | // and feed back into $_POST 121 | $_POST[$key] = $value; 122 | } 123 | } 124 | 125 | // DEBUG 126 | // dmp($_POST); 127 | } 128 | 129 | 130 | // ------------------------------------------------------------- 131 | // Inject css & js into admin head 132 | function glz_custom_fields_inject_css_js($debug = false) 133 | { 134 | global $event, $prefs, $use_minified, $debug; 135 | 136 | $msg = array(); 137 | $min = ($use_minified) ? '.min' : ''; 138 | 139 | // do we have a date-picker or time-picker custom field 140 | $date_picker = glz_check_custom_set_exists("date-picker"); 141 | $time_picker = glz_check_custom_set_exists("time-picker"); 142 | 143 | // glz_cf stylesheets (load from file when $debug is set to true) 144 | if ($debug) { 145 | $css_url = glz_relative_url($prefs['glz_cf_css_asset_url']).'/glz_custom_fields'.$min.'.css'; 146 | $css = glz_inject_css($css_url, 1, array('media' => 'screen')); 147 | // Show hidden fields 148 | $css .= glz_inject_css('#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:flex}'); 149 | } else { 150 | $css = glz_custom_fields_head_css(); 151 | } 152 | // glz_cf javascript 153 | $js = ''; 154 | 155 | if ($event == 'article') { 156 | // If a date picker field exists 157 | if ($date_picker) { 158 | $css_datepicker_url = glz_relative_url($prefs['glz_cf_datepicker_url']).'/datePicker'.$min.'.css'; 159 | $css .= glz_inject_css($css_datepicker_url, 1, array('media' => 'screen')); 160 | foreach (array('date'.$min.'.js', 'datePicker'.$min.'.js') as $file) { 161 | $js .= glz_inject_js(glz_relative_url($prefs['glz_cf_datepicker_url'])."/".$file, 1); 162 | } 163 | $js_datepicker_msg = ' '.gTxt('glz_cf_public_error_datepicker').' Close'; 164 | $js_datepicker = << 0) { 170 | try { 171 | Date.firstDayOfWeek = {$prefs['glz_cf_datepicker_first_day']}; 172 | Date.format = '{$prefs["glz_cf_datepicker_format"]}'; 173 | Date.fullYearStart = '19'; 174 | $(".date-picker").datePicker({startDate:'{$prefs["glz_cf_datepicker_start_date"]}'}); 175 | $(".date-picker").dpSetOffset(29, -1); 176 | } catch(err) { 177 | $('#messagepane').html('{$js_datepicker_msg}'); 178 | } 179 | } 180 | } 181 | 182 | glzDatePicker(); 183 | }); 184 | JS; 185 | $js .= glz_inject_js($js_datepicker); 186 | } 187 | 188 | // If a time picker field exists 189 | if ($time_picker) { 190 | $css_timepicker_url = glz_relative_url($prefs['glz_cf_timepicker_url']).'/timePicker'.$min.'.css'; 191 | $css .= glz_inject_css($css_timepicker_url, 1, array('media' => 'screen')); 192 | $js_timepicker_url = glz_relative_url($prefs['glz_cf_timepicker_url']).'/timePicker'.$min.'.js'; 193 | $js .= glz_inject_js($js_timepicker_url, 1); 194 | $js_timepicker_msg = ' '.gTxt('glz_cf_public_error_timepicker').' Close'; 195 | $js_timepicker = << 0) { 201 | try { 202 | $("input.time-picker").timePicker({ 203 | startTime: '{$prefs["glz_cf_timepicker_start_time"]}', 204 | endTime: '{$prefs["glz_cf_timepicker_end_time"]}', 205 | step: {$prefs["glz_cf_timepicker_step"]}, 206 | show24Hours: {$prefs["glz_cf_timepicker_show_24"]} 207 | }); 208 | $(".glz-custom-timepicker .txp-form-field-value").on("click", function (){ 209 | $(this).children(".time-picker").trigger("click"); 210 | }); 211 | } catch(err) { 212 | $("#messagepane").html('{$js_timepicker_msg}'); 213 | } 214 | } 215 | } 216 | 217 | glzTimePicker(); 218 | }); 219 | JS; 220 | $js .= glz_inject_js($js_timepicker); 221 | } 222 | } 223 | if ($event == 'glz_custom_fields') { 224 | $js_sortable_url = glz_relative_url($prefs['glz_cf_js_asset_url']).'/glz_jqueryui.sortable'.$min.'.js'; 225 | $js .= glz_inject_js($js_sortable_url, 1); 226 | } 227 | 228 | // glz_cf javascript (load from file when $debug is set to true) 229 | if ($event != 'prefs') { 230 | if ($debug) { 231 | $js_core_url = glz_relative_url($prefs['glz_cf_js_asset_url']).'/glz_custom_fields'.$min.'.js'; 232 | $js .= glz_inject_js($js_core_url, 1); 233 | } else { 234 | $js .= glz_custom_fields_head_js(); 235 | } 236 | 237 | } 238 | 239 | echo $js.n.t. 240 | $css.n.t; 241 | } 242 | 243 | 244 | // ------------------------------------------------------------- 245 | // Install glz_cf tables and prefs 246 | function glz_custom_fields_install() 247 | { 248 | global $prefs; 249 | $msg = ''; 250 | 251 | // Set plugin preferences 252 | glz_cf_prefs_install(); 253 | 254 | // Change 'html' key of default custom fields from 'custom_set' 255 | // to 'text_input' to avoid confusion with glz set_types() 256 | safe_update('txp_prefs', "html = 'text_input'", "event = 'custom' AND html = 'custom_set'"); 257 | 258 | /* 259 | // LEGACY of the old '' tag? 260 | // Create a search section if not already available (for searching by custom fields) 261 | if (empty(safe_row("name", 'txp_section', "name='search'"))) { 262 | 263 | // Retrieve skin name used for 'default' section 264 | $current_skin = safe_field('skin', 'txp_section', "name='default'"); 265 | 266 | // Add new 'search' section 267 | safe_insert('txp_section', " 268 | name = 'search', 269 | title = 'Search', 270 | skin = '".$current_skin."', 271 | page = 'default', 272 | css = 'default', 273 | description = '', 274 | on_frontpage = '0', 275 | in_rss = '0', 276 | searchable = '0' 277 | "); 278 | 279 | $msg = gTxt('glz_cf_search_section_created'); 280 | } 281 | */ 282 | // Create 'custom_fields' table if it does not already exist 283 | safe_create( 284 | 'custom_fields', 285 | "`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, 286 | `name` varchar(255) NOT NULL default '', 287 | `value` varchar(255) NOT NULL default '', 288 | PRIMARY KEY (id), 289 | KEY (`name`(50))", 290 | "ENGINE=MyISAM" 291 | ); 292 | 293 | // Add an 'id' column to an existing legacy 'custom_fields' table 294 | if (!getRows("SHOW COLUMNS FROM ".safe_pfx('custom_fields')." LIKE 'id'")) { 295 | safe_alter( 296 | 'custom_fields', 297 | "ADD `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT KEY" 298 | ); 299 | } 300 | 301 | // Migrate existing custom_field data to new 'custom_fields' table 302 | 303 | // Skip if glz_cf migration has already been performed 304 | if (isset($prefs['glz_cf_migrated'])) { 305 | return; 306 | } 307 | 308 | // Skip if 'custom_fields' table already contains values (don't overwrite anything) 309 | if (($count = safe_count('custom_fields', "1 = 1")) !== false) { 310 | // Set flag in 'txp_prefs' that migration has already been performed 311 | set_pref("glz_cf_migrated", "1", "glz_custom_f", PREF_HIDDEN); 312 | $msg = gTxt('glz_cf_migration_skip'); 313 | return; 314 | } 315 | 316 | // Get all custom field sets from prefs 317 | $all_custom_sets = glz_db_get_all_custom_sets(); 318 | 319 | // Iterate over all custom_fields and retrieve all values 320 | // in custom field columns in textpattern table 321 | foreach ($all_custom_sets as $custom => $custom_set) { 322 | 323 | // Check only custom fields that have been set (have a name) 324 | if ($custom_set['name']) { 325 | 326 | // Get all existing custom values for ALL articles 327 | $all_values = glz_db_get_all_existing_cf_values( 328 | glz_custom_number($custom), 329 | array( 330 | 'custom_set_name' => $custom_set['name'], 331 | 'status' => 0 332 | ) 333 | ); 334 | 335 | // If we have results, assemble SQL insert statement to add them to custom_fields table 336 | if (count($all_values) > 0) { 337 | $insert = ''; 338 | foreach ($all_values as $escaped_value => $value) { 339 | // skip empty values or values > 255 characters (=probably textareas?) 340 | if (!empty($escaped_value) && strlen($escaped_value) < 255) { 341 | $insert .= "('{$custom}','{$escaped_value}'),"; 342 | } 343 | } 344 | // Trim final comma and space 345 | $insert = rtrim($insert, ', '); 346 | $query = " 347 | INSERT INTO 348 | ".safe_pfx('custom_fields')." (`name`,`value`) 349 | VALUES 350 | {$insert} 351 | "; 352 | 353 | if (isset($query) && !empty($query)) { 354 | 355 | // Add all custom field values to 'custom_fields' table 356 | safe_query($query); 357 | 358 | // Update the type of this custom field to select 359 | // (might want to make this user-adjustable at some point) 360 | safe_update( 361 | 'txp_prefs', 362 | "val = '".$custom_set['name']."', 363 | html = 'select', 364 | position = '".$custom_set['position']."'", 365 | "name = '{$custom}'" 366 | ); 367 | $msg = gTxt('glz_cf_migration_success'); 368 | } 369 | } 370 | } 371 | } 372 | 373 | // Set flag in txp_prefs that migration has been performed 374 | set_pref("glz_cf_migrated", "1", "glz_custom_f", PREF_HIDDEN); 375 | } 376 | 377 | 378 | /** 379 | * Uninstaller. 380 | * 381 | * IMPORTANT: There has been no uninstall function until to now to prevent 382 | * accidental loss of user input if uninstalling the plugin. 383 | * 384 | * This is intended just as an on-demand clean-up script and is hidden 385 | * behind a 'safety catch'. In the 'txp_prefs' table, set the column 'type' 386 | * of 'glz_cf_permit_full_deinstall' to '1' to reveal the switch in the 387 | * preferences panel. The installer sets this to hidden from the beginning. 388 | * 389 | */ 390 | 391 | function glz_custom_fields_uninstall() 392 | { 393 | global $prefs; 394 | 395 | // To prevent inadvertent data loss, full deinstallation is only permitted 396 | // if the 'safety catch' has been disabled: set 'glz_cf_permit_full_deinstall' = 1 397 | if ($prefs['glz_cf_permit_full_deinstall'] == '1') { 398 | 399 | // Delete 'custom_fields' table 400 | safe_query( 401 | 'DROP TABLE IF EXISTS '.safe_pfx('custom_fields') 402 | ); 403 | 404 | // Get all custom fields > 10 405 | $additional_cfs = safe_rows('name', 'txp_prefs', "name LIKE 'custom\___\_set' AND name <> 'custom_10_set'"); 406 | 407 | $drop_query =''; 408 | foreach ($additional_cfs as $val) { 409 | // Delete prefs labels for custom fields > 10 410 | safe_delete('txp_lang', "name = '".$val['name']."'"); 411 | // Build DROP query for 'textpattern' table 412 | $drop_query .= 'DROP '.str_replace("_set", "", $val['name']).', '; 413 | } 414 | // Trim final comma and space from drop statement 415 | $drop_query = rtrim($drop_query, ', '); 416 | // Drop used 'custom_X' > 10 columns from 'textpattern' table 417 | safe_alter('textpattern', $drop_query); 418 | 419 | // Delete all saved language strings 420 | safe_delete('txp_lang', "event = 'glz_cf' OR name LIKE 'instructions\_glz\_cf%'"); 421 | 422 | // Delete custom field entries > 10 from 'txp_prefs' (custom_ __ _set = must have two chars in the middle) 423 | safe_delete('txp_prefs', "name LIKE 'custom\___\_set' AND name <> 'custom_10_set' AND event = 'custom'"); 424 | 425 | // Delete plugin prefs 426 | safe_delete('txp_prefs', "event LIKE 'glz\_custom\_f%'"); 427 | 428 | // Reset all remaining custom fields (1-10) back to original type 'custom_set' 429 | safe_update('txp_prefs', "html = 'custom_set'", "event = 'custom'"); 430 | 431 | // The following also clears the built-in custom fields 1-10 432 | // For the "full whammy" uncomment these too. 433 | /* 434 | // Zero custom field user input in the 'textpattern' table 435 | safe_update('textpattern', "custom_1 = NULL, custom_2 = NULL, custom_3 = NULL, custom_4 = NULL, custom_5 = NULL, custom_6 = NULL, custom_7 = NULL, custom_8 = NULL, custom_9 = NULL, custom_10 = NULL", "1 = 1"); 436 | // Erase names from 'txp_prefs' tables 437 | safe_update('txp_prefs', "val = NULL", "name LIKE 'custom\_%%\_set'"); 438 | */ 439 | $message = "‘glz_custom_fields’ has been deinstalled. ALL CUSTOM FIELD USER DATA has also been removed."; 440 | 441 | } else { 442 | 443 | // Regular deinstall 444 | 445 | // Should we restore the 'html' type for custom fields 1-10 to 'text_input'? 446 | // Yes: it prevents errors occurring (or is there an automatic fallback) 447 | // No: switching them back loses their settings. The data is kept but the 448 | // custom_field type is then lost in the case of a reinstallation. 449 | 450 | $message = "‘glz_custom_fields’ has been deinstalled. Your custom field data has NOT been deleted and will reappear if you reinstall ‘glz_custom_fields’."; 451 | } 452 | 453 | } 454 | 455 | 456 | // ------------------------------------------------------------- 457 | // Re-route 'Options' link on Plugins panel to Admin › Preferences 458 | function glz_custom_fields_prefs_redirect() 459 | { 460 | header("Location: index.php?event=prefs#prefs_group_glz_custom_f"); 461 | } 462 | 463 | 464 | // ------------------------------------------------------------- 465 | // Custom field sortable position router function 466 | function glz_cf_positionsort_steps($event='', $step='', $msg='') 467 | { 468 | switch ($step) { 469 | case 'get_js': 470 | glz_cf_positionsort(gps('js')); 471 | break; 472 | case 'put': 473 | glz_cf_positionsort(gps('type')); 474 | break; 475 | } 476 | } 477 | 478 | 479 | // ------------------------------------------------------------- 480 | // Custom field sortable position inject js 481 | function glz_cf_positionsort_js() 482 | { 483 | $js_positionsort = glz_inject_js('index.php?event=glz_custom_fields&step=get_js', 1); 484 | echo << $sort) { 501 | if (!safe_update('txp_prefs', 'position=\''.doSlash($sort).'\'', 'name=\''.doSlash($customfield).'\'')) { 502 | $success = false; 503 | } 504 | } 505 | echo json_encode(array('success' => $success)); 506 | 507 | } else { 508 | 509 | $position = array(); 510 | foreach (safe_rows('name, position', 'txp_prefs', "event = 'custom'") as $row) { 511 | $customfield = $row['name']; 512 | $sort = $row['position']; 513 | if (!strlen($sort)) { 514 | $sort = 0; 515 | } 516 | $position['glz_' . $customfield] = ctype_digit($sort) ? (int)$sort : $sort; 517 | } 518 | 519 | // Language strings 520 | $ui_sort = gTxt('glz_cf_col_sort'); 521 | $msg_success = gTxt('glz_cf_sort_success'); 522 | $msg_error = gTxt('glz_cf_sort_error'); 523 | 524 | echo 'var position = ', json_encode($position), ';'."\n"; 525 | echo <<$ui_sort').find('th').each(function() { 528 | var th = $(this); 529 | th.html(th.text()); 530 | }); 531 | $('#glz_custom_fields_container table').addClass('sortable').find('tbody tr').prepend('').appendTo('#glz_custom_fields_container tbody').sortElements(function(a, b) { 532 | var a_sort = position[$(a).attr('id')]; 533 | var b_sort = position[$(b).attr('id')]; 534 | if (a_sort == b_sort) { 535 | return 0; 536 | } 537 | return a_sort > b_sort ? 1 : -1; 538 | }).parent().sortable({ 539 | items: 'tr', 540 | helper: function(e, ui) { 541 | $('.ui-sortable').parent().addClass('fixed-width'); 542 | ui.children().each(function() { 543 | $(this).width($(this).width()); 544 | }); 545 | return ui; 546 | }, 547 | axis: 'y', 548 | handle: 'td:first-child', 549 | start: function(event, ui) { 550 | }, 551 | stop: function() { 552 | $('.ui-sortable').parent().removeClass('fixed-width'); 553 | var position = {}; 554 | $(this).find('tr').each(function() { 555 | var tr = $(this); 556 | position[tr.attr('id').replace('glz_', '')] = tr.index(); 557 | }); 558 | var set_message = function(message, type) { 559 | $('#messagepane').html('' + message + ' Close'); 560 | } 561 | $.ajax( 562 | 'index.php?event=glz_custom_fields&step=put', { 563 | type: 'POST', 564 | data: position, 565 | dataType: 'json', 566 | success: function(response) { 567 | if (response.success) { 568 | set_message('$msg_success', 'success') 569 | } else { 570 | this.error(); 571 | } 572 | }, 573 | error: function() { 574 | set_message('$msg_error', 'error'); 575 | } 576 | } 577 | ); 578 | } 579 | }).find('tr').find('td:first-child').html('☰'); 580 | }); 581 | EOB; 582 | 583 | } 584 | exit(); 585 | } 586 | -------------------------------------------------------------------------------- /src/glz/css-js.php: -------------------------------------------------------------------------------- 1 | area 14 | # 15 | ################## 16 | 17 | 18 | // ------------------------------------------------------------- 19 | // Contains minified output of glz_custom_fields.css file 20 | // To update: copy the minified output of the actual css file into the function 21 | function glz_custom_fields_head_css() 22 | { 23 | $css = <<<'CSS' 24 | .glz-cf-setup-switch{float:inline-end}#glz_custom_fields_container .txp-list-col-id{width:3em;text-align:center}#glz_custom_fields_container .txp-list-col-options,#glz_custom_fields_container .txp-list-col-position{width:5em}#glz_custom_fields_container .txp-list-col-title .cf-instructions.ui-icon{width:2em;height:17px;float:inline-end;background-repeat:no-repeat;background-position:center 2px;opacity:.33;cursor:pointer}#glz_custom_fields_container .txp-list-col-title.disabled .cf-instructions{opacity:1 !important;pointer-events:auto}#glz_custom_fields_container .txp-list-col-options{text-align:center}#glz_custom_fields_container .txp-list-col-options .ui-icon{width:4em;background-repeat:no-repeat;background-position:50%}#glz_custom_fields_container .txp-list-col-options .ui-icon:hover{-webkit-filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%);filter:brightness(0) saturate(100%) invert(17%) sepia(51%) saturate(5958%) hue-rotate(211deg) brightness(89%) contrast(101%)}#glz_custom_fields_container table.fixed-width{table-layout:fixed}#glz_custom_fields_container table.sortable .txp-list-col-sort{width:3em;text-align:center}#glz_custom_fields_container table.sortable .ui-sortable-handle{cursor:row-resize;text-align:center;opacity:.66}#glz_custom_fields_container table.sortable .txp-list-col-position{display:none}#glz_custom_fields_container .ui-sortable-helper,#glz_custom_fields_container .ui-sortable-placeholder{display:table}#add_edit_custom_field .hidden{display:none}@media screen and (min-width:47em){.txp-edit .txp-form-field .txp-form-field-instructions,.txp-tabs-vertical-group .txp-form-field-instructions{padding-left:50%}}.check-path{float:inline-end;font-size:.7em;font-weight:400}[dir=rtl] .check-path{float:inline-start}.ui-tabs-nav .check-path{display:none}#prefs-glz_cf_css_asset_url,#prefs-glz_cf_js_asset_url{display:none}.glz-custom-field-reset.disabled:hover{text-decoration:none}.glz-custom-field-reset.disabled{cursor:default} 25 | CSS; 26 | $css = glz_inject_css($css); 27 | return $css; 28 | } 29 | 30 | 31 | // ------------------------------------------------------------- 32 | // Contains minified output of glz_custom_fields.css file 33 | // To update copy the minified output of the actual js file into the function 34 | function glz_custom_fields_head_js() 35 | { 36 | $js = <<<'JS' 37 | $(function(){function e(){$(".glz-custom-radio").length>0&&$(".glz-custom-radio").each(function(){var e=$(this).find("input:first").attr("name");$(this).find("label:first").after(' Reset'),$("input:radio[name="+e+"]").is(":checked")||$(".glz-custom-field-reset[name="+e+"]").addClass("disabled")})}function t(){$glz_value_field.find("textarea#value").length&&(s.textarea_value=$glz_value_field.find("textarea#value").html(),$glz_value_field.find("textarea#value").remove()),$glz_value_field.find("input#value").length?0==$glz_value_field.find("input#value").prop("disabled")&&(s.path_value=$glz_value_field.find("input#value").attr("value")):$glz_value_field.find(".txp-form-field-value").prepend(''),$glz_value_field.find("input#value").attr("value","-----").prop("disabled",!0),$glz_value_instructions.html("")}function l(){$glz_value_field.find("input#value").length&&(0==$glz_value_field.find("input#value").prop("disabled")&&(s.path_value=$glz_value_field.find("input#value").attr("value")),$glz_value_field.find("input#value").remove(),$glz_value_instructions.html("")),$glz_value_field.find("textarea#value").length||$(".edit-custom-set-value .txp-form-field-value").prepend(''),s.textarea_value&&$glz_value_field.find("textarea#value").html(s.textarea_value),$glz_value_instructions.html(s.messages.textarea)}function a(){$glz_value_field.find("textarea#value").length&&(s.textarea_value=$glz_value_field.find("textarea#value").html(),$glz_value_field.find("textarea#value").remove(),$glz_value_instructions.html("")),$glz_value_field.find("input#value").length||$glz_value_field.find(".txp-form-field-value").prepend(''),"-----"==$glz_value_field.find("input#value").attr("value")&&$glz_value_field.find("input#value").attr("value",""),$glz_value_field.find("input#value").prop("disabled",!1),$glz_value_instructions.html(s.messages.customscriptpath),s.path_value&&$glz_value_field.find("input#value").attr("value",s.path_value)}function i(){-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,["multi-select","custom-script"]))?$glz_select_instructions.html(''+s.messages.configure+""):$glz_select_instructions.html("")}textpattern.Relay.register("txpAsyncForm.success",e);var s;s={},s.special_custom_types=["date-picker","time-picker"],s.no_value_custom_types=["text_input","textarea"],$glz_value_field=$(".edit-custom-set-value"),$glz_value_instructions=$glz_value_field.find(".txp-form-field-instructions"),$glz_select_instructions=$(".edit-custom-set-type").find(".txp-form-field-instructions"),s.messages={textarea:$(".glz-custom-textarea-msg").html(),configure:$glz_select_instructions.text(),customscriptpath:$(".glz-custom-script-msg").text()},$(".glz-custom-script-msg").remove(),$(".glz-custom-textarea-msg").remove(),i(),-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():"custom-script"==$("select#custom_set_type :selected").attr("value")&&a(),$("select#custom_set_type").change(function(){i(),-1!=$.inArray($("select#custom_set_type :selected").attr("value"),[].concat(s.special_custom_types,s.no_value_custom_types))?t():"custom-script"==$("select#custom_set_type :selected").attr("value")?a():l()}),e(),$(".txp-layout").on("click",".glz-custom-field-reset",function(){if($(this).hasClass("disabled"))return!1;var e=$(this).attr("name");return $("input[name="+e+"]").prop("checked",!1),$("input[name="+e+"].default").prop("checked",!0),$(this).addClass("disabled"),0===$(this).siblings(".txp-form-radio-reset").length&&0===$("input[name="+e+"]:checked").length&&$(this).after(''),!1}),$(".txp-layout").on("click",".glz-custom-radio .radio",function(){var e=$(this).attr("name");$this_reset_button=$(".glz-custom-field-reset[name="+e+"]"),$this_reset_button.hasClass("disabled")&&($("input[type=hidden][name="+e+"]").remove(),$this_reset_button.removeClass("disabled"))})}); 38 | JS; 39 | $js = glz_inject_js($js); 40 | return $js; 41 | } 42 | 43 | 44 | // ------------------------------------------------------------- 45 | // Helper function: nonced script tags if CSP headers set 46 | function glz_inject_js($js, $src = 0, $atts = '') 47 | { 48 | if (class_exists('\Textpattern\UI\Script')) { 49 | $js_out = new \Textpattern\UI\Script(); 50 | if ($src) { 51 | $js_out->setSource($js); 52 | } else { 53 | $js_out->setContent($js); 54 | } 55 | if (is_array($atts)) { 56 | foreach ($atts as $key => $value) { 57 | $js_out->setAtt($key, $value); 58 | } 59 | } 60 | } else { 61 | if ($src) { 62 | $atts = array( 63 | 'src' => $js 64 | ); 65 | $js_out = ''; 66 | } else { 67 | $js_out = '' . $js . ''; 68 | } 69 | } 70 | return $js_out; 71 | } 72 | 73 | 74 | // ------------------------------------------------------------- 75 | // Helper function: nonced style or link tags if CSP headers set 76 | function glz_inject_css($css, $src = 0, $atts = '') 77 | { 78 | if (class_exists('\Textpattern\UI\Style')) { 79 | $css_out = new \Textpattern\UI\Style(); 80 | if ($src) { 81 | $css_out->setSource($css); 82 | } else { 83 | $css_out->setContent($css); 84 | } 85 | if (is_array($atts)) { 86 | foreach ($atts as $key => $value) { 87 | $css_out->setAtt($key, $value); 88 | } 89 | } 90 | } else { 91 | if ($src) { 92 | $atts = array( 93 | 'rel' => 'stylesheet', 94 | 'href' => $css 95 | ); 96 | $css_out = tag_void('link', $atts); 97 | } else { 98 | $css_out = '' . $css . ''; 99 | } 100 | } 101 | return $css_out; 102 | } 103 | -------------------------------------------------------------------------------- /src/glz/field-types.php: -------------------------------------------------------------------------------- 1 | $value) { 108 | $selected = glz_selected_checked('selected', $key, $custom_value, $default_value); 109 | $value = trim($value, '{}'); 110 | $out[] = ""; 111 | } 112 | 113 | // We'll need the extra attributes as well as a name that will produce an array 114 | if ($multi) { 115 | $multi = ' multiple="multiple" size="'.$prefs['glz_cf_multiselect_size'].'"'; 116 | $name .= "[]"; 117 | } 118 | 119 | return ""; 123 | } else { 124 | return gTxt('glz_cf_field_problems', array('{custom_set_name}' => $name)); 125 | } 126 | } 127 | 128 | 129 | // ------------------------------------------------------------- 130 | // Had to duplicate the default checkbox() to keep the looping in here and check against existing value/s 131 | function glz_checkbox($name = '', $arr_values = '', $custom_value = '', $default_value = '') 132 | { 133 | if (is_array($arr_values)) { 134 | $out = array(); 135 | 136 | // If there is no custom_value coming from the article, let's use our default one 137 | if (empty($custom_value)) { 138 | $custom_value = $default_value; 139 | } 140 | 141 | foreach ($arr_values as $key => $value) { 142 | $checked = glz_selected_checked('checked', $key, $custom_value); 143 | $value = trim($value, '{}'); 144 | $out[] = "
      " : " />")."
      "; 145 | } 146 | 147 | return join('', $out); 148 | } else { 149 | return gTxt('glz_cf_field_problems', array('{custom_set_name}' => $name)); 150 | } 151 | } 152 | 153 | 154 | // ------------------------------------------------------------- 155 | // Had to duplicate the default radio() to keep the looping in here and check against existing value/s 156 | function glz_radio($name = '', $id = '', $arr_values = '', $custom_value = '', $default_value = '') 157 | { 158 | if (is_array($arr_values)) { 159 | $out = array(); 160 | 161 | // If there is no custom_value coming from the article, let's use our default one 162 | if (empty($custom_value)) { 163 | $custom_value = $default_value; 164 | } 165 | 166 | foreach ($arr_values as $key => $value) { 167 | $checked = glz_selected_checked('checked', $key, $custom_value); 168 | $default = ($default_value == $key) ? ' '.'default' : ''; 169 | $value = trim($value, '{}'); 170 | $out[] = "
      " : " />")."
      "; 171 | } 172 | 173 | return join('', $out); 174 | } else { 175 | return gTxt('glz_cf_field_problems', array('{custom_set_name}' => $name)); 176 | } 177 | } 178 | 179 | 180 | // ------------------------------------------------------------- 181 | // Checking if this custom field has selected or checked values 182 | function glz_selected_checked($range_unit, $value, $custom_value = '') 183 | { 184 | if (!empty($custom_value)) { 185 | // We're comparing against a key which is a "clean" value 186 | $custom_value = htmlspecialchars($custom_value); 187 | 188 | // Make an array if $custom_value contains multiple values 189 | if (strpos($custom_value, '|')) { 190 | $arr_custom_value = explode('|', $custom_value); 191 | } 192 | } 193 | 194 | if (isset($arr_custom_value)) { 195 | $out = (in_array($value, $arr_custom_value)) ? " $range_unit=\"$range_unit\"" : ""; 196 | } else { 197 | $out = ($value == $custom_value) ? " $range_unit=\"$range_unit\"" : ""; 198 | } 199 | 200 | return $out; 201 | } 202 | 203 | 204 | //------------------------------------------------------------- 205 | // Evals a PHP script and displays output right under the custom field label 206 | function glz_custom_script($script, $custom, $custom_id, $custom_value) 207 | { 208 | global $prefs; 209 | if (is_file($prefs['glz_cf_custom_scripts_path'].$script)) { 210 | include_once($prefs['glz_cf_custom_scripts_path'].$script); 211 | $custom_function = basename($script, ".php"); 212 | if (is_callable($custom_function)) { 213 | return call_user_func_array($custom_function, array($custom, $custom_id, $custom_value)); 214 | } else { 215 | return gTxt('glz_cf_not_callable', array('{function}' => $custom_function, '{file}' => $script)); 216 | } 217 | } else { 218 | return ' '.gTxt('glz_cf_not_found', array('{file}' => $prefs['glz_cf_custom_scripts_path'].$script)).''; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/glz/helpers.php: -------------------------------------------------------------------------------- 1 | array( 24 | 'text_input', 25 | 'checkbox', 26 | 'radio', 27 | 'select', 28 | 'multi-select', 29 | 'textarea' 30 | ), 31 | 'special' => array( 32 | 'date-picker', 33 | 'time-picker', 34 | 'custom-script' 35 | ) 36 | ); 37 | } 38 | 39 | 40 | // ------------------------------------------------------------- 41 | // Outputs only custom fields that have been set, i.e. have a name assigned to them 42 | function glz_check_custom_set($all_custom_sets, $step) 43 | { 44 | $out = array(); 45 | foreach ($all_custom_sets as $key => $custom_field) { 46 | if (!empty($custom_field['name'])) { 47 | if (($step == "body") && ($custom_field['type'] == "textarea")) { 48 | $out[$key] = $custom_field; 49 | } elseif (($step == "custom_fields") && ($custom_field['type'] != "textarea")) { 50 | $out[$key] = $custom_field; 51 | } 52 | } 53 | } 54 | return $out; 55 | } 56 | 57 | 58 | // ------------------------------------------------------------- 59 | // Converts all values into id safe ones [A-Za-z0-9-] 60 | function glz_cf_idname($text) 61 | { 62 | return str_replace("_", "-", glz_sanitize_for_cf($text)); 63 | } 64 | 65 | 66 | // ------------------------------------------------------------- 67 | // Converts input into a gTxt-safe lang string 'cf_' prefix + [a-z0-9_] 68 | function glz_cf_langname($text) 69 | { 70 | return 'cf_'.glz_sanitize_for_cf($text); 71 | } 72 | 73 | 74 | // ------------------------------------------------------------- 75 | // Gets translated title or instruction string if one exists 76 | // @name = custom_field_name 77 | // @cf_number = $custom_field_number for instructions 78 | // returns language string or nothing if none exists 79 | function glz_cf_gtxt($name, $cf_number = null) 80 | { 81 | // get language string 82 | if (!empty($cf_number)) { 83 | // still work if 'custom_X' or 'custom_X_set' is passed in as cf_number 84 | if (strstr($cf_number, 'custom_')) { 85 | $parts = explode("_", $cf_number); 86 | $cf_number = $parts[1]; 87 | } 88 | $cf_name = 'instructions_custom_'.$cf_number; 89 | } else { 90 | $cf_name = glz_cf_langname($name); 91 | } 92 | $cf_gtxt = gTxt($cf_name); 93 | // retrieve gTxt value if it exists 94 | return ($cf_gtxt != $cf_name) ? $cf_gtxt : ''; 95 | } 96 | 97 | 98 | // ------------------------------------------------------------- 99 | // Cleans strings for custom field names and cf_language_names 100 | function glz_sanitize_for_cf($text, $lite = false) 101 | { 102 | $text = trim($text); 103 | 104 | if ($lite) { 105 | // lite (legacy) 106 | // U&lc letters, numbers, spaces, dashes and underscores 107 | return preg_replace('/[^A-Za-z0-9\s\_\-]/', '', $text); 108 | } else { 109 | // strict 110 | // lowercase letters, numbers and single underscores; may not start with a number 111 | $patterns[0] = "/[\_\s\-]+/"; // space(s), dash(es), underscore(s) 112 | $replacements[0] = "_"; 113 | $patterns[1] = "/[^a-z0-9\_]/"; // only a-z, 0-9 and underscore 114 | $replacements[1] = ""; 115 | $patterns[2] = "/^\d+/"; // numbers at start of string 116 | $replacements[2] = ""; 117 | 118 | return trim(preg_replace($patterns, $replacements, strtolower($text)), "_"); 119 | } 120 | } 121 | 122 | 123 | // ------------------------------------------------------------- 124 | // Checks if a custom field contains invalid characters, starts with a number or has double underscores 125 | function glz_is_valid_cf_name($text) 126 | { 127 | global $msg; 128 | 129 | if (preg_match('/[^a-z0-9\_]/', $text)) { 130 | $msg = array(gTxt('glz_cf_name_invalid_chars', array('{custom_name_input}' => $text)), E_WARNING); 131 | } elseif (preg_match('/^\d+/', $text)) { 132 | $msg = array(gTxt('glz_cf_name_invalid_starts_with_number', array('{custom_name_input}' => $text)), E_WARNING); 133 | } elseif (preg_match('/\_{2,}/', $text)) { 134 | $msg = array(gTxt('glz_cf_name_invalid_double_underscores', array('{custom_name_input}' => $text)), E_WARNING); 135 | } 136 | } 137 | 138 | // ------------------------------------------------------------- 139 | // Checks if specified start date matches current date format 140 | function glz_is_valid_start_date($date) 141 | { 142 | global $prefs; 143 | $formats = array( 144 | "d/m/Y" => "dd/mm/yyyy", 145 | "m/d/Y" => "mm/dd/yyyy", 146 | "Y-m-d" => "yyyy-mm-dd", 147 | "d m y" => "dd mm yy", 148 | "d.m.Y" => "dd.mm.yyyy" 149 | ); 150 | 151 | $datepicker_format = array_search($prefs['glz_cf_datepicker_format'], $formats); 152 | 153 | $d = DateTime::createFromFormat($datepicker_format, $date); 154 | return $d && $d->format($datepicker_format) == $date; 155 | } 156 | 157 | 158 | // ------------------------------------------------------------- 159 | // Accommodate relative urls in prefs 160 | // $addhost = true prepends the hostname 161 | function glz_relative_url($url, $addhost = false) 162 | { 163 | $parsed_url = parse_url($url); 164 | if (empty($parsed_url['scheme']) && empty($parsed_url['hostname'])) { 165 | if ($addhost) { 166 | $hostname = (empty($txpcfg['admin_url']) ? hu : ahu); 167 | } else { 168 | $hostname = "/"; 169 | } 170 | $url = $hostname.ltrim($url, '/'); 171 | } 172 | return $url; 173 | } 174 | 175 | 176 | // ------------------------------------------------------------- 177 | // Removes empty values from arrays - used for new custom fields 178 | function glz_array_empty_values($value) 179 | { 180 | if (!empty($value)) { 181 | return $value; 182 | } 183 | } 184 | 185 | 186 | // ------------------------------------------------------------- 187 | // Removes { } from values which are marked as default 188 | function glz_clean_default($value) 189 | { 190 | if(!empty($value)) { 191 | $pattern = "/^.*\{(.*)\}.*/"; 192 | return preg_replace($pattern, "$1", $value); 193 | } 194 | } 195 | 196 | 197 | // ------------------------------------------------------------- 198 | // Calls glz_clean_default() in an array context 199 | function glz_clean_default_array_values($value) 200 | { 201 | $value = glz_clean_default($value); 202 | } 203 | 204 | 205 | // ------------------------------------------------------------- 206 | // Return our default value from all custom_field values 207 | function glz_default_value($all_values) 208 | { 209 | if (is_array($all_values)) { 210 | preg_match("/(\{.*\})/", join(" ", $all_values), $default); 211 | return ((!empty($default) && $default[0]) ? $default[0] : ''); 212 | } 213 | } 214 | 215 | 216 | // ------------------------------------------------------------- 217 | // Custom_set without "_set" e.g. custom_1_set => custom_1 218 | // or custom set formatted for IDs e.g. custom-1 219 | function glz_custom_number($custom_set, $delimiter="_") 220 | { 221 | // Trim "_set" from the end of the string 222 | $custom_field = substr($custom_set, 0, -4); 223 | 224 | // If a delimeter is specified custom_X to custom{delimeter}X 225 | if ($delimiter != "_") { 226 | $custom_field = str_replace("_", $delimiter, $custom_field); 227 | } 228 | return $custom_field; 229 | } 230 | 231 | 232 | // ------------------------------------------------------------- 233 | // Custom_set digit e.g. custom_1_set => 1 234 | function glz_custom_digit($custom_set) 235 | { 236 | $out = explode("_", $custom_set); 237 | // $out[0] will always be 'custom' 238 | return $out[1]; // so take $out[1] 239 | } 240 | 241 | 242 | // ------------------------------------------------------------- 243 | // Returns the custom_X_set from a custom set name e.g. "rating" gives us custom_1_set 244 | function glz_get_custom_set($value) 245 | { 246 | $result = safe_field( 247 | "name", 248 | 'txp_prefs', 249 | "event = 'custom' AND val = '".doSlash(val)."'" 250 | ); 251 | if (!$result) { 252 | // No result -> return error message 253 | trigger_error(gTxt('glz_cf_doesnt_exist', array('{custom_set_name}' => $value)), E_USER_WARNING); 254 | return false; 255 | } 256 | return true; 257 | } 258 | 259 | 260 | // ------------------------------------------------------------- 261 | // Get the article ID, even if it's newly saved 262 | function glz_get_article_id() 263 | { 264 | return (!empty($GLOBALS['ID']) ? $GLOBALS['ID'] : gps('ID')); 265 | } 266 | 267 | 268 | // ------------------------------------------------------------- 269 | // Is the custom field name already taken? 270 | function glz_check_custom_set_name($custom_set_name, $custom_set) 271 | { 272 | // Check that the name input by the user as well as its sanitized version don't already exist 273 | return safe_field( 274 | "name", 275 | 'txp_prefs', 276 | "event = 'custom' AND val IN ('".doSlash($custom_set_name)."', '".glz_sanitize_for_cf($custom_set_name)."') AND name <> '".doSlash($custom_set)."'" 277 | ); 278 | } 279 | 280 | 281 | // ------------------------------------------------------------- 282 | // Edit/delete buttons in custom_fields table require a form each 283 | function glz_form_buttons($action, $value, $custom_set, $custom_set_name, $custom_set_type, $custom_set_position, $onsubmit='') 284 | { 285 | $onsubmit = ($onsubmit) ? 'onsubmit="'.$onsubmit.'"' : ''; 286 | 287 | // ui-icon (see admin hive styling) 288 | if ($action == "delete") { 289 | $ui_icon = "close"; 290 | } 291 | if ($action == "reset") { 292 | $ui_icon = "trash"; 293 | } 294 | if ($action == "add") { 295 | $ui_icon = "circlesmall-plus"; 296 | } 297 | 298 | $tagend = get_pref('doctype') === 'html5' ? ">" : " />"; 299 | 300 | return 301 | '
      302 | 305 | 309 | 310 | 311 | '.gTxt("glz_cf_action_".$action).' 312 | 313 |
      '; 314 | } 315 | 316 | 317 | // TODO: Appears to be unused?! 318 | // ------------------------------------------------------------- 319 | // Returns all sections/categories that are searchable 320 | function glz_all_searchable_sections_categories($type) 321 | { 322 | $type = (in_array($type, array('category', 'section')) ? $type : 'section'); 323 | $condition = ""; 324 | 325 | if ($type == "section") { 326 | $condition .= "searchable='1'"; 327 | } else { 328 | $condition .= "name <> 'root' AND type='article'"; 329 | } 330 | 331 | $result = safe_rows('*', "txp_{$type}", $condition); 332 | 333 | $out = array(); 334 | foreach ($result as $value) { 335 | $out[$value['name']] = $value['title']; 336 | } 337 | 338 | return $out; 339 | } 340 | -------------------------------------------------------------------------------- /src/glz/main.php: -------------------------------------------------------------------------------- 1 | string conversion on save/create 62 | if (($step === 'edit') || ($step === 'create')) { 63 | register_callback('glz_custom_fields_before_save', 'article', '', 1); 64 | } 65 | } 66 | 67 | // Custom fields tab under extensions 68 | add_privs('glz_custom_fields', '1,2'); 69 | register_tab('extensions', 'glz_custom_fields', gTxt('glz_cf_tab_name')); 70 | register_callback('glz_cf_dispatcher', 'glz_custom_fields'); 71 | 72 | // Write tab: replace regular custom fields with glz custom fields 73 | // -> custom fields 74 | register_callback('glz_custom_fields_replace', 'article_ui', 'custom_fields'); 75 | // -> textareas 76 | register_callback('glz_custom_fields_replace', 'article_ui', 'body'); 77 | 78 | } 79 | 80 | 81 | /** 82 | * Jump off to relevant stub for handling actions. 83 | */ 84 | function glz_cf_dispatcher() 85 | { 86 | global $event, $step; 87 | 88 | // Available steps 89 | $steps = array( 90 | 'add' => true, 91 | 'edit' => true, 92 | 'save' => true, 93 | 'reset' => true, 94 | 'delete' => true 95 | ); 96 | 97 | // Use default step if nothing matches 98 | if(!$step || ((!bouncer($step, $steps)) || !isset($steps[$step]))) { 99 | $step = 'list'; 100 | } 101 | 102 | // Run the function 103 | $func = 'glz_cf_' . $step; 104 | $func(); 105 | } 106 | -------------------------------------------------------------------------------- /src/glz/prefspane.php: -------------------------------------------------------------------------------- 1 | array('pref.subevent', 'html', 'default-value') 27 | $plugin_prefs = array( 28 | 'values_ordering' => array('', 'glz_prefs_orderby', 'custom'), 29 | 'multiselect_size' => array('', 'glz_text_input_small', '5'), 30 | 'css_asset_url' => array('', 'glz_url_input', $base_url.'plugins/glz_custom_fields'), 31 | 'js_asset_url' => array('', 'glz_url_input', $base_url.'plugins/glz_custom_fields'), 32 | 'custom_scripts_path' => array('', 'glz_url_input', $base_path.'/plugins/glz_custom_fields'), 33 | 'use_sortable' => array('', 'yesnoradio', 1), 34 | 'permit_full_deinstall' => array('', 'yesnoradio', 0), 35 | 'datepicker_url' => array('glz_cf_datepicker', 'glz_url_input', $base_url.'plugins/glz_custom_fields/jquery.datePicker'), 36 | 'datepicker_format' => array('glz_cf_datepicker', 'glz_prefs_datepicker_format', 'dd/mm/yyyy'), 37 | 'datepicker_first_day' => array('glz_cf_datepicker', 'glz_prefs_datepicker_firstday', 1), 38 | 'datepicker_start_date' => array('glz_cf_datepicker', 'glz_input_start_date', '01/01/2018'), 39 | 'timepicker_url' => array('glz_cf_timepicker', 'glz_url_input', $base_url.'plugins/glz_custom_fields/jquery.timePicker'), 40 | 'timepicker_start_time' => array('glz_cf_timepicker', 'glz_text_input_small', '00:00'), 41 | 'timepicker_end_time' => array('glz_cf_timepicker', 'glz_text_input_small', '23:30'), 42 | 'timepicker_step' => array('glz_cf_timepicker', 'glz_text_input_small', 30), 43 | 'timepicker_show_24' => array('glz_cf_timepicker', 'glz_prefs_timepicker_format', true) 44 | ); 45 | 46 | foreach ($plugin_prefs as $name => $val) { 47 | if (get_pref($name, false) === false) { 48 | // If pref is new, create new pref with 'glz_cf_' prefix 49 | create_pref('glz_cf_'.$name, $val[2], 'glz_custom_f'.($val[0] ? '.'.$val[0] : ''), PREF_PLUGIN, $val[1], $position, ''); 50 | } else { 51 | // If pref exists, add 'glz_cf_' prefix to name, reassign position and html type and set to type PREF_PLUGIN 52 | safe_update( 53 | 'txp_prefs', 54 | "name = 'glz_cf_".$name."', 55 | event = 'glz_custom_f".($val[0] ? ".".$val[0] : "")."', 56 | html = '".$val[1]."', 57 | type = ".PREF_PLUGIN.", 58 | position = ".$position, 59 | "name = '".$name."'" 60 | ); 61 | } 62 | $position++; 63 | } 64 | 65 | // Make some $prefs hidden (for safety and troubleshooting) 66 | foreach (array( 67 | 'use_sortable', 68 | 'permit_full_deinstall' 69 | ) as $name) { 70 | safe_update( 71 | 'txp_prefs', 72 | "type = ".PREF_HIDDEN, 73 | "name = 'glz_cf_".$name."'" 74 | ); 75 | } 76 | 77 | // Set 'migrated' pref to 'glz_cf_migrated' and to hidden (type = 2); 78 | if (get_pref('migrated')) { 79 | safe_update( 80 | 'txp_prefs', 81 | "name = 'glz_cf_migrated', 82 | type = ".PREF_HIDDEN, 83 | "name = 'migrated'" 84 | ); 85 | } 86 | 87 | // Remove no longer needed 'max_custom_fields' pref 88 | safe_delete( 89 | 'txp_prefs', 90 | "name = 'max_custom_fields'" 91 | ); 92 | } 93 | 94 | 95 | /** 96 | * Renders a HTML choice of GLZ value ordering. 97 | * 98 | * @param string $name HTML name and id of the widget 99 | * @param string $val Initial (or current) selected item 100 | * @return string HTML 101 | */ 102 | function glz_prefs_orderby($name, $val) 103 | { 104 | $vals = array( 105 | 'ascending' => gTxt('glz_cf_prefs_value_asc'), 106 | 'descending' => gTxt('glz_cf_prefs_value_desc'), 107 | 'custom' => gTxt('glz_cf_prefs_value_custom') 108 | ); 109 | return selectInput($name, $vals, $val, '', '', $name); 110 | } 111 | 112 | /** 113 | * Renders a HTML choice of date formats. 114 | * 115 | * @param string $name HTML name and id of the widget 116 | * @param string $val Initial (or current) selected item 117 | * @return string HTML 118 | */ 119 | function glz_prefs_datepicker_format($name, $val) 120 | { 121 | $vals = array( 122 | "dd/mm/yyyy" => gTxt('glz_cf_prefs_slash_dmyy'), 123 | "mm/dd/yyyy" => gTxt('glz_cf_prefs_slash_mdyy'), 124 | "yyyy-mm-dd" => gTxt('glz_cf_prefs_dash_yymd'), 125 | "dd mm yy" => gTxt('glz_cf_prefs_space_dmy'), 126 | "dd.mm.yyyy" => gTxt('glz_cf_prefs_dot_dmyy') 127 | ); 128 | return selectInput($name, $vals, $val, '', '', $name); 129 | } 130 | 131 | /** 132 | * Renders a HTML choice of weekdays. 133 | * 134 | * @param string $name HTML name and id of the widget 135 | * @param string $val Initial (or current) selected item 136 | * @return string HTML 137 | */ 138 | function glz_prefs_datepicker_firstday($name, $val) 139 | { 140 | $vals = array( 141 | 0 => gTxt('glz_cf_prefs_sunday'), 142 | 1 => gTxt('glz_cf_prefs_monday'), 143 | 2 => gTxt('glz_cf_prefs_tuesday'), 144 | 3 => gTxt('glz_cf_prefs_wednesday'), 145 | 4 => gTxt('glz_cf_prefs_thursday'), 146 | 5 => gTxt('glz_cf_prefs_friday'), 147 | 6 => gTxt('glz_cf_prefs_saturday') 148 | ); 149 | return selectInput($name, $vals, $val, '', '', $name); 150 | } 151 | 152 | /** 153 | * Renders a HTML choice of time formats. 154 | * 155 | * @param string $name HTML name and id of the widget 156 | * @param string $val Initial (or current) selected item 157 | * @return string HTML 158 | */ 159 | function glz_prefs_timepicker_format($name, $val) 160 | { 161 | $vals = array( 162 | 'true' => gTxt('glz_cf_prefs_24_hours'), 163 | 'false' => gTxt('glz_cf_prefs_12_hours') 164 | ); 165 | return selectInput($name, $vals, $val, '', '', $name); 166 | } 167 | 168 | 169 | /** 170 | * Renders a small-width HTML <input> element. 171 | * Checks if start date matches current datepicker date format 172 | * 173 | * @param string $name HTML name and id of the text box 174 | * @param string $val Initial (or current) content of the text box 175 | * @return string HTML 176 | */ 177 | function glz_input_start_date($name, $val) 178 | { 179 | $out = text_input($name, $val, INPUT_SMALL); 180 | // Output error notice if start date does not match date format 181 | if (!glz_is_valid_start_date($val)) { 182 | $out .= '
      '.gTxt('glz_cf_datepicker_start_date_error').''; 183 | } 184 | return $out; 185 | } 186 | 187 | 188 | /** 189 | * Renders a medium-width HTML <input> element. 190 | * 191 | * @param string $name HTML name and id of the text box 192 | * @param string $val Initial (or current) content of the text box 193 | * @return string HTML 194 | */ 195 | function glz_text_input_medium($name, $val) 196 | { 197 | return text_input($name, $val, INPUT_MEDIUM); 198 | } 199 | 200 | /** 201 | * Renders a small-width HTML <input> element. 202 | * 203 | * @param string $name HTML name and id of the text box 204 | * @param string $val Initial (or current) content of the text box 205 | * @return string HTML 206 | */ 207 | function glz_text_input_small($name, $val) 208 | { 209 | return text_input($name, $val, INPUT_SMALL); 210 | } 211 | 212 | /** 213 | * Renders a regular-width HTML <input> element for an URL with path check. 214 | * 215 | * @param string $name HTML name and id of the text box 216 | * @param string $val Initial (or current) content of the text box 217 | * @return string HTML 218 | */ 219 | function glz_url_input($name, $val) 220 | { 221 | global $use_minified; 222 | $min = ($use_minified === true) ? '.min' : ''; 223 | $check_paths = (gps('check_paths') == "1") ? true : false; 224 | 225 | // Output regular-width text_input for url 226 | $out = fInput('text', $name, $val, '', '', '', INPUT_REGULAR, '', $name); 227 | 228 | // Array of possible expected url inputs and corresponding files and error-msg-stubs 229 | // 'pref_name' => array('/targetfilename.ext', 'gTxt_folder (inserted into error msg)') 230 | // paths do not require a target filename, urls do. 231 | $glz_cf_url_inputs = array( 232 | 'glz_cf_css_asset_url' => array('/glz_custom_fields'.$min.'.css', 'glz_cf_css_folder'), 233 | 'glz_cf_js_asset_url' => array('/glz_custom_fields'.$min.'.js', 'glz_cf_js_folder'), 234 | 'glz_cf_datepicker_url' => array('/datePicker'.$min.'.js', 'glz_cf_datepicker_folder'), 235 | 'glz_cf_timepicker_url' => array('/timePicker'.$min.'.js', 'glz_cf_timepicker_folder'), 236 | 'glz_cf_custom_scripts_path' => array('', 'glz_cf_custom_folder') 237 | ); 238 | // File url or path to test = prefs_val (=url/path) + targetfilename (first item in array) 239 | $glz_cf_url_to_test = $val.$glz_cf_url_inputs[$name][0]; 240 | // gTxt string ref for folder name for error message (second item in array) 241 | $glz_cf_url_input_error_stub = $glz_cf_url_inputs[$name][1]; 242 | 243 | // See if url / path is readable. If not, produce error message 244 | if ($glz_cf_url_to_test && $check_paths == true) { 245 | // permit relative URLs but conduct url test with hostname 246 | if (strstr($name, 'url')) { 247 | $glz_cf_url_to_test = glz_relative_url($glz_cf_url_to_test, $addhost = true); 248 | } 249 | $url_error = (@fopen($glz_cf_url_to_test, "r")) ? '' : gTxt('glz_cf_folder_error', array('{folder}' => gTxt($glz_cf_url_input_error_stub) )); 250 | 251 | // Output error notice if one exists, else success notice 252 | $out .= (!empty($url_error)) ? 253 | '
      '.$url_error.'' : 254 | '
      '.gTxt('glz_cf_folder_success').''; 255 | } 256 | 257 | return $out; 258 | } 259 | -------------------------------------------------------------------------------- /src/glz/public-tags.php: -------------------------------------------------------------------------------- 1 | register('glz_custom_field', 'custom_field'); 28 | } 29 | 30 | function glz_custom_field($atts, $thing = null) 31 | { 32 | // Extract attributes as vars 33 | extract(lAtts(array( 34 | 'title' => '0', 35 | 'name' => '' 36 | ), $atts, false)); // false: suppress warnings 37 | 38 | // Unset otherwise non-existent attribute 39 | unset($atts['title']); 40 | 41 | // if $title is specified, divert to glz_cf_gtxt 42 | return $title ? glz_cf_gtxt($name) : custom_field($atts, $thing); 43 | } 44 | -------------------------------------------------------------------------------- /textpacks/de-de.textpack: -------------------------------------------------------------------------------- 1 | #@glz_cf 2 | #@language de-de 3 | glz_cf_tab_name => Benutzerdefinierte Felder 4 | glz_cf_title => Benutzerdefiniertes Feld 5 | glz_cf_no_name => Das benutzerdefinierte Feld hat keinen Namen. 6 | glz_cf_deleted => Das benutzerdefinierte Feld {custom_set_id} wurde gelöscht. 7 | glz_cf_deleted_error => Es gab ein Problem beim Löschen des benutzerdefinierten Felds {custom_set_id}. 8 | glz_cf_reset => Benutzerdefiniertes Feld {custom_set_id} wurde zurückgesetzt. 9 | glz_cf_reset_error => Es gab ein Problem beim Zurücksetzen des benutzerdefinierten Felds {custom_set_id}. 10 | glz_cf_created => {custom_set_name} wurde erstellt. 11 | glz_cf_updated => {custom_set_name} wurde aktualisiert. 12 | glz_cf_exists => {custom_set_name} existiert bereits. 13 | glz_cf_doesnt_exist => {custom_set_name} existiert nicht. 14 | glz_cf_name_renamed_notice => Unzulässiger Name: {custom_name_input} wurde als {custom_name_output} erstellt. » Erklärung. 15 | glz_cf_name_invalid_chars => {custom_name_input} wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nur kleingeschriebene Zeichen, Ziffern und Unterstriche enthalten, jedoch keine Leerzeichen. » Erklärung. 16 | glz_cf_name_invalid_starts_with_number => {custom_name_input} wurde aktualisiert, sollte aber umbenannt werden: Namen sollten nicht mit einer Ziffer beginnen. » Erklärung. 17 | glz_cf_name_invalid_double_underscores => {custom_name_input} wurde aktualisiert, sollte aber umbenannt werden: Namen sollten keine doppelten Unterstreichungszeichen enthalten. » Erklärung. 18 | glz_cf_no_values => Ein Checkbox-Feld benötigt mindestens einen Wert! 19 | glz_cf_not_enough_values => Ein {cf_type}-Feld benötigt mindestens zwei Werte! 20 | glz_cf_field_problems => {custom_set_name} ist fehlerhaft. Bitte berichtigen.. 21 | glz_cf_types_normal => Normal 22 | glz_cf_types_special => Spezial 23 | glz_cf_text_input => Texteingabe 24 | glz_cf_select => Auswahlmenü 25 | glz_cf_multi-select => Multiselect 26 | glz_cf_textarea => Textblock 27 | glz_cf_checkbox => Checkboxen 28 | glz_cf_radio => Radiobuttons 29 | glz_cf_date-picker => Datumswähler 30 | glz_cf_time-picker => Zeitwähler 31 | glz_cf_custom-script => Benutzerdefiniertes Skript 32 | glz_cf_type_not_supported => Typ nicht unterstützt 33 | glz_cf_action_add => Hinzufügen 34 | glz_cf_action_edit => Bearbeiten 35 | glz_cf_action_delete => Löschen 36 | glz_cf_action_reset => Zurücksetzen 37 | glz_cf_not_specified => {what} wurde nicht angegeben. 38 | glz_cf_migration_success => Datenmigration der benutzerdefinierten Felder erfolgreich. 39 | glz_cf_migration_skip => custom_fields Datenbanktabelle existiert bereits: Die Datenmigration wurde übersprungen. 40 | glz_cf_search_section_created => search Rubrik wurde erstellt. 41 | glz_cf_not_found => {file} wurde nicht gefunden. Bitte den Pfad überprüfen. 42 | glz_cf_not_callable => {function}() konnte nicht aufgerufen werden. Bitte sicherstellen, dass {file} ausführbar ist. 43 | glz_cf_no_such_custom_field => Kein benutzerdefiniertes Feld mit dieser ID! 44 | glz_cf_prefpane_title => Benutzerdefinierte Felder 45 | glz_cf_js_textarea_msg => Einen Wert pro Zeile.
      Bei Bedarf einen {voreingestellten} Wert angeben. 46 | glz_cf_js_script_msg => php-Dateiname im custom scripts Pfad. 47 | glz_cf_js_configure_msg => Zum Konfigurieren siehe Einstellungen » Benutzerdefinierte Felder 48 | glz_cf_public_error_datepicker => Problembehebung erforderlich: Pfad des Datumswähler-Plugins überprüfen 49 | glz_cf_public_error_timepicker => Problembehebung erforderlich: Pfad des Zeitwähler-Plugins überprüfen 50 | position => Position 51 | glz_cf_col_sort => Reihenfolge 52 | glz_cf_action_new_title => Neues benutzerdefiniertes Feld 53 | glz_cf_action_edit_title => {custom_set_name} bearbeiten. 54 | glz_cf_add_new_cf => Neues benutzerdefiniertes Feld 55 | glz_cf_multiple_values_instructions => Einen Wert pro Zeile
      Bei Bedarf einen {voreingestellten} Wert angeben. 56 | glz_cf_edit_name_hint => Nur alphanumerische Zeichen und einzelne Unterstriche 57 | glz_cf_edit_position_hint => Wird automatisch vergeben, falls leer. 58 | glz_cf_edit_name => Name: 59 | glz_cf_edit_title => Titel: 60 | glz_cf_edit_title_hint => Feldbezeichnung 61 | glz_cf_edit_instructions => Anleitungstext: 62 | glz_cf_edit_instructions_hint => Optionaler Hinweistext (erscheint zwischen Label und Element) 63 | glz_cf_edit_type => Typ: 64 | glz_cf_edit_position => Position: 65 | glz_cf_edit_value => Wert(e): 66 | glz_cf_confirm_reset => ALLE DATEN für {custom} ZURÜCKSETZEN – Sind Sie sicher? 67 | glz_cf_confirm_delete => ALLE DATEN für {custom} LÖSCHEN – Sind Sie sicher? 68 | glz_custom_f => Benutzerdefinierte Felder Pfade überprüfen 69 | glz_cf_values_ordering => Anzeigereihenfolge bei mehreren Werten 70 | glz_cf_prefs_value_asc => Aufsteigend 71 | glz_cf_prefs_value_desc => Absteigend 72 | glz_cf_prefs_value_custom => Wie eingetragen 73 | glz_cf_multiselect_size => Zeilenanzahl der Multiselects 74 | glz_cf_css_folder => URL des CSS-Verzeichnis für glz custom fields 75 | glz_cf_js_folder => URL des JS-Verzeichnis für glz custom fields 76 | glz_cf_custom_folder => Pfad für benutzerdefinierte Skripte 77 | glz_cf_use_sortable => Reihenfolge der Felder per Maus ändern 78 | glz_cf_permit_full_deinstall => Beim Deinstallieren alle Daten löschen 79 | glz_cf_datepicker_folder => URL des Datumswähler-Verzeichnisses 80 | glz_cf_timepicker_folder => URL des Zeitwähler-Verzeichnisses 81 | glz_cf_folder_error => URL/Pfad enthält Fehler, bitte überprüfen. 82 | glz_cf_folder_success => URL/Pfad ist korrekt. 83 | glz_cf_css_asset_url => URL des CSS-Verzeichnisses 84 | glz_cf_js_asset_url => URL des JS-Verzeichnisses 85 | glz_cf_custom_scripts_path => Pfad für benutzerdefinierte Skripte 86 | glz_cf_custom_scripts_path_error => Pfad für benutzerdefinierte Skripte fehlerhaft, bitte überprüfen. 87 | glz_cf_datepicker => Datumswähler 88 | glz_cf_datepicker_url => URL des Datumswähler-Plugins 89 | glz_cf_datepicker_url_error => URL/Pfad enthält Fehler, bitte überprüfen. 90 | glz_cf_datepicker_format => Datumsformat 91 | glz_cf_datepicker_first_day => Wochenbeginn 92 | glz_cf_datepicker_start_date => Anfangsdatum 93 | glz_cf_datepicker_start_date_error => Format des Anfangsdatums muss dem Datumsformat entsprechen. 94 | glz_cf_prefs_monday => Montag 95 | glz_cf_prefs_tuesday => Dienstag 96 | glz_cf_prefs_wednesday => Mittwoch 97 | glz_cf_prefs_thursday => Donnerstag 98 | glz_cf_prefs_friday => Freitag 99 | glz_cf_prefs_saturday => Samstag 100 | glz_cf_prefs_sunday => Sonntag 101 | glz_cf_prefs_slash_dmyy => TT/MM/JJJJ 102 | glz_cf_prefs_slash_mdyy => MM/TT/JJJJ 103 | glz_cf_prefs_dash_yymd => JJJJ-MM-TT 104 | glz_cf_prefs_space_dmy => TT MM JJ 105 | glz_cf_prefs_dot_dmyy => TT.MM.JJJJ 106 | glz_cf_timepicker => Zeitwähler 107 | glz_cf_timepicker_url => URL des Zeitwähler-Plugins 108 | glz_cf_timepicker_url_error => URL/Pfad enthält Fehler, bitte überprüfen. 109 | glz_cf_timepicker_start_time => Erster Zeiteintrag im Menü 110 | glz_cf_timepicker_end_time => Letzter Zeiteintrag im Menü 111 | glz_cf_timepicker_step => Zeitsprünge des Menüs in Minuten 112 | glz_cf_timepicker_show_24 => Zeitformat 113 | glz_cf_prefs_24_hours => 24 Stunden 114 | glz_cf_prefs_12_hours => 12 Stunden 115 | glz_cf_sort_success => Reihenfolge geändert 116 | glz_cf_sort_error => Reihenfolge konnte nicht geändert werden. 117 | #@prefs 118 | custom_x_set => Name des benutzerdefinierten Feldes Nr. {number} 119 | instructions_glz_cf_datepicker_start_date => Die Angabe muss mit dem Datumsformat übereinstimmen. 120 | instructions_glz_cf_custom_scripts_path => Pfad relativ zum root-Verzeichnis 121 | instructions_glz_cf_permit_full_deinstall => ACHTUNG: "Ja" löscht ALLE DATEN in den benutzerdefinierten Feldern. 122 | -------------------------------------------------------------------------------- /textpacks/en-gb.textpack: -------------------------------------------------------------------------------- 1 | #@glz_cf 2 | #@language en, en-gb, en-us, en-ca 3 | glz_cf_tab_name => Custom Fields 4 | glz_cf_title => Custom Field 5 | glz_cf_no_name => A custom field must have a name 6 | glz_cf_deleted => Custom field {custom_set_id} was deleted 7 | glz_cf_deleted_error => There was a problem deleting custom field {custom_set_id} 8 | glz_cf_reset => Custom field {custom_set_id} was reset 9 | glz_cf_reset_error => There was a problem resetting custom field {custom_set_id} 10 | glz_cf_created => {custom_set_name} was created 11 | glz_cf_updated => {custom_set_name} was updated 12 | glz_cf_exists => {custom_set_name} already exists 13 | glz_cf_doesnt_exist => {custom_set_name} is not set 14 | glz_cf_name_renamed_notice => Invalid name: {custom_name_input} has been created as {custom_name_output}. » See why. 15 | glz_cf_name_invalid_chars => {custom_name_input} was updated, but consider renaming it. Names should contain only lowercase letters, numbers, underscores and no spaces. » See plugin help. 16 | glz_cf_name_invalid_starts_with_number => {custom_name_input} was updated, but consider renaming it. Names should not start with a number. » See plugin help. 17 | glz_cf_name_invalid_double_underscores => {custom_name_input} was updated, but consider renaming it. Names should not have double underscores. » See plugin help. 18 | glz_cf_no_values => A checkbox custom field must have at least one value 19 | glz_cf_not_enough_values => A {cf_type} custom field must have at least two values 20 | glz_cf_field_problems => {custom_set_name} has some problems. Go fix it. 21 | glz_cf_types_normal => Normal 22 | glz_cf_types_special => Special 23 | glz_cf_text_input => Text Input 24 | glz_cf_select => Select 25 | glz_cf_multi-select => Multi-Select 26 | glz_cf_textarea => Textarea 27 | glz_cf_checkbox => Checkbox 28 | glz_cf_radio => Radio 29 | glz_cf_date-picker => Date Picker 30 | glz_cf_time-picker => Time Picker 31 | glz_cf_custom-script => Custom Script 32 | glz_cf_type_not_supported => Type not supported 33 | glz_cf_action_add => Add 34 | glz_cf_action_edit => Edit 35 | glz_cf_action_delete => Delete 36 | glz_cf_action_reset => Reset 37 | glz_cf_not_specified => {what} is not specified 38 | glz_cf_migration_success => Migrating custom fields was successful 39 | glz_cf_migration_skip => custom_fields table already has data in it, migration skipped. 40 | glz_cf_search_section_created => search section has been created 41 | glz_cf_not_found => {file} cannot be found, check path 42 | glz_cf_not_callable => {function}() cannot be called. Ensure {file} can be executed. 43 | glz_cf_no_such_custom_field => No such custom field! 44 | glz_cf_prefpane_title => Custom Fields Preferences 45 | glz_cf_js_textarea_msg => Each value on a separate line
      One {default} value allowed 46 | glz_cf_js_script_msg => php file name in your custom scripts path 47 | glz_cf_js_configure_msg => For settings, see custom field preferences 48 | glz_cf_public_error_datepicker => Fix the DatePicker jQuery plugin » Check path 49 | glz_cf_public_error_timepicker => Fix the TimePicker jQuery plugin » Check path 50 | position => Position 51 | glz_cf_col_sort => Sort 52 | glz_cf_action_new_title => New custom field 53 | glz_cf_action_edit_title => Edit {custom_set_name} 54 | glz_cf_add_new_cf => New custom field 55 | glz_cf_multiple_values_instructions => Each value on a separate line
      One {default} value allowed 56 | glz_cf_edit_name_hint => Alphanumeric characters and underscores 57 | glz_cf_edit_position_hint => Automatically assigned if blank 58 | glz_cf_edit_name => Name: 59 | glz_cf_edit_title => Title: 60 | glz_cf_edit_title_hint => Descriptive field label 61 | glz_cf_edit_instructions => Instructions: 62 | glz_cf_edit_instructions_hint => Optional help text (appears alongside label) 63 | glz_cf_edit_type => Type: 64 | glz_cf_edit_position => Position: 65 | glz_cf_edit_value => Value(s): 66 | glz_cf_confirm_reset => This will RESET ALL data for custom field {custom}. Are you sure? 67 | glz_cf_confirm_delete => This will DELETE ALL data for custom field {custom}. Are you sure? 68 | glz_custom_f => Custom fields preferences Check paths 69 | glz_cf_values_ordering => Order for custom field values 70 | glz_cf_prefs_value_asc => Ascending 71 | glz_cf_prefs_value_desc => Descending 72 | glz_cf_prefs_value_custom => As entered 73 | glz_cf_multiselect_size => Multi-select field size 74 | glz_cf_css_folder => Glz custom fields CSS folder 75 | glz_cf_js_folder => Glz custom fields JS folder 76 | glz_cf_custom_folder => Custom scripts path 77 | glz_cf_use_sortable => Set custom field order per drag and drop 78 | glz_cf_permit_full_deinstall => Delete all data when deinstalling 79 | glz_cf_datepicker_folder => Date picker folder 80 | glz_cf_timepicker_folder => Time picker folder 81 | glz_cf_folder_error => {folder} does not exist, please create it. 82 | glz_cf_folder_success => URL / path is correct 83 | glz_cf_css_asset_url => CSS folder URL 84 | glz_cf_js_asset_url => JS folder URL 85 | glz_cf_custom_scripts_path => Custom scripts path 86 | glz_cf_custom_scripts_path_error => Custom scripts folder does not exist, please create it. 87 | glz_cf_datepicker => Date picker 88 | glz_cf_datepicker_url => Date picker plugin folder URL 89 | glz_cf_datepicker_url_error => Date picker folder does not exist, please create it. 90 | glz_cf_datepicker_format => Date format 91 | glz_cf_datepicker_first_day => First day of week 92 | glz_cf_datepicker_start_date => Start date 93 | glz_cf_datepicker_start_date_error => Start date does not match date format 94 | glz_cf_prefs_monday => Monday 95 | glz_cf_prefs_tuesday => Tuesday 96 | glz_cf_prefs_wednesday => Wednesday 97 | glz_cf_prefs_thursday => Thursday 98 | glz_cf_prefs_friday => Friday 99 | glz_cf_prefs_saturday => Saturday 100 | glz_cf_prefs_sunday => Sunday 101 | glz_cf_prefs_slash_dmyy => dd/mm/yyyy 102 | glz_cf_prefs_slash_mdyy => mm/dd/yyyy 103 | glz_cf_prefs_dash_yymd => yyyy-mm-dd 104 | glz_cf_prefs_space_dmy => dd mm yy 105 | glz_cf_prefs_dot_dmyy => dd.mm.yyyy 106 | glz_cf_timepicker => Time picker 107 | glz_cf_timepicker_url => Time picker plugin folder URL 108 | glz_cf_timepicker_url_error => Time picker folder does not exist, please create it. 109 | glz_cf_timepicker_start_time => Start time 110 | glz_cf_timepicker_end_time => End time 111 | glz_cf_timepicker_step => Step 112 | glz_cf_timepicker_show_24 => Time format 113 | glz_cf_prefs_24_hours => 24 hours 114 | glz_cf_prefs_12_hours => 12 hours 115 | glz_cf_sort_success => Custom field order changed 116 | glz_cf_sort_error => Custom field order save failed 117 | #@prefs 118 | custom_x_set => Custom field {number} name 119 | instructions_glz_cf_datepicker_start_date => Format MUST match "Date format" 120 | instructions_glz_cf_custom_scripts_path => Path from your web root folder 121 | instructions_glz_cf_permit_full_deinstall => WARNING: "Yes" = Deinstalling deletes All USER INPUT in custom fields 122 | -------------------------------------------------------------------------------- /textpacks/fr-fr.textpack: -------------------------------------------------------------------------------- 1 | #@glz_cf 2 | #@language fr 3 | glz_cf_tab_name => Saisie personnalisée 4 | glz_cf_title => Champ personnalisé 5 | glz_cf_no_name => Un champ personnalisé doit avoir un nom 6 | glz_cf_deleted => Champ personnalisé {custom_set_id} supprimé 7 | glz_cf_deleted_error => Échec de suppression du champ personnalisé {custom_set_id} 8 | glz_cf_reset => Champ personnalisé {custom_set_id} réinitialisé 9 | glz_cf_reset_error => Échec de la réinitialisation du champ personnalisé {custom_set_id} 10 | glz_cf_created => {custom_set_name} correctement créé 11 | glz_cf_updated => {custom_set_name} correctement mis à jour 12 | glz_cf_exists => {custom_set_name} est déjà présent 13 | glz_cf_doesnt_exist => {custom_set_name} n’existe pas 14 | glz_cf_name_renamed_notice => Nom du champ personnalisé invalide : {custom_name_input} a été créé comme {custom_name_output}. » Voyez pourquoi. 15 | glz_cf_name_invalid_chars => {custom_name_input} mis à jour, mais envisager de le renommer. Le nom du champ personnalisé doit contenir uniquement des lettres minuscules, des nombres, des sous-tirets mais aucun espaces. » Voyez l’aide du plugin. 16 | glz_cf_name_invalid_starts_with_number => {custom_name_input} mis à jour, mais envisagez de le renommer. Le nom du champ personnalisé ne peut pas commencer par un nombre. » Voyez l’aide du plugin. 17 | glz_cf_name_invalid_double_underscores => {custom_name_input}Voyez l’aide du plugin. 18 | glz_cf_no_values => Un champ personnalisé du type case à cocher doit avoir au moins une valeur 19 | glz_cf_not_enough_values => Un champ personnalisé du type {cf_type} doit avoir au moins deux valeurs 20 | glz_cf_field_problems => {custom_set_name} comporte des problèmes. Corrigez-les. 21 | glz_cf_types_normal => Normal 22 | glz_cf_types_special => Spécial 23 | glz_cf_text_input => Saisie de texte 24 | glz_cf_select => Select 25 | glz_cf_multi-select => Multi-select 26 | glz_cf_textarea => textarea 27 | glz_cf_checkbox => Case à cocher 28 | glz_cf_radio => Bouton radio 29 | glz_cf_date-picker => Sélecteur de dates 30 | glz_cf_time-picker => Sélecteur d’heures 31 | glz_cf_custom-script => Script personnalisé 32 | glz_cf_type_not_supported => Type non pris en charge 33 | glz_cf_action_add => Ajouter 34 | glz_cf_action_edit => Éditer 35 | glz_cf_action_delete => Supprimer 36 | glz_cf_action_reset => Réinitialisation 37 | glz_cf_not_specified => {what} n’est pas spécifié 38 | glz_cf_migration_success => La mise à jour des champs personnalisés a été correctement effectuée 39 | glz_cf_migration_skip => La table custom_fields contient déjà des données : la mise à jour a été ignorée 40 | glz_cf_search_section_created => recherche créée 41 | glz_cf_not_found => {file} n’a pu être chargé correctement, vérifiez son chemin d’accès 42 | glz_cf_not_callable => La fonction {function}() ne peut pas être utilisée. Vérifiez que {file} est exécutable. 43 | glz_cf_no_such_custom_field => Ce champ personnalisé n’existe pas ! 44 | glz_cf_prefpane_title => Saisie personnalisée 45 | glz_cf_js_textarea_msg => Chaque valeur sur une ligne distincte
      Une valeur par {défaut} autorisée 46 | glz_cf_js_script_msg => Nom du fichier php dans votre chemin des scripts personnalisés 47 | glz_cf_js_configure_msg => Pour les paramètres, voyez les préférences des champs personnalisés 48 | glz_cf_public_error_datepicker => Corrigez le plugin jQuery DatePicker » Vérifiez son chemin d’accès 49 | glz_cf_public_error_timepicker => Corrigez le plugin jQuery TimePicker » Vérifiez son chemin d’accès 50 | position => Position 51 | glz_cf_col_sort => Trier 52 | glz_cf_action_new_title => Nouveau champ personnalisé 53 | glz_cf_action_edit_title => Édition du {custom_set_name} 54 | glz_cf_add_new_cf => Nouveau champ personnalisé 55 | glz_cf_multiple_values_instructions => Chaque valeur sur une ligne distincte
      Une valeur par {défaut} autorisé 56 | glz_cf_edit_name_hint => Caractères alphanumériques et sous-tirets 57 | glz_cf_edit_position_hint => Attribué automatiquement si vide 58 | glz_cf_edit_name => Nom du champ personnalisé 59 | glz_cf_edit_title => Titre du champ personnalisé 60 | glz_cf_edit_title_hint => Étiquette descriptive 61 | glz_cf_edit_instructions => Instructions 62 | glz_cf_edit_instructions_hint => Texte d’aide facultatif (apparaît à côté de l’étiquette) 63 | glz_cf_edit_type => Type de champ personnalisé : 64 | glz_cf_edit_position => Position du champ personnalisé 65 | glz_cf_edit_value => Valeur(s) du champ personnalisé 66 | glz_cf_confirm_reset => Cette action va RÉINITIALISER toutes les données du champ personnalisé {custom}. Êtes-vous certain ? 67 | glz_cf_confirm_delete => Cette action va SUPPRIMER toutes les données du champ personnalisé {custom}. Êtes-vous certain ? 68 | glz_custom_f => Saisie personnalisée Vérifiez les chemins d’accès 69 | glz_cf_values_ordering => Ordre de tri des champs personnalisés 70 | glz_cf_prefs_value_asc => Ordre ascendant 71 | glz_cf_prefs_value_desc => Ordre descendant 72 | glz_cf_prefs_value_custom => Tel qu’enregistré 73 | glz_cf_multiselect_size => Taille du champ multi-select 74 | glz_cf_css_folder => Répertoire CSS Glz custom fields 75 | glz_cf_js_folder => Répertoire JS Glz custom fields 76 | glz_cf_custom_folder => Chemin des scripts personnalisés 77 | glz_cf_use_sortable => Trier les champs personnalisés par glisser/déposer 78 | glz_cf_permit_full_deinstall => Supprimer toutes les données lors de la désinstallation 79 | glz_cf_datepicker_folder => Répertoire du sélecteur de dates 80 | glz_cf_timepicker_folder => Répertoire du sélecteur d’heures 81 | glz_cf_folder_error => {folder} n’existe pas, veuillez le créer. 82 | glz_cf_folder_success => URL / chemin est correct 83 | glz_cf_css_asset_url => URL du répertoire CSS 84 | glz_cf_js_asset_url => URL du répertoire JS 85 | glz_cf_custom_scripts_path => Chemin des scripts personnalisés 86 | glz_cf_custom_scripts_path_error => Le répertoire des scripts personnalisés n’existe pas, veuillez le créer. 87 | glz_cf_datepicker => Sélecteur de dates 88 | glz_cf_datepicker_url => URL du répertoire du plugin sélecteur de dates 89 | glz_cf_datepicker_url_error => Le répertoire du sélecteur de dates n’existe pas, veuillez le créer. 90 | glz_cf_datepicker_format => Format de la date 91 | glz_cf_datepicker_first_day => Début de la semaine 92 | glz_cf_datepicker_start_date => Date de commencement du calendrier 93 | glz_cf_datepicker_start_date_error => La date de commencement ne correspond pas au format de la date 94 | glz_cf_prefs_monday => Lundi 95 | glz_cf_prefs_tuesday => Mardi 96 | glz_cf_prefs_wednesday => Mercredi 97 | glz_cf_prefs_thursday => Jeudi 98 | glz_cf_prefs_friday => Vendredi 99 | glz_cf_prefs_saturday => Samedi 100 | glz_cf_prefs_sunday => Dimanche 101 | glz_cf_prefs_slash_dmyy => jj/mm/aaaa 102 | glz_cf_prefs_slash_mdyy => mm/jj/aaaa 103 | glz_cf_prefs_dash_yymd => aaaa-mm-jj 104 | glz_cf_prefs_space_dmy => jj mm aa 105 | glz_cf_prefs_dot_dmyy => jj.mm.aaaa 106 | glz_cf_timepicker => Sélecteur de temps 107 | glz_cf_timepicker_url => URL du répertoire du plugin sélecteur d’heures 108 | glz_cf_timepicker_url_error => Le répertoire du sélecteur d’heures n’existe pas, veuillez le créer. 109 | glz_cf_timepicker_start_time => Heure de début 110 | glz_cf_timepicker_end_time => Heure de fin 111 | glz_cf_timepicker_step => Décalage horaire (en minutes) 112 | glz_cf_timepicker_show_24 => Format horaire 113 | glz_cf_prefs_24_hours => 24 heures 114 | glz_cf_prefs_12_hours => 12 heures 115 | glz_cf_sort_success => L’ordre de tri du champ personnalisé a été modifié 116 | glz_cf_sort_error => L’enregistrement de l’ordre de tri des champs personnalisés a échoué 117 | #@prefs 118 | custom_x_set => Nom du champ personnalisé {number} 119 | instructions_glz_cf_datepicker_start_date => Le format DOIT correspondre au format de la date 120 | instructions_glz_cf_custom_scripts_path => Chemin depuis le répertoire racine (root) 121 | instructions_glz_cf_permit_full_deinstall => Attention : "Oui" = La désinstallation supprime TOUTES les données définies par l’utilisateur dans les champs personnalisés 122 | --------------------------------------------------------------------------------