WP Job Manager - Version 1.21.0

Version Description

  • Feature - Ajax loading history - back button will take you back to current position in the search. If you are on > page 1, a 'load previous' button will be shown so you can paginate either way.
  • Feature - Ajax file upload during job submission.
  • Feature - Cookie set when submitting a job to allow resuming if you leave the page.
  • Feature - job_apply shortcode to show application area in other places on your site.
  • Feature - Allow admin fields to be priority sorted.
  • Feature - Featured job widget.
  • Feature - Scroll to top on pagination click.
  • Feature - Option to "Hide content within expired listings". If disabled, expired listings will be listed normally with applications disabled.
  • Fix - Prevent attachments being uploaded several times.
  • Fix - Expiry date on first save.
  • Fix - Relist should go back to form.
  • Fix - jquery.com CDN for CSS.
  • Tweak - Geocode street and street number separately.
  • Tweak - File upload field markup.
  • Tweak - Added filters around taxonomy definition.
  • Tweak - Chanced search logic/query to use the new meta queries in 4.1.
  • Tweak - Implement transient caching for searches.
  • Tweak - Removed wp_dropdown_user due to performance concerns.
  • Tweak - Use menu_order to make featured listings sticky. Improves performance.
  • Tweak - Retrieve AJAX jobs with GET rather than POST request to take advantage of more caching. Plugins looking for POST data will need to update to look for GET/REQUEST instead.
  • Tweak - Prevent themes that (sigh) mess with content hooks from breaking inputs.
  • Tweak - Remove unused job-category field.
  • Tweak - Hide company div if company name missing.
Download this release

Release Info

Developer mikejolley
Plugin Icon 128x128 WP Job Manager
Version 1.21.0
Comparing to
See all releases

Code changes from version 1.20.1 to 1.21.0

Files changed (46) hide show
  1. assets/css/frontend.css +1 -1
  2. assets/css/frontend.less +30 -10
  3. assets/js/admin.js +7 -0
  4. assets/js/admin.min.js +1 -1
  5. assets/js/ajax-file-upload.js +76 -0
  6. assets/js/ajax-file-upload.min.js +1 -0
  7. assets/js/ajax-filters.js +101 -43
  8. assets/js/ajax-filters.min.js +1 -1
  9. assets/js/job-submission.js +1 -1
  10. assets/js/job-submission.min.js +1 -1
  11. assets/js/jquery-deserialize/jquery.deserialize.js +142 -0
  12. assets/js/jquery-fileupload/jquery.fileupload.js +1467 -0
  13. assets/js/jquery-fileupload/jquery.iframe-transport.js +217 -0
  14. assets/js/multiselect.js +1 -1
  15. assets/js/multiselect.min.js +1 -1
  16. assets/js/term-multiselect.js +1 -1
  17. assets/js/term-multiselect.min.js +1 -1
  18. includes/admin/class-wp-job-manager-admin.php +1 -1
  19. includes/admin/class-wp-job-manager-settings.php +11 -2
  20. includes/admin/class-wp-job-manager-writepanels.php +75 -35
  21. includes/class-wp-job-manager-ajax.php +45 -15
  22. includes/class-wp-job-manager-api.php +2 -2
  23. includes/class-wp-job-manager-cache-helper.php +54 -0
  24. includes/class-wp-job-manager-geocode.php +4 -8
  25. includes/class-wp-job-manager-install.php +10 -1
  26. includes/class-wp-job-manager-post-types.php +30 -22
  27. includes/class-wp-job-manager-shortcodes.php +45 -1
  28. includes/class-wp-job-manager-widgets.php +107 -4
  29. includes/forms/class-wp-job-manager-form-submit-job.php +57 -82
  30. languages/wp-job-manager.pot +219 -176
  31. readme.txt +27 -2
  32. templates/content-single-job_listing-company.php +4 -0
  33. templates/content-single-job_listing-meta.php +2 -0
  34. templates/content-single-job_listing.php +7 -9
  35. templates/form-fields/checkbox-field.php +1 -9
  36. templates/form-fields/file-field.php +22 -23
  37. templates/form-fields/job-category-field.php +0 -10
  38. templates/form-fields/password-field.php +1 -10
  39. templates/form-fields/term-select-field.php +5 -6
  40. templates/form-fields/text-field.php +1 -10
  41. templates/form-fields/textarea-field.php +1 -10
  42. templates/form-fields/uploaded-file-html.php +12 -0
  43. templates/job-filters.php +2 -2
  44. wp-job-manager-functions.php +232 -120
  45. wp-job-manager-template.php +11 -2
  46. wp-job-manager.php +30 -6
assets/css/frontend.css CHANGED
@@ -1 +1 @@
1
- .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}@font-face{font-family:job-manager;src:url(../font/job-manager.eot?4963673);src:url(../font/job-manager.eot?4963673#iefix) format('embedded-opentype'),url(../font/job-manager.woff?4963673) format('woff'),url(../font/job-manager.ttf?4963673) format('truetype'),url(../font/job-manager.svg?4963673#job-manager) format('svg');font-weight:400;font-style:normal}@font-face{font-family:jm-logo;src:url(../font/jm-logo/jm.eot?ycsbky);src:url(../font/jm-logo/jm.eot?#iefixycsbky) format('embedded-opentype'),url(../font/jm-logo/jm.woff?ycsbky) format('woff'),url(../font/jm-logo/jm.ttf?ycsbky) format('truetype'),url(../font/jm-logo/jm.svg?ycsbky#icomoon) format('svg');font-weight:400;font-style:normal}.jm-icon{font-family:job-manager!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.display-icon{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline}.job-manager-error,.job-manager-info,.job-manager-message{padding:1em 1em 1em 3.5em;margin:0 0 2em;position:relative;-webkit-border-radius:4px;border-radius:4px;background:#fff;background:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background:-webkit-linear-gradient(#fff,#fff);background:-moz-linear-gradient(center top,#fff 0,#fff 100%);background:-moz-gradient(center top,#fff 0,#fff 100%);color:#666;text-shadow:0 1px 0 #fff;list-style:none outside;zoom:1;width:auto;box-shadow:inset 0 -2px 6px rgba(0,0,0,.05),inset 0 -2px 30px rgba(0,0,0,.015),inset 0 1px 0 #fff,0 1px 2px rgba(0,0,0,.3)}.job-manager-error:after,.job-manager-error:before,.job-manager-info:after,.job-manager-info:before,.job-manager-message:after,.job-manager-message:before{content:"";display:table}.job-manager-error:after,.job-manager-info:after,.job-manager-message:after{clear:both}.job-manager-error:before,.job-manager-info:before,.job-manager-message:before{content:"";height:1.5em;width:1.5em;display:block;position:absolute;top:0;left:1em;font-family:sans-serif;font-size:1em;line-height:1.5;text-align:center;color:#fff;text-shadow:0 1px 0 rgba(0,0,0,.2);padding-top:1em;-webkit-border-bottom-left-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:4px;box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1);-webkit-box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1);-moz-box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1)}.job-manager-error li,.job-manager-info li,.job-manager-message li{list-style:none outside;padding-left:0;margin-left:0}.job-manager-error.job-manager-message,.job-manager-info.job-manager-message,.job-manager-message.job-manager-message{border-top:3px solid #8fae1b}.job-manager-error.job-manager-message:before,.job-manager-info.job-manager-message:before,.job-manager-message.job-manager-message:before{background-color:#8fae1b;content:"\2713"}.job-manager-error.job-manager-info,.job-manager-info.job-manager-info,.job-manager-message.job-manager-info{border-top:3px solid #1e85be}.job-manager-error.job-manager-info:before,.job-manager-info.job-manager-info:before,.job-manager-message.job-manager-info:before{background-color:#1e85be;content:"i";font-family:Times,Georgia,serif;font-style:italic}.job-manager-error.job-manager-error,.job-manager-info.job-manager-error,.job-manager-message.job-manager-error{border-top:3px solid #b81c23}.job-manager-error.job-manager-error:before,.job-manager-info.job-manager-error:before,.job-manager-message.job-manager-error:before{background-color:#b81c23;content:"\00d7";font-weight:700}.job-manager-form fieldset{margin:0 0 1em;padding:0 0 1em;line-height:2em;border:0;border-bottom:1px solid #eee;zoom:1}.job-manager-form fieldset:after,.job-manager-form fieldset:before{content:"";display:table}.job-manager-form fieldset:after{clear:both}.job-manager-form fieldset label{display:block;margin:0;width:29%;float:left;vertical-align:middle}.job-manager-form fieldset label small{opacity:.75;font-size:.83em}.job-manager-form fieldset div.field{width:70%;float:right;vertical-align:middle}.job-manager-form fieldset .wp-editor-container{border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.job-manager-form fieldset .account-sign-in .button{margin-right:.5em}.job-manager-form fieldset .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}.job-manager-form fieldset abbr.required{color:red;font-weight:700;border:0}.job-manager-form fieldset input.input-text,.job-manager-form fieldset select,.job-manager-form fieldset textarea{margin:0;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-form fieldset small.description{opacity:.75;font-size:.83em;margin:1.2em 0 0;display:block;line-height:1.2em}.job-manager-form fieldset .job-manager-uploaded-files{display:table}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file{line-height:1.5em;font-style:italic;display:table-row}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview{display:table-cell;vertical-align:middle;padding:.5em 0}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview img{height:64px;margin:0}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-name{display:table-cell;vertical-align:middle;padding:.5em 0 .5em 1em}.job-manager-form .submit-job{padding:1em 0}.job-manager-form .job-manager-term-checklist{list-style:none outside;max-height:200px;overflow:auto;margin:0}.job-manager-form .job-manager-term-checklist li{list-style:none outside;margin:0;display:block;float:none}.job-manager-form .job-manager-term-checklist li label{width:auto;float:none}.job-manager-form .job-manager-term-checklist li li{margin:0 0 0 2em}div.job_listings{margin-bottom:1em}div.job_listings ul.job_listings{margin:0}.rp4wp-related-job_listing>ul,ul.job_listings{padding:0;margin:0;border-top:1px solid #eee}.rp4wp-related-job_listing>ul.loading,ul.job_listings.loading{min-height:96px;border-bottom:1px solid #eee;background:url(../images/ajax-loader.gif) no-repeat center 32px}.rp4wp-related-job_listing>ul li.job_listing,.rp4wp-related-job_listing>ul li.no_job_listings_found,ul.job_listings li.job_listing,ul.job_listings li.no_job_listings_found{list-style:none outside;padding:0;margin:0;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing.job_position_filled a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_filled a,ul.job_listings li.job_listing.job_position_filled a,ul.job_listings li.no_job_listings_found.job_position_filled a{opacity:.25}.rp4wp-related-job_listing>ul li.job_listing.no_job_listings_found,.rp4wp-related-job_listing>ul li.no_job_listings_found.no_job_listings_found,ul.job_listings li.job_listing.no_job_listings_found,ul.job_listings li.no_job_listings_found.no_job_listings_found{padding:1em;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing a,.rp4wp-related-job_listing>ul li.no_job_listings_found a,ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{display:block;padding:1em 1em 1em 2em;border:0;overflow:hidden;zoom:1;position:relative;line-height:1.5em;text-decoration:none}.rp4wp-related-job_listing>ul li.job_listing a:focus,.rp4wp-related-job_listing>ul li.job_listing a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found a:hover,ul.job_listings li.job_listing a:focus,ul.job_listings li.job_listing a:hover,ul.job_listings li.no_job_listings_found a:focus,ul.job_listings li.no_job_listings_found a:hover{background-color:#fcfcfc}.rp4wp-related-job_listing>ul li.job_listing a img.company_logo,.rp4wp-related-job_listing>ul li.no_job_listings_found a img.company_logo,ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{width:42px;height:42px;position:absolute;left:1em;float:left;margin-right:1em;vertical-align:middle;box-shadow:none}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.job_listing a ul.meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a ul.meta,ul.job_listings li.job_listing a div.location,ul.job_listings li.job_listing a div.position,ul.job_listings li.job_listing a ul.meta,ul.job_listings li.no_job_listings_found a div.location,ul.job_listings li.no_job_listings_found a div.position,ul.job_listings li.no_job_listings_found a ul.meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:55%;padding:0 0 0 42px;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a div.position h3,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position h3,ul.job_listings li.job_listing a div.position h3,ul.job_listings li.no_job_listings_found a div.position h3{margin:0;padding:0;line-height:inherit;font-size:inherit}.rp4wp-related-job_listing>ul li.job_listing a div.position .company,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company,ul.job_listings li.job_listing a div.position .company,ul.job_listings li.no_job_listings_found a div.position .company{color:#999}.rp4wp-related-job_listing>ul li.job_listing a div.position .company .tagline,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company .tagline,ul.job_listings li.job_listing a div.position .company .tagline,ul.job_listings li.no_job_listings_found a div.position .company .tagline{margin-left:.5em}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:left;text-align:left;width:25%;padding:0 0 0 1em;color:#999;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a .meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta,ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;text-align:right;width:20%;padding:0 0 0 1em;margin:0;line-height:1.5em;color:#999;list-style:none outside}.rp4wp-related-job_listing>ul li.job_listing a .meta li,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta li,ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{list-style:none outside;display:block;margin:0}.rp4wp-related-job_listing>ul li.job_listing a .meta .job-type,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta .job-type,ul.job_listings li.job_listing a .meta .job-type,ul.job_listings li.no_job_listings_found a .meta .job-type{font-weight:700}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a,ul.job_listings li.job_listing.job_position_featured a,ul.job_listings li.no_job_listings_found.job_position_featured a{background:#fefee5}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:hover,ul.job_listings li.job_listing.job_position_featured a:focus,ul.job_listings li.job_listing.job_position_featured a:hover,ul.job_listings li.no_job_listings_found.job_position_featured a:focus,ul.job_listings li.no_job_listings_found.job_position_featured a:hover{background-color:#fefed8}.widget ul.job_listings li.job_listing a{padding:1em 0}.widget ul.job_listings li.job_listing .position{float:none;width:auto;padding:0}.widget ul.job_listings li.job_listing ul.meta{float:none;width:auto;padding:0;margin:0;text-align:left}.widget ul.job_listings li.job_listing ul.meta li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400}.widget ul.job_listings li.job_listing ul.meta li:after{padding:0 0 0 .5em;content:"\2023"}.widget ul.job_listings li.job_listing ul.meta li:last-child:after{content:''}.job-manager .job-type,.job-types .job-type,.job_listing .job-type{color:#f08d3c}.job-manager .full-time,.job-types .full-time,.job_listing .full-time{color:#90da36}.job-manager .part-time,.job-types .part-time,.job_listing .part-time{color:#f08d3c}.job-manager .temporary,.job-types .temporary,.job_listing .temporary{color:#d93674}.job-manager .freelance,.job-types .freelance,.job_listing .freelance{color:#39c}.job-manager .internship,.job-types .internship,.job_listing .internship{color:#6033cc}.single_job_listing .company{position:relative;border:1px solid #eee;padding:1em;margin:0 0 2em;display:block;clear:both;min-height:3em;box-shadow:0 1px 1px rgba(0,0,0,.1)}.single_job_listing .company img{width:3em;height:3em;position:absolute;left:1em;float:left;vertical-align:middle;box-shadow:none}.single_job_listing .company .name{margin:0 0 0 3em;padding:0 0 0 1em;line-height:1.5em}.single_job_listing .company .name a{float:right;margin-left:1em}.single_job_listing .company .tagline{display:block;margin:0 0 0 42px;padding:0 0 0 1em;line-height:1.5em;font-style:italic;color:#999}.single_job_listing .company .website:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e809'}.single_job_listing .company .company_twitter:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80a'}.single_job_listing .company .company_video{border-top:1px solid #eee;padding:1em 0 0;margin:1em 0 0}.single_job_listing .company .company_video iframe{margin:0;display:block}.single_job_listing .meta{list-style:none outside;padding:0;margin:0 0 1.5em;overflow:hidden;zoom:1;clear:both}.single_job_listing .meta li{margin:0 1em 0 0;padding:.5em;float:left;line-height:1em;color:#999}.single_job_listing .meta .job-type{color:#fff;background-color:#f08d3c}.single_job_listing .meta .full-time{background-color:#90da36}.single_job_listing .meta .part-time{background-color:#f08d3c}.single_job_listing .meta .temporary{background-color:#d93674}.single_job_listing .meta .freelance{background-color:#39c}.single_job_listing .meta .internship{background-color:#6033cc}.single_job_listing .meta .position-filled{color:#b81c23}.single_job_listing .meta .location:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e81d'}.single_job_listing .meta .date-posted:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80f'}.single_job_listing .meta .position-filled:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80e'}.single_job_listing .application{padding:0;margin:0 0 1em;overflow:hidden}.single_job_listing .application .application_button,.single_job_listing .application .application_details{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.single_job_listing .application .application_button{text-align:center;font-size:1.1em;line-height:1em;display:inline-block;margin:0 .5em 0 0;padding:.90909091em 2em;outline:0}.single_job_listing .application .application_details{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.single_job_listing .application .application_details p{margin:0 0 .75em}.single_job_listing .application .application_details:before{content:"";position:absolute;margin:-10px 0 0 0;top:0;left:5em;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #eee}.single_job_listing .application .application_details:after{content:"";position:absolute;margin:-9px 0 0 1px;left:5em;top:0;width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-bottom:9px solid #fff}.job_filters{background:#eee;zoom:1}.job_filters:after,.job_filters:before{content:"";display:table}.job_filters:after{clear:both}.job_filters .search_jobs{padding:1em;zoom:1}.job_filters .search_jobs:after,.job_filters .search_jobs:before{content:"";display:table}.job_filters .search_jobs:after{clear:both}.job_filters .search_jobs div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_filters .search_jobs div label{display:none}.job_filters .search_jobs div.filter_first,.job_filters .search_jobs div.search_keywords{float:left;padding-right:.5em;width:50%}.job_filters .search_jobs div.filter_last,.job_filters .search_jobs div.search_location{float:right;padding-left:.5em;width:50%}.job_filters .search_jobs div.filter_wide,.job_filters .search_jobs div.search_categories{padding-top:.5em;clear:both;width:100%}.job_filters .search_jobs div .showing_jobs a{padding:.25em}.job_filters .search_jobs div .showing_jobs a.active{background:#ddd;text-decoration:none}.job_filters .search_jobs input,.job_filters .search_jobs select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.job_filters .job_types{list-style:none outside;margin:0;padding:0;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5;zoom:1}.job_filters .job_types:after,.job_filters .job_types:before{content:"";display:table}.job_filters .job_types:after{clear:both}.job_filters .job_types li{margin:0;padding:.5em 1em;float:left;border-right:1px solid #eee}.job_filters .job_types li:last-child{border-right:0}.job_filters .showing_jobs{padding:.5em 1em;display:none;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5}.job_filters .showing_jobs a{float:right;padding-left:10px;border:0}div.job_listings .job-manager-pagination{text-align:center;display:block;padding:1em 0;border-bottom:1px solid #eee;line-height:1}div.job_listings .load_more_jobs{text-align:center;display:block;padding:1em 1em 1em 2em;border-bottom:1px solid #eee;font-weight:700}div.job_listings .load_more_jobs.loading{background:url(../images/ajax-loader.gif) no-repeat center}div.job_listings .load_more_jobs.loading strong{visibility:hidden}div.job_listings .load_more_jobs:focus,div.job_listings .load_more_jobs:hover{background-color:#fcfcfc;border-bottom:1px solid #eee}.job_listing_preview{padding:0 1em 1em;border:5px solid #eee}.entry-content .job_listing_preview_title,.job_listing_preview_title{padding:.5em 1em;vertical-align:middle;position:relative;background:#eee}.entry-content .job_listing_preview_title h2,.job_listing_preview_title h2{margin:0;clear:none}.entry-content .job_listing_preview_title .button,.job_listing_preview_title .button{float:right;margin-left:.25em}.job_summary_shortcode{border:1px solid #ccc;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;text-align:center;position:relative;box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_summary_shortcode.aligncenter{display:block;margin:2em auto}.job_summary_shortcode.alignleft{float:left;margin:0 2em 2em 0}.job_summary_shortcode.alignright{float:right;margin:0 0 2em 2em}.job_summary_shortcode a{text-decoration:none;color:inherit}.job_summary_shortcode img{margin:0;padding:0;display:block;width:100%;-moz-border-radius:0;-webkit-border-radius:0;-moz-border-top-left-radius:3px;-moz-border-top-right-radius:3px;-webkit-border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-radius:0;border-top-left-radius:3px;border-top-right-radius:3px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.job_summary_shortcode .job_summary_content{padding:0 1em}.job_summary_shortcode .meta{font-style:italic;color:#777}.job_summary_shortcode .job-type{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em;color:#fff;text-shadow:0 1px 0 rgba(255,255,255,.5);box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);position:absolute;top:0;right:0;padding:.5em;height:1em;width:auto;min-width:1em;font-size:1em;text-align:center;vertical-align:middle;line-height:1em;margin:-.5em -.5em 0 0}.job_summary_shortcode .job-type.full-time{background-color:#90da36}.job_summary_shortcode .job-type.part-time{background-color:#f08d3c}.job_summary_shortcode .job-type.temporary{background-color:#d93674}.job_summary_shortcode .job-type.freelance{background-color:#39c}.job_summary_shortcode .job-type.internship{background-color:#6033cc}#job-manager-job-dashboard .account-sign-in .button{margin-right:.5em}#job-manager-job-dashboard .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}#job-manager-job-dashboard table ul.job-dashboard-actions{margin:0;padding:0;visibility:hidden;font-size:.92em}#job-manager-job-dashboard table ul.job-dashboard-actions li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400;list-style:none outside}#job-manager-job-dashboard table ul.job-dashboard-actions li:after{padding:0 0 0 .5em;content:"\2023"}#job-manager-job-dashboard table ul.job-dashboard-actions li:last-child:after{content:''}#job-manager-job-dashboard table ul.job-dashboard-actions li .job-dashboard-action-delete{color:red}#job-manager-job-dashboard table tr:focus ul.job-dashboard-actions,#job-manager-job-dashboard table tr:hover ul.job-dashboard-actions{visibility:visible}#job-manager-job-dashboard table td,#job-manager-job-dashboard table th{padding:.5em 1em .5em 0}#job-manager-job-dashboard table .job_title small{color:#999}#content nav.job-manager-pagination,#job-manager-job-dashboard table .applications,#job-manager-job-dashboard table .expires,#job-manager-job-dashboard table .filled,nav.job-manager-pagination{text-align:center}#content nav.job-manager-pagination ul,nav.job-manager-pagination ul{display:inline-block;white-space:nowrap;padding:0;clear:both;border-left:1px solid #eee;margin:1px}#content nav.job-manager-pagination ul li,nav.job-manager-pagination ul li{border-right:1px solid #eee;border-top:1px solid #eee;border-bottom:1px solid #eee;padding:0;margin:0;float:left;display:inline;overflow:hidden}#content nav.job-manager-pagination ul li a,#content nav.job-manager-pagination ul li span,nav.job-manager-pagination ul li a,nav.job-manager-pagination ul li span{margin:0;text-decoration:none;line-height:1em;font-size:1em;font-weight:400;padding:.5em;min-width:1em;display:block;border:0}#content nav.job-manager-pagination ul li a:focus,#content nav.job-manager-pagination ul li a:hover,#content nav.job-manager-pagination ul li span.current,nav.job-manager-pagination ul li a:focus,nav.job-manager-pagination ul li a:hover,nav.job-manager-pagination ul li span.current{background:#eee;color:#888}@media only screen and (max-width:767px){ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{padding:1em}ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{visibility:hidden}ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:60%;padding:0}ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:right;width:40%;line-height:2em;font-size:.75em;padding:0 0 0 1em;text-align:right}ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;width:40%;line-height:2em;font-size:.75em}ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{font-size:1em}}.chosen-container{width:100%!important}.twenty-eleven ul.job_listings li.job_listing,.twenty-eleven ul.job_listings li.no_job_listings_found{padding:0!important}.twenty-ten .chosen-choices,.twenty-ten .job_types{margin:0!important}
1
+ .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}@font-face{font-family:job-manager;src:url(../font/job-manager.eot?4963673);src:url(../font/job-manager.eot?4963673#iefix) format('embedded-opentype'),url(../font/job-manager.woff?4963673) format('woff'),url(../font/job-manager.ttf?4963673) format('truetype'),url(../font/job-manager.svg?4963673#job-manager) format('svg');font-weight:400;font-style:normal}@font-face{font-family:jm-logo;src:url(../font/jm-logo/jm.eot?ycsbky);src:url(../font/jm-logo/jm.eot?#iefixycsbky) format('embedded-opentype'),url(../font/jm-logo/jm.woff?ycsbky) format('woff'),url(../font/jm-logo/jm.ttf?ycsbky) format('truetype'),url(../font/jm-logo/jm.svg?ycsbky#icomoon) format('svg');font-weight:400;font-style:normal}.jm-icon{font-family:job-manager!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.display-icon{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline}.job-manager-error,.job-manager-info,.job-manager-message{padding:1em 1em 1em 3.5em;margin:0 0 2em;position:relative;-webkit-border-radius:4px;border-radius:4px;background:#fff;background:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background:-webkit-linear-gradient(#fff,#fff);background:-moz-linear-gradient(center top,#fff 0,#fff 100%);background:-moz-gradient(center top,#fff 0,#fff 100%);color:#666;text-shadow:0 1px 0 #fff;list-style:none outside;zoom:1;width:auto;box-shadow:inset 0 -2px 6px rgba(0,0,0,.05),inset 0 -2px 30px rgba(0,0,0,.015),inset 0 1px 0 #fff,0 1px 2px rgba(0,0,0,.3)}.job-manager-error:after,.job-manager-error:before,.job-manager-info:after,.job-manager-info:before,.job-manager-message:after,.job-manager-message:before{content:"";display:table}.job-manager-error:after,.job-manager-info:after,.job-manager-message:after{clear:both}.job-manager-error:before,.job-manager-info:before,.job-manager-message:before{content:"";height:1.5em;width:1.5em;display:block;position:absolute;top:0;left:1em;font-family:sans-serif;font-size:1em;line-height:1.5;text-align:center;color:#fff;text-shadow:0 1px 0 rgba(0,0,0,.2);padding-top:1em;-webkit-border-bottom-left-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:4px;box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1);-webkit-box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1);-moz-box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.1)}.job-manager-error li,.job-manager-info li,.job-manager-message li{list-style:none outside;padding-left:0;margin-left:0}.job-manager-error.job-manager-message,.job-manager-info.job-manager-message,.job-manager-message.job-manager-message{border-top:3px solid #8fae1b}.job-manager-error.job-manager-message:before,.job-manager-info.job-manager-message:before,.job-manager-message.job-manager-message:before{background-color:#8fae1b;content:"\2713"}.job-manager-error.job-manager-info,.job-manager-info.job-manager-info,.job-manager-message.job-manager-info{border-top:3px solid #1e85be}.job-manager-error.job-manager-info:before,.job-manager-info.job-manager-info:before,.job-manager-message.job-manager-info:before{background-color:#1e85be;content:"i";font-family:Times,Georgia,serif;font-style:italic}.job-manager-error.job-manager-error,.job-manager-info.job-manager-error,.job-manager-message.job-manager-error{border-top:3px solid #b81c23}.job-manager-error.job-manager-error:before,.job-manager-info.job-manager-error:before,.job-manager-message.job-manager-error:before{background-color:#b81c23;content:"\00d7";font-weight:700}.job-manager-form fieldset{margin:0 0 1em;padding:0 0 1em;line-height:2em;border:0;border-bottom:1px solid #eee;zoom:1}.job-manager-form fieldset:after,.job-manager-form fieldset:before{content:"";display:table}.job-manager-form fieldset:after{clear:both}.job-manager-form fieldset label{display:block;margin:0;width:29%;float:left;vertical-align:middle}.job-manager-form fieldset label small{opacity:.75;font-size:.83em}.job-manager-form fieldset div.field{width:70%;float:right;vertical-align:middle}.job-manager-form fieldset .wp-editor-container{border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.job-manager-form fieldset .account-sign-in .button{margin-right:.5em}.job-manager-form fieldset .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}.job-manager-form fieldset abbr.required{color:red;font-weight:700;border:0}.job-manager-form fieldset input.input-text,.job-manager-form fieldset select,.job-manager-form fieldset textarea{margin:0;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-form fieldset small.description{opacity:.75;font-size:.83em;margin:1.2em 0 0;display:block;line-height:1.2em}.job-manager-form fieldset .job-manager-uploaded-files{display:table}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file{line-height:2em;font-style:italic;margin-bottom:1em;display:block}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview img{height:64px;margin:0;vertical-align:top}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview a{line-height:64px;display:inline-block;padding:0 0 0 1em}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-name{display:block}.job-manager-form .submit-job{padding:1em 0}.job-manager-form .job-manager-term-checklist{list-style:none outside;max-height:200px;overflow:auto;margin:0}.job-manager-form .job-manager-term-checklist li{list-style:none outside;margin:0;display:block;float:none}.job-manager-form .job-manager-term-checklist li label{width:auto;float:none}.job-manager-form .job-manager-term-checklist li li{margin:0 0 0 2em}div.job_listings{margin-bottom:1em}div.job_listings ul.job_listings{margin:0}.rp4wp-related-job_listing>ul,ul.job_listings{padding:0;margin:0;border-top:1px solid #eee}.rp4wp-related-job_listing>ul.loading,ul.job_listings.loading{min-height:96px;border-bottom:1px solid #eee;background:url(../images/ajax-loader.gif) no-repeat center 32px}.rp4wp-related-job_listing>ul li.job_listing,.rp4wp-related-job_listing>ul li.no_job_listings_found,ul.job_listings li.job_listing,ul.job_listings li.no_job_listings_found{list-style:none outside;padding:0;margin:0;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing.job_position_filled a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_filled a,ul.job_listings li.job_listing.job_position_filled a,ul.job_listings li.no_job_listings_found.job_position_filled a{opacity:.25}.rp4wp-related-job_listing>ul li.job_listing.no_job_listings_found,.rp4wp-related-job_listing>ul li.no_job_listings_found.no_job_listings_found,ul.job_listings li.job_listing.no_job_listings_found,ul.job_listings li.no_job_listings_found.no_job_listings_found{padding:1em;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing a,.rp4wp-related-job_listing>ul li.no_job_listings_found a,ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{display:block;padding:1em 1em 1em 2em;border:0;overflow:hidden;zoom:1;position:relative;line-height:1.5em;text-decoration:none}.rp4wp-related-job_listing>ul li.job_listing a:focus,.rp4wp-related-job_listing>ul li.job_listing a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found a:hover,ul.job_listings li.job_listing a:focus,ul.job_listings li.job_listing a:hover,ul.job_listings li.no_job_listings_found a:focus,ul.job_listings li.no_job_listings_found a:hover{background-color:#fcfcfc}.rp4wp-related-job_listing>ul li.job_listing a img.company_logo,.rp4wp-related-job_listing>ul li.no_job_listings_found a img.company_logo,ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{width:42px;height:42px;position:absolute;left:1em;float:left;margin-right:1em;vertical-align:middle;box-shadow:none}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.job_listing a ul.meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a ul.meta,ul.job_listings li.job_listing a div.location,ul.job_listings li.job_listing a div.position,ul.job_listings li.job_listing a ul.meta,ul.job_listings li.no_job_listings_found a div.location,ul.job_listings li.no_job_listings_found a div.position,ul.job_listings li.no_job_listings_found a ul.meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:55%;padding:0 0 0 42px;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a div.position h3,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position h3,ul.job_listings li.job_listing a div.position h3,ul.job_listings li.no_job_listings_found a div.position h3{margin:0;padding:0;line-height:inherit;font-size:inherit}.rp4wp-related-job_listing>ul li.job_listing a div.position .company,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company,ul.job_listings li.job_listing a div.position .company,ul.job_listings li.no_job_listings_found a div.position .company{color:#999}.rp4wp-related-job_listing>ul li.job_listing a div.position .company .tagline,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company .tagline,ul.job_listings li.job_listing a div.position .company .tagline,ul.job_listings li.no_job_listings_found a div.position .company .tagline{margin-left:.5em}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:left;text-align:left;width:25%;padding:0 0 0 1em;color:#999;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a .meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta,ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;text-align:right;width:20%;padding:0 0 0 1em;margin:0;line-height:1.5em;color:#999;list-style:none outside}.rp4wp-related-job_listing>ul li.job_listing a .meta li,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta li,ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{list-style:none outside;display:block;margin:0}.rp4wp-related-job_listing>ul li.job_listing a .meta .job-type,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta .job-type,ul.job_listings li.job_listing a .meta .job-type,ul.job_listings li.no_job_listings_found a .meta .job-type{font-weight:700}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a,ul.job_listings li.job_listing.job_position_featured a,ul.job_listings li.no_job_listings_found.job_position_featured a{background:#fefee5}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:hover,ul.job_listings li.job_listing.job_position_featured a:focus,ul.job_listings li.job_listing.job_position_featured a:hover,ul.job_listings li.no_job_listings_found.job_position_featured a:focus,ul.job_listings li.no_job_listings_found.job_position_featured a:hover{background-color:#fefed8}.widget ul.job_listings li.job_listing a{padding:1em 0}.widget ul.job_listings li.job_listing .position{float:none;width:auto;padding:0}.widget ul.job_listings li.job_listing ul.meta{float:none;width:auto;padding:0;margin:0;text-align:left}.widget ul.job_listings li.job_listing ul.meta li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400}.widget ul.job_listings li.job_listing ul.meta li:after{padding:0 0 0 .5em;content:"\2023"}.widget ul.job_listings li.job_listing ul.meta li:last-child:after{content:''}.job-manager .job-type,.job-types .job-type,.job_listing .job-type{color:#f08d3c}.job-manager .full-time,.job-types .full-time,.job_listing .full-time{color:#90da36}.job-manager .part-time,.job-types .part-time,.job_listing .part-time{color:#f08d3c}.job-manager .temporary,.job-types .temporary,.job_listing .temporary{color:#d93674}.job-manager .freelance,.job-types .freelance,.job_listing .freelance{color:#39c}.job-manager .internship,.job-types .internship,.job_listing .internship{color:#6033cc}.single_job_listing .company{position:relative;border:1px solid #eee;padding:1em;margin:0 0 2em;display:block;clear:both;min-height:3em;box-shadow:0 1px 1px rgba(0,0,0,.1)}.single_job_listing .company img{width:3em;height:3em;position:absolute;left:1em;float:left;vertical-align:middle;box-shadow:none}.single_job_listing .company .name{margin:0 0 0 3em;padding:0 0 0 1em;line-height:1.5em}.single_job_listing .company .name a{float:right;margin-left:1em}.single_job_listing .company .tagline{display:block;margin:0 0 0 42px;padding:0 0 0 1em;line-height:1.5em;font-style:italic;color:#999}.single_job_listing .company .website:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e809'}.single_job_listing .company .company_twitter:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80a'}.single_job_listing .company .company_video{border-top:1px solid #eee;padding:1em 0 0;margin:1em 0 0}.single_job_listing .company .company_video iframe{margin:0;display:block}.single_job_listing .meta{list-style:none outside;padding:0;margin:0 0 1.5em;overflow:hidden;zoom:1;clear:both}.single_job_listing .meta li{margin:0 1em 0 0;padding:.5em;float:left;line-height:1em;color:#999}.single_job_listing .meta .job-type{color:#fff;background-color:#f08d3c}.single_job_listing .meta .full-time{background-color:#90da36}.single_job_listing .meta .part-time{background-color:#f08d3c}.single_job_listing .meta .temporary{background-color:#d93674}.single_job_listing .meta .freelance{background-color:#39c}.single_job_listing .meta .internship{background-color:#6033cc}.single_job_listing .meta .listing-expired,.single_job_listing .meta .position-filled{color:#b81c23}.single_job_listing .meta .location:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e81d'}.single_job_listing .meta .date-posted:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80f'}.single_job_listing .meta .listing-expired:before,.single_job_listing .meta .position-filled:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80e'}.job-manager-application-wrapper{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application,.single_job_listing .application{padding:0;margin:0 0 1em;overflow:hidden}.job-manager-application-wrapper .application .application_button,.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_button,.single_job_listing .application .application_details{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-application-wrapper .application .application_button,.single_job_listing .application .application_button{text-align:center;font-size:1.1em;line-height:1em;display:inline-block;margin:0 .5em 0 0;padding:.90909091em 2em;outline:0}.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_details{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application .application_details p,.single_job_listing .application .application_details p{margin:0 0 .75em}.job-manager-application-wrapper .application .application_details:before,.single_job_listing .application .application_details:before{content:"";position:absolute;margin:-10px 0 0 0;top:0;left:5em;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #eee}.job-manager-application-wrapper .application .application_details:after,.single_job_listing .application .application_details:after{content:"";position:absolute;margin:-9px 0 0 1px;left:5em;top:0;width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-bottom:9px solid #fff}.job_filters{background:#eee;zoom:1}.job_filters:after,.job_filters:before{content:"";display:table}.job_filters:after{clear:both}.job_filters .search_jobs{padding:1em;zoom:1}.job_filters .search_jobs:after,.job_filters .search_jobs:before{content:"";display:table}.job_filters .search_jobs:after{clear:both}.job_filters .search_jobs div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_filters .search_jobs div label{display:none}.job_filters .search_jobs div.filter_first,.job_filters .search_jobs div.search_keywords{float:left;padding-right:.5em;width:50%}.job_filters .search_jobs div.filter_last,.job_filters .search_jobs div.search_location{float:right;padding-left:.5em;width:50%}.job_filters .search_jobs div.filter_wide,.job_filters .search_jobs div.search_categories{padding-top:.5em;clear:both;width:100%}.job_filters .search_jobs div .showing_jobs a{padding:.25em}.job_filters .search_jobs div .showing_jobs a.active{background:#ddd;text-decoration:none}.job_filters .search_jobs input,.job_filters .search_jobs select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.job_filters .job_types{list-style:none outside;margin:0;padding:0;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5;zoom:1}.job_filters .job_types:after,.job_filters .job_types:before{content:"";display:table}.job_filters .job_types:after{clear:both}.job_filters .job_types li{margin:0;padding:.5em 1em;float:left;border-right:1px solid #eee}.job_filters .job_types li:last-child{border-right:0}.job_filters .showing_jobs{padding:.5em 1em;display:none;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5}.job_filters .showing_jobs a{float:right;padding-left:10px;border:0}div.job_listings .job-manager-pagination{text-align:center;display:block;padding:1em 0;border-bottom:1px solid #eee;line-height:1}div.job_listings .load_previous{border-top:1px solid #eee}div.job_listings .load_more_jobs+ul.job_listings{border-top:0}div.job_listings .load_more_jobs{text-align:center;display:block;padding:1em 1em 1em 2em;border-bottom:1px solid #eee;font-weight:700}div.job_listings .load_more_jobs.loading{background:url(../images/ajax-loader.gif) no-repeat center}div.job_listings .load_more_jobs.loading strong{visibility:hidden}div.job_listings .load_more_jobs:focus,div.job_listings .load_more_jobs:hover{background-color:#fcfcfc;border-bottom:1px solid #eee}.job_listing_preview{padding:0 1em 1em;border:5px solid #eee}.entry-content .job_listing_preview_title,.job_listing_preview_title{padding:.5em 1em;vertical-align:middle;position:relative;background:#eee}.entry-content .job_listing_preview_title h2,.job_listing_preview_title h2{margin:0;clear:none}.entry-content .job_listing_preview_title .button,.job_listing_preview_title .button{float:right;margin-left:.25em}.job_summary_shortcode{border:1px solid #ccc;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;text-align:center;position:relative;box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_summary_shortcode.aligncenter{display:block;margin:2em auto}.job_summary_shortcode.alignleft{float:left;margin:0 2em 2em 0}.job_summary_shortcode.alignright{float:right;margin:0 0 2em 2em}.job_summary_shortcode a{text-decoration:none;color:inherit}.job_summary_shortcode img{margin:0;padding:0;display:block;width:100%;-moz-border-radius:0;-webkit-border-radius:0;-moz-border-top-left-radius:3px;-moz-border-top-right-radius:3px;-webkit-border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-radius:0;border-top-left-radius:3px;border-top-right-radius:3px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.job_summary_shortcode .job_summary_content{padding:0 1em}.job_summary_shortcode .meta{font-style:italic;color:#777}.job_summary_shortcode .job-type{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em;color:#fff;text-shadow:0 1px 0 rgba(255,255,255,.5);box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);position:absolute;top:0;right:0;padding:.5em;height:1em;width:auto;min-width:1em;font-size:1em;text-align:center;vertical-align:middle;line-height:1em;margin:-.5em -.5em 0 0}.job_summary_shortcode .job-type.full-time{background-color:#90da36}.job_summary_shortcode .job-type.part-time{background-color:#f08d3c}.job_summary_shortcode .job-type.temporary{background-color:#d93674}.job_summary_shortcode .job-type.freelance{background-color:#39c}.job_summary_shortcode .job-type.internship{background-color:#6033cc}#job-manager-job-dashboard .account-sign-in .button{margin-right:.5em}#job-manager-job-dashboard .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}#job-manager-job-dashboard table ul.job-dashboard-actions{margin:0;padding:0;visibility:hidden;font-size:.92em}#job-manager-job-dashboard table ul.job-dashboard-actions li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400;list-style:none outside}#job-manager-job-dashboard table ul.job-dashboard-actions li:after{padding:0 0 0 .5em;content:"\2023"}#job-manager-job-dashboard table ul.job-dashboard-actions li:last-child:after{content:''}#job-manager-job-dashboard table ul.job-dashboard-actions li .job-dashboard-action-delete{color:red}#job-manager-job-dashboard table tr:focus ul.job-dashboard-actions,#job-manager-job-dashboard table tr:hover ul.job-dashboard-actions{visibility:visible}#job-manager-job-dashboard table td,#job-manager-job-dashboard table th{padding:.5em 1em .5em 0}#job-manager-job-dashboard table .job_title small{color:#999}#content nav.job-manager-pagination,#job-manager-job-dashboard table .applications,#job-manager-job-dashboard table .expires,#job-manager-job-dashboard table .filled,nav.job-manager-pagination{text-align:center}#content nav.job-manager-pagination ul,nav.job-manager-pagination ul{display:inline-block;white-space:nowrap;padding:0;clear:both;border-left:1px solid #eee;margin:1px}#content nav.job-manager-pagination ul li,nav.job-manager-pagination ul li{border-right:1px solid #eee;border-top:1px solid #eee;border-bottom:1px solid #eee;padding:0;margin:0;float:left;display:inline;overflow:hidden}#content nav.job-manager-pagination ul li a,#content nav.job-manager-pagination ul li span,nav.job-manager-pagination ul li a,nav.job-manager-pagination ul li span{margin:0;text-decoration:none;line-height:1em;font-size:1em;font-weight:400;padding:.5em;min-width:1em;display:block;border:0}#content nav.job-manager-pagination ul li a:focus,#content nav.job-manager-pagination ul li a:hover,#content nav.job-manager-pagination ul li span.current,nav.job-manager-pagination ul li a:focus,nav.job-manager-pagination ul li a:hover,nav.job-manager-pagination ul li span.current{background:#eee;color:#888}@media only screen and (max-width:767px){ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{padding:1em}ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{visibility:hidden}ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:60%;padding:0}ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:right;width:40%;line-height:2em;font-size:.75em;padding:0 0 0 1em;text-align:right}ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;width:40%;line-height:2em;font-size:.75em}ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{font-size:1em}}.chosen-container{width:100%!important}.twenty-eleven ul.job_listings li.job_listing,.twenty-eleven ul.job_listings li.no_job_listings_found{padding:0!important}.twenty-ten .chosen-choices,.twenty-ten .job_types{margin:0!important}
assets/css/frontend.less CHANGED
@@ -162,22 +162,24 @@
162
  .job-manager-uploaded-files {
163
  display: table;
164
  .job-manager-uploaded-file {
165
- line-height: 1.5em;
166
  font-style: italic;
167
- display: table-row;
 
168
  .job-manager-uploaded-file-preview {
169
- display: table-cell;
170
- vertical-align: middle;
171
- padding: .5em 0 .5em 0;
172
  img {
173
  height: 64px;
174
  margin: 0;
 
 
 
 
 
 
175
  }
176
  }
177
  .job-manager-uploaded-file-name {
178
- display: table-cell;
179
- vertical-align: middle;
180
- padding: .5em 0 .5em 1em;
181
  }
182
  }
183
  }
@@ -465,7 +467,7 @@ ul.job_listings {
465
  .internship {
466
  background-color: @internship;
467
  }
468
- .position-filled {
469
  color: #b81c23;
470
  }
471
  .location:before {
@@ -476,11 +478,23 @@ ul.job_listings {
476
  .display-icon;
477
  content: '\e80f';
478
  }
479
- .position-filled:before {
480
  .display-icon;
481
  content: '\e80e';
482
  }
483
  }
 
 
 
 
 
 
 
 
 
 
 
 
484
  .application {
485
  padding: 0;
486
  margin: 0 0 1em;
@@ -630,6 +644,12 @@ div.job_listings {
630
  border-bottom: 1px solid #eee;
631
  line-height: 1;
632
  }
 
 
 
 
 
 
633
  .load_more_jobs {
634
  text-align: center;
635
  display: block;
162
  .job-manager-uploaded-files {
163
  display: table;
164
  .job-manager-uploaded-file {
165
+ line-height: 2em;
166
  font-style: italic;
167
+ margin-bottom: 1em;
168
+ display: block;
169
  .job-manager-uploaded-file-preview {
 
 
 
170
  img {
171
  height: 64px;
172
  margin: 0;
173
+ vertical-align: top;
174
+ }
175
+ a {
176
+ line-height: 64px;
177
+ display: inline-block;
178
+ padding: 0 0 0 1em;
179
  }
180
  }
181
  .job-manager-uploaded-file-name {
182
+ display: block;
 
 
183
  }
184
  }
185
  }
467
  .internship {
468
  background-color: @internship;
469
  }
470
+ .position-filled, .listing-expired {
471
  color: #b81c23;
472
  }
473
  .location:before {
478
  .display-icon;
479
  content: '\e80f';
480
  }
481
+ .position-filled:before, .listing-expired:before {
482
  .display-icon;
483
  content: '\e80e';
484
  }
485
  }
486
+ }
487
+ .job-manager-application-wrapper {
488
+ clear: both;
489
+ border: 1px solid #eee;
490
+ padding: .75em 1em 0;
491
+ margin: 1em 0;
492
+ line-height: 1.5em;
493
+ display: block;
494
+ position: relative;
495
+ box-shadow: 0 1px 1px rgba(0,0,0,0.1);
496
+ }
497
+ .single_job_listing, .job-manager-application-wrapper {
498
  .application {
499
  padding: 0;
500
  margin: 0 0 1em;
644
  border-bottom: 1px solid #eee;
645
  line-height: 1;
646
  }
647
+ .load_previous {
648
+ border-top: 1px solid #eee;
649
+ }
650
+ .load_more_jobs + ul.job_listings {
651
+ border-top: 0;
652
+ }
653
  .load_more_jobs {
654
  text-align: center;
655
  display: block;
assets/js/admin.js CHANGED
@@ -7,6 +7,13 @@ jQuery(document).ready(function($) {
7
  'delay' : 200
8
  });
9
 
 
 
 
 
 
 
 
10
  // Datepicker
11
  $( "input#_job_expires" ).datepicker({
12
  dateFormat: 'yy-mm-dd',
7
  'delay' : 200
8
  });
9
 
10
+ // Author
11
+ $( "p.form-field-author" ).on( 'click', 'a.change-author', function() {
12
+ $(this).closest( 'p' ).find('.current-author').hide();
13
+ $(this).closest( 'p' ).find('.change-author').show();
14
+ return false;
15
+ });
16
+
17
  // Datepicker
18
  $( "input#_job_expires" ).datepicker({
19
  dateFormat: 'yy-mm-dd',
assets/js/admin.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(a){a(".tips, .help_tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),a("input#_job_expires").datepicker({dateFormat:"yy-mm-dd",minDate:0});var b,c,d;a(".wp_job_manager_add_another_file_button").live("click",function(b){b.preventDefault();var c=a(this).closest(".form-field"),d=a(this).data("field_name"),e=a(this).data("field_placeholder"),f=a(this).data("uploader_button_text"),g=a(this).data("uploader_button");c.prepend('<span class="file_url"><input type="text" name="'+d+'[]" placeholder="'+e+'" /> <button class="button wp_job_manager_upload_file_button" data-uploader_button_text="'+f+'">'+g+"</button></span>")}),a(".wp_job_manager_upload_file_button").live("click",function(e){return e.preventDefault(),d=a(this).closest(".file_url"),c=d.find("input"),b?void b.open():(b=wp.media.frames.file_frame=wp.media({title:a(this).data("uploader_title"),button:{text:a(this).data("uploader_button_text")},multiple:!1}),b.on("select",function(){attachment=b.state().get("selection").first().toJSON(),a(c).val(attachment.url)}),void b.open())})});
1
+ jQuery(document).ready(function(a){a(".tips, .help_tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),a("p.form-field-author").on("click","a.change-author",function(){return a(this).closest("p").find(".current-author").hide(),a(this).closest("p").find(".change-author").show(),!1}),a("input#_job_expires").datepicker({dateFormat:"yy-mm-dd",minDate:0});var b,c,d;a(".wp_job_manager_add_another_file_button").live("click",function(b){b.preventDefault();var c=a(this).closest(".form-field"),d=a(this).data("field_name"),e=a(this).data("field_placeholder"),f=a(this).data("uploader_button_text"),g=a(this).data("uploader_button");c.prepend('<span class="file_url"><input type="text" name="'+d+'[]" placeholder="'+e+'" /> <button class="button wp_job_manager_upload_file_button" data-uploader_button_text="'+f+'">'+g+"</button></span>")}),a(".wp_job_manager_upload_file_button").live("click",function(e){return e.preventDefault(),d=a(this).closest(".file_url"),c=d.find("input"),b?void b.open():(b=wp.media.frames.file_frame=wp.media({title:a(this).data("uploader_title"),button:{text:a(this).data("uploader_button_text")},multiple:!1}),b.on("select",function(){attachment=b.state().get("selection").first().toJSON(),a(c).val(attachment.url)}),void b.open())})});
assets/js/ajax-file-upload.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+ $('.wp-job-manager-file-upload').fileupload({
3
+ dataType: 'json',
4
+ url: job_manager_ajax_file_upload.ajax_url,
5
+ maxNumberOfFiles: 1,
6
+ formData: {
7
+ script: true,
8
+ action: 'job_manager_upload_file'
9
+ },
10
+ add: function (e, data) {
11
+ var $file_field = $( this );
12
+ var $form = $file_field.closest( 'form' );
13
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
14
+ var uploadErrors = [];
15
+
16
+ // Validate type
17
+ var allowed_types = $(this).data('file_types');
18
+
19
+ if ( allowed_types ) {
20
+ var acceptFileTypes = new RegExp( "(\.|\/)(" + allowed_types + ")$", "i" );
21
+
22
+ if ( data.originalFiles[0]['name'].length && ! acceptFileTypes.test( data.originalFiles[0]['name'] ) ) {
23
+ uploadErrors.push( job_manager_ajax_file_upload.i18n_invalid_file_type + ' ' + allowed_types );
24
+ }
25
+ }
26
+
27
+ if ( uploadErrors.length > 0 ) {
28
+ alert( uploadErrors.join( "\n" ) );
29
+ } else {
30
+ $form.find(':input[type="submit"]').attr( 'disabled', 'disabled' );
31
+ data.context = $('<progress value="" max="100"></progress>').appendTo( $uploaded_files );
32
+ data.submit();
33
+ }
34
+ },
35
+ progress: function (e, data) {
36
+ var $file_field = $( this );
37
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
38
+ var progress = parseInt(data.loaded / data.total * 100, 10);
39
+ data.context.val( progress );
40
+ },
41
+ done: function (e, data) {
42
+ var $file_field = $( this );
43
+ var $form = $file_field.closest( 'form' );
44
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
45
+ var multiple = $file_field.attr( 'multiple' ) ? 1 : 0;
46
+ var image_types = [ 'jpg', 'gif', 'png', 'jpeg', 'jpe' ];
47
+
48
+ data.context.remove();
49
+
50
+ $.each(data.result.files, function(index, file) {
51
+ if ( file.error ) {
52
+ alert( file.error );
53
+ } else {
54
+ if ( $.inArray( file.extension, image_types ) >= 0 ) {
55
+ var html = $.parseHTML( job_manager_ajax_file_upload.js_field_html_img );
56
+ $( html ).find('.job-manager-uploaded-file-preview img').attr( 'src', file.url );
57
+ } else {
58
+ var html = $.parseHTML( job_manager_ajax_file_upload.js_field_html );
59
+ $( html ).find('.job-manager-uploaded-file-name code').text( file.name );
60
+ }
61
+
62
+ $( html ).find('.input-text').val( file.url );
63
+ $( html ).find('.input-text').attr( 'name', 'current_' + $file_field.attr( 'name' ) );
64
+
65
+ if ( multiple ) {
66
+ $uploaded_files.append( html );
67
+ } else {
68
+ $uploaded_files.html( html );
69
+ }
70
+ }
71
+ });
72
+
73
+ $form.find(':input[type="submit"]').removeAttr( 'disabled' );
74
+ }
75
+ });
76
+ });
assets/js/ajax-file-upload.min.js ADDED
@@ -0,0 +1 @@
 
1
+ jQuery(function(a){a(".wp-job-manager-file-upload").fileupload({dataType:"json",url:job_manager_ajax_file_upload.ajax_url,maxNumberOfFiles:1,formData:{script:!0,action:"job_manager_upload_file"},add:function(b,c){var d=a(this),e=d.closest("form"),f=d.parent().find(".job-manager-uploaded-files"),g=[],h=a(this).data("file_types");if(h){var i=new RegExp("(.|/)("+h+")$","i");c.originalFiles[0].name.length&&!i.test(c.originalFiles[0].name)&&g.push(job_manager_ajax_file_upload.i18n_invalid_file_type+" "+h)}g.length>0?alert(g.join("\n")):(e.find(':input[type="submit"]').attr("disabled","disabled"),c.context=a('<progress value="" max="100"></progress>').appendTo(f),c.submit())},progress:function(b,c){var d=a(this),e=(d.parent().find(".job-manager-uploaded-files"),parseInt(c.loaded/c.total*100,10));c.context.val(e)},done:function(b,c){var d=a(this),e=d.closest("form"),f=d.parent().find(".job-manager-uploaded-files"),g=d.attr("multiple")?1:0,h=["jpg","gif","png","jpeg","jpe"];c.context.remove(),a.each(c.result.files,function(b,c){if(c.error)alert(c.error);else{if(a.inArray(c.extension,h)>=0){var e=a.parseHTML(job_manager_ajax_file_upload.js_field_html_img);a(e).find(".job-manager-uploaded-file-preview img").attr("src",c.url)}else{var e=a.parseHTML(job_manager_ajax_file_upload.js_field_html);a(e).find(".job-manager-uploaded-file-name code").text(c.name)}a(e).find(".input-text").val(c.url),a(e).find(".input-text").attr("name","current_"+d.attr("name")),g?f.append(e):f.html(e)}}),e.find(':input[type="submit"]').removeAttr("disabled")}})});
assets/js/ajax-filters.js CHANGED
@@ -2,35 +2,42 @@ jQuery( document ).ready( function ( $ ) {
2
 
3
  var xhr = [];
4
 
5
- $( '.job_listings' ).on( 'update_results', function ( event, page, append ) {
6
- var data = '';
7
- var target = $( this );
8
- var form = target.find( '.job_filters' );
9
- var showing = target.find( '.showing_jobs' );
10
- var results = target.find( '.job_listings' );
11
- var per_page = target.data( 'per_page' );
12
- var orderby = target.data( 'orderby' );
13
- var order = target.data( 'order' );
14
- var featured = target.data( 'featured' );
15
- var filled = target.data( 'filled' );
16
- var index = $( 'div.job_listings' ).index(this);
17
 
18
  if ( xhr[index] ) {
19
  xhr[index].abort();
20
  }
21
 
22
- if ( append ) {
23
- $( '.load_more_jobs', target ).addClass( 'loading' );
24
- } else {
25
  $( results ).addClass( 'loading' );
26
  $( 'li.job_listing, li.no_job_listings_found', results ).css( 'visibility', 'hidden' );
 
 
 
 
 
 
 
 
 
27
  }
28
 
29
  if ( true == target.data( 'show_filters' ) ) {
30
 
31
  var filter_job_type = [];
32
 
33
- $( ':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"]', form ).each( function () {
34
  filter_job_type.push( $( this ).val() );
35
  } );
36
 
@@ -94,7 +101,7 @@ jQuery( document ).ready( function ( $ ) {
94
  }
95
 
96
  xhr[index] = $.ajax( {
97
- type: 'POST',
98
  url: job_manager_ajax_filters.ajax_url,
99
  data: data,
100
  success: function ( response ) {
@@ -125,7 +132,9 @@ jQuery( document ).ready( function ( $ ) {
125
  }
126
 
127
  if ( result.html ) {
128
- if ( append ) {
 
 
129
  $( results ).append( result.html );
130
  } else {
131
  $( results ).html( result.html );
@@ -139,10 +148,10 @@ jQuery( document ).ready( function ( $ ) {
139
  target.append( result.pagination );
140
  }
141
  } else {
142
- if ( ! result.found_jobs || result.max_num_pages === page ) {
143
- $( '.load_more_jobs', target ).hide();
144
- } else {
145
- $( '.load_more_jobs', target ).show().data( 'page', page );
146
  }
147
  $( '.load_more_jobs', target ).removeClass( 'loading' );
148
  $( 'li.job_listing', results ).css( 'visibility', 'visible' );
@@ -160,22 +169,18 @@ jQuery( document ).ready( function ( $ ) {
160
  } );
161
  } );
162
 
163
- $( '#search_keywords, #search_location, .job_types input, #search_categories' ).change( function () {
164
- var target = $( this ).closest( 'div.job_listings' );
165
-
166
  target.triggerHandler( 'update_results', [ 1, false ] );
 
167
  } )
168
 
169
  .on( "keyup", function(e) {
170
- if ( e.which === 13 ) {
171
- $( this ).trigger( 'change' );
172
- }
173
  } );
174
 
175
- $( '.job_filters' ).each(function() {
176
- $( this ).find( '#search_keywords, #search_location, .job_types input, #search_categories' ).eq(0).change();
177
- });
178
-
179
  $( '.job_filters' ).on( 'click', '.reset', function () {
180
  var target = $( this ).closest( 'div.job_listings' );
181
  var form = $( this ).closest( 'form' );
@@ -187,24 +192,33 @@ jQuery( document ).ready( function ( $ ) {
187
 
188
  target.triggerHandler( 'reset' );
189
  target.triggerHandler( 'update_results', [ 1, false ] );
 
190
 
191
  return false;
192
  } );
193
 
194
- $( '.load_more_jobs' ).click( function () {
195
- var target = $( this ).closest( 'div.job_listings' );
196
- var page = $( this ).data( 'page' );
 
197
 
198
- if ( !page ) {
199
- page = 1;
 
 
 
 
 
 
 
 
200
  } else {
201
- page = parseInt( page );
 
 
202
  }
203
 
204
- $( this ).data( 'page', ( page + 1 ) );
205
-
206
- target.triggerHandler( 'update_results', [ page + 1, true ] );
207
-
208
  return false;
209
  } );
210
 
@@ -212,8 +226,14 @@ jQuery( document ).ready( function ( $ ) {
212
  var target = $( this ).closest( 'div.job_listings' );
213
  var page = $( this ).data( 'page' );
214
 
 
 
215
  target.triggerHandler( 'update_results', [ page, false ] );
216
 
 
 
 
 
217
  return false;
218
  } );
219
 
@@ -221,6 +241,44 @@ jQuery( document ).ready( function ( $ ) {
221
  if ( job_manager_ajax_filters.is_rtl == 1 ) {
222
  $( 'select[name^="search_categories"]' ).addClass( 'chosen-rtl' );
223
  }
224
- $( 'select[name^="search_categories"]' ).chosen();
225
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  } );
2
 
3
  var xhr = [];
4
 
5
+ $( '.job_listings' ).on( 'update_results', function ( event, page, append, loading_previous ) {
6
+ var data = '';
7
+ var target = $( this );
8
+ var form = target.find( '.job_filters' );
9
+ var showing = target.find( '.showing_jobs' );
10
+ var results = target.find( '.job_listings' );
11
+ var per_page = target.data( 'per_page' );
12
+ var orderby = target.data( 'orderby' );
13
+ var order = target.data( 'order' );
14
+ var featured = target.data( 'featured' );
15
+ var filled = target.data( 'filled' );
16
+ var index = $( 'div.job_listings' ).index(this);
17
 
18
  if ( xhr[index] ) {
19
  xhr[index].abort();
20
  }
21
 
22
+ if ( ! append ) {
 
 
23
  $( results ).addClass( 'loading' );
24
  $( 'li.job_listing, li.no_job_listings_found', results ).css( 'visibility', 'hidden' );
25
+
26
+ // Not appending. If page > 1, we should show a load previous button so the user can get to earlier-page listings if needed
27
+ if ( page > 1 && true != target.data( 'show_pagination' ) ) {
28
+ $( results ).before( '<a class="load_more_jobs load_previous" href="#"><strong>' + job_manager_ajax_filters.i18n_load_prev_listings + '</strong></a>' );
29
+ } else {
30
+ target.find( '.load_previous' ).remove();
31
+ }
32
+
33
+ target.find( '.load_more_jobs' ).data( 'page', page );
34
  }
35
 
36
  if ( true == target.data( 'show_filters' ) ) {
37
 
38
  var filter_job_type = [];
39
 
40
+ $( ':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"], :input[name="filter_job_type"]', form ).each( function () {
41
  filter_job_type.push( $( this ).val() );
42
  } );
43
 
101
  }
102
 
103
  xhr[index] = $.ajax( {
104
+ type: 'GET',
105
  url: job_manager_ajax_filters.ajax_url,
106
  data: data,
107
  success: function ( response ) {
132
  }
133
 
134
  if ( result.html ) {
135
+ if ( append && loading_previous ) {
136
+ $( results ).prepend( result.html );
137
+ } else if ( append ) {
138
  $( results ).append( result.html );
139
  } else {
140
  $( results ).html( result.html );
148
  target.append( result.pagination );
149
  }
150
  } else {
151
+ if ( ! result.found_jobs || result.max_num_pages <= page ) {
152
+ $( '.load_more_jobs:not(.load_previous)', target ).hide();
153
+ } else if ( ! loading_previous ) {
154
+ $( '.load_more_jobs', target ).show();
155
  }
156
  $( '.load_more_jobs', target ).removeClass( 'loading' );
157
  $( 'li.job_listing', results ).css( 'visibility', 'visible' );
169
  } );
170
  } );
171
 
172
+ $( '#search_keywords, #search_location, .job_types :input, #search_categories' ).change( function() {
173
+ var target = $( this ).closest( 'div.job_listings' );
 
174
  target.triggerHandler( 'update_results', [ 1, false ] );
175
+ job_manager_store_state( target, 1 );
176
  } )
177
 
178
  .on( "keyup", function(e) {
179
+ if ( e.which === 13 ) {
180
+ $( this ).trigger( 'change' );
181
+ }
182
  } );
183
 
 
 
 
 
184
  $( '.job_filters' ).on( 'click', '.reset', function () {
185
  var target = $( this ).closest( 'div.job_listings' );
186
  var form = $( this ).closest( 'form' );
192
 
193
  target.triggerHandler( 'reset' );
194
  target.triggerHandler( 'update_results', [ 1, false ] );
195
+ job_manager_store_state( target, 1 );
196
 
197
  return false;
198
  } );
199
 
200
+ $( 'body' ).on( 'click', '.load_more_jobs', function() {
201
+ var target = $( this ).closest( 'div.job_listings' );
202
+ var page = parseInt( $( this ).data( 'page' ) || 1 );
203
+ var loading_previous = false;
204
 
205
+ $(this).addClass( 'loading' );
206
+
207
+ if ( $(this).is('.load_previous') ) {
208
+ page = page - 1;
209
+ loading_previous = true;
210
+ if ( page === 1 ) {
211
+ $(this).remove();
212
+ } else {
213
+ $( this ).data( 'page', page );
214
+ }
215
  } else {
216
+ page = page + 1;
217
+ $( this ).data( 'page', page );
218
+ job_manager_store_state( target, page );
219
  }
220
 
221
+ target.triggerHandler( 'update_results', [ page, true, loading_previous ] );
 
 
 
222
  return false;
223
  } );
224
 
226
  var target = $( this ).closest( 'div.job_listings' );
227
  var page = $( this ).data( 'page' );
228
 
229
+ job_manager_store_state( target, page );
230
+
231
  target.triggerHandler( 'update_results', [ page, false ] );
232
 
233
+ $( "body, html" ).animate({
234
+ scrollTop: target.offset().top
235
+ }, 600 );
236
+
237
  return false;
238
  } );
239
 
241
  if ( job_manager_ajax_filters.is_rtl == 1 ) {
242
  $( 'select[name^="search_categories"]' ).addClass( 'chosen-rtl' );
243
  }
244
+ $( 'select[name^="search_categories"]' ).chosen({ search_contains: true });
245
  }
246
+
247
+ if ( window.history && window.history.pushState ) {
248
+ $supports_html5_history = true;
249
+ } else {
250
+ $supports_html5_history = false;
251
+ }
252
+
253
+ var location = document.location.href.split('#')[0];
254
+
255
+ function job_manager_store_state( target, page ) {
256
+ if ( $supports_html5_history ) {
257
+ var form = target.find( '.job_filters' );
258
+ var data = $( form ).serialize();
259
+ var index = $( 'div.job_listings' ).index( target );
260
+ window.history.replaceState( { id: 'job_manager_state', page: page, data: data, index: index }, '', location + '#s=1' );
261
+ }
262
+ }
263
+
264
+ // Inital job and form population
265
+ $(window).on( "load", function( event ) {
266
+ $( '.job_filters' ).each( function() {
267
+ var target = $( this ).closest( 'div.job_listings' );
268
+ var form = target.find( '.job_filters' );
269
+ var inital_page = 1;
270
+ var index = $( 'div.job_listings' ).index( target );
271
+
272
+ if ( window.history.state && window.location.hash ) {
273
+ var state = window.history.state;
274
+ if ( state.id && 'job_manager_state' === state.id && index == state.index ) {
275
+ inital_page = state.page;
276
+ form.deserialize( state.data );
277
+ form.find( ':input[name^="search_categories"]' ).not(':input[type="hidden"]').trigger( 'chosen:updated' );
278
+ }
279
+ }
280
+
281
+ target.triggerHandler( 'update_results', [ inital_page, false ] );
282
+ });
283
+ });
284
  } );
assets/js/ajax-filters.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(a){var b=[];a(".job_listings").on("update_results",function(c,d,e){var f="",g=a(this),h=g.find(".job_filters"),i=g.find(".showing_jobs"),j=g.find(".job_listings"),k=g.data("per_page"),l=g.data("orderby"),m=g.data("order"),n=g.data("featured"),o=g.data("filled"),p=a("div.job_listings").index(this);if(b[p]&&b[p].abort(),e?a(".load_more_jobs",g).addClass("loading"):(a(j).addClass("loading"),a("li.job_listing, li.no_job_listings_found",j).css("visibility","hidden")),1==g.data("show_filters")){var q=[];a(':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"]',h).each(function(){q.push(a(this).val())});var r=h.find(":input[name^=search_categories], :input[name^=search_categories]").map(function(){return a(this).val()}).get(),s="",t="",u=h.find(":input[name=search_keywords]"),v=h.find(":input[name=search_location]");u.val()!==u.attr("placeholder")&&(s=u.val()),v.val()!==v.attr("placeholder")&&(t=v.val()),f={action:"job_manager_get_listings",search_keywords:s,search_location:t,search_categories:r,filter_job_type:q,per_page:k,orderby:l,order:m,page:d,featured:n,filled:o,show_pagination:g.data("show_pagination"),form_data:h.serialize()}}else{var r=g.data("categories"),s=g.data("keywords"),t=g.data("location");r&&(r=r.split(",")),f={action:"job_manager_get_listings",search_categories:r,search_keywords:s,search_location:t,per_page:k,orderby:l,order:m,page:d,featured:n,filled:o,show_pagination:g.data("show_pagination")}}b[p]=a.ajax({type:"POST",url:job_manager_ajax_filters.ajax_url,data:f,success:function(b){if(b)try{b.indexOf("<!--WPJM-->")>=0&&(b=b.split("<!--WPJM-->")[1]),b.indexOf("<!--WPJM_END-->")>=0&&(b=b.split("<!--WPJM_END-->")[0]);var c=a.parseJSON(b);c.showing?a(i).show().html("<span>"+c.showing+"</span>"+c.showing_links):a(i).hide(),c.showing_all?a(i).addClass("wp-job-manager-showing-all"):a(i).removeClass("wp-job-manager-showing-all"),c.html&&(e?a(j).append(c.html):a(j).html(c.html)),1==g.data("show_pagination")?(g.find(".job-manager-pagination").remove(),c.pagination&&g.append(c.pagination)):(c.found_jobs&&c.max_num_pages!==d?a(".load_more_jobs",g).show().data("page",d):a(".load_more_jobs",g).hide(),a(".load_more_jobs",g).removeClass("loading"),a("li.job_listing",j).css("visibility","visible")),a(j).removeClass("loading"),g.triggerHandler("updated_results",c)}catch(f){}}})}),a("#search_keywords, #search_location, .job_types input, #search_categories").change(function(){var b=a(this).closest("div.job_listings");b.triggerHandler("update_results",[1,!1])}).on("keyup",function(b){13===b.which&&a(this).trigger("change")}),a(".job_filters").each(function(){a(this).find("#search_keywords, #search_location, .job_types input, #search_categories").eq(0).change()}),a(".job_filters").on("click",".reset",function(){var b=a(this).closest("div.job_listings"),c=a(this).closest("form");return c.find(':input[name="search_keywords"]').not(':input[type="hidden"]').val(""),c.find(':input[name="search_location"]').not(':input[type="hidden"]').val(""),c.find(':input[name^="search_categories"]').not(':input[type="hidden"]').val(0).trigger("chosen:updated"),a(':input[name="filter_job_type[]"]',c).not(':input[type="hidden"]').attr("checked","checked"),b.triggerHandler("reset"),b.triggerHandler("update_results",[1,!1]),!1}),a(".load_more_jobs").click(function(){var b=a(this).closest("div.job_listings"),c=a(this).data("page");return c=c?parseInt(c):1,a(this).data("page",c+1),b.triggerHandler("update_results",[c+1,!0]),!1}),a("div.job_listings").on("click",".job-manager-pagination a",function(){var b=a(this).closest("div.job_listings"),c=a(this).data("page");return b.triggerHandler("update_results",[c,!1]),!1}),a.isFunction(a.fn.chosen)&&(1==job_manager_ajax_filters.is_rtl&&a('select[name^="search_categories"]').addClass("chosen-rtl"),a('select[name^="search_categories"]').chosen())});
1
+ jQuery(document).ready(function(a){function b(b,c){if($supports_html5_history){var e=b.find(".job_filters"),f=a(e).serialize(),g=a("div.job_listings").index(b);window.history.replaceState({id:"job_manager_state",page:c,data:f,index:g},"",d+"#s=1")}}var c=[];a(".job_listings").on("update_results",function(b,d,e,f){var g="",h=a(this),i=h.find(".job_filters"),j=h.find(".showing_jobs"),k=h.find(".job_listings"),l=h.data("per_page"),m=h.data("orderby"),n=h.data("order"),o=h.data("featured"),p=h.data("filled"),q=a("div.job_listings").index(this);if(c[q]&&c[q].abort(),e||(a(k).addClass("loading"),a("li.job_listing, li.no_job_listings_found",k).css("visibility","hidden"),d>1&&1!=h.data("show_pagination")?a(k).before('<a class="load_more_jobs load_previous" href="#"><strong>'+job_manager_ajax_filters.i18n_load_prev_listings+"</strong></a>"):h.find(".load_previous").remove(),h.find(".load_more_jobs").data("page",d)),1==h.data("show_filters")){var r=[];a(':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"], :input[name="filter_job_type"]',i).each(function(){r.push(a(this).val())});var s=i.find(":input[name^=search_categories], :input[name^=search_categories]").map(function(){return a(this).val()}).get(),t="",u="",v=i.find(":input[name=search_keywords]"),w=i.find(":input[name=search_location]");v.val()!==v.attr("placeholder")&&(t=v.val()),w.val()!==w.attr("placeholder")&&(u=w.val()),g={action:"job_manager_get_listings",search_keywords:t,search_location:u,search_categories:s,filter_job_type:r,per_page:l,orderby:m,order:n,page:d,featured:o,filled:p,show_pagination:h.data("show_pagination"),form_data:i.serialize()}}else{var s=h.data("categories"),t=h.data("keywords"),u=h.data("location");s&&(s=s.split(",")),g={action:"job_manager_get_listings",search_categories:s,search_keywords:t,search_location:u,per_page:l,orderby:m,order:n,page:d,featured:o,filled:p,show_pagination:h.data("show_pagination")}}c[q]=a.ajax({type:"GET",url:job_manager_ajax_filters.ajax_url,data:g,success:function(b){if(b)try{b.indexOf("<!--WPJM-->")>=0&&(b=b.split("<!--WPJM-->")[1]),b.indexOf("<!--WPJM_END-->")>=0&&(b=b.split("<!--WPJM_END-->")[0]);var c=a.parseJSON(b);c.showing?a(j).show().html("<span>"+c.showing+"</span>"+c.showing_links):a(j).hide(),c.showing_all?a(j).addClass("wp-job-manager-showing-all"):a(j).removeClass("wp-job-manager-showing-all"),c.html&&(e&&f?a(k).prepend(c.html):e?a(k).append(c.html):a(k).html(c.html)),1==h.data("show_pagination")?(h.find(".job-manager-pagination").remove(),c.pagination&&h.append(c.pagination)):(!c.found_jobs||c.max_num_pages<=d?a(".load_more_jobs:not(.load_previous)",h).hide():f||a(".load_more_jobs",h).show(),a(".load_more_jobs",h).removeClass("loading"),a("li.job_listing",k).css("visibility","visible")),a(k).removeClass("loading"),h.triggerHandler("updated_results",c)}catch(g){}}})}),a("#search_keywords, #search_location, .job_types :input, #search_categories").change(function(){var c=a(this).closest("div.job_listings");c.triggerHandler("update_results",[1,!1]),b(c,1)}).on("keyup",function(b){13===b.which&&a(this).trigger("change")}),a(".job_filters").on("click",".reset",function(){var c=a(this).closest("div.job_listings"),d=a(this).closest("form");return d.find(':input[name="search_keywords"]').not(':input[type="hidden"]').val(""),d.find(':input[name="search_location"]').not(':input[type="hidden"]').val(""),d.find(':input[name^="search_categories"]').not(':input[type="hidden"]').val(0).trigger("chosen:updated"),a(':input[name="filter_job_type[]"]',d).not(':input[type="hidden"]').attr("checked","checked"),c.triggerHandler("reset"),c.triggerHandler("update_results",[1,!1]),b(c,1),!1}),a("body").on("click",".load_more_jobs",function(){var c=a(this).closest("div.job_listings"),d=parseInt(a(this).data("page")||1),e=!1;return a(this).addClass("loading"),a(this).is(".load_previous")?(d-=1,e=!0,1===d?a(this).remove():a(this).data("page",d)):(d+=1,a(this).data("page",d),b(c,d)),c.triggerHandler("update_results",[d,!0,e]),!1}),a("div.job_listings").on("click",".job-manager-pagination a",function(){var c=a(this).closest("div.job_listings"),d=a(this).data("page");return b(c,d),c.triggerHandler("update_results",[d,!1]),a("body, html").animate({scrollTop:c.offset().top},600),!1}),a.isFunction(a.fn.chosen)&&(1==job_manager_ajax_filters.is_rtl&&a('select[name^="search_categories"]').addClass("chosen-rtl"),a('select[name^="search_categories"]').chosen({search_contains:!0})),$supports_html5_history=window.history&&window.history.pushState?!0:!1;var d=document.location.href.split("#")[0];a(window).on("load",function(){a(".job_filters").each(function(){var b=a(this).closest("div.job_listings"),c=b.find(".job_filters"),d=1,e=a("div.job_listings").index(b);if(window.history.state&&window.location.hash){var f=window.history.state;f.id&&"job_manager_state"===f.id&&e==f.index&&(d=f.page,c.deserialize(f.data),c.find(':input[name^="search_categories"]').not(':input[type="hidden"]').trigger("chosen:updated"))}b.triggerHandler("update_results",[d,!1])})})});
assets/js/job-submission.js CHANGED
@@ -1,5 +1,5 @@
1
  jQuery(document).ready(function($) {
2
- jQuery( '.job-manager-remove-uploaded-file' ).click(function() {
3
  jQuery(this).closest( '.job-manager-uploaded-file' ).remove();
4
  return false;
5
  });
1
  jQuery(document).ready(function($) {
2
+ jQuery('body').on( 'click', '.job-manager-remove-uploaded-file', function() {
3
  jQuery(this).closest( '.job-manager-uploaded-file' ).remove();
4
  return false;
5
  });
assets/js/job-submission.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(){jQuery(".job-manager-remove-uploaded-file").click(function(){return jQuery(this).closest(".job-manager-uploaded-file").remove(),!1})});
1
+ jQuery(document).ready(function(){jQuery("body").on("click",".job-manager-remove-uploaded-file",function(){return jQuery(this).closest(".job-manager-uploaded-file").remove(),!1})});
assets/js/jquery-deserialize/jquery.deserialize.js ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @author Kyle Florence <kyle[dot]florence[at]gmail[dot]com>
3
+ * @website https://github.com/kflorence/jquery-deserialize/
4
+ * @version 1.2.1
5
+ *
6
+ * Dual licensed under the MIT and GPLv2 licenses.
7
+ */
8
+ (function( jQuery, undefined ) {
9
+
10
+ var push = Array.prototype.push,
11
+ rcheck = /^(?:radio|checkbox)$/i,
12
+ rplus = /\+/g,
13
+ rselect = /^(?:option|select-one|select-multiple)$/i,
14
+ rvalue = /^(?:button|color|date|datetime|datetime-local|email|hidden|month|number|password|range|reset|search|submit|tel|text|textarea|time|url|week)$/i;
15
+
16
+ function getElements( elements ) {
17
+ return elements.map(function() {
18
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
19
+ }).filter( ":input:not(:disabled)" ).get();
20
+ }
21
+
22
+ function getElementsByName( elements ) {
23
+ var current,
24
+ elementsByName = {};
25
+
26
+ jQuery.each( elements, function( i, element ) {
27
+ current = elementsByName[ element.name ];
28
+ elementsByName[ element.name ] = current === undefined ? element :
29
+ ( jQuery.isArray( current ) ? current.concat( element ) : [ current, element ] );
30
+ });
31
+
32
+ return elementsByName;
33
+ }
34
+
35
+ jQuery.fn.deserialize = function( data, options ) {
36
+ var i, length,
37
+ elements = getElements( this ),
38
+ normalized = [];
39
+
40
+ if ( !data || !elements.length ) {
41
+ return this;
42
+ }
43
+
44
+ if ( jQuery.isArray( data ) ) {
45
+ normalized = data;
46
+
47
+ } else if ( jQuery.isPlainObject( data ) ) {
48
+ var key, value;
49
+
50
+ for ( key in data ) {
51
+ jQuery.isArray( value = data[ key ] ) ?
52
+ push.apply( normalized, jQuery.map( value, function( v ) {
53
+ return { name: key, value: v };
54
+ })) : push.call( normalized, { name: key, value: value } );
55
+ }
56
+
57
+ } else if ( typeof data === "string" ) {
58
+ var parts;
59
+
60
+ data = data.split( "&" );
61
+
62
+ for ( i = 0, length = data.length; i < length; i++ ) {
63
+ parts = data[ i ].split( "=" );
64
+ push.call( normalized, {
65
+ name: decodeURIComponent( parts[ 0 ].replace( rplus, "%20" ) ),
66
+ value: decodeURIComponent( parts[ 1 ].replace( rplus, "%20" ) )
67
+ });
68
+ }
69
+ }
70
+
71
+ if ( !( length = normalized.length ) ) {
72
+ return this;
73
+ }
74
+
75
+ var current, element, j, len, name, property, type, value,
76
+ change = jQuery.noop,
77
+ complete = jQuery.noop,
78
+ names = {};
79
+
80
+ options = options || {};
81
+ elements = getElementsByName( elements );
82
+
83
+ // Backwards compatible with old arguments: data, callback
84
+ if ( jQuery.isFunction( options ) ) {
85
+ complete = options;
86
+
87
+ } else {
88
+ change = jQuery.isFunction( options.change ) ? options.change : change;
89
+ complete = jQuery.isFunction( options.complete ) ? options.complete : complete;
90
+ }
91
+
92
+ for ( i = 0; i < length; i++ ) {
93
+ current = normalized[ i ];
94
+
95
+ name = current.name;
96
+ value = current.value;
97
+
98
+ if ( !( element = elements[ name ] ) ) {
99
+ continue;
100
+ }
101
+
102
+ type = ( len = element.length ) ? element[ 0 ] : element;
103
+ type = ( type.type || type.nodeName ).toLowerCase();
104
+ property = null;
105
+
106
+ if ( rvalue.test( type ) ) {
107
+ if ( len ) {
108
+ j = names[ name ];
109
+ element = element[ names[ name ] = ( j == undefined ) ? 0 : ++j ];
110
+ }
111
+
112
+ change.call( element, ( element.value = value ) );
113
+
114
+ } else if ( rcheck.test( type ) ) {
115
+ property = "checked";
116
+
117
+ } else if ( rselect.test( type ) ) {
118
+ property = "selected";
119
+ }
120
+
121
+ if ( property ) {
122
+ if ( !len ) {
123
+ element = [ element ];
124
+ len = 1;
125
+ }
126
+
127
+ for ( j = 0; j < len; j++ ) {
128
+ current = element[ j ];
129
+
130
+ if ( current.value == value ) {
131
+ change.call( current, ( current[ property ] = true ) && value );
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ complete.call( this );
138
+
139
+ return this;
140
+ };
141
+
142
+ })( jQuery );
assets/js/jquery-fileupload/jquery.fileupload.js ADDED
@@ -0,0 +1,1467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * jQuery File Upload Plugin 5.42.3
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2010, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* jshint nomen:false */
13
+ /* global define, require, window, document, location, Blob, FormData */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ 'jquery.ui.widget'
22
+ ], factory);
23
+ } else if (typeof exports === 'object') {
24
+ // Node/CommonJS:
25
+ factory(
26
+ require('jquery'),
27
+ require('./vendor/jquery.ui.widget')
28
+ );
29
+ } else {
30
+ // Browser globals:
31
+ factory(window.jQuery);
32
+ }
33
+ }(function ($) {
34
+ 'use strict';
35
+
36
+ // Detect file input support, based on
37
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
38
+ $.support.fileInput = !(new RegExp(
39
+ // Handle devices which give false positives for the feature detection:
40
+ '(Android (1\\.[0156]|2\\.[01]))' +
41
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
42
+ '|(w(eb)?OSBrowser)|(webOS)' +
43
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
44
+ ).test(window.navigator.userAgent) ||
45
+ // Feature detection for all other devices:
46
+ $('<input type="file">').prop('disabled'));
47
+
48
+ // The FileReader API is not actually used, but works as feature detection,
49
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
50
+ // but not non-multipart XHR file uploads.
51
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
52
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
53
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
54
+ $.support.xhrFormDataFileUpload = !!window.FormData;
55
+
56
+ // Detect support for Blob slicing (required for chunked uploads):
57
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
58
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
59
+
60
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
61
+ function getDragHandler(type) {
62
+ var isDragOver = type === 'dragover';
63
+ return function (e) {
64
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
65
+ var dataTransfer = e.dataTransfer;
66
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
67
+ this._trigger(
68
+ type,
69
+ $.Event(type, {delegatedEvent: e})
70
+ ) !== false) {
71
+ e.preventDefault();
72
+ if (isDragOver) {
73
+ dataTransfer.dropEffect = 'copy';
74
+ }
75
+ }
76
+ };
77
+ }
78
+
79
+ // The fileupload widget listens for change events on file input fields defined
80
+ // via fileInput setting and paste or drop events of the given dropZone.
81
+ // In addition to the default jQuery Widget methods, the fileupload widget
82
+ // exposes the "add" and "send" methods, to add or directly send files using
83
+ // the fileupload API.
84
+ // By default, files added via file input selection, paste, drag & drop or
85
+ // "add" method are uploaded immediately, but it is possible to override
86
+ // the "add" callback option to queue file uploads.
87
+ $.widget('blueimp.fileupload', {
88
+
89
+ options: {
90
+ // The drop target element(s), by the default the complete document.
91
+ // Set to null to disable drag & drop support:
92
+ dropZone: $(document),
93
+ // The paste target element(s), by the default undefined.
94
+ // Set to a DOM node or jQuery object to enable file pasting:
95
+ pasteZone: undefined,
96
+ // The file input field(s), that are listened to for change events.
97
+ // If undefined, it is set to the file input fields inside
98
+ // of the widget element on plugin initialization.
99
+ // Set to null to disable the change listener.
100
+ fileInput: undefined,
101
+ // By default, the file input field is replaced with a clone after
102
+ // each input field change event. This is required for iframe transport
103
+ // queues and allows change events to be fired for the same file
104
+ // selection, but can be disabled by setting the following option to false:
105
+ replaceFileInput: true,
106
+ // The parameter name for the file form data (the request argument name).
107
+ // If undefined or empty, the name property of the file input field is
108
+ // used, or "files[]" if the file input name property is also empty,
109
+ // can be a string or an array of strings:
110
+ paramName: undefined,
111
+ // By default, each file of a selection is uploaded using an individual
112
+ // request for XHR type uploads. Set to false to upload file
113
+ // selections in one request each:
114
+ singleFileUploads: true,
115
+ // To limit the number of files uploaded with one XHR request,
116
+ // set the following option to an integer greater than 0:
117
+ limitMultiFileUploads: undefined,
118
+ // The following option limits the number of files uploaded with one
119
+ // XHR request to keep the request size under or equal to the defined
120
+ // limit in bytes:
121
+ limitMultiFileUploadSize: undefined,
122
+ // Multipart file uploads add a number of bytes to each uploaded file,
123
+ // therefore the following option adds an overhead for each file used
124
+ // in the limitMultiFileUploadSize configuration:
125
+ limitMultiFileUploadSizeOverhead: 512,
126
+ // Set the following option to true to issue all file upload requests
127
+ // in a sequential order:
128
+ sequentialUploads: false,
129
+ // To limit the number of concurrent uploads,
130
+ // set the following option to an integer greater than 0:
131
+ limitConcurrentUploads: undefined,
132
+ // Set the following option to true to force iframe transport uploads:
133
+ forceIframeTransport: false,
134
+ // Set the following option to the location of a redirect url on the
135
+ // origin server, for cross-domain iframe transport uploads:
136
+ redirect: undefined,
137
+ // The parameter name for the redirect url, sent as part of the form
138
+ // data and set to 'redirect' if this option is empty:
139
+ redirectParamName: undefined,
140
+ // Set the following option to the location of a postMessage window,
141
+ // to enable postMessage transport uploads:
142
+ postMessage: undefined,
143
+ // By default, XHR file uploads are sent as multipart/form-data.
144
+ // The iframe transport is always using multipart/form-data.
145
+ // Set to false to enable non-multipart XHR uploads:
146
+ multipart: true,
147
+ // To upload large files in smaller chunks, set the following option
148
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
149
+ // or the browser does not support the required Blob API, files will
150
+ // be uploaded as a whole.
151
+ maxChunkSize: undefined,
152
+ // When a non-multipart upload or a chunked multipart upload has been
153
+ // aborted, this option can be used to resume the upload by setting
154
+ // it to the size of the already uploaded bytes. This option is most
155
+ // useful when modifying the options object inside of the "add" or
156
+ // "send" callbacks, as the options are cloned for each file upload.
157
+ uploadedBytes: undefined,
158
+ // By default, failed (abort or error) file uploads are removed from the
159
+ // global progress calculation. Set the following option to false to
160
+ // prevent recalculating the global progress data:
161
+ recalculateProgress: true,
162
+ // Interval in milliseconds to calculate and trigger progress events:
163
+ progressInterval: 100,
164
+ // Interval in milliseconds to calculate progress bitrate:
165
+ bitrateInterval: 500,
166
+ // By default, uploads are started automatically when adding files:
167
+ autoUpload: true,
168
+
169
+ // Error and info messages:
170
+ messages: {
171
+ uploadedBytes: 'Uploaded bytes exceed file size'
172
+ },
173
+
174
+ // Translation function, gets the message key to be translated
175
+ // and an object with context specific data as arguments:
176
+ i18n: function (message, context) {
177
+ message = this.messages[message] || message.toString();
178
+ if (context) {
179
+ $.each(context, function (key, value) {
180
+ message = message.replace('{' + key + '}', value);
181
+ });
182
+ }
183
+ return message;
184
+ },
185
+
186
+ // Additional form data to be sent along with the file uploads can be set
187
+ // using this option, which accepts an array of objects with name and
188
+ // value properties, a function returning such an array, a FormData
189
+ // object (for XHR file uploads), or a simple object.
190
+ // The form of the first fileInput is given as parameter to the function:
191
+ formData: function (form) {
192
+ return form.serializeArray();
193
+ },
194
+
195
+ // The add callback is invoked as soon as files are added to the fileupload
196
+ // widget (via file input selection, drag & drop, paste or add API call).
197
+ // If the singleFileUploads option is enabled, this callback will be
198
+ // called once for each file in the selection for XHR file uploads, else
199
+ // once for each file selection.
200
+ //
201
+ // The upload starts when the submit method is invoked on the data parameter.
202
+ // The data object contains a files property holding the added files
203
+ // and allows you to override plugin options as well as define ajax settings.
204
+ //
205
+ // Listeners for this callback can also be bound the following way:
206
+ // .bind('fileuploadadd', func);
207
+ //
208
+ // data.submit() returns a Promise object and allows to attach additional
209
+ // handlers using jQuery's Deferred callbacks:
210
+ // data.submit().done(func).fail(func).always(func);
211
+ add: function (e, data) {
212
+ if (e.isDefaultPrevented()) {
213
+ return false;
214
+ }
215
+ if (data.autoUpload || (data.autoUpload !== false &&
216
+ $(this).fileupload('option', 'autoUpload'))) {
217
+ data.process().done(function () {
218
+ data.submit();
219
+ });
220
+ }
221
+ },
222
+
223
+ // Other callbacks:
224
+
225
+ // Callback for the submit event of each file upload:
226
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
227
+
228
+ // Callback for the start of each file upload request:
229
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
230
+
231
+ // Callback for successful uploads:
232
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
233
+
234
+ // Callback for failed (abort or error) uploads:
235
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
236
+
237
+ // Callback for completed (success, abort or error) requests:
238
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
239
+
240
+ // Callback for upload progress events:
241
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
242
+
243
+ // Callback for global upload progress events:
244
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
245
+
246
+ // Callback for uploads start, equivalent to the global ajaxStart event:
247
+ // start: function (e) {}, // .bind('fileuploadstart', func);
248
+
249
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
250
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
251
+
252
+ // Callback for change events of the fileInput(s):
253
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
254
+
255
+ // Callback for paste events to the pasteZone(s):
256
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
257
+
258
+ // Callback for drop events of the dropZone(s):
259
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
260
+
261
+ // Callback for dragover events of the dropZone(s):
262
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
263
+
264
+ // Callback for the start of each chunk upload request:
265
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
266
+
267
+ // Callback for successful chunk uploads:
268
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
269
+
270
+ // Callback for failed (abort or error) chunk uploads:
271
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
272
+
273
+ // Callback for completed (success, abort or error) chunk upload requests:
274
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
275
+
276
+ // The plugin options are used as settings object for the ajax calls.
277
+ // The following are jQuery ajax settings required for the file uploads:
278
+ processData: false,
279
+ contentType: false,
280
+ cache: false
281
+ },
282
+
283
+ // A list of options that require reinitializing event listeners and/or
284
+ // special initialization code:
285
+ _specialOptions: [
286
+ 'fileInput',
287
+ 'dropZone',
288
+ 'pasteZone',
289
+ 'multipart',
290
+ 'forceIframeTransport'
291
+ ],
292
+
293
+ _blobSlice: $.support.blobSlice && function () {
294
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
295
+ return slice.apply(this, arguments);
296
+ },
297
+
298
+ _BitrateTimer: function () {
299
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
300
+ this.loaded = 0;
301
+ this.bitrate = 0;
302
+ this.getBitrate = function (now, loaded, interval) {
303
+ var timeDiff = now - this.timestamp;
304
+ if (!this.bitrate || !interval || timeDiff > interval) {
305
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
306
+ this.loaded = loaded;
307
+ this.timestamp = now;
308
+ }
309
+ return this.bitrate;
310
+ };
311
+ },
312
+
313
+ _isXHRUpload: function (options) {
314
+ return !options.forceIframeTransport &&
315
+ ((!options.multipart && $.support.xhrFileUpload) ||
316
+ $.support.xhrFormDataFileUpload);
317
+ },
318
+
319
+ _getFormData: function (options) {
320
+ var formData;
321
+ if ($.type(options.formData) === 'function') {
322
+ return options.formData(options.form);
323
+ }
324
+ if ($.isArray(options.formData)) {
325
+ return options.formData;
326
+ }
327
+ if ($.type(options.formData) === 'object') {
328
+ formData = [];
329
+ $.each(options.formData, function (name, value) {
330
+ formData.push({name: name, value: value});
331
+ });
332
+ return formData;
333
+ }
334
+ return [];
335
+ },
336
+
337
+ _getTotal: function (files) {
338
+ var total = 0;
339
+ $.each(files, function (index, file) {
340
+ total += file.size || 1;
341
+ });
342
+ return total;
343
+ },
344
+
345
+ _initProgressObject: function (obj) {
346
+ var progress = {
347
+ loaded: 0,
348
+ total: 0,
349
+ bitrate: 0
350
+ };
351
+ if (obj._progress) {
352
+ $.extend(obj._progress, progress);
353
+ } else {
354
+ obj._progress = progress;
355
+ }
356
+ },
357
+
358
+ _initResponseObject: function (obj) {
359
+ var prop;
360
+ if (obj._response) {
361
+ for (prop in obj._response) {
362
+ if (obj._response.hasOwnProperty(prop)) {
363
+ delete obj._response[prop];
364
+ }
365
+ }
366
+ } else {
367
+ obj._response = {};
368
+ }
369
+ },
370
+
371
+ _onProgress: function (e, data) {
372
+ if (e.lengthComputable) {
373
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
374
+ loaded;
375
+ if (data._time && data.progressInterval &&
376
+ (now - data._time < data.progressInterval) &&
377
+ e.loaded !== e.total) {
378
+ return;
379
+ }
380
+ data._time = now;
381
+ loaded = Math.floor(
382
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
383
+ ) + (data.uploadedBytes || 0);
384
+ // Add the difference from the previously loaded state
385
+ // to the global loaded counter:
386
+ this._progress.loaded += (loaded - data._progress.loaded);
387
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
388
+ now,
389
+ this._progress.loaded,
390
+ data.bitrateInterval
391
+ );
392
+ data._progress.loaded = data.loaded = loaded;
393
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
394
+ now,
395
+ loaded,
396
+ data.bitrateInterval
397
+ );
398
+ // Trigger a custom progress event with a total data property set
399
+ // to the file size(s) of the current upload and a loaded data
400
+ // property calculated accordingly:
401
+ this._trigger(
402
+ 'progress',
403
+ $.Event('progress', {delegatedEvent: e}),
404
+ data
405
+ );
406
+ // Trigger a global progress event for all current file uploads,
407
+ // including ajax calls queued for sequential file uploads:
408
+ this._trigger(
409
+ 'progressall',
410
+ $.Event('progressall', {delegatedEvent: e}),
411
+ this._progress
412
+ );
413
+ }
414
+ },
415
+
416
+ _initProgressListener: function (options) {
417
+ var that = this,
418
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
419
+ // Accesss to the native XHR object is required to add event listeners
420
+ // for the upload progress event:
421
+ if (xhr.upload) {
422
+ $(xhr.upload).bind('progress', function (e) {
423
+ var oe = e.originalEvent;
424
+ // Make sure the progress event properties get copied over:
425
+ e.lengthComputable = oe.lengthComputable;
426
+ e.loaded = oe.loaded;
427
+ e.total = oe.total;
428
+ that._onProgress(e, options);
429
+ });
430
+ options.xhr = function () {
431
+ return xhr;
432
+ };
433
+ }
434
+ },
435
+
436
+ _isInstanceOf: function (type, obj) {
437
+ // Cross-frame instanceof check
438
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
439
+ },
440
+
441
+ _initXHRData: function (options) {
442
+ var that = this,
443
+ formData,
444
+ file = options.files[0],
445
+ // Ignore non-multipart setting if not supported:
446
+ multipart = options.multipart || !$.support.xhrFileUpload,
447
+ paramName = $.type(options.paramName) === 'array' ?
448
+ options.paramName[0] : options.paramName;
449
+ options.headers = $.extend({}, options.headers);
450
+ if (options.contentRange) {
451
+ options.headers['Content-Range'] = options.contentRange;
452
+ }
453
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
454
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
455
+ encodeURI(file.name) + '"';
456
+ }
457
+ if (!multipart) {
458
+ options.contentType = file.type || 'application/octet-stream';
459
+ options.data = options.blob || file;
460
+ } else if ($.support.xhrFormDataFileUpload) {
461
+ if (options.postMessage) {
462
+ // window.postMessage does not allow sending FormData
463
+ // objects, so we just add the File/Blob objects to
464
+ // the formData array and let the postMessage window
465
+ // create the FormData object out of this array:
466
+ formData = this._getFormData(options);
467
+ if (options.blob) {
468
+ formData.push({
469
+ name: paramName,
470
+ value: options.blob
471
+ });
472
+ } else {
473
+ $.each(options.files, function (index, file) {
474
+ formData.push({
475
+ name: ($.type(options.paramName) === 'array' &&
476
+ options.paramName[index]) || paramName,
477
+ value: file
478
+ });
479
+ });
480
+ }
481
+ } else {
482
+ if (that._isInstanceOf('FormData', options.formData)) {
483
+ formData = options.formData;
484
+ } else {
485
+ formData = new FormData();
486
+ $.each(this._getFormData(options), function (index, field) {
487
+ formData.append(field.name, field.value);
488
+ });
489
+ }
490
+ if (options.blob) {
491
+ formData.append(paramName, options.blob, file.name);
492
+ } else {
493
+ $.each(options.files, function (index, file) {
494
+ // This check allows the tests to run with
495
+ // dummy objects:
496
+ if (that._isInstanceOf('File', file) ||
497
+ that._isInstanceOf('Blob', file)) {
498
+ formData.append(
499
+ ($.type(options.paramName) === 'array' &&
500
+ options.paramName[index]) || paramName,
501
+ file,
502
+ file.uploadName || file.name
503
+ );
504
+ }
505
+ });
506
+ }
507
+ }
508
+ options.data = formData;
509
+ }
510
+ // Blob reference is not needed anymore, free memory:
511
+ options.blob = null;
512
+ },
513
+
514
+ _initIframeSettings: function (options) {
515
+ var targetHost = $('<a></a>').prop('href', options.url).prop('host');
516
+ // Setting the dataType to iframe enables the iframe transport:
517
+ options.dataType = 'iframe ' + (options.dataType || '');
518
+ // The iframe transport accepts a serialized array as form data:
519
+ options.formData = this._getFormData(options);
520
+ // Add redirect url to form data on cross-domain uploads:
521
+ if (options.redirect && targetHost && targetHost !== location.host) {
522
+ options.formData.push({
523
+ name: options.redirectParamName || 'redirect',
524
+ value: options.redirect
525
+ });
526
+ }
527
+ },
528
+
529
+ _initDataSettings: function (options) {
530
+ if (this._isXHRUpload(options)) {
531
+ if (!this._chunkedUpload(options, true)) {
532
+ if (!options.data) {
533
+ this._initXHRData(options);
534
+ }
535
+ this._initProgressListener(options);
536
+ }
537
+ if (options.postMessage) {
538
+ // Setting the dataType to postmessage enables the
539
+ // postMessage transport:
540
+ options.dataType = 'postmessage ' + (options.dataType || '');
541
+ }
542
+ } else {
543
+ this._initIframeSettings(options);
544
+ }
545
+ },
546
+
547
+ _getParamName: function (options) {
548
+ var fileInput = $(options.fileInput),
549
+ paramName = options.paramName;
550
+ if (!paramName) {
551
+ paramName = [];
552
+ fileInput.each(function () {
553
+ var input = $(this),
554
+ name = input.prop('name') || 'files[]',
555
+ i = (input.prop('files') || [1]).length;
556
+ while (i) {
557
+ paramName.push(name);
558
+ i -= 1;
559
+ }
560
+ });
561
+ if (!paramName.length) {
562
+ paramName = [fileInput.prop('name') || 'files[]'];
563
+ }
564
+ } else if (!$.isArray(paramName)) {
565
+ paramName = [paramName];
566
+ }
567
+ return paramName;
568
+ },
569
+
570
+ _initFormSettings: function (options) {
571
+ // Retrieve missing options from the input field and the
572
+ // associated form, if available:
573
+ if (!options.form || !options.form.length) {
574
+ options.form = $(options.fileInput.prop('form'));
575
+ // If the given file input doesn't have an associated form,
576
+ // use the default widget file input's form:
577
+ if (!options.form.length) {
578
+ options.form = $(this.options.fileInput.prop('form'));
579
+ }
580
+ }
581
+ options.paramName = this._getParamName(options);
582
+ if (!options.url) {
583
+ options.url = options.form.prop('action') || location.href;
584
+ }
585
+ // The HTTP request method must be "POST" or "PUT":
586
+ options.type = (options.type ||
587
+ ($.type(options.form.prop('method')) === 'string' &&
588
+ options.form.prop('method')) || ''
589
+ ).toUpperCase();
590
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
591
+ options.type !== 'PATCH') {
592
+ options.type = 'POST';
593
+ }
594
+ if (!options.formAcceptCharset) {
595
+ options.formAcceptCharset = options.form.attr('accept-charset');
596
+ }
597
+ },
598
+
599
+ _getAJAXSettings: function (data) {
600
+ var options = $.extend({}, this.options, data);
601
+ this._initFormSettings(options);
602
+ this._initDataSettings(options);
603
+ return options;
604
+ },
605
+
606
+ // jQuery 1.6 doesn't provide .state(),
607
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
608
+ _getDeferredState: function (deferred) {
609
+ if (deferred.state) {
610
+ return deferred.state();
611
+ }
612
+ if (deferred.isResolved()) {
613
+ return 'resolved';
614
+ }
615
+ if (deferred.isRejected()) {
616
+ return 'rejected';
617
+ }
618
+ return 'pending';
619
+ },
620
+
621
+ // Maps jqXHR callbacks to the equivalent
622
+ // methods of the given Promise object:
623
+ _enhancePromise: function (promise) {
624
+ promise.success = promise.done;
625
+ promise.error = promise.fail;
626
+ promise.complete = promise.always;
627
+ return promise;
628
+ },
629
+
630
+ // Creates and returns a Promise object enhanced with
631
+ // the jqXHR methods abort, success, error and complete:
632
+ _getXHRPromise: function (resolveOrReject, context, args) {
633
+ var dfd = $.Deferred(),
634
+ promise = dfd.promise();
635
+ context = context || this.options.context || promise;
636
+ if (resolveOrReject === true) {
637
+ dfd.resolveWith(context, args);
638
+ } else if (resolveOrReject === false) {
639
+ dfd.rejectWith(context, args);
640
+ }
641
+ promise.abort = dfd.promise;
642
+ return this._enhancePromise(promise);
643
+ },
644
+
645
+ // Adds convenience methods to the data callback argument:
646
+ _addConvenienceMethods: function (e, data) {
647
+ var that = this,
648
+ getPromise = function (args) {
649
+ return $.Deferred().resolveWith(that, args).promise();
650
+ };
651
+ data.process = function (resolveFunc, rejectFunc) {
652
+ if (resolveFunc || rejectFunc) {
653
+ data._processQueue = this._processQueue =
654
+ (this._processQueue || getPromise([this])).pipe(
655
+ function () {
656
+ if (data.errorThrown) {
657
+ return $.Deferred()
658
+ .rejectWith(that, [data]).promise();
659
+ }
660
+ return getPromise(arguments);
661
+ }
662
+ ).pipe(resolveFunc, rejectFunc);
663
+ }
664
+ return this._processQueue || getPromise([this]);
665
+ };
666
+ data.submit = function () {
667
+ if (this.state() !== 'pending') {
668
+ data.jqXHR = this.jqXHR =
669
+ (that._trigger(
670
+ 'submit',
671
+ $.Event('submit', {delegatedEvent: e}),
672
+ this
673
+ ) !== false) && that._onSend(e, this);
674
+ }
675
+ return this.jqXHR || that._getXHRPromise();
676
+ };
677
+ data.abort = function () {
678
+ if (this.jqXHR) {
679
+ return this.jqXHR.abort();
680
+ }
681
+ this.errorThrown = 'abort';
682
+ that._trigger('fail', null, this);
683
+ return that._getXHRPromise(false);
684
+ };
685
+ data.state = function () {
686
+ if (this.jqXHR) {
687
+ return that._getDeferredState(this.jqXHR);
688
+ }
689
+ if (this._processQueue) {
690
+ return that._getDeferredState(this._processQueue);
691
+ }
692
+ };
693
+ data.processing = function () {
694
+ return !this.jqXHR && this._processQueue && that
695
+ ._getDeferredState(this._processQueue) === 'pending';
696
+ };
697
+ data.progress = function () {
698
+ return this._progress;
699
+ };
700
+ data.response = function () {
701
+ return this._response;
702
+ };
703
+ },
704
+
705
+ // Parses the Range header from the server response
706
+ // and returns the uploaded bytes:
707
+ _getUploadedBytes: function (jqXHR) {
708
+ var range = jqXHR.getResponseHeader('Range'),
709
+ parts = range && range.split('-'),
710
+ upperBytesPos = parts && parts.length > 1 &&
711
+ parseInt(parts[1], 10);
712
+ return upperBytesPos && upperBytesPos + 1;
713
+ },
714
+
715
+ // Uploads a file in multiple, sequential requests
716
+ // by splitting the file up in multiple blob chunks.
717
+ // If the second parameter is true, only tests if the file
718
+ // should be uploaded in chunks, but does not invoke any
719
+ // upload requests:
720
+ _chunkedUpload: function (options, testOnly) {
721
+ options.uploadedBytes = options.uploadedBytes || 0;
722
+ var that = this,
723
+ file = options.files[0],
724
+ fs = file.size,
725
+ ub = options.uploadedBytes,
726
+ mcs = options.maxChunkSize || fs,
727
+ slice = this._blobSlice,
728
+ dfd = $.Deferred(),
729
+ promise = dfd.promise(),
730
+ jqXHR,
731
+ upload;
732
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
733
+ options.data) {
734
+ return false;
735
+ }
736
+ if (testOnly) {
737
+ return true;
738
+ }
739
+ if (ub >= fs) {
740
+ file.error = options.i18n('uploadedBytes');
741
+ return this._getXHRPromise(
742
+ false,
743
+ options.context,
744
+ [null, 'error', file.error]
745
+ );
746
+ }
747
+ // The chunk upload method:
748
+ upload = function () {
749
+ // Clone the options object for each chunk upload:
750
+ var o = $.extend({}, options),
751
+ currentLoaded = o._progress.loaded;
752
+ o.blob = slice.call(
753
+ file,
754
+ ub,
755
+ ub + mcs,
756
+ file.type
757
+ );
758
+ // Store the current chunk size, as the blob itself
759
+ // will be dereferenced after data processing:
760
+ o.chunkSize = o.blob.size;
761
+ // Expose the chunk bytes position range:
762
+ o.contentRange = 'bytes ' + ub + '-' +
763
+ (ub + o.chunkSize - 1) + '/' + fs;
764
+ // Process the upload data (the blob and potential form data):
765
+ that._initXHRData(o);
766
+ // Add progress listeners for this chunk upload:
767
+ that._initProgressListener(o);
768
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
769
+ that._getXHRPromise(false, o.context))
770
+ .done(function (result, textStatus, jqXHR) {
771
+ ub = that._getUploadedBytes(jqXHR) ||
772
+ (ub + o.chunkSize);
773
+ // Create a progress event if no final progress event
774
+ // with loaded equaling total has been triggered
775
+ // for this chunk:
776
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
777
+ that._onProgress($.Event('progress', {
778
+ lengthComputable: true,
779
+ loaded: ub - o.uploadedBytes,
780
+ total: ub - o.uploadedBytes
781
+ }), o);
782
+ }
783
+ options.uploadedBytes = o.uploadedBytes = ub;
784
+ o.result = result;
785
+ o.textStatus = textStatus;
786
+ o.jqXHR = jqXHR;
787
+ that._trigger('chunkdone', null, o);
788
+ that._trigger('chunkalways', null, o);
789
+ if (ub < fs) {
790
+ // File upload not yet complete,
791
+ // continue with the next chunk:
792
+ upload();
793
+ } else {
794
+ dfd.resolveWith(
795
+ o.context,
796
+ [result, textStatus, jqXHR]
797
+ );
798
+ }
799
+ })
800
+ .fail(function (jqXHR, textStatus, errorThrown) {
801
+ o.jqXHR = jqXHR;
802
+ o.textStatus = textStatus;
803
+ o.errorThrown = errorThrown;
804
+ that._trigger('chunkfail', null, o);
805
+ that._trigger('chunkalways', null, o);
806
+ dfd.rejectWith(
807
+ o.context,
808
+ [jqXHR, textStatus, errorThrown]
809
+ );
810
+ });
811
+ };
812
+ this._enhancePromise(promise);
813
+ promise.abort = function () {
814
+ return jqXHR.abort();
815
+ };
816
+ upload();
817
+ return promise;
818
+ },
819
+
820
+ _beforeSend: function (e, data) {
821
+ if (this._active === 0) {
822
+ // the start callback is triggered when an upload starts
823
+ // and no other uploads are currently running,
824
+ // equivalent to the global ajaxStart event:
825
+ this._trigger('start');
826
+ // Set timer for global bitrate progress calculation:
827
+ this._bitrateTimer = new this._BitrateTimer();
828
+ // Reset the global progress values:
829
+ this._progress.loaded = this._progress.total = 0;
830
+ this._progress.bitrate = 0;
831
+ }
832
+ // Make sure the container objects for the .response() and
833
+ // .progress() methods on the data object are available
834
+ // and reset to their initial state:
835
+ this._initResponseObject(data);
836
+ this._initProgressObject(data);
837
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
838
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
839
+ data._progress.bitrate = data.bitrate = 0;
840
+ this._active += 1;
841
+ // Initialize the global progress values:
842
+ this._progress.loaded += data.loaded;
843
+ this._progress.total += data.total;
844
+ },
845
+
846
+ _onDone: function (result, textStatus, jqXHR, options) {
847
+ var total = options._progress.total,
848
+ response = options._response;
849
+ if (options._progress.loaded < total) {
850
+ // Create a progress event if no final progress event
851
+ // with loaded equaling total has been triggered:
852
+ this._onProgress($.Event('progress', {
853
+ lengthComputable: true,
854
+ loaded: total,
855
+ total: total
856
+ }), options);
857
+ }
858
+ response.result = options.result = result;
859
+ response.textStatus = options.textStatus = textStatus;
860
+ response.jqXHR = options.jqXHR = jqXHR;
861
+ this._trigger('done', null, options);
862
+ },
863
+
864
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
865
+ var response = options._response;
866
+ if (options.recalculateProgress) {
867
+ // Remove the failed (error or abort) file upload from
868
+ // the global progress calculation:
869
+ this._progress.loaded -= options._progress.loaded;
870
+ this._progress.total -= options._progress.total;
871
+ }
872
+ response.jqXHR = options.jqXHR = jqXHR;
873
+ response.textStatus = options.textStatus = textStatus;
874
+ response.errorThrown = options.errorThrown = errorThrown;
875
+ this._trigger('fail', null, options);
876
+ },
877
+
878
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
879
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
880
+ // options object via done and fail callbacks
881
+ this._trigger('always', null, options);
882
+ },
883
+
884
+ _onSend: function (e, data) {
885
+ if (!data.submit) {
886
+ this._addConvenienceMethods(e, data);
887
+ }
888
+ var that = this,
889
+ jqXHR,
890
+ aborted,
891
+ slot,
892
+ pipe,
893
+ options = that._getAJAXSettings(data),
894
+ send = function () {
895
+ that._sending += 1;
896
+ // Set timer for bitrate progress calculation:
897
+ options._bitrateTimer = new that._BitrateTimer();
898
+ jqXHR = jqXHR || (
899
+ ((aborted || that._trigger(
900
+ 'send',
901
+ $.Event('send', {delegatedEvent: e}),
902
+ options
903
+ ) === false) &&
904
+ that._getXHRPromise(false, options.context, aborted)) ||
905
+ that._chunkedUpload(options) || $.ajax(options)
906
+ ).done(function (result, textStatus, jqXHR) {
907
+ that._onDone(result, textStatus, jqXHR, options);
908
+ }).fail(function (jqXHR, textStatus, errorThrown) {
909
+ that._onFail(jqXHR, textStatus, errorThrown, options);
910
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
911
+ that._onAlways(
912
+ jqXHRorResult,
913
+ textStatus,
914
+ jqXHRorError,
915
+ options
916
+ );
917
+ that._sending -= 1;
918
+ that._active -= 1;
919
+ if (options.limitConcurrentUploads &&
920
+ options.limitConcurrentUploads > that._sending) {
921
+ // Start the next queued upload,
922
+ // that has not been aborted:
923
+ var nextSlot = that._slots.shift();
924
+ while (nextSlot) {
925
+ if (that._getDeferredState(nextSlot) === 'pending') {
926
+ nextSlot.resolve();
927
+ break;
928
+ }
929
+ nextSlot = that._slots.shift();
930
+ }
931
+ }
932
+ if (that._active === 0) {
933
+ // The stop callback is triggered when all uploads have
934
+ // been completed, equivalent to the global ajaxStop event:
935
+ that._trigger('stop');
936
+ }
937
+ });
938
+ return jqXHR;
939
+ };
940
+ this._beforeSend(e, options);
941
+ if (this.options.sequentialUploads ||
942
+ (this.options.limitConcurrentUploads &&
943
+ this.options.limitConcurrentUploads <= this._sending)) {
944
+ if (this.options.limitConcurrentUploads > 1) {
945
+ slot = $.Deferred();
946
+ this._slots.push(slot);
947
+ pipe = slot.pipe(send);
948
+ } else {
949
+ this._sequence = this._sequence.pipe(send, send);
950
+ pipe = this._sequence;
951
+ }
952
+ // Return the piped Promise object, enhanced with an abort method,
953
+ // which is delegated to the jqXHR object of the current upload,
954
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
955
+ pipe.abort = function () {
956
+ aborted = [undefined, 'abort', 'abort'];
957
+ if (!jqXHR) {
958
+ if (slot) {
959
+ slot.rejectWith(options.context, aborted);
960
+ }
961
+ return send();
962
+ }
963
+ return jqXHR.abort();
964
+ };
965
+ return this._enhancePromise(pipe);
966
+ }
967
+ return send();
968
+ },
969
+
970
+ _onAdd: function (e, data) {
971
+ var that = this,
972
+ result = true,
973
+ options = $.extend({}, this.options, data),
974
+ files = data.files,
975
+ filesLength = files.length,
976
+ limit = options.limitMultiFileUploads,
977
+ limitSize = options.limitMultiFileUploadSize,
978
+ overhead = options.limitMultiFileUploadSizeOverhead,
979
+ batchSize = 0,
980
+ paramName = this._getParamName(options),
981
+ paramNameSet,
982
+ paramNameSlice,
983
+ fileSet,
984
+ i,
985
+ j = 0;
986
+ if (limitSize && (!filesLength || files[0].size === undefined)) {
987
+ limitSize = undefined;
988
+ }
989
+ if (!(options.singleFileUploads || limit || limitSize) ||
990
+ !this._isXHRUpload(options)) {
991
+ fileSet = [files];
992
+ paramNameSet = [paramName];
993
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
994
+ fileSet = [];
995
+ paramNameSet = [];
996
+ for (i = 0; i < filesLength; i += limit) {
997
+ fileSet.push(files.slice(i, i + limit));
998
+ paramNameSlice = paramName.slice(i, i + limit);
999
+ if (!paramNameSlice.length) {
1000
+ paramNameSlice = paramName;
1001
+ }
1002
+ paramNameSet.push(paramNameSlice);
1003
+ }
1004
+ } else if (!options.singleFileUploads && limitSize) {
1005
+ fileSet = [];
1006
+ paramNameSet = [];
1007
+ for (i = 0; i < filesLength; i = i + 1) {
1008
+ batchSize += files[i].size + overhead;
1009
+ if (i + 1 === filesLength ||
1010
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1011
+ (limit && i + 1 - j >= limit)) {
1012
+ fileSet.push(files.slice(j, i + 1));
1013
+ paramNameSlice = paramName.slice(j, i + 1);
1014
+ if (!paramNameSlice.length) {
1015
+ paramNameSlice = paramName;
1016
+ }
1017
+ paramNameSet.push(paramNameSlice);
1018
+ j = i + 1;
1019
+ batchSize = 0;
1020
+ }
1021
+ }
1022
+ } else {
1023
+ paramNameSet = paramName;
1024
+ }
1025
+ data.originalFiles = files;
1026
+ $.each(fileSet || files, function (index, element) {
1027
+ var newData = $.extend({}, data);
1028
+ newData.files = fileSet ? element : [element];
1029
+ newData.paramName = paramNameSet[index];
1030
+ that._initResponseObject(newData);
1031
+ that._initProgressObject(newData);
1032
+ that._addConvenienceMethods(e, newData);
1033
+ result = that._trigger(
1034
+ 'add',
1035
+ $.Event('add', {delegatedEvent: e}),
1036
+ newData
1037
+ );
1038
+ return result;
1039
+ });
1040
+ return result;
1041
+ },
1042
+
1043
+ _replaceFileInput: function (data) {
1044
+ var input = data.fileInput,
1045
+ inputClone = input.clone(true);
1046
+ // Add a reference for the new cloned file input to the data argument:
1047
+ data.fileInputClone = inputClone;
1048
+ $('<form></form>').append(inputClone)[0].reset();
1049
+ // Detaching allows to insert the fileInput on another form
1050
+ // without loosing the file input value:
1051
+ input.after(inputClone).detach();
1052
+ // Avoid memory leaks with the detached file input:
1053
+ $.cleanData(input.unbind('remove'));
1054
+ // Replace the original file input element in the fileInput
1055
+ // elements set with the clone, which has been copied including
1056
+ // event handlers:
1057
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
1058
+ if (el === input[0]) {
1059
+ return inputClone[0];
1060
+ }
1061
+ return el;
1062
+ });
1063
+ // If the widget has been initialized on the file input itself,
1064
+ // override this.element with the file input clone:
1065
+ if (input[0] === this.element[0]) {
1066
+ this.element = inputClone;
1067
+ }
1068
+ },
1069
+
1070
+ _handleFileTreeEntry: function (entry, path) {
1071
+ var that = this,
1072
+ dfd = $.Deferred(),
1073
+ errorHandler = function (e) {
1074
+ if (e && !e.entry) {
1075
+ e.entry = entry;
1076
+ }
1077
+ // Since $.when returns immediately if one
1078
+ // Deferred is rejected, we use resolve instead.
1079
+ // This allows valid files and invalid items
1080
+ // to be returned together in one set:
1081
+ dfd.resolve([e]);
1082
+ },
1083
+ successHandler = function (entries) {
1084
+ that._handleFileTreeEntries(
1085
+ entries,
1086
+ path + entry.name + '/'
1087
+ ).done(function (files) {
1088
+ dfd.resolve(files);
1089
+ }).fail(errorHandler);
1090
+ },
1091
+ readEntries = function () {
1092
+ dirReader.readEntries(function (results) {
1093
+ if (!results.length) {
1094
+ successHandler(entries);
1095
+ } else {
1096
+ entries = entries.concat(results);
1097
+ readEntries();
1098
+ }
1099
+ }, errorHandler);
1100
+ },
1101
+ dirReader, entries = [];
1102
+ path = path || '';
1103
+ if (entry.isFile) {
1104
+ if (entry._file) {
1105
+ // Workaround for Chrome bug #149735
1106
+ entry._file.relativePath = path;
1107
+ dfd.resolve(entry._file);
1108
+ } else {
1109
+ entry.file(function (file) {
1110
+ file.relativePath = path;
1111
+ dfd.resolve(file);
1112
+ }, errorHandler);
1113
+ }
1114
+ } else if (entry.isDirectory) {
1115
+ dirReader = entry.createReader();
1116
+ readEntries();
1117
+ } else {
1118
+ // Return an empy list for file system items
1119
+ // other than files or directories:
1120
+ dfd.resolve([]);
1121
+ }
1122
+ return dfd.promise();
1123
+ },
1124
+
1125
+ _handleFileTreeEntries: function (entries, path) {
1126
+ var that = this;
1127
+ return $.when.apply(
1128
+ $,
1129
+ $.map(entries, function (entry) {
1130
+ return that._handleFileTreeEntry(entry, path);
1131
+ })
1132
+ ).pipe(function () {
1133
+ return Array.prototype.concat.apply(
1134
+ [],
1135
+ arguments
1136
+ );
1137
+ });
1138
+ },
1139
+
1140
+ _getDroppedFiles: function (dataTransfer) {
1141
+ dataTransfer = dataTransfer || {};
1142
+ var items = dataTransfer.items;
1143
+ if (items && items.length && (items[0].webkitGetAsEntry ||
1144
+ items[0].getAsEntry)) {
1145
+ return this._handleFileTreeEntries(
1146
+ $.map(items, function (item) {
1147
+ var entry;
1148
+ if (item.webkitGetAsEntry) {
1149
+ entry = item.webkitGetAsEntry();
1150
+ if (entry) {
1151
+ // Workaround for Chrome bug #149735:
1152
+ entry._file = item.getAsFile();
1153
+ }
1154
+ return entry;
1155
+ }
1156
+ return item.getAsEntry();
1157
+ })
1158
+ );
1159
+ }
1160
+ return $.Deferred().resolve(
1161
+ $.makeArray(dataTransfer.files)
1162
+ ).promise();
1163
+ },
1164
+
1165
+ _getSingleFileInputFiles: function (fileInput) {
1166
+ fileInput = $(fileInput);
1167
+ var entries = fileInput.prop('webkitEntries') ||
1168
+ fileInput.prop('entries'),
1169
+ files,
1170
+ value;
1171
+ if (entries && entries.length) {
1172
+ return this._handleFileTreeEntries(entries);
1173
+ }
1174
+ files = $.makeArray(fileInput.prop('files'));
1175
+ if (!files.length) {
1176
+ value = fileInput.prop('value');
1177
+ if (!value) {
1178
+ return $.Deferred().resolve([]).promise();
1179
+ }
1180
+ // If the files property is not available, the browser does not
1181
+ // support the File API and we add a pseudo File object with
1182
+ // the input value as name with path information removed:
1183
+ files = [{name: value.replace(/^.*\\/, '')}];
1184
+ } else if (files[0].name === undefined && files[0].fileName) {
1185
+ // File normalization for Safari 4 and Firefox 3:
1186
+ $.each(files, function (index, file) {
1187
+ file.name = file.fileName;
1188
+ file.size = file.fileSize;
1189
+ });
1190
+ }
1191
+ return $.Deferred().resolve(files).promise();
1192
+ },
1193
+
1194
+ _getFileInputFiles: function (fileInput) {
1195
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
1196
+ return this._getSingleFileInputFiles(fileInput);
1197
+ }
1198
+ return $.when.apply(
1199
+ $,
1200
+ $.map(fileInput, this._getSingleFileInputFiles)
1201
+ ).pipe(function () {
1202
+ return Array.prototype.concat.apply(
1203
+ [],
1204
+ arguments
1205
+ );
1206
+ });
1207
+ },
1208
+
1209
+ _onChange: function (e) {
1210
+ var that = this,
1211
+ data = {
1212
+ fileInput: $(e.target),
1213
+ form: $(e.target.form)
1214
+ };
1215
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1216
+ data.files = files;
1217
+ if (that.options.replaceFileInput) {
1218
+ that._replaceFileInput(data);
1219
+ }
1220
+ if (that._trigger(
1221
+ 'change',
1222
+ $.Event('change', {delegatedEvent: e}),
1223
+ data
1224
+ ) !== false) {
1225
+ that._onAdd(e, data);
1226
+ }
1227
+ });
1228
+ },
1229
+
1230
+ _onPaste: function (e) {
1231
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1232
+ e.originalEvent.clipboardData.items,
1233
+ data = {files: []};
1234
+ if (items && items.length) {
1235
+ $.each(items, function (index, item) {
1236
+ var file = item.getAsFile && item.getAsFile();
1237
+ if (file) {
1238
+ data.files.push(file);
1239
+ }
1240
+ });
1241
+ if (this._trigger(
1242
+ 'paste',
1243
+ $.Event('paste', {delegatedEvent: e}),
1244
+ data
1245
+ ) !== false) {
1246
+ this._onAdd(e, data);
1247
+ }
1248
+ }
1249
+ },
1250
+
1251
+ _onDrop: function (e) {
1252
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1253
+ var that = this,
1254
+ dataTransfer = e.dataTransfer,
1255
+ data = {};
1256
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1257
+ e.preventDefault();
1258
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1259
+ data.files = files;
1260
+ if (that._trigger(
1261
+ 'drop',
1262
+ $.Event('drop', {delegatedEvent: e}),
1263
+ data
1264
+ ) !== false) {
1265
+ that._onAdd(e, data);
1266
+ }
1267
+ });
1268
+ }
1269
+ },
1270
+
1271
+ _onDragOver: getDragHandler('dragover'),
1272
+
1273
+ _onDragEnter: getDragHandler('dragenter'),
1274
+
1275
+ _onDragLeave: getDragHandler('dragleave'),
1276
+
1277
+ _initEventHandlers: function () {
1278
+ if (this._isXHRUpload(this.options)) {
1279
+ this._on(this.options.dropZone, {
1280
+ dragover: this._onDragOver,
1281
+ drop: this._onDrop,
1282
+ // event.preventDefault() on dragenter is required for IE10+:
1283
+ dragenter: this._onDragEnter,
1284
+ // dragleave is not required, but added for completeness:
1285
+ dragleave: this._onDragLeave
1286
+ });
1287
+ this._on(this.options.pasteZone, {
1288
+ paste: this._onPaste
1289
+ });
1290
+ }
1291
+ if ($.support.fileInput) {
1292
+ this._on(this.options.fileInput, {
1293
+ change: this._onChange
1294
+ });
1295
+ }
1296
+ },
1297
+
1298
+ _destroyEventHandlers: function () {
1299
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1300
+ this._off(this.options.pasteZone, 'paste');
1301
+ this._off(this.options.fileInput, 'change');
1302
+ },
1303
+
1304
+ _setOption: function (key, value) {
1305
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
1306
+ if (reinit) {
1307
+ this._destroyEventHandlers();
1308
+ }
1309
+ this._super(key, value);
1310
+ if (reinit) {
1311
+ this._initSpecialOptions();
1312
+ this._initEventHandlers();
1313
+ }
1314
+ },
1315
+
1316
+ _initSpecialOptions: function () {
1317
+ var options = this.options;
1318
+ if (options.fileInput === undefined) {
1319
+ options.fileInput = this.element.is('input[type="file"]') ?
1320
+ this.element : this.element.find('input[type="file"]');
1321
+ } else if (!(options.fileInput instanceof $)) {
1322
+ options.fileInput = $(options.fileInput);
1323
+ }
1324
+ if (!(options.dropZone instanceof $)) {
1325
+ options.dropZone = $(options.dropZone);
1326
+ }
1327
+ if (!(options.pasteZone instanceof $)) {
1328
+ options.pasteZone = $(options.pasteZone);
1329
+ }
1330
+ },
1331
+
1332
+ _getRegExp: function (str) {
1333
+ var parts = str.split('/'),
1334
+ modifiers = parts.pop();
1335
+ parts.shift();
1336
+ return new RegExp(parts.join('/'), modifiers);
1337
+ },
1338
+
1339
+ _isRegExpOption: function (key, value) {
1340
+ return key !== 'url' && $.type(value) === 'string' &&
1341
+ /^\/.*\/[igm]{0,3}$/.test(value);
1342
+ },
1343
+
1344
+ _initDataAttributes: function () {
1345
+ var that = this,
1346
+ options = this.options,
1347
+ data = this.element.data();
1348
+ // Initialize options set via HTML5 data-attributes:
1349
+ $.each(
1350
+ this.element[0].attributes,
1351
+ function (index, attr) {
1352
+ var key = attr.name.toLowerCase(),
1353
+ value;
1354
+ if (/^data-/.test(key)) {
1355
+ // Convert hyphen-ated key to camelCase:
1356
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
1357
+ return str.charAt(1).toUpperCase();
1358
+ });
1359
+ value = data[key];
1360
+ if (that._isRegExpOption(key, value)) {
1361
+ value = that._getRegExp(value);
1362
+ }
1363
+ options[key] = value;
1364
+ }
1365
+ }
1366
+ );
1367
+ },
1368
+
1369
+ _create: function () {
1370
+ this._initDataAttributes();
1371
+ this._initSpecialOptions();
1372
+ this._slots = [];
1373
+ this._sequence = this._getXHRPromise(true);
1374
+ this._sending = this._active = 0;
1375
+ this._initProgressObject(this);
1376
+ this._initEventHandlers();
1377
+ },
1378
+
1379
+ // This method is exposed to the widget API and allows to query
1380
+ // the number of active uploads:
1381
+ active: function () {
1382
+ return this._active;
1383
+ },
1384
+
1385
+ // This method is exposed to the widget API and allows to query
1386
+ // the widget upload progress.
1387
+ // It returns an object with loaded, total and bitrate properties
1388
+ // for the running uploads:
1389
+ progress: function () {
1390
+ return this._progress;
1391
+ },
1392
+
1393
+ // This method is exposed to the widget API and allows adding files
1394
+ // using the fileupload API. The data parameter accepts an object which
1395
+ // must have a files property and can contain additional options:
1396
+ // .fileupload('add', {files: filesList});
1397
+ add: function (data) {
1398
+ var that = this;
1399
+ if (!data || this.options.disabled) {
1400
+ return;
1401
+ }
1402
+ if (data.fileInput && !data.files) {
1403
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1404
+ data.files = files;
1405
+ that._onAdd(null, data);
1406
+ });
1407
+ } else {
1408
+ data.files = $.makeArray(data.files);
1409
+ this._onAdd(null, data);
1410
+ }
1411
+ },
1412
+
1413
+ // This method is exposed to the widget API and allows sending files
1414
+ // using the fileupload API. The data parameter accepts an object which
1415
+ // must have a files or fileInput property and can contain additional options:
1416
+ // .fileupload('send', {files: filesList});
1417
+ // The method returns a Promise object for the file upload call.
1418
+ send: function (data) {
1419
+ if (data && !this.options.disabled) {
1420
+ if (data.fileInput && !data.files) {
1421
+ var that = this,
1422
+ dfd = $.Deferred(),
1423
+ promise = dfd.promise(),
1424
+ jqXHR,
1425
+ aborted;
1426
+ promise.abort = function () {
1427
+ aborted = true;
1428
+ if (jqXHR) {
1429
+ return jqXHR.abort();
1430
+ }
1431
+ dfd.reject(null, 'abort', 'abort');
1432
+ return promise;
1433
+ };
1434
+ this._getFileInputFiles(data.fileInput).always(
1435
+ function (files) {
1436
+ if (aborted) {
1437
+ return;
1438
+ }
1439
+ if (!files.length) {
1440
+ dfd.reject();
1441
+ return;
1442
+ }
1443
+ data.files = files;
1444
+ jqXHR = that._onSend(null, data);
1445
+ jqXHR.then(
1446
+ function (result, textStatus, jqXHR) {
1447
+ dfd.resolve(result, textStatus, jqXHR);
1448
+ },
1449
+ function (jqXHR, textStatus, errorThrown) {
1450
+ dfd.reject(jqXHR, textStatus, errorThrown);
1451
+ }
1452
+ );
1453
+ }
1454
+ );
1455
+ return this._enhancePromise(promise);
1456
+ }
1457
+ data.files = $.makeArray(data.files);
1458
+ if (data.files.length) {
1459
+ return this._onSend(null, data);
1460
+ }
1461
+ }
1462
+ return this._getXHRPromise(false, data && data.context);
1463
+ }
1464
+
1465
+ });
1466
+
1467
+ }));
assets/js/jquery-fileupload/jquery.iframe-transport.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * jQuery Iframe Transport Plugin 1.8.3
3
+ * https://github.com/blueimp/jQuery-File-Upload
4
+ *
5
+ * Copyright 2011, Sebastian Tschan
6
+ * https://blueimp.net
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /* global define, require, window, document */
13
+
14
+ (function (factory) {
15
+ 'use strict';
16
+ if (typeof define === 'function' && define.amd) {
17
+ // Register as an anonymous AMD module:
18
+ define(['jquery'], factory);
19
+ } else if (typeof exports === 'object') {
20
+ // Node/CommonJS:
21
+ factory(require('jquery'));
22
+ } else {
23
+ // Browser globals:
24
+ factory(window.jQuery);
25
+ }
26
+ }(function ($) {
27
+ 'use strict';
28
+
29
+ // Helper variable to create unique names for the transport iframes:
30
+ var counter = 0;
31
+
32
+ // The iframe transport accepts four additional options:
33
+ // options.fileInput: a jQuery collection of file input fields
34
+ // options.paramName: the parameter name for the file form data,
35
+ // overrides the name property of the file input field(s),
36
+ // can be a string or an array of strings.
37
+ // options.formData: an array of objects with name and value properties,
38
+ // equivalent to the return data of .serializeArray(), e.g.:
39
+ // [{name: 'a', value: 1}, {name: 'b', value: 2}]
40
+ // options.initialIframeSrc: the URL of the initial iframe src,
41
+ // by default set to "javascript:false;"
42
+ $.ajaxTransport('iframe', function (options) {
43
+ if (options.async) {
44
+ // javascript:false as initial iframe src
45
+ // prevents warning popups on HTTPS in IE6:
46
+ /*jshint scripturl: true */
47
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
48
+ /*jshint scripturl: false */
49
+ form,
50
+ iframe,
51
+ addParamChar;
52
+ return {
53
+ send: function (_, completeCallback) {
54
+ form = $('<form style="display:none;"></form>');
55
+ form.attr('accept-charset', options.formAcceptCharset);
56
+ addParamChar = /\?/.test(options.url) ? '&' : '?';
57
+ // XDomainRequest only supports GET and POST:
58
+ if (options.type === 'DELETE') {
59
+ options.url = options.url + addParamChar + '_method=DELETE';
60
+ options.type = 'POST';
61
+ } else if (options.type === 'PUT') {
62
+ options.url = options.url + addParamChar + '_method=PUT';
63
+ options.type = 'POST';
64
+ } else if (options.type === 'PATCH') {
65
+ options.url = options.url + addParamChar + '_method=PATCH';
66
+ options.type = 'POST';
67
+ }
68
+ // IE versions below IE8 cannot set the name property of
69
+ // elements that have already been added to the DOM,
70
+ // so we set the name along with the iframe HTML markup:
71
+ counter += 1;
72
+ iframe = $(
73
+ '<iframe src="' + initialIframeSrc +
74
+ '" name="iframe-transport-' + counter + '"></iframe>'
75
+ ).bind('load', function () {
76
+ var fileInputClones,
77
+ paramNames = $.isArray(options.paramName) ?
78
+ options.paramName : [options.paramName];
79
+ iframe
80
+ .unbind('load')
81
+ .bind('load', function () {
82
+ var response;
83
+ // Wrap in a try/catch block to catch exceptions thrown
84
+ // when trying to access cross-domain iframe contents:
85
+ try {
86
+ response = iframe.contents();
87
+ // Google Chrome and Firefox do not throw an
88
+ // exception when calling iframe.contents() on
89
+ // cross-domain requests, so we unify the response:
90
+ if (!response.length || !response[0].firstChild) {
91
+ throw new Error();
92
+ }
93
+ } catch (e) {
94
+ response = undefined;
95
+ }
96
+ // The complete callback returns the
97
+ // iframe content document as response object:
98
+ completeCallback(
99
+ 200,
100
+ 'success',
101
+ {'iframe': response}
102
+ );
103
+ // Fix for IE endless progress bar activity bug
104
+ // (happens on form submits to iframe targets):
105
+ $('<iframe src="' + initialIframeSrc + '"></iframe>')
106
+ .appendTo(form);
107
+ window.setTimeout(function () {
108
+ // Removing the form in a setTimeout call
109
+ // allows Chrome's developer tools to display
110
+ // the response result
111
+ form.remove();
112
+ }, 0);
113
+ });
114
+ form
115
+ .prop('target', iframe.prop('name'))
116
+ .prop('action', options.url)
117
+ .prop('method', options.type);
118
+ if (options.formData) {
119
+ $.each(options.formData, function (index, field) {
120
+ $('<input type="hidden"/>')
121
+ .prop('name', field.name)
122
+ .val(field.value)
123
+ .appendTo(form);
124
+ });
125
+ }
126
+ if (options.fileInput && options.fileInput.length &&
127
+ options.type === 'POST') {
128
+ fileInputClones = options.fileInput.clone();
129
+ // Insert a clone for each file input field:
130
+ options.fileInput.after(function (index) {
131
+ return fileInputClones[index];
132
+ });
133
+ if (options.paramName) {
134
+ options.fileInput.each(function (index) {
135
+ $(this).prop(
136
+ 'name',
137
+ paramNames[index] || options.paramName
138
+ );
139
+ });
140
+ }
141
+ // Appending the file input fields to the hidden form
142
+ // removes them from their original location:
143
+ form
144
+ .append(options.fileInput)
145
+ .prop('enctype', 'multipart/form-data')
146
+ // enctype must be set as encoding for IE:
147
+ .prop('encoding', 'multipart/form-data');
148
+ // Remove the HTML5 form attribute from the input(s):
149
+ options.fileInput.removeAttr('form');
150
+ }
151
+ form.submit();
152
+ // Insert the file input fields at their original location
153
+ // by replacing the clones with the originals:
154
+ if (fileInputClones && fileInputClones.length) {
155
+ options.fileInput.each(function (index, input) {
156
+ var clone = $(fileInputClones[index]);
157
+ // Restore the original name and form properties:
158
+ $(input)
159
+ .prop('name', clone.prop('name'))
160
+ .attr('form', clone.attr('form'));
161
+ clone.replaceWith(input);
162
+ });
163
+ }
164
+ });
165
+ form.append(iframe).appendTo(document.body);
166
+ },
167
+ abort: function () {
168
+ if (iframe) {
169
+ // javascript:false as iframe src aborts the request
170
+ // and prevents warning popups on HTTPS in IE6.
171
+ // concat is used to avoid the "Script URL" JSLint error:
172
+ iframe
173
+ .unbind('load')
174
+ .prop('src', initialIframeSrc);
175
+ }
176
+ if (form) {
177
+ form.remove();
178
+ }
179
+ }
180
+ };
181
+ }
182
+ });
183
+
184
+ // The iframe transport returns the iframe content document as response.
185
+ // The following adds converters from iframe to text, json, html, xml
186
+ // and script.
187
+ // Please note that the Content-Type for JSON responses has to be text/plain
188
+ // or text/html, if the browser doesn't include application/json in the
189
+ // Accept header, else IE will show a download dialog.
190
+ // The Content-Type for XML responses on the other hand has to be always
191
+ // application/xml or text/xml, so IE properly parses the XML response.
192
+ // See also
193
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
194
+ $.ajaxSetup({
195
+ converters: {
196
+ 'iframe text': function (iframe) {
197
+ return iframe && $(iframe[0].body).text();
198
+ },
199
+ 'iframe json': function (iframe) {
200
+ return iframe && $.parseJSON($(iframe[0].body).text());
201
+ },
202
+ 'iframe html': function (iframe) {
203
+ return iframe && $(iframe[0].body).html();
204
+ },
205
+ 'iframe xml': function (iframe) {
206
+ var xmlDoc = iframe && iframe[0];
207
+ return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
208
+ $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
209
+ $(xmlDoc.body).html());
210
+ },
211
+ 'iframe script': function (iframe) {
212
+ return iframe && $.globalEval($(iframe[0].body).text());
213
+ }
214
+ }
215
+ });
216
+
217
+ }));
assets/js/multiselect.js CHANGED
@@ -1,3 +1,3 @@
1
  jQuery(function(){
2
- jQuery( '.job-manager-multiselect' ).chosen();
3
  });
1
  jQuery(function(){
2
+ jQuery( '.job-manager-multiselect' ).chosen({ search_contains: true });
3
  });
assets/js/multiselect.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(function(){jQuery(".job-manager-multiselect").chosen()});
1
+ jQuery(function(){jQuery(".job-manager-multiselect").chosen({search_contains:!0})});
assets/js/term-multiselect.js CHANGED
@@ -1,3 +1,3 @@
1
  jQuery(function(){
2
- jQuery( '.job-manager-category-dropdown' ).chosen();
3
  });
1
  jQuery(function(){
2
+ jQuery( '.job-manager-category-dropdown' ).chosen({ search_contains: true });
3
  });
assets/js/term-multiselect.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(function(){jQuery(".job-manager-category-dropdown").chosen()});
1
+ jQuery(function(){jQuery(".job-manager-category-dropdown").chosen({search_contains:!0})});
includes/admin/class-wp-job-manager-admin.php CHANGED
@@ -39,7 +39,7 @@ class WP_Job_Manager_Admin {
39
  if ( in_array( $screen->id, apply_filters( 'job_manager_admin_screen_ids', array( 'edit-job_listing', 'job_listing', 'job_listing_page_job-manager-settings', 'job_listing_page_job-manager-addons' ) ) ) ) {
40
  $jquery_version = isset( $wp_scripts->registered['jquery-ui-core']->ver ) ? $wp_scripts->registered['jquery-ui-core']->ver : '1.9.2';
41
 
42
- wp_enqueue_style( 'jquery-ui-style', '//ajax.googleapis.com/ajax/libs/jqueryui/' . $jquery_version . '/themes/smoothness/jquery-ui.css' );
43
  wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css' );
44
  wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL. '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
45
  wp_enqueue_script( 'job_manager_admin_js', JOB_MANAGER_PLUGIN_URL. '/assets/js/admin.min.js', array( 'jquery', 'jquery-tiptip', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
39
  if ( in_array( $screen->id, apply_filters( 'job_manager_admin_screen_ids', array( 'edit-job_listing', 'job_listing', 'job_listing_page_job-manager-settings', 'job_listing_page_job-manager-addons' ) ) ) ) {
40
  $jquery_version = isset( $wp_scripts->registered['jquery-ui-core']->ver ) ? $wp_scripts->registered['jquery-ui-core']->ver : '1.9.2';
41
 
42
+ wp_enqueue_style( 'jquery-ui-style', '//code.jquery.com/ui/' . $jquery_version . '/themes/smoothness/jquery-ui.css', array(), $jquery_version );
43
  wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css' );
44
  wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL. '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
45
  wp_enqueue_script( 'job_manager_admin_js', JOB_MANAGER_PLUGIN_URL. '/assets/js/admin.min.js', array( 'jquery', 'jquery-tiptip', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
includes/admin/class-wp-job-manager-settings.php CHANGED
@@ -54,14 +54,23 @@ class WP_Job_Manager_Settings {
54
  'std' => '0',
55
  'label' => __( 'Filled Positions', 'wp-job-manager' ),
56
  'cb_label' => __( 'Hide filled positions', 'wp-job-manager' ),
57
- 'desc' => __( 'If enabled, filled positions will be hidden.', 'wp-job-manager' ),
 
 
 
 
 
 
 
 
 
58
  'type' => 'checkbox',
59
  'attributes' => array()
60
  ),
61
  array(
62
  'name' => 'job_manager_enable_categories',
63
  'std' => '0',
64
- 'label' => __( 'Job Categories', 'wp-job-manager' ),
65
  'cb_label' => __( 'Enable categories for listings', 'wp-job-manager' ),
66
  'desc' => __( 'Choose whether to enable categories. Categories must be setup by an admin to allow users to choose them during submission.', 'wp-job-manager' ),
67
  'type' => 'checkbox',
54
  'std' => '0',
55
  'label' => __( 'Filled Positions', 'wp-job-manager' ),
56
  'cb_label' => __( 'Hide filled positions', 'wp-job-manager' ),
57
+ 'desc' => __( 'If enabled, filled positions will be hidden from archives.', 'wp-job-manager' ),
58
+ 'type' => 'checkbox',
59
+ 'attributes' => array()
60
+ ),
61
+ array(
62
+ 'name' => 'job_manager_hide_expired_content',
63
+ 'std' => '1',
64
+ 'label' => __( 'Expired Listings', 'wp-job-manager' ),
65
+ 'cb_label' => __( 'Hide content within expired listings', 'wp-job-manager' ),
66
+ 'desc' => __( 'If enabled, the content within expired listings will be hidden. Otherwise, expired listings will be displayed as normal (without the application area).', 'wp-job-manager' ),
67
  'type' => 'checkbox',
68
  'attributes' => array()
69
  ),
70
  array(
71
  'name' => 'job_manager_enable_categories',
72
  'std' => '0',
73
+ 'label' => __( 'Categories', 'wp-job-manager' ),
74
  'cb_label' => __( 'Enable categories for listings', 'wp-job-manager' ),
75
  'desc' => __( 'Choose whether to enable categories. Categories must be setup by an admin to allow users to choose them during submission.', 'wp-job-manager' ),
76
  'type' => 'checkbox',
includes/admin/class-wp-job-manager-writepanels.php CHANGED
@@ -30,64 +30,90 @@ class WP_Job_Manager_Writepanels {
30
  '_job_location' => array(
31
  'label' => __( 'Location', 'wp-job-manager' ),
32
  'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
33
- 'description' => __( 'Leave this blank if the location is not important', 'wp-job-manager' )
 
34
  ),
35
  '_application' => array(
36
  'label' => __( 'Application email/URL', 'wp-job-manager' ),
37
  'placeholder' => __( 'URL or email which applicants use to apply', 'wp-job-manager' ),
38
  'description' => __( 'This field is required for the "application" area to appear beneath the listing.', 'wp-job-manager' ),
39
- 'value' => metadata_exists( 'post', $post->ID, '_application' ) ? get_post_meta( $post->ID, '_application', true ) : $current_user->user_email
 
40
  ),
41
  '_company_name' => array(
42
- 'label' => __( 'Company name', 'wp-job-manager' ),
43
- 'placeholder' => ''
 
44
  ),
45
  '_company_website' => array(
46
- 'label' => __( 'Company website', 'wp-job-manager' ),
47
- 'placeholder' => ''
 
48
  ),
49
  '_company_tagline' => array(
50
- 'label' => __( 'Company tagline', 'wp-job-manager' ),
51
- 'placeholder' => __( 'Brief description about the company', 'wp-job-manager' )
 
52
  ),
53
  '_company_twitter' => array(
54
- 'label' => __( 'Company Twitter', 'wp-job-manager' ),
55
- 'placeholder' => '@yourcompany'
 
56
  ),
57
  '_company_logo' => array(
58
- 'label' => __( 'Company logo', 'wp-job-manager' ),
59
  'placeholder' => __( 'URL to the company logo', 'wp-job-manager' ),
60
- 'type' => 'file'
 
61
  ),
62
  '_company_video' => array(
63
- 'label' => __( 'Company video', 'wp-job-manager' ),
64
  'placeholder' => __( 'URL to the company video', 'wp-job-manager' ),
65
- 'type' => 'file'
 
66
  ),
67
  '_filled' => array(
68
- 'label' => __( 'Position filled?', 'wp-job-manager' ),
69
- 'type' => 'checkbox'
 
70
  )
71
  );
72
  if ( $current_user->has_cap( 'manage_job_listings' ) ) {
73
  $fields['_featured'] = array(
74
- 'label' => __( 'Feature this listing?', 'wp-job-manager' ),
75
- 'type' => 'checkbox',
76
- 'description' => __( 'Featured listings will be sticky during searches, and can be styled differently.', 'wp-job-manager' )
 
77
  );
78
  $fields['_job_expires'] = array(
79
  'label' => __( 'Expires', 'wp-job-manager' ),
80
- 'placeholder' => __( 'yyyy-mm-dd', 'wp-job-manager' )
 
81
  );
82
  }
83
  if ( $current_user->has_cap( 'edit_others_job_listings' ) ) {
84
  $fields['_job_author'] = array(
85
- 'label' => __( 'Posted by', 'wp-job-manager' ),
86
- 'type' => 'author'
 
87
  );
88
  }
89
 
90
- return apply_filters( 'job_manager_job_listing_data_fields', $fields );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  /**
@@ -263,21 +289,27 @@ class WP_Job_Manager_Writepanels {
263
  public function input_author( $key, $field ) {
264
  global $thepostid, $post;
265
 
266
- if ( empty( $field['value'] ) )
267
  $field['value'] = get_post_meta( $thepostid, $key, true );
 
268
  ?>
269
- <p class="form-field">
270
  <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>:</label>
271
- <?php
272
- wp_dropdown_users( array(
273
- 'who' => '',
274
- 'show_option_none' => __( 'Guest user', 'wp-job-manager' ),
275
- 'name' => $key,
276
- 'selected' => $post->post_author,
277
- 'include_selected' => true
278
- ) );
279
- ?>
280
- <?php if ( ! empty( $field['description'] ) ) : ?><span class="description"><?php echo $field['description']; ?></span><?php endif; ?>
 
 
 
 
 
281
  </p>
282
  <?php
283
  }
@@ -404,6 +436,14 @@ class WP_Job_Manager_Writepanels {
404
  break;
405
  }
406
  }
 
 
 
 
 
 
 
 
407
  }
408
  }
409
  }
30
  '_job_location' => array(
31
  'label' => __( 'Location', 'wp-job-manager' ),
32
  'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
33
+ 'description' => __( 'Leave this blank if the location is not important', 'wp-job-manager' ),
34
+ 'priority' => 1
35
  ),
36
  '_application' => array(
37
  'label' => __( 'Application email/URL', 'wp-job-manager' ),
38
  'placeholder' => __( 'URL or email which applicants use to apply', 'wp-job-manager' ),
39
  'description' => __( 'This field is required for the "application" area to appear beneath the listing.', 'wp-job-manager' ),
40
+ 'value' => metadata_exists( 'post', $post->ID, '_application' ) ? get_post_meta( $post->ID, '_application', true ) : $current_user->user_email,
41
+ 'priority' => 2
42
  ),
43
  '_company_name' => array(
44
+ 'label' => __( 'Company name', 'wp-job-manager' ),
45
+ 'placeholder' => '',
46
+ 'priority' => 3
47
  ),
48
  '_company_website' => array(
49
+ 'label' => __( 'Company website', 'wp-job-manager' ),
50
+ 'placeholder' => '',
51
+ 'priority' => 4
52
  ),
53
  '_company_tagline' => array(
54
+ 'label' => __( 'Company tagline', 'wp-job-manager' ),
55
+ 'placeholder' => __( 'Brief description about the company', 'wp-job-manager' ),
56
+ 'priority' => 5
57
  ),
58
  '_company_twitter' => array(
59
+ 'label' => __( 'Company Twitter', 'wp-job-manager' ),
60
+ 'placeholder' => '@yourcompany',
61
+ 'priority' => 6
62
  ),
63
  '_company_logo' => array(
64
+ 'label' => __( 'Company logo', 'wp-job-manager' ),
65
  'placeholder' => __( 'URL to the company logo', 'wp-job-manager' ),
66
+ 'type' => 'file',
67
+ 'priority' => 7
68
  ),
69
  '_company_video' => array(
70
+ 'label' => __( 'Company video', 'wp-job-manager' ),
71
  'placeholder' => __( 'URL to the company video', 'wp-job-manager' ),
72
+ 'type' => 'file',
73
+ 'priority' => 8
74
  ),
75
  '_filled' => array(
76
+ 'label' => __( 'Position filled?', 'wp-job-manager' ),
77
+ 'type' => 'checkbox',
78
+ 'priority' => 9
79
  )
80
  );
81
  if ( $current_user->has_cap( 'manage_job_listings' ) ) {
82
  $fields['_featured'] = array(
83
+ 'label' => __( 'Feature this listing?', 'wp-job-manager' ),
84
+ 'type' => 'checkbox',
85
+ 'description' => __( 'Featured listings will be sticky during searches, and can be styled differently.', 'wp-job-manager' ),
86
+ 'priority' => 10
87
  );
88
  $fields['_job_expires'] = array(
89
  'label' => __( 'Expires', 'wp-job-manager' ),
90
+ 'placeholder' => __( 'yyyy-mm-dd', 'wp-job-manager' ),
91
+ 'priority' => 11
92
  );
93
  }
94
  if ( $current_user->has_cap( 'edit_others_job_listings' ) ) {
95
  $fields['_job_author'] = array(
96
+ 'label' => __( 'Posted by', 'wp-job-manager' ),
97
+ 'type' => 'author',
98
+ 'priority' => 12
99
  );
100
  }
101
 
102
+ $fields = apply_filters( 'job_manager_job_listing_data_fields', $fields );
103
+
104
+ uasort( $fields, array( $this, 'sort_by_priority' ) );
105
+
106
+ return $fields;
107
+ }
108
+
109
+ /**
110
+ * Sort array by priority value
111
+ */
112
+ protected function sort_by_priority( $a, $b ) {
113
+ if ( ! isset( $a['priority'] ) || ! isset( $b['priority'] ) || $a['priority'] === $b['priority'] ) {
114
+ return 0;
115
+ }
116
+ return ( $a['priority'] < $b['priority'] ) ? -1 : 1;
117
  }
118
 
119
  /**
289
  public function input_author( $key, $field ) {
290
  global $thepostid, $post;
291
 
292
+ if ( empty( $field['value'] ) ) {
293
  $field['value'] = get_post_meta( $thepostid, $key, true );
294
+ }
295
  ?>
296
+ <p class="form-field form-field-author">
297
  <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>:</label>
298
+ <span class="current-author">
299
+ <?php
300
+ $posted_by = get_user_by( 'id', $post->post_author );
301
+
302
+ if ( $posted_by ) {
303
+ echo '#' . $post->post_author . ' - <a href="' . admin_url( 'user-edit.php?user_id=' . $post->post_author ) . '">' . $posted_by->user_login . '</a>';
304
+ } else {
305
+ _e( 'Guest User', 'wp-job-manager' );
306
+ }
307
+ ?> (<a href="#" class="change-author"><?php _e( 'Change', 'wp-job-manager' ); ?></a>)
308
+ </span>
309
+ <span class="hidden change-author">
310
+ <input type="number" name="<?php echo esc_attr( $key ); ?>" id="<?php echo esc_attr( $key ); ?>" step="1" value="<?php echo esc_attr( $post->post_author ); ?>" style="width: 4em;" />
311
+ <span class="description"><?php _e( 'Enter the ID of the user, or leave blank if submitted by a guest.', 'wp-job-manager' ) ?></span>
312
+ </span>
313
  </p>
314
  <?php
315
  }
436
  break;
437
  }
438
  }
439
+
440
+ if ( '_featured' === $key ) {
441
+ if ( ! empty( $_POST[ $key ] ) ) {
442
+ $wpdb->update( $wpdb->posts, array( 'menu_order' => 0 ), array( 'ID' => $post_id ) );
443
+ } else {
444
+ $wpdb->update( $wpdb->posts, array( 'menu_order' => 1 ), array( 'ID' => $post_id, 'menu_order' => 0 ) );
445
+ }
446
+ }
447
  }
448
  }
449
  }
includes/class-wp-job-manager-ajax.php CHANGED
@@ -13,6 +13,8 @@ class WP_Job_Manager_Ajax {
13
  public function __construct() {
14
  add_action( 'wp_ajax_nopriv_job_manager_get_listings', array( $this, 'get_listings' ) );
15
  add_action( 'wp_ajax_job_manager_get_listings', array( $this, 'get_listings' ) );
 
 
16
  }
17
 
18
  /**
@@ -22,12 +24,13 @@ class WP_Job_Manager_Ajax {
22
  global $wp_post_types;
23
 
24
  $result = array();
25
- $search_location = sanitize_text_field( stripslashes( $_POST['search_location'] ) );
26
- $search_keywords = sanitize_text_field( stripslashes( $_POST['search_keywords'] ) );
27
- $search_categories = isset( $_POST['search_categories'] ) ? $_POST['search_categories'] : '';
28
- $filter_job_types = isset( $_POST['filter_job_type'] ) ? array_filter( array_map( 'sanitize_title', (array) $_POST['filter_job_type'] ) ) : null;
29
  $types = get_job_listing_types();
30
  $post_type_label = $wp_post_types['job_listing']->labels->name;
 
31
 
32
  if ( is_array( $search_categories ) ) {
33
  $search_categories = array_filter( array_map( 'sanitize_text_field', array_map( 'stripslashes', $search_categories ) ) );
@@ -39,19 +42,20 @@ class WP_Job_Manager_Ajax {
39
  'search_location' => $search_location,
40
  'search_keywords' => $search_keywords,
41
  'search_categories' => $search_categories,
42
- 'job_types' => is_null( $filter_job_types ) ? '' : $filter_job_types + array( 0 ),
43
- 'orderby' => sanitize_text_field( $_POST['orderby'] ),
44
- 'order' => sanitize_text_field( $_POST['order'] ),
45
- 'offset' => ( absint( $_POST['page'] ) - 1 ) * absint( $_POST['per_page'] ),
46
- 'posts_per_page' => absint( $_POST['per_page'] )
47
  );
48
 
49
- if ( isset( $_POST['featured'] ) && ( $_POST['featured'] === 'true' || $_POST['featured'] === 'false' ) ) {
50
- $args['featured'] = $_POST['featured'] === 'true' ? true : false;
51
  }
52
 
53
- if ( isset( $_POST['filled'] ) && ( $_POST['filled'] === 'true' || $_POST['filled'] === 'false' ) ) {
54
- $args['filled'] = $_POST['filled'] === 'true' ? true : false;
 
55
  }
56
 
57
  ob_start();
@@ -135,8 +139,8 @@ class WP_Job_Manager_Ajax {
135
  ) );
136
 
137
  // Generate pagination
138
- if ( isset( $_POST['show_pagination'] ) && $_POST['show_pagination'] === 'true' ) {
139
- $result['pagination'] = get_job_listing_pagination( $jobs->max_num_pages, absint( $_POST['page'] ) );
140
  }
141
 
142
  $result['max_num_pages'] = $jobs->max_num_pages;
@@ -147,6 +151,32 @@ class WP_Job_Manager_Ajax {
147
 
148
  die();
149
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  }
151
 
152
  new WP_Job_Manager_Ajax();
13
  public function __construct() {
14
  add_action( 'wp_ajax_nopriv_job_manager_get_listings', array( $this, 'get_listings' ) );
15
  add_action( 'wp_ajax_job_manager_get_listings', array( $this, 'get_listings' ) );
16
+ add_action( 'wp_ajax_nopriv_job_manager_upload_file', array( $this, 'upload_file' ) );
17
+ add_action( 'wp_ajax_job_manager_upload_file', array( $this, 'upload_file' ) );
18
  }
19
 
20
  /**
24
  global $wp_post_types;
25
 
26
  $result = array();
27
+ $search_location = sanitize_text_field( stripslashes( $_REQUEST['search_location'] ) );
28
+ $search_keywords = sanitize_text_field( stripslashes( $_REQUEST['search_keywords'] ) );
29
+ $search_categories = isset( $_REQUEST['search_categories'] ) ? $_REQUEST['search_categories'] : '';
30
+ $filter_job_types = isset( $_REQUEST['filter_job_type'] ) ? array_filter( array_map( 'sanitize_title', (array) $_REQUEST['filter_job_type'] ) ) : null;
31
  $types = get_job_listing_types();
32
  $post_type_label = $wp_post_types['job_listing']->labels->name;
33
+ $orderby = sanitize_text_field( $_REQUEST['orderby'] );
34
 
35
  if ( is_array( $search_categories ) ) {
36
  $search_categories = array_filter( array_map( 'sanitize_text_field', array_map( 'stripslashes', $search_categories ) ) );
42
  'search_location' => $search_location,
43
  'search_keywords' => $search_keywords,
44
  'search_categories' => $search_categories,
45
+ 'job_types' => is_null( $filter_job_types ) || sizeof( $types ) === sizeof( $filter_job_types ) ? '' : $filter_job_types + array( 0 ),
46
+ 'orderby' => $orderby,
47
+ 'order' => sanitize_text_field( $_REQUEST['order'] ),
48
+ 'offset' => ( absint( $_REQUEST['page'] ) - 1 ) * absint( $_REQUEST['per_page'] ),
49
+ 'posts_per_page' => absint( $_REQUEST['per_page'] )
50
  );
51
 
52
+ if ( isset( $_REQUEST['filled'] ) && ( $_REQUEST['filled'] === 'true' || $_REQUEST['filled'] === 'false' ) ) {
53
+ $args['filled'] = $_REQUEST['filled'] === 'true' ? true : false;
54
  }
55
 
56
+ if ( isset( $_REQUEST['featured'] ) && ( $_REQUEST['featured'] === 'true' || $_REQUEST['featured'] === 'false' ) ) {
57
+ $args['featured'] = $_REQUEST['featured'] === 'true' ? true : false;
58
+ $args['orderby'] = 'featured' === $orderby ? 'date' : $orderby;
59
  }
60
 
61
  ob_start();
139
  ) );
140
 
141
  // Generate pagination
142
+ if ( isset( $_REQUEST['show_pagination'] ) && $_REQUEST['show_pagination'] === 'true' ) {
143
+ $result['pagination'] = get_job_listing_pagination( $jobs->max_num_pages, absint( $_REQUEST['page'] ) );
144
  }
145
 
146
  $result['max_num_pages'] = $jobs->max_num_pages;
151
 
152
  die();
153
  }
154
+
155
+ /**
156
+ * Upload file via ajax
157
+ *
158
+ * No nonce field since the form may be statically cached.
159
+ */
160
+ public function upload_file() {
161
+ $data = array( 'files' => array() );
162
+
163
+ if ( ! empty( $_FILES ) ) {
164
+ foreach ( $_FILES as $file_key => $file ) {
165
+ $files_to_upload = job_manager_prepare_uploaded_files( $file );
166
+ foreach ( $files_to_upload as $file_to_upload ) {
167
+ $uploaded_file = job_manager_upload_file( $file_to_upload, array( 'file_key' => $file_key ) );
168
+
169
+ if ( is_wp_error( $uploaded_file ) ) {
170
+ $data['files'][] = array( 'error' => $uploaded_file->get_error_message() );
171
+ } else {
172
+ $data['files'][] = $uploaded_file;
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ wp_send_json( $data );
179
+ }
180
  }
181
 
182
  new WP_Job_Manager_Ajax();
includes/class-wp-job-manager-api.php CHANGED
@@ -51,9 +51,9 @@ class WP_Job_Manager_API {
51
  global $wp;
52
 
53
  if ( ! empty( $_GET['job-manager-api'] ) )
54
- $wp->query_vars['wc-api'] = $_GET['job-manager-api'];
55
 
56
- if ( ! empty( $wp->query_vars['wc-api'] ) ) {
57
  // Buffer, we won't want any output here
58
  ob_start();
59
 
51
  global $wp;
52
 
53
  if ( ! empty( $_GET['job-manager-api'] ) )
54
+ $wp->query_vars['job-manager-api'] = $_GET['job-manager-api'];
55
 
56
+ if ( ! empty( $wp->query_vars['job-manager-api'] ) ) {
57
  // Buffer, we won't want any output here
58
  ob_start();
59
 
includes/class-wp-job-manager-cache-helper.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit;
5
+ }
6
+
7
+ /**
8
+ * WP_Job_Manager_Cache_Helper class.
9
+ */
10
+ class WP_Job_Manager_Cache_Helper {
11
+
12
+ public static function init() {
13
+ add_action( 'save_post', array( __CLASS__, 'flush_get_job_listings_cache' ) );
14
+ }
15
+
16
+ /**
17
+ * Flush the cache
18
+ */
19
+ public static function flush_get_job_listings_cache( $post_id ) {
20
+ if ( 'job_listing' === get_post_type( $post_id ) ) {
21
+ self::get_transient_version( 'get_job_listings', true );
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Get transient version
27
+ *
28
+ * When using transients with unpredictable names, e.g. those containing an md5
29
+ * hash in the name, we need a way to invalidate them all at once.
30
+ *
31
+ * When using default WP transients we're able to do this with a DB query to
32
+ * delete transients manually.
33
+ *
34
+ * With external cache however, this isn't possible. Instead, this function is used
35
+ * to append a unique string (based on time()) to each transient. When transients
36
+ * are invalidated, the transient version will increment and data will be regenerated.
37
+ *
38
+ * @param string $group Name for the group of transients we need to invalidate
39
+ * @param boolean $refresh true to force a new version
40
+ * @return string transient version based on time(), 10 digits
41
+ */
42
+ public static function get_transient_version( $group, $refresh = false ) {
43
+ $transient_name = $group . '-transient-version';
44
+ $transient_value = get_transient( $transient_name );
45
+
46
+ if ( false === $transient_value || true === $refresh ) {
47
+ $transient_value = time();
48
+ set_transient( $transient_name, $transient_value );
49
+ }
50
+ return $transient_value;
51
+ }
52
+ }
53
+
54
+ WP_Job_Manager_Cache_Helper::init();
includes/class-wp-job-manager-geocode.php CHANGED
@@ -169,6 +169,7 @@ class WP_Job_Manager_Geocode {
169
 
170
  if ( ! empty( $geocoded_address->results[0]->address_components ) ) {
171
  $address_data = $geocoded_address->results[0]->address_components;
 
172
  $address['street'] = false;
173
  $address['city'] = false;
174
  $address['state_short'] = false;
@@ -180,15 +181,10 @@ class WP_Job_Manager_Geocode {
180
  foreach ( $address_data as $data ) {
181
  switch ( $data->types[0] ) {
182
  case 'street_number' :
183
- $address['street'] = sanitize_text_field( $data->long_name );
184
  break;
185
  case 'route' :
186
- $route = sanitize_text_field( $data->long_name );
187
-
188
- if ( ! empty( $address['street'] ) )
189
- $address['street'] = $address['street'] . ' ' . $route;
190
- else
191
- $address['street'] = $route;
192
  break;
193
  case 'sublocality_level_1' :
194
  case 'locality' :
@@ -211,7 +207,7 @@ class WP_Job_Manager_Geocode {
211
  }
212
  }
213
 
214
- return $address;
215
  }
216
  }
217
 
169
 
170
  if ( ! empty( $geocoded_address->results[0]->address_components ) ) {
171
  $address_data = $geocoded_address->results[0]->address_components;
172
+ $address['street_number'] = false;
173
  $address['street'] = false;
174
  $address['city'] = false;
175
  $address['state_short'] = false;
181
  foreach ( $address_data as $data ) {
182
  switch ( $data->types[0] ) {
183
  case 'street_number' :
184
+ $address['street_number'] = sanitize_text_field( $data->long_name );
185
  break;
186
  case 'route' :
187
+ $address['street'] = sanitize_text_field( $data->long_name );
 
 
 
 
 
188
  break;
189
  case 'sublocality_level_1' :
190
  case 'locality' :
207
  }
208
  }
209
 
210
+ return apply_filters( 'job_manager_geolocation_get_location_data', $address, $geocoded_address );
211
  }
212
  }
213
 
includes/class-wp-job-manager-install.php CHANGED
@@ -14,6 +14,8 @@ class WP_Job_Manager_Install {
14
  * @return void
15
  */
16
  public function __construct() {
 
 
17
  $this->init_user_roles();
18
  $this->default_terms();
19
  $this->cron();
@@ -24,7 +26,12 @@ class WP_Job_Manager_Install {
24
  set_transient( '_job_manager_activation_redirect', 1, HOUR_IN_SECONDS );
25
  }
26
 
27
- update_option( 'wp_job_manager_version', JOB_MANAGER_VERSION );
 
 
 
 
 
28
 
29
  // Update legacy options
30
  if ( false === get_option( 'job_manager_submit_job_form_page_id', false ) && get_option( 'job_manager_submit_page_slug' ) ) {
@@ -35,6 +42,8 @@ class WP_Job_Manager_Install {
35
  $page_id = get_page_by_path( get_option( 'job_manager_job_dashboard_page_slug' ) )->ID;
36
  update_option( 'job_manager_job_dashboard_page_id', $page_id );
37
  }
 
 
38
  }
39
 
40
  /**
14
  * @return void
15
  */
16
  public function __construct() {
17
+ global $wpdb;
18
+
19
  $this->init_user_roles();
20
  $this->default_terms();
21
  $this->cron();
26
  set_transient( '_job_manager_activation_redirect', 1, HOUR_IN_SECONDS );
27
  }
28
 
29
+ // Update featured posts ordering
30
+ if ( version_compare( get_option( 'wp_job_manager_version', JOB_MANAGER_VERSION ), '1.21.0', '<' ) ) {
31
+ $featured_ids = array_map( 'absint', $wpdb->get_col( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_featured' AND meta_value='1';" ) );
32
+ $wpdb->query( "UPDATE {$wpdb->posts} SET menu_order = 1 WHERE ID NOT IN (" . implode( ',', $featured_ids ) . ") AND post_type='job_listing' AND menu_order=0;" );
33
+ $wpdb->query( "UPDATE {$wpdb->posts} SET menu_order = 0 WHERE ID IN (" . implode( ',', $featured_ids ) . ") AND post_type='job_listing';" );
34
+ }
35
 
36
  // Update legacy options
37
  if ( false === get_option( 'job_manager_submit_job_form_page_id', false ) && get_option( 'job_manager_submit_page_slug' ) ) {
42
  $page_id = get_page_by_path( get_option( 'job_manager_job_dashboard_page_slug' ) )->ID;
43
  update_option( 'job_manager_job_dashboard_page_id', $page_id );
44
  }
45
+
46
+ update_option( 'wp_job_manager_version', JOB_MANAGER_VERSION );
47
  }
48
 
49
  /**
includes/class-wp-job-manager-post-types.php CHANGED
@@ -75,8 +75,8 @@ class WP_Job_Manager_Post_Types {
75
  }
76
 
77
  register_taxonomy( "job_listing_category",
78
- array( "job_listing" ),
79
- array(
80
  'hierarchical' => true,
81
  'update_count_callback' => '_update_post_term_count',
82
  'label' => $plural,
@@ -102,7 +102,7 @@ class WP_Job_Manager_Post_Types {
102
  'assign_terms' => $admin_capability,
103
  ),
104
  'rewrite' => $rewrite,
105
- )
106
  );
107
  }
108
 
@@ -122,8 +122,8 @@ class WP_Job_Manager_Post_Types {
122
  }
123
 
124
  register_taxonomy( "job_listing_type",
125
- array( "job_listing" ),
126
- array(
127
  'hierarchical' => true,
128
  'label' => $plural,
129
  'labels' => array(
@@ -148,7 +148,7 @@ class WP_Job_Manager_Post_Types {
148
  'assign_terms' => $admin_capability,
149
  ),
150
  'rewrite' => $rewrite,
151
- )
152
  );
153
 
154
  /**
@@ -425,29 +425,37 @@ class WP_Job_Manager_Post_Types {
425
  }
426
 
427
  // See if it is already set
428
- $expires = get_post_meta( $post->ID, '_job_expires', true );
429
-
430
- if ( ! empty( $expires ) ) {
431
  return;
432
  }
433
 
434
- // Get duration from the product if set...
435
- $duration = get_post_meta( $post->ID, '_job_duration', true );
 
 
436
 
437
- // ...otherwise use the global option
438
- if ( ! $duration )
439
- $duration = absint( get_option( 'job_manager_submission_duration' ) );
 
440
 
441
- if ( $duration ) {
442
- $expires = date( 'Y-m-d', strtotime( "+{$duration} days", current_time( 'timestamp' ) ) );
443
- update_post_meta( $post->ID, '_job_expires', $expires );
 
444
 
445
- // In case we are saving a post, ensure post data is updated so the field is not overridden
446
- if ( isset( $_POST[ '_job_expires' ] ) )
447
- $_POST[ '_job_expires' ] = $expires;
448
 
449
- } else {
450
- update_post_meta( $post->ID, '_job_expires', '' );
 
 
 
 
 
 
451
  }
452
  }
453
 
75
  }
76
 
77
  register_taxonomy( "job_listing_category",
78
+ apply_filters( 'register_taxonomy_job_listing_category_object_type', array( 'job_listing' ) ),
79
+ apply_filters( 'register_taxonomy_job_listing_category_args', array(
80
  'hierarchical' => true,
81
  'update_count_callback' => '_update_post_term_count',
82
  'label' => $plural,
102
  'assign_terms' => $admin_capability,
103
  ),
104
  'rewrite' => $rewrite,
105
+ ) )
106
  );
107
  }
108
 
122
  }
123
 
124
  register_taxonomy( "job_listing_type",
125
+ apply_filters( 'register_taxonomy_job_listing_type_object_type', array( 'job_listing' ) ),
126
+ apply_filters( 'register_taxonomy_job_listing_type_args', array(
127
  'hierarchical' => true,
128
  'label' => $plural,
129
  'labels' => array(
148
  'assign_terms' => $admin_capability,
149
  ),
150
  'rewrite' => $rewrite,
151
+ ) )
152
  );
153
 
154
  /**
425
  }
426
 
427
  // See if it is already set
428
+ if ( metadata_exists( 'post', $post->ID, '_job_expires' ) ) {
 
 
429
  return;
430
  }
431
 
432
+ // No metadata set so we can generate an expiry date
433
+ // See if the user has set the expiry manually:
434
+ if ( ! empty( $_POST[ '_job_expires' ] ) ) {
435
+ update_post_meta( $post->ID, '_job_expires', date( 'Y-m-d', strtotime( sanitize_text_field( $_POST[ '_job_expires' ] ) ) ) );
436
 
437
+ // No manual setting? Lets generate a date
438
+ } else {
439
+ // Get duration from the product if set...
440
+ $duration = get_post_meta( $post->ID, '_job_duration', true );
441
 
442
+ // ...otherwise use the global option
443
+ if ( ! $duration ) {
444
+ $duration = absint( get_option( 'job_manager_submission_duration' ) );
445
+ }
446
 
447
+ if ( $duration ) {
448
+ $expires = date( 'Y-m-d', strtotime( "+{$duration} days", current_time( 'timestamp' ) ) );
449
+ update_post_meta( $post->ID, '_job_expires', $expires );
450
 
451
+ // In case we are saving a post, ensure post data is updated so the field is not overridden
452
+ if ( isset( $_POST[ '_job_expires' ] ) ) {
453
+ $_POST[ '_job_expires' ] = $expires;
454
+ }
455
+
456
+ } else {
457
+ update_post_meta( $post->ID, '_job_expires', '' );
458
+ }
459
  }
460
  }
461
 
includes/class-wp-job-manager-shortcodes.php CHANGED
@@ -26,6 +26,7 @@ class WP_Job_Manager_Shortcodes {
26
  add_shortcode( 'jobs', array( $this, 'output_jobs' ) );
27
  add_shortcode( 'job', array( $this, 'output_job' ) );
28
  add_shortcode( 'job_summary', array( $this, 'output_job_summary' ) );
 
29
  }
30
 
31
  /**
@@ -98,7 +99,7 @@ class WP_Job_Manager_Shortcodes {
98
  break;
99
  case 'relist' :
100
  // redirect to post page
101
- wp_redirect( add_query_arg( array( 'step' => 'preview', 'job_id' => absint( $job_id ) ), job_manager_get_permalink( 'submit_job_form' ) ) );
102
 
103
  break;
104
  default :
@@ -464,6 +465,49 @@ class WP_Job_Manager_Shortcodes {
464
 
465
  return ob_get_clean();
466
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
  }
468
 
469
  new WP_Job_Manager_Shortcodes();
26
  add_shortcode( 'jobs', array( $this, 'output_jobs' ) );
27
  add_shortcode( 'job', array( $this, 'output_job' ) );
28
  add_shortcode( 'job_summary', array( $this, 'output_job_summary' ) );
29
+ add_shortcode( 'job_apply', array( $this, 'output_job_apply' ) );
30
  }
31
 
32
  /**
99
  break;
100
  case 'relist' :
101
  // redirect to post page
102
+ wp_redirect( add_query_arg( array( 'job_id' => absint( $job_id ) ), job_manager_get_permalink( 'submit_job_form' ) ) );
103
 
104
  break;
105
  default :
465
 
466
  return ob_get_clean();
467
  }
468
+
469
+ /**
470
+ * Show the application area
471
+ */
472
+ public function output_job_apply( $atts ) {
473
+ extract( shortcode_atts( array(
474
+ 'id' => ''
475
+ ), $atts ) );
476
+
477
+ ob_start();
478
+
479
+ $args = array(
480
+ 'post_type' => 'job_listing',
481
+ 'post_status' => 'publish'
482
+ );
483
+
484
+ if ( ! $id ) {
485
+ return '';
486
+ } else {
487
+ $args['p'] = absint( $id );
488
+ }
489
+
490
+ $jobs = new WP_Query( $args );
491
+
492
+ if ( $jobs->have_posts() ) : ?>
493
+
494
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
495
+
496
+ <div class="job-manager-application-wrapper">
497
+ <?php
498
+ $apply = get_the_job_application_method();
499
+ do_action( 'job_manager_application_details_' . $apply->type, $apply );
500
+ ?>
501
+ </div>
502
+
503
+ <?php endwhile; ?>
504
+
505
+ <?php endif;
506
+
507
+ wp_reset_postdata();
508
+
509
+ return ob_get_clean();
510
+ }
511
  }
512
 
513
  new WP_Job_Manager_Shortcodes();
includes/class-wp-job-manager-widgets.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
4
 
5
  /**
6
  * Job Manager Widget base
@@ -17,6 +17,13 @@ class WP_Job_Manager_Widget extends WP_Widget {
17
  * Constructor
18
  */
19
  public function __construct() {
 
 
 
 
 
 
 
20
  $widget_ops = array(
21
  'classname' => $this->widget_cssclass,
22
  'description' => $this->widget_description
@@ -166,7 +173,7 @@ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
166
  'label' => __( 'Number of listings to show', 'wp-job-manager' )
167
  )
168
  );
169
- parent::__construct();
170
  }
171
 
172
  /**
@@ -179,8 +186,9 @@ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
179
  * @return void
180
  */
181
  public function widget( $args, $instance ) {
182
- if ( $this->get_cached_widget( $args ) )
183
  return;
 
184
 
185
  ob_start();
186
 
@@ -230,4 +238,99 @@ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
230
  }
231
  }
232
 
233
- register_widget( 'WP_Job_Manager_Widget_Recent_Jobs' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit;
4
 
5
  /**
6
  * Job Manager Widget base
17
  * Constructor
18
  */
19
  public function __construct() {
20
+ $this->register();
21
+ }
22
+
23
+ /**
24
+ * Register Widget
25
+ */
26
+ public function register() {
27
  $widget_ops = array(
28
  'classname' => $this->widget_cssclass,
29
  'description' => $this->widget_description
173
  'label' => __( 'Number of listings to show', 'wp-job-manager' )
174
  )
175
  );
176
+ $this->register();
177
  }
178
 
179
  /**
186
  * @return void
187
  */
188
  public function widget( $args, $instance ) {
189
+ if ( $this->get_cached_widget( $args ) ) {
190
  return;
191
+ }
192
 
193
  ob_start();
194
 
238
  }
239
  }
240
 
241
+ /**
242
+ * Featured Jobs Widget
243
+ */
244
+ class WP_Job_Manager_Widget_Featured_Jobs extends WP_Job_Manager_Widget {
245
+
246
+ /**
247
+ * Constructor
248
+ */
249
+ public function __construct() {
250
+ global $wp_post_types;
251
+
252
+ $this->widget_cssclass = 'job_manager widget_featured_jobs';
253
+ $this->widget_description = __( 'Display a list of featured listings on your site.', 'wp-job-manager' );
254
+ $this->widget_id = 'widget_featured_jobs';
255
+ $this->widget_name = sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name );
256
+ $this->settings = array(
257
+ 'title' => array(
258
+ 'type' => 'text',
259
+ 'std' => sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ),
260
+ 'label' => __( 'Title', 'wp-job-manager' )
261
+ ),
262
+ 'number' => array(
263
+ 'type' => 'number',
264
+ 'step' => 1,
265
+ 'min' => 1,
266
+ 'max' => '',
267
+ 'std' => 10,
268
+ 'label' => __( 'Number of listings to show', 'wp-job-manager' )
269
+ )
270
+ );
271
+ $this->register();
272
+ }
273
+
274
+ /**
275
+ * widget function.
276
+ *
277
+ * @see WP_Widget
278
+ * @access public
279
+ * @param array $args
280
+ * @param array $instance
281
+ * @return void
282
+ */
283
+ public function widget( $args, $instance ) {
284
+ if ( $this->get_cached_widget( $args ) ) {
285
+ return;
286
+ }
287
+
288
+ ob_start();
289
+
290
+ extract( $args );
291
+
292
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
293
+ $number = absint( $instance['number'] );
294
+ $jobs = get_job_listings( array(
295
+ 'posts_per_page' => $number,
296
+ 'orderby' => 'date',
297
+ 'order' => 'DESC',
298
+ 'featured' => true
299
+ ) );
300
+
301
+ if ( $jobs->have_posts() ) : ?>
302
+
303
+ <?php echo $before_widget; ?>
304
+
305
+ <?php if ( $title ) echo $before_title . $title . $after_title; ?>
306
+
307
+ <ul class="job_listings">
308
+
309
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
310
+
311
+ <?php get_job_manager_template_part( 'content-widget', 'job_listing' ); ?>
312
+
313
+ <?php endwhile; ?>
314
+
315
+ </ul>
316
+
317
+ <?php echo $after_widget; ?>
318
+
319
+ <?php else : ?>
320
+
321
+ <?php get_job_manager_template_part( 'content-widget', 'no-jobs-found' ); ?>
322
+
323
+ <?php endif;
324
+
325
+ wp_reset_postdata();
326
+
327
+ $content = ob_get_clean();
328
+
329
+ echo $content;
330
+
331
+ $this->cache_widget( $args, $content );
332
+ }
333
+ }
334
+
335
+ register_widget( 'WP_Job_Manager_Widget_Recent_Jobs' );
336
+ register_widget( 'WP_Job_Manager_Widget_Featured_Jobs' );
includes/forms/class-wp-job-manager-form-submit-job.php CHANGED
@@ -48,6 +48,17 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
48
 
49
  self::$job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST[ 'job_id' ] ) : 0;
50
 
 
 
 
 
 
 
 
 
 
 
 
51
  if ( self::$job_id ) {
52
  $job_status = get_post_status( self::$job_id );
53
  if ( 'expired' === $job_status ) {
@@ -218,10 +229,13 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
218
  'required' => false,
219
  'placeholder' => '',
220
  'priority' => 6,
 
 
221
  'allowed_mime_types' => array(
222
- 'jpg' => 'image/jpeg',
223
- 'gif' => 'image/gif',
224
- 'png' => 'image/png'
 
225
  )
226
  )
227
  )
@@ -388,17 +402,28 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
388
  }
389
  if ( ! empty( $field['taxonomy'] ) && in_array( $field['type'], array( 'term-checklist', 'term-select', 'term-multiselect' ) ) ) {
390
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
391
- foreach ( $values[ $group_key ][ $key ] as $term ) {
392
- if ( ! term_exists( $term, $field['taxonomy'] ) ) {
393
- return new WP_Error( 'validation-error', sprintf( __( '%s is invalid', 'wp-job-manager' ), $field['label'] ) );
394
- }
395
- }
396
- } elseif ( ! empty( $values[ $group_key ][ $key ] ) ) {
397
- if ( ! term_exists( $values[ $group_key ][ $key ], $field['taxonomy'] ) ) {
398
  return new WP_Error( 'validation-error', sprintf( __( '%s is invalid', 'wp-job-manager' ), $field['label'] ) );
399
  }
400
  }
401
  }
 
 
 
 
 
 
 
 
 
 
 
 
402
  }
403
  }
404
 
@@ -484,7 +509,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
484
  self::init_fields();
485
 
486
  // Load data if neccessary
487
- if ( ! empty( $_POST['edit_job'] ) && self::$job_id ) {
488
  $job = get_post( self::$job_id );
489
  foreach ( self::$fields as $group_key => $group_fields ) {
490
  foreach ( $group_fields as $key => $field ) {
@@ -654,6 +679,15 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
654
  wp_update_post( $job_data );
655
  } else {
656
  self::$job_id = wp_insert_post( $job_data );
 
 
 
 
 
 
 
 
 
657
  }
658
  }
659
 
@@ -714,7 +748,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
714
 
715
  // Loop attachments already attached to the job
716
  foreach ( $attachments as $attachment_key => $attachment ) {
717
- $attachment_urls[] = wp_get_attachment_url( $attachment );
718
  }
719
 
720
  foreach ( $maybe_attach as $attachment_url ) {
@@ -840,93 +874,34 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
840
 
841
  /**
842
  * Upload a file
843
- *
844
  * @return string or array
845
  */
846
  public static function upload_file( $field_key, $field ) {
847
-
848
- /** WordPress Administration File API */
849
- include_once( ABSPATH . 'wp-admin/includes/file.php' );
850
-
851
- /** WordPress Media Administration API */
852
- include_once( ABSPATH . 'wp-admin/includes/media.php' );
853
-
854
  if ( isset( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ]['name'] ) ) {
855
- $file = $_FILES[ $field_key ];
856
-
857
  if ( ! empty( $field['allowed_mime_types'] ) ) {
858
  $allowed_mime_types = $field['allowed_mime_types'];
859
  } else {
860
  $allowed_mime_types = get_allowed_mime_types();
861
  }
862
 
863
- if ( empty( $file['name'] ) ) {
864
- return false;
865
- }
866
-
867
- if ( is_array( $file['name'] ) ) {
868
- $file_urls = array();
869
-
870
- foreach ( $file['name'] as $key => $value ) {
871
- if ( ! empty( $file['name'][ $key ] ) ) {
872
-
873
- if ( ! in_array( $file['type'][ $key ], $allowed_mime_types ) ) {
874
- throw new Exception( sprintf( __( '"%s" (filetype %s) needs to be one of the following file types: %s', 'wp-job-manager' ), $field['label'], $file['type'][ $key ], implode( ', ', array_keys( $allowed_mime_types ) ) ) );
875
- }
876
-
877
- $upload_file = array(
878
- 'name' => $file['name'][ $key ],
879
- 'type' => $file['type'][ $key ],
880
- 'tmp_name' => $file['tmp_name'][ $key ],
881
- 'error' => $file['error'][ $key ],
882
- 'size' => $file['size'][ $key ]
883
- );
884
-
885
- add_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
886
- $upload = wp_handle_upload( $upload_file, apply_filters( 'submit_job_wp_handle_upload_overrides', array( 'test_form' => false ) ) );
887
- remove_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
888
 
889
- if ( ! empty( $upload['error'] ) ) {
890
- throw new Exception( $upload['error'] );
891
- }
892
 
893
- $file_urls[] = $upload['url'];
894
- }
 
 
895
  }
 
896
 
 
897
  return $file_urls;
898
  } else {
899
- if ( ! in_array( $file['type'], $allowed_mime_types ) ) {
900
- throw new Exception( sprintf( __( '"%s" (filetype %s) needs to be one of the following file types: %s', 'wp-job-manager' ), $field['label'], $file['type'], implode( ', ', array_keys( $allowed_mime_types ) ) ) );
901
- }
902
-
903
- add_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
904
- $upload = wp_handle_upload( $file, apply_filters( 'submit_job_wp_handle_upload_overrides', array( 'test_form' => false ) ) );
905
- remove_filter( 'upload_dir', array( __CLASS__, 'upload_dir' ) );
906
-
907
- if ( ! empty( $upload['error'] ) ) {
908
- throw new Exception( $upload['error'] );
909
- } else {
910
- return $upload['url'];
911
- }
912
  }
913
  }
914
  }
915
-
916
- /**
917
- * Filter the upload directory
918
- */
919
- public static function upload_dir( $pathdata ) {
920
- if ( empty( $pathdata['subdir'] ) ) {
921
- $pathdata['path'] = $pathdata['path'] . '/job_listings';
922
- $pathdata['url'] = $pathdata['url']. '/job_listings';
923
- $pathdata['subdir'] = '/job_listings';
924
- } else {
925
- $new_subdir = '/job_listings' . $pathdata['subdir'];
926
- $pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
927
- $pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
928
- $pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
929
- }
930
- return $pathdata;
931
- }
932
  }
48
 
49
  self::$job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST[ 'job_id' ] ) : 0;
50
 
51
+ // Allow resuming from cookie.
52
+ if ( ! self::$job_id && ! empty( $_COOKIE['wp-job-manager-submitting-job-id'] ) && ! empty( $_COOKIE['wp-job-manager-submitting-job-key'] ) ) {
53
+ $job_id = absint( $_COOKIE['wp-job-manager-submitting-job-id'] );
54
+ $job_status = get_post_status( $job_id );
55
+
56
+ if ( 'preview' === $job_status && get_post_meta( $job_id, '_submitting_key', true ) === $_COOKIE['wp-job-manager-submitting-job-key'] ) {
57
+ self::$job_id = $job_id;
58
+ }
59
+ }
60
+
61
+ // Load job details
62
  if ( self::$job_id ) {
63
  $job_status = get_post_status( self::$job_id );
64
  if ( 'expired' === $job_status ) {
229
  'required' => false,
230
  'placeholder' => '',
231
  'priority' => 6,
232
+ 'ajax' => true,
233
+ 'multiple' => false,
234
  'allowed_mime_types' => array(
235
+ 'jpg' => 'image/jpeg',
236
+ 'jpeg' => 'image/jpeg',
237
+ 'gif' => 'image/gif',
238
+ 'png' => 'image/png'
239
  )
240
  )
241
  )
402
  }
403
  if ( ! empty( $field['taxonomy'] ) && in_array( $field['type'], array( 'term-checklist', 'term-select', 'term-multiselect' ) ) ) {
404
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
405
+ $check_value = $values[ $group_key ][ $key ];
406
+ } else {
407
+ $check_value = array( $values[ $group_key ][ $key ] );
408
+ }
409
+ foreach ( $check_value as $term ) {
410
+ if ( ! term_exists( $term, $field['taxonomy'] ) ) {
 
411
  return new WP_Error( 'validation-error', sprintf( __( '%s is invalid', 'wp-job-manager' ), $field['label'] ) );
412
  }
413
  }
414
  }
415
+ if ( 'file' === $field['type'] && ! empty( $field['allowed_mime_types'] ) ) {
416
+ if ( is_array( $values[ $group_key ][ $key ] ) ) {
417
+ $check_value = $values[ $group_key ][ $key ];
418
+ } else {
419
+ $check_value = array( $values[ $group_key ][ $key ] );
420
+ }
421
+ foreach ( $check_value as $file_url ) {
422
+ if ( ( $info = wp_check_filetype( $file_url ) ) && ! in_array( $info['type'], $field['allowed_mime_types'] ) ) {
423
+ throw new Exception( sprintf( __( '"%s" (filetype %s) needs to be one of the following file types: %s', 'wp-job-manager' ), $field['label'], $info['ext'], implode( ', ', array_keys( $field['allowed_mime_types'] ) ) ) );
424
+ }
425
+ }
426
+ }
427
  }
428
  }
429
 
509
  self::init_fields();
510
 
511
  // Load data if neccessary
512
+ if ( self::$job_id ) {
513
  $job = get_post( self::$job_id );
514
  foreach ( self::$fields as $group_key => $group_fields ) {
515
  foreach ( $group_fields as $key => $field ) {
679
  wp_update_post( $job_data );
680
  } else {
681
  self::$job_id = wp_insert_post( $job_data );
682
+
683
+ if ( ! headers_sent() ) {
684
+ $submitting_key = uniqid();
685
+
686
+ setcookie( 'wp-job-manager-submitting-job-id', self::$job_id, false, COOKIEPATH, COOKIE_DOMAIN, false );
687
+ setcookie( 'wp-job-manager-submitting-job-key', $submitting_key, false, COOKIEPATH, COOKIE_DOMAIN, false );
688
+
689
+ update_post_meta( self::$job_id, '_submitting_key', $submitting_key );
690
+ }
691
  }
692
  }
693
 
748
 
749
  // Loop attachments already attached to the job
750
  foreach ( $attachments as $attachment_key => $attachment ) {
751
+ $attachment_urls[] = str_replace( WP_CONTENT_URL, WP_CONTENT_DIR, wp_get_attachment_url( $attachment ) );
752
  }
753
 
754
  foreach ( $maybe_attach as $attachment_url ) {
874
 
875
  /**
876
  * Upload a file
 
877
  * @return string or array
878
  */
879
  public static function upload_file( $field_key, $field ) {
 
 
 
 
 
 
 
880
  if ( isset( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ]['name'] ) ) {
 
 
881
  if ( ! empty( $field['allowed_mime_types'] ) ) {
882
  $allowed_mime_types = $field['allowed_mime_types'];
883
  } else {
884
  $allowed_mime_types = get_allowed_mime_types();
885
  }
886
 
887
+ $file_urls = array();
888
+ $files_to_upload = job_manager_prepare_uploaded_files( $_FILES[ $field_key ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889
 
890
+ foreach ( $files_to_upload as $file_to_upload ) {
891
+ $uploaded_file = job_manager_upload_file( $file_to_upload, array( 'file_key' => $file_key ) );
 
892
 
893
+ if ( is_wp_error( $uploaded_file ) ) {
894
+ throw new Exception( $uploaded_file->get_error_message() );
895
+ } else {
896
+ $file_urls[] = $upload['url'];
897
  }
898
+ }
899
 
900
+ if ( ! empty( $field['multiple'] ) ) {
901
  return $file_urls;
902
  } else {
903
+ return current( $file_urls );
 
 
 
 
 
 
 
 
 
 
 
 
904
  }
905
  }
906
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
907
  }
languages/wp-job-manager.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the same license as the WP Job Manager package.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Job Manager 1.20.0\n"
6
- "Report-Msgid-Bugs-To: http://wordpress.org/tag/WP-Job-Manager\n"
7
- "POT-Creation-Date: 2015-01-24 13:48:39+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -17,10 +17,12 @@ msgstr ""
17
  msgid "WP Job Manager Add-ons"
18
  msgstr ""
19
 
20
- #: includes/admin/class-wp-job-manager-addons.php:58
21
- msgid ""
22
- "Buying multiple add-ons? <a href=\"%s\">Check out the core add-on bundle "
23
- "&rarr;</a>"
 
 
24
  msgstr ""
25
 
26
  #: includes/admin/class-wp-job-manager-admin.php:58
@@ -112,8 +114,8 @@ msgstr ""
112
 
113
  #: includes/admin/class-wp-job-manager-cpt.php:256
114
  #: includes/admin/class-wp-job-manager-writepanels.php:31
115
- #: includes/class-wp-job-manager-widgets.php:158
116
- #: includes/forms/class-wp-job-manager-form-submit-job.php:138
117
  #: templates/job-filters.php:17 templates/job-filters.php:18
118
  msgid "Location"
119
  msgstr ""
@@ -123,11 +125,12 @@ msgid "Posted"
123
  msgstr ""
124
 
125
  #: includes/admin/class-wp-job-manager-cpt.php:258
126
- #: includes/admin/class-wp-job-manager-writepanels.php:79
127
  msgid "Expires"
128
  msgstr ""
129
 
130
  #: includes/admin/class-wp-job-manager-cpt.php:259
 
131
  msgid "Categories"
132
  msgstr ""
133
 
@@ -136,7 +139,7 @@ msgid "Featured?"
136
  msgstr ""
137
 
138
  #: includes/admin/class-wp-job-manager-cpt.php:261
139
- #: includes/class-wp-job-manager-shortcodes.php:164
140
  msgid "Filled?"
141
  msgstr ""
142
 
@@ -207,208 +210,218 @@ msgid "Hide filled positions"
207
  msgstr ""
208
 
209
  #: includes/admin/class-wp-job-manager-settings.php:57
210
- msgid "If enabled, filled positions will be hidden."
211
  msgstr ""
212
 
213
  #: includes/admin/class-wp-job-manager-settings.php:64
214
- msgid "Job Categories"
215
  msgstr ""
216
 
217
  #: includes/admin/class-wp-job-manager-settings.php:65
218
- msgid "Enable categories for listings"
219
  msgstr ""
220
 
221
  #: includes/admin/class-wp-job-manager-settings.php:66
222
  msgid ""
 
 
 
 
 
 
 
 
 
 
223
  "Choose whether to enable categories. Categories must be setup by an admin to "
224
  "allow users to choose them during submission."
225
  msgstr ""
226
 
227
- #: includes/admin/class-wp-job-manager-settings.php:73
228
  msgid "Multi-select Categories"
229
  msgstr ""
230
 
231
- #: includes/admin/class-wp-job-manager-settings.php:74
232
  msgid "Enable category multiselect by default"
233
  msgstr ""
234
 
235
- #: includes/admin/class-wp-job-manager-settings.php:75
236
- #: includes/admin/class-wp-job-manager-settings.php:83
237
  msgid ""
238
  "If enabled, the category select box will default to a multiselect on the "
239
  "[jobs] shortcode."
240
  msgstr ""
241
 
242
- #: includes/admin/class-wp-job-manager-settings.php:82
243
  msgid "Category Filter Type"
244
  msgstr ""
245
 
246
- #: includes/admin/class-wp-job-manager-settings.php:86
247
  msgid "Jobs will be shown if within ANY selected category"
248
  msgstr ""
249
 
250
- #: includes/admin/class-wp-job-manager-settings.php:87
251
  msgid "Jobs will be shown if within ALL selected categories"
252
  msgstr ""
253
 
254
- #: includes/admin/class-wp-job-manager-settings.php:93
255
  msgid "Job Submission"
256
  msgstr ""
257
 
258
- #: includes/admin/class-wp-job-manager-settings.php:98
259
  msgid "Account Required"
260
  msgstr ""
261
 
262
- #: includes/admin/class-wp-job-manager-settings.php:99
263
  msgid "Submitting listings requires an account"
264
  msgstr ""
265
 
266
- #: includes/admin/class-wp-job-manager-settings.php:100
267
  msgid ""
268
  "If disabled, non-logged in users will be able to submit listings without "
269
  "creating an account."
270
  msgstr ""
271
 
272
- #: includes/admin/class-wp-job-manager-settings.php:107
273
  msgid "Account Creation"
274
  msgstr ""
275
 
276
- #: includes/admin/class-wp-job-manager-settings.php:108
277
  msgid "Allow account creation"
278
  msgstr ""
279
 
280
- #: includes/admin/class-wp-job-manager-settings.php:109
281
  msgid ""
282
  "If enabled, non-logged in users will be able to create an account by "
283
  "entering their email address on the submission form."
284
  msgstr ""
285
 
286
- #: includes/admin/class-wp-job-manager-settings.php:116
287
- msgid "Username Generation"
288
  msgstr ""
289
 
290
- #: includes/admin/class-wp-job-manager-settings.php:117
291
  msgid "Automatically Generate Username from Email Address"
292
  msgstr ""
293
 
294
- #: includes/admin/class-wp-job-manager-settings.php:118
295
  msgid ""
296
  "If enabled, a username will be generated from the first part of the user "
297
  "email address. Otherwise, a username field will be shown."
298
  msgstr ""
299
 
300
- #: includes/admin/class-wp-job-manager-settings.php:125
301
  msgid "Account Role"
302
  msgstr ""
303
 
304
- #: includes/admin/class-wp-job-manager-settings.php:126
305
  msgid ""
306
  "If you enable registration on your submission form, choose a role for the "
307
  "new user."
308
  msgstr ""
309
 
310
- #: includes/admin/class-wp-job-manager-settings.php:133
311
- msgid "Approval Required"
312
  msgstr ""
313
 
314
- #: includes/admin/class-wp-job-manager-settings.php:134
315
- msgid "New submissions require admin approval"
316
  msgstr ""
317
 
318
- #: includes/admin/class-wp-job-manager-settings.php:135
319
  msgid "If enabled, new submissions will be inactive, pending admin approval."
320
  msgstr ""
321
 
322
- #: includes/admin/class-wp-job-manager-settings.php:142
323
  msgid "Allow Pending Edits"
324
  msgstr ""
325
 
326
- #: includes/admin/class-wp-job-manager-settings.php:143
327
  msgid "Submissions awaiting approval can be edited"
328
  msgstr ""
329
 
330
- #: includes/admin/class-wp-job-manager-settings.php:144
331
  msgid ""
332
  "If enabled, submissions awaiting admin approval can be edited by the user."
333
  msgstr ""
334
 
335
- #: includes/admin/class-wp-job-manager-settings.php:151
336
  msgid "Listing Duration"
337
  msgstr ""
338
 
339
- #: includes/admin/class-wp-job-manager-settings.php:152
340
  msgid ""
341
  "How many <strong>days</strong> listings are live before expiring. Can be "
342
  "left blank to never expire."
343
  msgstr ""
344
 
345
- #: includes/admin/class-wp-job-manager-settings.php:158
346
  msgid "Application Method"
347
  msgstr ""
348
 
349
- #: includes/admin/class-wp-job-manager-settings.php:159
350
  msgid "Choose the contact method for listings."
351
  msgstr ""
352
 
353
- #: includes/admin/class-wp-job-manager-settings.php:162
354
  msgid "Email address or website URL"
355
  msgstr ""
356
 
357
- #: includes/admin/class-wp-job-manager-settings.php:163
358
  msgid "Email addresses only"
359
  msgstr ""
360
 
361
- #: includes/admin/class-wp-job-manager-settings.php:164
362
  msgid "Website URLs only"
363
  msgstr ""
364
 
365
- #: includes/admin/class-wp-job-manager-settings.php:170
366
  msgid "Pages"
367
  msgstr ""
368
 
369
- #: includes/admin/class-wp-job-manager-settings.php:175
370
  msgid "Submit Job Form Page"
371
  msgstr ""
372
 
373
- #: includes/admin/class-wp-job-manager-settings.php:176
374
  msgid ""
375
  "Select the page where you have placed the [submit_job_form] shortcode. This "
376
  "lets the plugin know where the form is located."
377
  msgstr ""
378
 
379
- #: includes/admin/class-wp-job-manager-settings.php:182
380
  msgid "Job Dashboard Page"
381
  msgstr ""
382
 
383
- #: includes/admin/class-wp-job-manager-settings.php:183
384
  msgid ""
385
  "Select the page where you have placed the [job_dashboard] shortcode. This "
386
  "lets the plugin know where the dashboard is located."
387
  msgstr ""
388
 
389
- #: includes/admin/class-wp-job-manager-settings.php:189
390
  msgid "Job Listings Page"
391
  msgstr ""
392
 
393
- #: includes/admin/class-wp-job-manager-settings.php:190
394
  msgid ""
395
  "Select the page where you have placed the [jobs] shortcode. This lets the "
396
  "plugin know where the job listings page is located."
397
  msgstr ""
398
 
399
- #: includes/admin/class-wp-job-manager-settings.php:242
400
  msgid "Settings successfully saved"
401
  msgstr ""
402
 
403
- #: includes/admin/class-wp-job-manager-settings.php:302
404
  msgid "--no page--"
405
  msgstr ""
406
 
407
- #: includes/admin/class-wp-job-manager-settings.php:307
408
  msgid "Select a page&hellip;"
409
  msgstr ""
410
 
411
- #: includes/admin/class-wp-job-manager-settings.php:348
412
  msgid "Save Changes"
413
  msgstr ""
414
 
@@ -611,118 +624,126 @@ msgid "Help other users on the forums"
611
  msgstr ""
612
 
613
  #: includes/admin/class-wp-job-manager-writepanels.php:32
614
- #: includes/forms/class-wp-job-manager-form-submit-job.php:142
615
  msgid "e.g. \"London\""
616
  msgstr ""
617
 
618
  #: includes/admin/class-wp-job-manager-writepanels.php:33
619
- #: includes/forms/class-wp-job-manager-form-submit-job.php:139
620
  msgid "Leave this blank if the location is not important"
621
  msgstr ""
622
 
623
- #: includes/admin/class-wp-job-manager-writepanels.php:36
624
- #: includes/forms/class-wp-job-manager-form-submit-job.php:123
625
  msgid "Application email/URL"
626
  msgstr ""
627
 
628
- #: includes/admin/class-wp-job-manager-writepanels.php:37
629
  msgid "URL or email which applicants use to apply"
630
  msgstr ""
631
 
632
- #: includes/admin/class-wp-job-manager-writepanels.php:38
633
  msgid ""
634
  "This field is required for the \"application\" area to appear beneath the "
635
  "listing."
636
  msgstr ""
637
 
638
- #: includes/admin/class-wp-job-manager-writepanels.php:42
639
- #: includes/forms/class-wp-job-manager-form-submit-job.php:180
640
  msgid "Company name"
641
  msgstr ""
642
 
643
- #: includes/admin/class-wp-job-manager-writepanels.php:46
644
  msgid "Company website"
645
  msgstr ""
646
 
647
- #: includes/admin/class-wp-job-manager-writepanels.php:50
648
  msgid "Company tagline"
649
  msgstr ""
650
 
651
- #: includes/admin/class-wp-job-manager-writepanels.php:51
652
  msgid "Brief description about the company"
653
  msgstr ""
654
 
655
- #: includes/admin/class-wp-job-manager-writepanels.php:54
656
  msgid "Company Twitter"
657
  msgstr ""
658
 
659
- #: includes/admin/class-wp-job-manager-writepanels.php:58
660
  msgid "Company logo"
661
  msgstr ""
662
 
663
- #: includes/admin/class-wp-job-manager-writepanels.php:59
664
  msgid "URL to the company logo"
665
  msgstr ""
666
 
667
- #: includes/admin/class-wp-job-manager-writepanels.php:63
668
  msgid "Company video"
669
  msgstr ""
670
 
671
- #: includes/admin/class-wp-job-manager-writepanels.php:64
672
  msgid "URL to the company video"
673
  msgstr ""
674
 
675
- #: includes/admin/class-wp-job-manager-writepanels.php:68
676
  msgid "Position filled?"
677
  msgstr ""
678
 
679
- #: includes/admin/class-wp-job-manager-writepanels.php:74
680
  msgid "Feature this listing?"
681
  msgstr ""
682
 
683
- #: includes/admin/class-wp-job-manager-writepanels.php:76
684
  msgid ""
685
  "Featured listings will be sticky during searches, and can be styled "
686
  "differently."
687
  msgstr ""
688
 
689
- #: includes/admin/class-wp-job-manager-writepanels.php:80
690
  msgid "yyyy-mm-dd"
691
  msgstr ""
692
 
693
- #: includes/admin/class-wp-job-manager-writepanels.php:85
694
  msgid "Posted by"
695
  msgstr ""
696
 
697
- #: includes/admin/class-wp-job-manager-writepanels.php:102
698
  msgid "%s Data"
699
  msgstr ""
700
 
701
- #: includes/admin/class-wp-job-manager-writepanels.php:128
702
- #: includes/admin/class-wp-job-manager-writepanels.php:134
703
- #: includes/admin/class-wp-job-manager-writepanels.php:143
704
  msgid "Use file"
705
  msgstr ""
706
 
707
- #: includes/admin/class-wp-job-manager-writepanels.php:128
708
- #: includes/admin/class-wp-job-manager-writepanels.php:134
709
- #: includes/admin/class-wp-job-manager-writepanels.php:143
710
  msgid "Upload"
711
  msgstr ""
712
 
713
- #: includes/admin/class-wp-job-manager-writepanels.php:143
714
  msgid "Add file"
715
  msgstr ""
716
 
717
- #: includes/admin/class-wp-job-manager-writepanels.php:274
718
- msgid "Guest user"
 
 
 
 
719
  msgstr ""
720
 
721
- #: includes/class-wp-job-manager-ajax.php:120
 
 
 
 
722
  msgid "located in &ldquo;%s&rdquo;"
723
  msgstr ""
724
 
725
- #: includes/class-wp-job-manager-ajax.php:127
726
  msgid "Showing all %s"
727
  msgstr ""
728
 
@@ -740,12 +761,12 @@ msgstr ""
740
  msgid "Geocoding error"
741
  msgstr ""
742
 
743
- #: includes/class-wp-job-manager-install.php:54
744
  msgid "Employer"
745
  msgstr ""
746
 
747
  #: includes/class-wp-job-manager-post-types.php:62
748
- #: includes/forms/class-wp-job-manager-form-submit-job.php:155
749
  msgid "Job category"
750
  msgstr ""
751
 
@@ -803,7 +824,7 @@ msgid "New %s Name"
803
  msgstr ""
804
 
805
  #: includes/class-wp-job-manager-post-types.php:109
806
- #: includes/forms/class-wp-job-manager-form-submit-job.php:146
807
  msgid "Job type"
808
  msgstr ""
809
 
@@ -864,7 +885,7 @@ msgid "This is where you can create and manage %s."
864
  msgstr ""
865
 
866
  #: includes/class-wp-job-manager-post-types.php:217
867
- #: wp-job-manager-functions.php:165
868
  msgctxt "post status"
869
  msgid "Expired"
870
  msgstr ""
@@ -875,81 +896,92 @@ msgid_plural "Expired <span class=\"count\">(%s)</span>"
875
  msgstr[0] ""
876
  msgstr[1] ""
877
 
878
- #: includes/class-wp-job-manager-shortcodes.php:64
879
  msgid "Invalid ID"
880
  msgstr ""
881
 
882
- #: includes/class-wp-job-manager-shortcodes.php:71
883
  msgid "This position has already been filled"
884
  msgstr ""
885
 
886
- #: includes/class-wp-job-manager-shortcodes.php:77
887
  msgid "%s has been filled"
888
  msgstr ""
889
 
890
- #: includes/class-wp-job-manager-shortcodes.php:82
891
  msgid "This position is not filled"
892
  msgstr ""
893
 
894
- #: includes/class-wp-job-manager-shortcodes.php:89
895
  msgid "%s has been marked as not filled"
896
  msgstr ""
897
 
898
- #: includes/class-wp-job-manager-shortcodes.php:96
899
  msgid "%s has been deleted"
900
  msgstr ""
901
 
902
- #: includes/class-wp-job-manager-shortcodes.php:163
903
- #: includes/class-wp-job-manager-widgets.php:148
904
- #: includes/forms/class-wp-job-manager-form-submit-job.php:131
 
905
  msgid "Title"
906
  msgstr ""
907
 
908
- #: includes/class-wp-job-manager-shortcodes.php:165
909
  msgid "Date Posted"
910
  msgstr ""
911
 
912
- #: includes/class-wp-job-manager-shortcodes.php:166
913
  msgid "Date Expires"
914
  msgstr ""
915
 
916
- #: includes/class-wp-job-manager-shortcodes.php:261
917
- #: includes/class-wp-job-manager-shortcodes.php:295
918
  msgid "Load more listings"
919
  msgstr ""
920
 
921
- #: includes/class-wp-job-manager-widgets.php:141
922
  msgid ""
923
  "Display a list of recent listings on your site, optionally matching a "
924
  "keyword and location."
925
  msgstr ""
926
 
927
- #: includes/class-wp-job-manager-widgets.php:143
928
- #: includes/class-wp-job-manager-widgets.php:147
929
  msgid "Recent %s"
930
  msgstr ""
931
 
932
- #: includes/class-wp-job-manager-widgets.php:153
933
  msgid "Keyword"
934
  msgstr ""
935
 
936
- #: includes/class-wp-job-manager-widgets.php:166
 
937
  msgid "Number of listings to show"
938
  msgstr ""
939
 
940
- #: includes/forms/class-wp-job-manager-form-edit-job.php:41
 
 
 
 
 
 
 
 
 
941
  msgid "Invalid listing"
942
  msgstr ""
943
 
944
- #: includes/forms/class-wp-job-manager-form-edit-job.php:77
945
  msgid "Save changes"
946
  msgstr ""
947
 
948
- #: includes/forms/class-wp-job-manager-form-edit-job.php:104
949
  msgid "Your changes have been saved."
950
  msgstr ""
951
 
952
- #: includes/forms/class-wp-job-manager-form-edit-job.php:104
953
  msgid "View &rarr;"
954
  msgstr ""
955
 
@@ -958,7 +990,7 @@ msgid "Submit Details"
958
  msgstr ""
959
 
960
  #: includes/forms/class-wp-job-manager-form-submit-job.php:28
961
- #: includes/forms/class-wp-job-manager-form-submit-job.php:777
962
  msgid "Preview"
963
  msgstr ""
964
 
@@ -966,119 +998,118 @@ msgstr ""
966
  msgid "Done"
967
  msgstr ""
968
 
969
- #: includes/forms/class-wp-job-manager-form-submit-job.php:115
970
  msgid "Application email"
971
  msgstr ""
972
 
973
- #: includes/forms/class-wp-job-manager-form-submit-job.php:116
974
  #: templates/account-signin.php:49
975
  msgid "you@yourdomain.com"
976
  msgstr ""
977
 
978
- #: includes/forms/class-wp-job-manager-form-submit-job.php:119
979
  msgid "Application URL"
980
  msgstr ""
981
 
982
- #: includes/forms/class-wp-job-manager-form-submit-job.php:120
983
- #: includes/forms/class-wp-job-manager-form-submit-job.php:190
984
  msgid "http://"
985
  msgstr ""
986
 
987
- #: includes/forms/class-wp-job-manager-form-submit-job.php:124
988
  msgid "Enter an email address or website URL"
989
  msgstr ""
990
 
991
- #: includes/forms/class-wp-job-manager-form-submit-job.php:164
992
  msgid "Description"
993
  msgstr ""
994
 
995
- #: includes/forms/class-wp-job-manager-form-submit-job.php:183
996
  msgid "Enter the name of the company"
997
  msgstr ""
998
 
999
- #: includes/forms/class-wp-job-manager-form-submit-job.php:187
1000
- #: templates/content-single-job_listing-company.php:15
1001
  msgid "Website"
1002
  msgstr ""
1003
 
1004
- #: includes/forms/class-wp-job-manager-form-submit-job.php:194
1005
  msgid "Tagline"
1006
  msgstr ""
1007
 
1008
- #: includes/forms/class-wp-job-manager-form-submit-job.php:197
1009
  msgid "Briefly describe your company"
1010
  msgstr ""
1011
 
1012
- #: includes/forms/class-wp-job-manager-form-submit-job.php:202
1013
  msgid "Video"
1014
  msgstr ""
1015
 
1016
- #: includes/forms/class-wp-job-manager-form-submit-job.php:205
1017
  msgid "A link to a video about your company"
1018
  msgstr ""
1019
 
1020
- #: includes/forms/class-wp-job-manager-form-submit-job.php:209
1021
  msgid "Twitter username"
1022
  msgstr ""
1023
 
1024
- #: includes/forms/class-wp-job-manager-form-submit-job.php:212
1025
  msgid "@yourcompany"
1026
  msgstr ""
1027
 
1028
- #: includes/forms/class-wp-job-manager-form-submit-job.php:216
1029
  msgid "Logo"
1030
  msgstr ""
1031
 
1032
- #: includes/forms/class-wp-job-manager-form-submit-job.php:387
1033
  msgid "%s is a required field"
1034
  msgstr ""
1035
 
1036
- #: includes/forms/class-wp-job-manager-form-submit-job.php:393
1037
- #: includes/forms/class-wp-job-manager-form-submit-job.php:398
1038
  msgid "%s is invalid"
1039
  msgstr ""
1040
 
1041
- #: includes/forms/class-wp-job-manager-form-submit-job.php:412
 
 
 
 
 
1042
  msgid "Please enter a valid application email address"
1043
  msgstr ""
1044
 
1045
- #: includes/forms/class-wp-job-manager-form-submit-job.php:421
1046
  msgid "Please enter a valid application URL"
1047
  msgstr ""
1048
 
1049
- #: includes/forms/class-wp-job-manager-form-submit-job.php:431
1050
  msgid "Please enter a valid application email address or URL"
1051
  msgstr ""
1052
 
1053
- #: includes/forms/class-wp-job-manager-form-submit-job.php:539
1054
  msgid "Preview &rarr;"
1055
  msgstr ""
1056
 
1057
- #: includes/forms/class-wp-job-manager-form-submit-job.php:570
1058
  msgid "Please enter a username."
1059
  msgstr ""
1060
 
1061
- #: includes/forms/class-wp-job-manager-form-submit-job.php:573
1062
  msgid "Please enter your email address."
1063
  msgstr ""
1064
 
1065
- #: includes/forms/class-wp-job-manager-form-submit-job.php:591
1066
  msgid "You must be signed in to post a new listing."
1067
  msgstr ""
1068
 
1069
- #: includes/forms/class-wp-job-manager-form-submit-job.php:771
1070
  msgid "Submit Listing &rarr;"
1071
  msgstr ""
1072
 
1073
- #: includes/forms/class-wp-job-manager-form-submit-job.php:772
1074
  msgid "&larr; Edit listing"
1075
  msgstr ""
1076
 
1077
- #: includes/forms/class-wp-job-manager-form-submit-job.php:874
1078
- #: includes/forms/class-wp-job-manager-form-submit-job.php:900
1079
- msgid "\"%s\" (filetype %s) needs to be one of the following file types: %s"
1080
- msgstr ""
1081
-
1082
  #: templates/account-signin.php:4
1083
  msgid "Your account"
1084
  msgstr ""
@@ -1147,21 +1178,21 @@ msgstr ""
1147
  msgid "This position has been filled"
1148
  msgstr ""
1149
 
1150
- #: templates/content-single-job_listing.php:6
1151
- msgid "This listing has expired"
1152
  msgstr ""
1153
 
1154
- #: templates/form-fields/file-field.php:9
1155
- #: templates/form-fields/file-field.php:18
1156
- msgid "remove"
1157
  msgstr ""
1158
 
1159
- #: templates/form-fields/file-field.php:18
1160
- msgid "or"
1161
  msgstr ""
1162
 
1163
- #: templates/form-fields/file-field.php:30
1164
- msgid "Maximum file size: %s."
 
1165
  msgstr ""
1166
 
1167
  #: templates/job-application-email.php:1
@@ -1241,48 +1272,48 @@ msgstr ""
1241
  msgid "%s submitted successfully. Your listing will be visible once approved."
1242
  msgstr ""
1243
 
1244
- #: wp-job-manager-functions.php:164
1245
  msgctxt "post status"
1246
  msgid "Draft"
1247
  msgstr ""
1248
 
1249
- #: wp-job-manager-functions.php:166
1250
  msgctxt "post status"
1251
  msgid "Preview"
1252
  msgstr ""
1253
 
1254
- #: wp-job-manager-functions.php:167
1255
  msgctxt "post status"
1256
  msgid "Pending approval"
1257
  msgstr ""
1258
 
1259
- #: wp-job-manager-functions.php:168
1260
  msgctxt "post status"
1261
  msgid "Pending payment"
1262
  msgstr ""
1263
 
1264
- #: wp-job-manager-functions.php:169
1265
  msgctxt "post status"
1266
  msgid "Active"
1267
  msgstr ""
1268
 
1269
- #: wp-job-manager-functions.php:271
1270
  msgid "Reset"
1271
  msgstr ""
1272
 
1273
- #: wp-job-manager-functions.php:275
1274
  msgid "RSS"
1275
  msgstr ""
1276
 
1277
- #: wp-job-manager-functions.php:345
1278
  msgid "Invalid email address."
1279
  msgstr ""
1280
 
1281
- #: wp-job-manager-functions.php:353
1282
  msgid "Your email address isn&#8217;t correct."
1283
  msgstr ""
1284
 
1285
- #: wp-job-manager-functions.php:357
1286
  msgid "This email is already registered, please choose another one."
1287
  msgstr ""
1288
 
@@ -1290,19 +1321,31 @@ msgstr ""
1290
  msgid "Choose a category&hellip;"
1291
  msgstr ""
1292
 
 
 
 
 
1293
  #: wp-job-manager-template.php:135
1294
  msgid "Inactive"
1295
  msgstr ""
1296
 
1297
- #: wp-job-manager-template.php:211
1298
  msgid "Application via \"%s\" listing on %s"
1299
  msgstr ""
1300
 
1301
- #: wp-job-manager-template.php:272
1302
  msgid "Anywhere"
1303
  msgstr ""
1304
 
1305
- #: wp-job-manager.php:125
 
 
 
 
 
 
 
 
1306
  msgid "Are you sure you want to delete this listing?"
1307
  msgstr ""
1308
 
2
  # This file is distributed under the same license as the WP Job Manager package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Job Manager 1.21.0\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/support/plugin/WP-Job-Manager\n"
7
+ "POT-Creation-Date: 2015-03-14 16:56:24+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
17
  msgid "WP Job Manager Add-ons"
18
  msgstr ""
19
 
20
+ #: includes/admin/class-wp-job-manager-addons.php:59
21
+ msgid "Do you need multiple add-ons?"
22
+ msgstr ""
23
+
24
+ #: includes/admin/class-wp-job-manager-addons.php:59
25
+ msgid "Check out the core add-on bundle &rarr;"
26
  msgstr ""
27
 
28
  #: includes/admin/class-wp-job-manager-admin.php:58
114
 
115
  #: includes/admin/class-wp-job-manager-cpt.php:256
116
  #: includes/admin/class-wp-job-manager-writepanels.php:31
117
+ #: includes/class-wp-job-manager-widgets.php:165
118
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:149
119
  #: templates/job-filters.php:17 templates/job-filters.php:18
120
  msgid "Location"
121
  msgstr ""
125
  msgstr ""
126
 
127
  #: includes/admin/class-wp-job-manager-cpt.php:258
128
+ #: includes/admin/class-wp-job-manager-writepanels.php:89
129
  msgid "Expires"
130
  msgstr ""
131
 
132
  #: includes/admin/class-wp-job-manager-cpt.php:259
133
+ #: includes/admin/class-wp-job-manager-settings.php:73
134
  msgid "Categories"
135
  msgstr ""
136
 
139
  msgstr ""
140
 
141
  #: includes/admin/class-wp-job-manager-cpt.php:261
142
+ #: includes/class-wp-job-manager-shortcodes.php:165
143
  msgid "Filled?"
144
  msgstr ""
145
 
210
  msgstr ""
211
 
212
  #: includes/admin/class-wp-job-manager-settings.php:57
213
+ msgid "If enabled, filled positions will be hidden from archives."
214
  msgstr ""
215
 
216
  #: includes/admin/class-wp-job-manager-settings.php:64
217
+ msgid "Expired Listings"
218
  msgstr ""
219
 
220
  #: includes/admin/class-wp-job-manager-settings.php:65
221
+ msgid "Hide content within expired listings"
222
  msgstr ""
223
 
224
  #: includes/admin/class-wp-job-manager-settings.php:66
225
  msgid ""
226
+ "If enabled, the content within expired listings will be hidden. Otherwise, "
227
+ "expired listings will be displayed as normal (without the application area)."
228
+ msgstr ""
229
+
230
+ #: includes/admin/class-wp-job-manager-settings.php:74
231
+ msgid "Enable categories for listings"
232
+ msgstr ""
233
+
234
+ #: includes/admin/class-wp-job-manager-settings.php:75
235
+ msgid ""
236
  "Choose whether to enable categories. Categories must be setup by an admin to "
237
  "allow users to choose them during submission."
238
  msgstr ""
239
 
240
+ #: includes/admin/class-wp-job-manager-settings.php:82
241
  msgid "Multi-select Categories"
242
  msgstr ""
243
 
244
+ #: includes/admin/class-wp-job-manager-settings.php:83
245
  msgid "Enable category multiselect by default"
246
  msgstr ""
247
 
248
+ #: includes/admin/class-wp-job-manager-settings.php:84
249
+ #: includes/admin/class-wp-job-manager-settings.php:92
250
  msgid ""
251
  "If enabled, the category select box will default to a multiselect on the "
252
  "[jobs] shortcode."
253
  msgstr ""
254
 
255
+ #: includes/admin/class-wp-job-manager-settings.php:91
256
  msgid "Category Filter Type"
257
  msgstr ""
258
 
259
+ #: includes/admin/class-wp-job-manager-settings.php:95
260
  msgid "Jobs will be shown if within ANY selected category"
261
  msgstr ""
262
 
263
+ #: includes/admin/class-wp-job-manager-settings.php:96
264
  msgid "Jobs will be shown if within ALL selected categories"
265
  msgstr ""
266
 
267
+ #: includes/admin/class-wp-job-manager-settings.php:102
268
  msgid "Job Submission"
269
  msgstr ""
270
 
271
+ #: includes/admin/class-wp-job-manager-settings.php:107
272
  msgid "Account Required"
273
  msgstr ""
274
 
275
+ #: includes/admin/class-wp-job-manager-settings.php:108
276
  msgid "Submitting listings requires an account"
277
  msgstr ""
278
 
279
+ #: includes/admin/class-wp-job-manager-settings.php:109
280
  msgid ""
281
  "If disabled, non-logged in users will be able to submit listings without "
282
  "creating an account."
283
  msgstr ""
284
 
285
+ #: includes/admin/class-wp-job-manager-settings.php:116
286
  msgid "Account Creation"
287
  msgstr ""
288
 
289
+ #: includes/admin/class-wp-job-manager-settings.php:117
290
  msgid "Allow account creation"
291
  msgstr ""
292
 
293
+ #: includes/admin/class-wp-job-manager-settings.php:118
294
  msgid ""
295
  "If enabled, non-logged in users will be able to create an account by "
296
  "entering their email address on the submission form."
297
  msgstr ""
298
 
299
+ #: includes/admin/class-wp-job-manager-settings.php:125
300
+ msgid "Account Username"
301
  msgstr ""
302
 
303
+ #: includes/admin/class-wp-job-manager-settings.php:126
304
  msgid "Automatically Generate Username from Email Address"
305
  msgstr ""
306
 
307
+ #: includes/admin/class-wp-job-manager-settings.php:127
308
  msgid ""
309
  "If enabled, a username will be generated from the first part of the user "
310
  "email address. Otherwise, a username field will be shown."
311
  msgstr ""
312
 
313
+ #: includes/admin/class-wp-job-manager-settings.php:134
314
  msgid "Account Role"
315
  msgstr ""
316
 
317
+ #: includes/admin/class-wp-job-manager-settings.php:135
318
  msgid ""
319
  "If you enable registration on your submission form, choose a role for the "
320
  "new user."
321
  msgstr ""
322
 
323
+ #: includes/admin/class-wp-job-manager-settings.php:142
324
+ msgid "Moderate New Listings"
325
  msgstr ""
326
 
327
+ #: includes/admin/class-wp-job-manager-settings.php:143
328
+ msgid "New listing submissions require admin approval"
329
  msgstr ""
330
 
331
+ #: includes/admin/class-wp-job-manager-settings.php:144
332
  msgid "If enabled, new submissions will be inactive, pending admin approval."
333
  msgstr ""
334
 
335
+ #: includes/admin/class-wp-job-manager-settings.php:151
336
  msgid "Allow Pending Edits"
337
  msgstr ""
338
 
339
+ #: includes/admin/class-wp-job-manager-settings.php:152
340
  msgid "Submissions awaiting approval can be edited"
341
  msgstr ""
342
 
343
+ #: includes/admin/class-wp-job-manager-settings.php:153
344
  msgid ""
345
  "If enabled, submissions awaiting admin approval can be edited by the user."
346
  msgstr ""
347
 
348
+ #: includes/admin/class-wp-job-manager-settings.php:160
349
  msgid "Listing Duration"
350
  msgstr ""
351
 
352
+ #: includes/admin/class-wp-job-manager-settings.php:161
353
  msgid ""
354
  "How many <strong>days</strong> listings are live before expiring. Can be "
355
  "left blank to never expire."
356
  msgstr ""
357
 
358
+ #: includes/admin/class-wp-job-manager-settings.php:167
359
  msgid "Application Method"
360
  msgstr ""
361
 
362
+ #: includes/admin/class-wp-job-manager-settings.php:168
363
  msgid "Choose the contact method for listings."
364
  msgstr ""
365
 
366
+ #: includes/admin/class-wp-job-manager-settings.php:171
367
  msgid "Email address or website URL"
368
  msgstr ""
369
 
370
+ #: includes/admin/class-wp-job-manager-settings.php:172
371
  msgid "Email addresses only"
372
  msgstr ""
373
 
374
+ #: includes/admin/class-wp-job-manager-settings.php:173
375
  msgid "Website URLs only"
376
  msgstr ""
377
 
378
+ #: includes/admin/class-wp-job-manager-settings.php:179
379
  msgid "Pages"
380
  msgstr ""
381
 
382
+ #: includes/admin/class-wp-job-manager-settings.php:184
383
  msgid "Submit Job Form Page"
384
  msgstr ""
385
 
386
+ #: includes/admin/class-wp-job-manager-settings.php:185
387
  msgid ""
388
  "Select the page where you have placed the [submit_job_form] shortcode. This "
389
  "lets the plugin know where the form is located."
390
  msgstr ""
391
 
392
+ #: includes/admin/class-wp-job-manager-settings.php:191
393
  msgid "Job Dashboard Page"
394
  msgstr ""
395
 
396
+ #: includes/admin/class-wp-job-manager-settings.php:192
397
  msgid ""
398
  "Select the page where you have placed the [job_dashboard] shortcode. This "
399
  "lets the plugin know where the dashboard is located."
400
  msgstr ""
401
 
402
+ #: includes/admin/class-wp-job-manager-settings.php:198
403
  msgid "Job Listings Page"
404
  msgstr ""
405
 
406
+ #: includes/admin/class-wp-job-manager-settings.php:199
407
  msgid ""
408
  "Select the page where you have placed the [jobs] shortcode. This lets the "
409
  "plugin know where the job listings page is located."
410
  msgstr ""
411
 
412
+ #: includes/admin/class-wp-job-manager-settings.php:251
413
  msgid "Settings successfully saved"
414
  msgstr ""
415
 
416
+ #: includes/admin/class-wp-job-manager-settings.php:311
417
  msgid "--no page--"
418
  msgstr ""
419
 
420
+ #: includes/admin/class-wp-job-manager-settings.php:316
421
  msgid "Select a page&hellip;"
422
  msgstr ""
423
 
424
+ #: includes/admin/class-wp-job-manager-settings.php:357
425
  msgid "Save Changes"
426
  msgstr ""
427
 
624
  msgstr ""
625
 
626
  #: includes/admin/class-wp-job-manager-writepanels.php:32
627
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:153
628
  msgid "e.g. \"London\""
629
  msgstr ""
630
 
631
  #: includes/admin/class-wp-job-manager-writepanels.php:33
632
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:150
633
  msgid "Leave this blank if the location is not important"
634
  msgstr ""
635
 
636
+ #: includes/admin/class-wp-job-manager-writepanels.php:37
637
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:134
638
  msgid "Application email/URL"
639
  msgstr ""
640
 
641
+ #: includes/admin/class-wp-job-manager-writepanels.php:38
642
  msgid "URL or email which applicants use to apply"
643
  msgstr ""
644
 
645
+ #: includes/admin/class-wp-job-manager-writepanels.php:39
646
  msgid ""
647
  "This field is required for the \"application\" area to appear beneath the "
648
  "listing."
649
  msgstr ""
650
 
651
+ #: includes/admin/class-wp-job-manager-writepanels.php:44
652
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:191
653
  msgid "Company name"
654
  msgstr ""
655
 
656
+ #: includes/admin/class-wp-job-manager-writepanels.php:49
657
  msgid "Company website"
658
  msgstr ""
659
 
660
+ #: includes/admin/class-wp-job-manager-writepanels.php:54
661
  msgid "Company tagline"
662
  msgstr ""
663
 
664
+ #: includes/admin/class-wp-job-manager-writepanels.php:55
665
  msgid "Brief description about the company"
666
  msgstr ""
667
 
668
+ #: includes/admin/class-wp-job-manager-writepanels.php:59
669
  msgid "Company Twitter"
670
  msgstr ""
671
 
672
+ #: includes/admin/class-wp-job-manager-writepanels.php:64
673
  msgid "Company logo"
674
  msgstr ""
675
 
676
+ #: includes/admin/class-wp-job-manager-writepanels.php:65
677
  msgid "URL to the company logo"
678
  msgstr ""
679
 
680
+ #: includes/admin/class-wp-job-manager-writepanels.php:70
681
  msgid "Company video"
682
  msgstr ""
683
 
684
+ #: includes/admin/class-wp-job-manager-writepanels.php:71
685
  msgid "URL to the company video"
686
  msgstr ""
687
 
688
+ #: includes/admin/class-wp-job-manager-writepanels.php:76
689
  msgid "Position filled?"
690
  msgstr ""
691
 
692
+ #: includes/admin/class-wp-job-manager-writepanels.php:83
693
  msgid "Feature this listing?"
694
  msgstr ""
695
 
696
+ #: includes/admin/class-wp-job-manager-writepanels.php:85
697
  msgid ""
698
  "Featured listings will be sticky during searches, and can be styled "
699
  "differently."
700
  msgstr ""
701
 
702
+ #: includes/admin/class-wp-job-manager-writepanels.php:90
703
  msgid "yyyy-mm-dd"
704
  msgstr ""
705
 
706
+ #: includes/admin/class-wp-job-manager-writepanels.php:96
707
  msgid "Posted by"
708
  msgstr ""
709
 
710
+ #: includes/admin/class-wp-job-manager-writepanels.php:128
711
  msgid "%s Data"
712
  msgstr ""
713
 
714
+ #: includes/admin/class-wp-job-manager-writepanels.php:154
715
+ #: includes/admin/class-wp-job-manager-writepanels.php:160
716
+ #: includes/admin/class-wp-job-manager-writepanels.php:169
717
  msgid "Use file"
718
  msgstr ""
719
 
720
+ #: includes/admin/class-wp-job-manager-writepanels.php:154
721
+ #: includes/admin/class-wp-job-manager-writepanels.php:160
722
+ #: includes/admin/class-wp-job-manager-writepanels.php:169
723
  msgid "Upload"
724
  msgstr ""
725
 
726
+ #: includes/admin/class-wp-job-manager-writepanels.php:169
727
  msgid "Add file"
728
  msgstr ""
729
 
730
+ #: includes/admin/class-wp-job-manager-writepanels.php:305
731
+ msgid "Guest User"
732
+ msgstr ""
733
+
734
+ #: includes/admin/class-wp-job-manager-writepanels.php:307
735
+ msgid "Change"
736
  msgstr ""
737
 
738
+ #: includes/admin/class-wp-job-manager-writepanels.php:311
739
+ msgid "Enter the ID of the user, or leave blank if submitted by a guest."
740
+ msgstr ""
741
+
742
+ #: includes/class-wp-job-manager-ajax.php:124
743
  msgid "located in &ldquo;%s&rdquo;"
744
  msgstr ""
745
 
746
+ #: includes/class-wp-job-manager-ajax.php:131
747
  msgid "Showing all %s"
748
  msgstr ""
749
 
761
  msgid "Geocoding error"
762
  msgstr ""
763
 
764
+ #: includes/class-wp-job-manager-install.php:63
765
  msgid "Employer"
766
  msgstr ""
767
 
768
  #: includes/class-wp-job-manager-post-types.php:62
769
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:166
770
  msgid "Job category"
771
  msgstr ""
772
 
824
  msgstr ""
825
 
826
  #: includes/class-wp-job-manager-post-types.php:109
827
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:157
828
  msgid "Job type"
829
  msgstr ""
830
 
885
  msgstr ""
886
 
887
  #: includes/class-wp-job-manager-post-types.php:217
888
+ #: wp-job-manager-functions.php:180
889
  msgctxt "post status"
890
  msgid "Expired"
891
  msgstr ""
896
  msgstr[0] ""
897
  msgstr[1] ""
898
 
899
+ #: includes/class-wp-job-manager-shortcodes.php:65
900
  msgid "Invalid ID"
901
  msgstr ""
902
 
903
+ #: includes/class-wp-job-manager-shortcodes.php:72
904
  msgid "This position has already been filled"
905
  msgstr ""
906
 
907
+ #: includes/class-wp-job-manager-shortcodes.php:78
908
  msgid "%s has been filled"
909
  msgstr ""
910
 
911
+ #: includes/class-wp-job-manager-shortcodes.php:83
912
  msgid "This position is not filled"
913
  msgstr ""
914
 
915
+ #: includes/class-wp-job-manager-shortcodes.php:90
916
  msgid "%s has been marked as not filled"
917
  msgstr ""
918
 
919
+ #: includes/class-wp-job-manager-shortcodes.php:97
920
  msgid "%s has been deleted"
921
  msgstr ""
922
 
923
+ #: includes/class-wp-job-manager-shortcodes.php:164
924
+ #: includes/class-wp-job-manager-widgets.php:155
925
+ #: includes/class-wp-job-manager-widgets.php:260
926
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:142
927
  msgid "Title"
928
  msgstr ""
929
 
930
+ #: includes/class-wp-job-manager-shortcodes.php:166
931
  msgid "Date Posted"
932
  msgstr ""
933
 
934
+ #: includes/class-wp-job-manager-shortcodes.php:167
935
  msgid "Date Expires"
936
  msgstr ""
937
 
938
+ #: includes/class-wp-job-manager-shortcodes.php:262
939
+ #: includes/class-wp-job-manager-shortcodes.php:296
940
  msgid "Load more listings"
941
  msgstr ""
942
 
943
+ #: includes/class-wp-job-manager-widgets.php:148
944
  msgid ""
945
  "Display a list of recent listings on your site, optionally matching a "
946
  "keyword and location."
947
  msgstr ""
948
 
949
+ #: includes/class-wp-job-manager-widgets.php:150
950
+ #: includes/class-wp-job-manager-widgets.php:154
951
  msgid "Recent %s"
952
  msgstr ""
953
 
954
+ #: includes/class-wp-job-manager-widgets.php:160
955
  msgid "Keyword"
956
  msgstr ""
957
 
958
+ #: includes/class-wp-job-manager-widgets.php:173
959
+ #: includes/class-wp-job-manager-widgets.php:268
960
  msgid "Number of listings to show"
961
  msgstr ""
962
 
963
+ #: includes/class-wp-job-manager-widgets.php:253
964
+ msgid "Display a list of featured listings on your site."
965
+ msgstr ""
966
+
967
+ #: includes/class-wp-job-manager-widgets.php:255
968
+ #: includes/class-wp-job-manager-widgets.php:259
969
+ msgid "Featured %s"
970
+ msgstr ""
971
+
972
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:38
973
  msgid "Invalid listing"
974
  msgstr ""
975
 
976
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:74
977
  msgid "Save changes"
978
  msgstr ""
979
 
980
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:101
981
  msgid "Your changes have been saved."
982
  msgstr ""
983
 
984
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:101
985
  msgid "View &rarr;"
986
  msgstr ""
987
 
990
  msgstr ""
991
 
992
  #: includes/forms/class-wp-job-manager-form-submit-job.php:28
993
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:811
994
  msgid "Preview"
995
  msgstr ""
996
 
998
  msgid "Done"
999
  msgstr ""
1000
 
1001
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:126
1002
  msgid "Application email"
1003
  msgstr ""
1004
 
1005
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:127
1006
  #: templates/account-signin.php:49
1007
  msgid "you@yourdomain.com"
1008
  msgstr ""
1009
 
1010
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:130
1011
  msgid "Application URL"
1012
  msgstr ""
1013
 
1014
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:131
1015
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:201
1016
  msgid "http://"
1017
  msgstr ""
1018
 
1019
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:135
1020
  msgid "Enter an email address or website URL"
1021
  msgstr ""
1022
 
1023
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:175
1024
  msgid "Description"
1025
  msgstr ""
1026
 
1027
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:194
1028
  msgid "Enter the name of the company"
1029
  msgstr ""
1030
 
1031
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:198
1032
+ #: templates/content-single-job_listing-company.php:19
1033
  msgid "Website"
1034
  msgstr ""
1035
 
1036
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:205
1037
  msgid "Tagline"
1038
  msgstr ""
1039
 
1040
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:208
1041
  msgid "Briefly describe your company"
1042
  msgstr ""
1043
 
1044
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:213
1045
  msgid "Video"
1046
  msgstr ""
1047
 
1048
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:216
1049
  msgid "A link to a video about your company"
1050
  msgstr ""
1051
 
1052
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:220
1053
  msgid "Twitter username"
1054
  msgstr ""
1055
 
1056
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:223
1057
  msgid "@yourcompany"
1058
  msgstr ""
1059
 
1060
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:227
1061
  msgid "Logo"
1062
  msgstr ""
1063
 
1064
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:401
1065
  msgid "%s is a required field"
1066
  msgstr ""
1067
 
1068
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:411
 
1069
  msgid "%s is invalid"
1070
  msgstr ""
1071
 
1072
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:423
1073
+ #: wp-job-manager-functions.php:640
1074
+ msgid "\"%s\" (filetype %s) needs to be one of the following file types: %s"
1075
+ msgstr ""
1076
+
1077
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:437
1078
  msgid "Please enter a valid application email address"
1079
  msgstr ""
1080
 
1081
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:446
1082
  msgid "Please enter a valid application URL"
1083
  msgstr ""
1084
 
1085
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:456
1086
  msgid "Please enter a valid application email address or URL"
1087
  msgstr ""
1088
 
1089
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:564
1090
  msgid "Preview &rarr;"
1091
  msgstr ""
1092
 
1093
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:595
1094
  msgid "Please enter a username."
1095
  msgstr ""
1096
 
1097
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:598
1098
  msgid "Please enter your email address."
1099
  msgstr ""
1100
 
1101
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:616
1102
  msgid "You must be signed in to post a new listing."
1103
  msgstr ""
1104
 
1105
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:805
1106
  msgid "Submit Listing &rarr;"
1107
  msgstr ""
1108
 
1109
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:806
1110
  msgid "&larr; Edit listing"
1111
  msgstr ""
1112
 
 
 
 
 
 
1113
  #: templates/account-signin.php:4
1114
  msgid "Your account"
1115
  msgstr ""
1178
  msgid "This position has been filled"
1179
  msgstr ""
1180
 
1181
+ #: templates/content-single-job_listing-meta.php:24
1182
+ msgid "Applications have closed"
1183
  msgstr ""
1184
 
1185
+ #: templates/content-single-job_listing.php:5
1186
+ msgid "This listing has expired."
 
1187
  msgstr ""
1188
 
1189
+ #: templates/form-fields/file-field.php:29
1190
+ msgid "Maximum file size: %s."
1191
  msgstr ""
1192
 
1193
+ #: templates/form-fields/uploaded-file-html.php:6
1194
+ #: templates/form-fields/uploaded-file-html.php:8
1195
+ msgid "remove"
1196
  msgstr ""
1197
 
1198
  #: templates/job-application-email.php:1
1272
  msgid "%s submitted successfully. Your listing will be visible once approved."
1273
  msgstr ""
1274
 
1275
+ #: wp-job-manager-functions.php:179
1276
  msgctxt "post status"
1277
  msgid "Draft"
1278
  msgstr ""
1279
 
1280
+ #: wp-job-manager-functions.php:181
1281
  msgctxt "post status"
1282
  msgid "Preview"
1283
  msgstr ""
1284
 
1285
+ #: wp-job-manager-functions.php:182
1286
  msgctxt "post status"
1287
  msgid "Pending approval"
1288
  msgstr ""
1289
 
1290
+ #: wp-job-manager-functions.php:183
1291
  msgctxt "post status"
1292
  msgid "Pending payment"
1293
  msgstr ""
1294
 
1295
+ #: wp-job-manager-functions.php:184
1296
  msgctxt "post status"
1297
  msgid "Active"
1298
  msgstr ""
1299
 
1300
+ #: wp-job-manager-functions.php:269
1301
  msgid "Reset"
1302
  msgstr ""
1303
 
1304
+ #: wp-job-manager-functions.php:273
1305
  msgid "RSS"
1306
  msgstr ""
1307
 
1308
+ #: wp-job-manager-functions.php:343
1309
  msgid "Invalid email address."
1310
  msgstr ""
1311
 
1312
+ #: wp-job-manager-functions.php:351
1313
  msgid "Your email address isn&#8217;t correct."
1314
  msgstr ""
1315
 
1316
+ #: wp-job-manager-functions.php:355
1317
  msgid "This email is already registered, please choose another one."
1318
  msgstr ""
1319
 
1321
  msgid "Choose a category&hellip;"
1322
  msgstr ""
1323
 
1324
+ #: wp-job-manager-functions.php:642
1325
+ msgid "Uploaded files need to be one of the following file types: %s"
1326
+ msgstr ""
1327
+
1328
  #: wp-job-manager-template.php:135
1329
  msgid "Inactive"
1330
  msgstr ""
1331
 
1332
+ #: wp-job-manager-template.php:220
1333
  msgid "Application via \"%s\" listing on %s"
1334
  msgstr ""
1335
 
1336
+ #: wp-job-manager-template.php:281
1337
  msgid "Anywhere"
1338
  msgstr ""
1339
 
1340
+ #: wp-job-manager.php:134
1341
+ msgid "Invalid file type. Accepted types:"
1342
+ msgstr ""
1343
+
1344
+ #: wp-job-manager.php:146
1345
+ msgid "Load previous listings"
1346
+ msgstr ""
1347
+
1348
+ #: wp-job-manager.php:149
1349
  msgid "Are you sure you want to delete this listing?"
1350
  msgstr ""
1351
 
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: mikejolley
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=mike.jolley@me.com&currency_code=&amount=&return=&item_name=Buy+me+a+coffee+for+A+New+Job+Board+Plugin+for+WordPress
4
  Tags: job listing, job board, job, jobs, company, hiring, employment, employees, candidate, freelance, internship
5
- Requires at least: 3.8
6
  Tested up to: 4.1
7
- Stable tag: 1.20.1
8
 
9
  Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
10
 
@@ -119,6 +119,31 @@ You can view (and contribute) translations via the [Transifex project here](http
119
 
120
  == Changelog ==
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  = 1.20.1 =
123
  * Fix - Core template overrides.
124
  * Updated localisations.
2
  Contributors: mikejolley
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=mike.jolley@me.com&currency_code=&amount=&return=&item_name=Buy+me+a+coffee+for+A+New+Job+Board+Plugin+for+WordPress
4
  Tags: job listing, job board, job, jobs, company, hiring, employment, employees, candidate, freelance, internship
5
+ Requires at least: 4.1
6
  Tested up to: 4.1
7
+ Stable tag: 1.21.0
8
 
9
  Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
10
 
119
 
120
  == Changelog ==
121
 
122
+ = 1.21.0 =
123
+ * Feature - Ajax loading history - back button will take you back to current position in the search. If you are on > page 1, a 'load previous' button will be shown so you can paginate either way.
124
+ * Feature - Ajax file upload during job submission.
125
+ * Feature - Cookie set when submitting a job to allow resuming if you leave the page.
126
+ * Feature - job_apply shortcode to show application area in other places on your site.
127
+ * Feature - Allow admin fields to be priority sorted.
128
+ * Feature - Featured job widget.
129
+ * Feature - Scroll to top on pagination click.
130
+ * Feature - Option to "Hide content within expired listings". If disabled, expired listings will be listed normally with applications disabled.
131
+ * Fix - Prevent attachments being uploaded several times.
132
+ * Fix - Expiry date on first save.
133
+ * Fix - Relist should go back to form.
134
+ * Fix - jquery.com CDN for CSS.
135
+ * Tweak - Geocode street and street number separately.
136
+ * Tweak - File upload field markup.
137
+ * Tweak - Added filters around taxonomy definition.
138
+ * Tweak - Chanced search logic/query to use the new meta queries in 4.1.
139
+ * Tweak - Implement transient caching for searches.
140
+ * Tweak - Removed wp_dropdown_user due to performance concerns.
141
+ * Tweak - Use menu_order to make featured listings sticky. Improves performance.
142
+ * Tweak - Retrieve AJAX jobs with GET rather than POST request to take advantage of more caching. Plugins looking for POST data will need to update to look for GET/REQUEST instead.
143
+ * Tweak - Prevent themes that (sigh) mess with content hooks from breaking inputs.
144
+ * Tweak - Remove unused job-category field.
145
+ * Tweak - Hide company div if company name missing.
146
+
147
  = 1.20.1 =
148
  * Fix - Core template overrides.
149
  * Updated localisations.
templates/content-single-job_listing-company.php CHANGED
@@ -6,6 +6,10 @@
6
  *
7
  * @since 1.14.0
8
  */
 
 
 
 
9
  ?>
10
  <div class="company" itemscope itemtype="http://data-vocabulary.org/Organization">
11
  <?php the_company_logo(); ?>
6
  *
7
  * @since 1.14.0
8
  */
9
+
10
+ if ( ! get_the_company_name() ) {
11
+ return;
12
+ }
13
  ?>
14
  <div class="company" itemscope itemtype="http://data-vocabulary.org/Organization">
15
  <?php the_company_logo(); ?>
templates/content-single-job_listing-meta.php CHANGED
@@ -20,6 +20,8 @@
20
 
21
  <?php if ( is_position_filled() ) : ?>
22
  <li class="position-filled"><?php _e( 'This position has been filled', 'wp-job-manager' ); ?></li>
 
 
23
  <?php endif; ?>
24
 
25
  <?php do_action( 'single_job_listing_meta_end' ); ?>
20
 
21
  <?php if ( is_position_filled() ) : ?>
22
  <li class="position-filled"><?php _e( 'This position has been filled', 'wp-job-manager' ); ?></li>
23
+ <?php elseif ( ! candidates_can_apply() ) : ?>
24
+ <li class="listing-expired"><?php _e( 'Applications have closed', 'wp-job-manager' ); ?></li>
25
  <?php endif; ?>
26
 
27
  <?php do_action( 'single_job_listing_meta_end' ); ?>
templates/content-single-job_listing.php CHANGED
@@ -1,35 +1,33 @@
1
  <div class="single_job_listing" itemscope itemtype="http://schema.org/JobPosting">
2
  <meta itemprop="title" content="<?php echo esc_attr( $post->post_title ); ?>" />
3
 
4
- <?php if ( $post->post_status == 'expired' ) : ?>
5
-
6
- <div class="job-manager-info"><?php _e( 'This listing has expired', 'wp-job-manager' ); ?></div>
7
-
8
  <?php else : ?>
9
 
10
- <?php
11
  /**
12
  * single_job_listing_start hook
13
  *
14
  * @hooked job_listing_meta_display - 20
15
  * @hooked job_listing_company_display - 30
16
  */
17
- do_action( 'single_job_listing_start' );
18
  ?>
19
 
20
  <div class="job_description" itemprop="description">
21
  <?php echo apply_filters( 'the_job_description', get_the_content() ); ?>
22
  </div>
23
 
24
- <?php if ( ! is_position_filled() && $post->post_status !== 'preview' ) : ?>
25
  <?php get_job_manager_template( 'job-application.php' ); ?>
26
  <?php endif; ?>
27
 
28
- <?php
29
  /**
30
  * single_job_listing_end hook
31
  */
32
- do_action( 'single_job_listing_end' );
33
  ?>
34
 
35
  <?php endif; ?>
1
  <div class="single_job_listing" itemscope itemtype="http://schema.org/JobPosting">
2
  <meta itemprop="title" content="<?php echo esc_attr( $post->post_title ); ?>" />
3
 
4
+ <?php if ( get_option( 'job_manager_hide_expired_content', 1 ) && 'expired' === $post->post_status ) : ?>
5
+ <div class="job-manager-info"><?php _e( 'This listing has expired.', 'wp-job-manager' ); ?></div>
 
 
6
  <?php else : ?>
7
 
8
+ <?php
9
  /**
10
  * single_job_listing_start hook
11
  *
12
  * @hooked job_listing_meta_display - 20
13
  * @hooked job_listing_company_display - 30
14
  */
15
+ do_action( 'single_job_listing_start' );
16
  ?>
17
 
18
  <div class="job_description" itemprop="description">
19
  <?php echo apply_filters( 'the_job_description', get_the_content() ); ?>
20
  </div>
21
 
22
+ <?php if ( candidates_can_apply() ) : ?>
23
  <?php get_job_manager_template( 'job-application.php' ); ?>
24
  <?php endif; ?>
25
 
26
+ <?php
27
  /**
28
  * single_job_listing_end hook
29
  */
30
+ do_action( 'single_job_listing_end' );
31
  ?>
32
 
33
  <?php endif; ?>
templates/form-fields/checkbox-field.php CHANGED
@@ -1,10 +1,2 @@
1
- <input
2
- type="checkbox"
3
- class="input-checkbox"
4
- name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"
5
- id="<?php echo esc_attr( $key ); ?>"
6
- <?php checked( ! empty( $field['value'] ), true ); ?>
7
- value="1"
8
- <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?>
9
- />
10
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
1
+ <input type="checkbox" class="input-checkbox" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" id="<?php echo esc_attr( $key ); ?>" <?php checked( ! empty( $field['value'] ), true ); ?> value="1" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?> />
 
 
 
 
 
 
 
 
2
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
templates/form-fields/file-field.php CHANGED
@@ -1,28 +1,27 @@
1
- <?php if ( ! empty( $field['value'] ) ) : ?>
2
- <div class="job-manager-uploaded-files">
3
- <?php if ( is_array( $field['value'] ) ) : ?>
4
- <?php foreach ( $field['value'] as $value ) : ?>
5
- <div class="job-manager-uploaded-file">
6
- <?php if ( in_array( substr( strrchr( $value, '.' ), 1 ), array( 'jpg', 'gif', 'png', 'jpeg', 'jpe' ) ) ) : ?>
7
- <span class="job-manager-uploaded-file-preview"><img src="<?php echo $value; ?>" /></span>
8
- <?php endif; ?>
9
- <?php echo '<span class="job-manager-uploaded-file-name"><code>' . basename( $value ) . ' <a class="job-manager-remove-uploaded-file" href="#">[' . __( 'remove', 'wp-job-manager' ) . ']</a></code></span>'; ?>
10
- <input type="hidden" class="input-text" name="current_<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>[]" value="<?php echo esc_attr( $value ); ?>" />
11
- </div>
12
- <?php endforeach; ?>
13
- <?php elseif ( $value = $field['value'] ) : ?>
14
- <div class="job-manager-uploaded-file">
15
- <?php if ( in_array( substr( strrchr( $value, '.' ), 1 ), array( 'jpg', 'gif', 'png', 'jpeg', 'jpe' ) ) ) : ?>
16
- <span class="job-manager-uploaded-file-preview"><img src="<?php echo $value; ?>" /></span>
17
- <?php endif; ?>
18
- <?php echo '<span class="job-manager-uploaded-file-name"><code>' . basename( $value ) . ' <a class="job-manager-remove-uploaded-file" href="#">[' . __( 'remove', 'wp-job-manager' ) . ']</a></code> ' . __( 'or', 'wp-job-manager' ) . '&hellip;</span>'; ?>
19
- <input type="hidden" class="input-text" name="current_<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" value="<?php echo esc_attr( $field['value'] ); ?>" />
20
- </div>
21
  <?php endif; ?>
22
- </div>
23
- <?php endif; ?>
24
 
25
- <input type="file" class="input-text" <?php if ( ! empty( $field['multiple'] ) ) echo 'multiple'; ?> name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?><?php if ( ! empty( $field['multiple'] ) ) echo '[]'; ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo empty( $field['placeholder'] ) ? '' : esc_attr( $field['placeholder'] ); ?>" />
26
  <small class="description">
27
  <?php if ( ! empty( $field['description'] ) ) : ?>
28
  <?php echo $field['description']; ?>
1
+ <?php
2
+ $classes = array( 'input-text' );
3
+ $allowed_mime_types = array_keys( ! empty( $field['allowed_mime_types'] ) ? $field['allowed_mime_types'] : get_allowed_mime_types() );
4
+ $field_name = isset( $field['name'] ) ? $field['name'] : $key;
5
+ $field_name .= ! empty( $field['multiple'] ) ? '[]' : '';
6
+
7
+ if ( ! empty( $field['ajax'] ) ) {
8
+ wp_enqueue_script( 'wp-job-manager-ajax-file-upload' );
9
+ $classes[] = 'wp-job-manager-file-upload';
10
+ }
11
+ ?>
12
+ <div class="job-manager-uploaded-files">
13
+ <?php if ( ! empty( $field['value'] ) ) : ?>
14
+ <?php if ( is_array( $field['value'] ) ) : ?>
15
+ <?php foreach ( $field['value'] as $value ) : ?>
16
+ <?php get_job_manager_template( 'form-fields/uploaded-file-html.php', array( 'key' => $key, 'name' => 'current_' . $field_name, 'value' => $value, 'field' => $field ) ); ?>
17
+ <?php endforeach; ?>
18
+ <?php elseif ( $value = $field['value'] ) : ?>
19
+ <?php get_job_manager_template( 'form-fields/uploaded-file-html.php', array( 'key' => $key, 'name' => 'current_' . $field_name, 'value' => $value, 'field' => $field ) ); ?>
20
+ <?php endif; ?>
21
  <?php endif; ?>
22
+ </div>
 
23
 
24
+ <input type="file" class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>" data-file_types="<?php echo esc_attr( implode( '|', $allowed_mime_types ) ); ?>" <?php if ( ! empty( $field['multiple'] ) ) echo 'multiple'; ?> name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?><?php if ( ! empty( $field['multiple'] ) ) echo '[]'; ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo empty( $field['placeholder'] ) ? '' : esc_attr( $field['placeholder'] ); ?>" <?php if ( ! empty( $field['required'] ) && empty( $field['value'] ) ) echo 'required'; ?> />
25
  <small class="description">
26
  <?php if ( ! empty( $field['description'] ) ) : ?>
27
  <?php echo $field['description']; ?>
templates/form-fields/job-category-field.php DELETED
@@ -1,10 +0,0 @@
1
- <?php wp_dropdown_categories( array(
2
- 'taxonomy' => 'job_listing_category',
3
- 'hierarchical' => 1,
4
- 'show_option_all' => false,
5
- 'name' => isset( $field['name'] ) ? $field['name'] : $key,
6
- 'orderby' => 'name',
7
- 'selected' => isset( $field['value'] ) ? $field['value'] : $field['default'],
8
- 'hide_empty' => false
9
- ) );
10
- if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
 
 
 
 
 
 
 
 
 
 
templates/form-fields/password-field.php CHANGED
@@ -1,11 +1,2 @@
1
- <input
2
- type="password"
3
- class="input-text"
4
- name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"
5
- id="<?php echo esc_attr( $key ); ?>"
6
- placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>"
7
- value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>"
8
- maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>"
9
- <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?>
10
- />
11
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
1
+ <input type="password" class="input-text" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>" maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?> />
 
 
 
 
 
 
 
 
 
2
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
templates/form-fields/term-select-field.php CHANGED
@@ -1,5 +1,4 @@
1
- <?php
2
-
3
  // Get selected value
4
  if ( isset( $field['value'] ) ) {
5
  $selected = $field['value'];
@@ -16,13 +15,13 @@ if ( is_array( $selected ) ) {
16
  $selected = current( $selected );
17
  }
18
 
19
- wp_dropdown_categories( array(
20
  'taxonomy' => $field['taxonomy'],
21
- 'hierarchical' => 1,
22
  'show_option_all' => false,
23
  'show_option_none' => $field['required'] ? '' : '-',
24
- 'name' => isset( $field['name'] ) ? $field['name'] : $key,
25
- 'orderby' => 'name',
26
  'selected' => $selected,
27
  'hide_empty' => false
28
  ) );
1
+ <?php
 
2
  // Get selected value
3
  if ( isset( $field['value'] ) ) {
4
  $selected = $field['value'];
15
  $selected = current( $selected );
16
  }
17
 
18
+ wp_dropdown_categories( array(
19
  'taxonomy' => $field['taxonomy'],
20
+ 'hierarchical' => 1,
21
  'show_option_all' => false,
22
  'show_option_none' => $field['required'] ? '' : '-',
23
+ 'name' => isset( $field['name'] ) ? $field['name'] : $key,
24
+ 'orderby' => 'name',
25
  'selected' => $selected,
26
  'hide_empty' => false
27
  ) );
templates/form-fields/text-field.php CHANGED
@@ -1,11 +1,2 @@
1
- <input
2
- type="text"
3
- class="input-text"
4
- name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"
5
- id="<?php echo esc_attr( $key ); ?>"
6
- placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>"
7
- value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>"
8
- maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>"
9
- <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?>
10
- />
11
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
1
+ <input type="text" class="input-text" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>" maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?> />
 
 
 
 
 
 
 
 
 
2
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
templates/form-fields/textarea-field.php CHANGED
@@ -1,11 +1,2 @@
1
- <textarea
2
- cols="20"
3
- rows="3"
4
- class="input-text"
5
- name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"
6
- id="<?php echo esc_attr( $key ); ?>"
7
- placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>"
8
- maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>"
9
- <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?>
10
- ><?php echo isset( $field['value'] ) ? esc_textarea( $field['value'] ) : ''; ?></textarea>
11
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
1
+ <textarea cols="20" rows="3" class="input-text" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" maxlength="<?php echo ! empty( $field['maxlength'] ) ? $field['maxlength'] : ''; ?>" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?>><?php echo isset( $field['value'] ) ? esc_textarea( $field['value'] ) : ''; ?></textarea>
 
 
 
 
 
 
 
 
 
2
  <?php if ( ! empty( $field['description'] ) ) : ?><small class="description"><?php echo $field['description']; ?></small><?php endif; ?>
templates/form-fields/uploaded-file-html.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="job-manager-uploaded-file">
2
+ <?php
3
+ $extension = ! empty( $extension ) ? $extension : substr( strrchr( $value, '.' ), 1 );
4
+
5
+ if ( 3 !== strlen( $extension ) || in_array( $extension, array( 'jpg', 'gif', 'png', 'jpeg', 'jpe' ) ) ) : ?>
6
+ <span class="job-manager-uploaded-file-preview"><img src="<?php echo esc_url( $value ); ?>" /> <a class="job-manager-remove-uploaded-file" href="#">[<?php _e( 'remove', 'wp-job-manager' ); ?>]</a></span>
7
+ <?php else : ?>
8
+ <span class="job-manager-uploaded-file-name"><code><?php echo esc_html( basename( $value ) ); ?></code> <a class="job-manager-remove-uploaded-file" href="#">[<?php _e( 'remove', 'wp-job-manager' ); ?>]</a></span>
9
+ <?php endif; ?>
10
+
11
+ <input type="hidden" class="input-text" name="<?php echo esc_attr( $name ); ?>" value="<?php echo esc_attr( $value ); ?>" />
12
+ </div>
templates/job-filters.php CHANGED
@@ -27,8 +27,8 @@
27
  <label for="search_categories"><?php _e( 'Category', 'wp-job-manager' ); ?></label>
28
  <?php if ( $show_category_multiselect ) : ?>
29
  <?php job_manager_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category, 'hide_empty' => false ) ); ?>
30
- <?php else : ?>
31
- <?php wp_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'show_option_all' => __( 'Any category', 'wp-job-manager' ), 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category ) ); ?>
32
  <?php endif; ?>
33
  </div>
34
  <?php endif; ?>
27
  <label for="search_categories"><?php _e( 'Category', 'wp-job-manager' ); ?></label>
28
  <?php if ( $show_category_multiselect ) : ?>
29
  <?php job_manager_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category, 'hide_empty' => false ) ); ?>
30
+ <?php else : ?>
31
+ <?php job_manager_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'show_option_all' => __( 'Any category', 'wp-job-manager' ), 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category, 'multiple' => false ) ); ?>
32
  <?php endif; ?>
33
  </div>
34
  <?php endif; ?>
wp-job-manager-functions.php CHANGED
@@ -7,50 +7,53 @@ if ( ! function_exists( 'get_job_listings' ) ) :
7
  * @return void
8
  */
9
  function get_job_listings( $args = array() ) {
10
- global $wpdb;
11
 
12
  $args = wp_parse_args( $args, array(
13
  'search_location' => '',
14
  'search_keywords' => '',
15
  'search_categories' => array(),
16
  'job_types' => array(),
17
- 'offset' => '',
18
- 'posts_per_page' => '-1',
19
  'orderby' => 'date',
20
  'order' => 'DESC',
21
  'featured' => null,
22
- 'filled' => null
 
23
  ) );
24
 
25
  $query_args = array(
26
- 'post_type' => 'job_listing',
27
- 'post_status' => 'publish',
28
- 'ignore_sticky_posts' => 1,
29
- 'offset' => absint( $args['offset'] ),
30
- 'posts_per_page' => intval( $args['posts_per_page'] ),
31
- 'orderby' => $args['orderby'],
32
- 'order' => $args['order'],
33
- 'tax_query' => array(),
34
- 'meta_query' => array()
 
 
 
 
35
  );
36
 
37
- if ( ! empty( $args['job_types'] ) ) {
38
- $query_args['tax_query'][] = array(
39
- 'taxonomy' => 'job_listing_type',
40
- 'field' => 'slug',
41
- 'terms' => $args['job_types']
42
- );
43
  }
44
 
45
- if ( ! empty( $args['search_categories'] ) ) {
46
- $field = is_numeric( $args['search_categories'][0] ) ? 'term_id' : 'slug';
47
-
48
- $query_args['tax_query'][] = array(
49
- 'taxonomy' => 'job_listing_category',
50
- 'field' => $field,
51
- 'terms' => $args['search_categories'],
52
- 'operator' => get_option( 'job_manager_category_filter_type', 'all' ) == 'all' ? 'AND' : 'IN'
53
- );
 
 
54
  }
55
 
56
  if ( ! is_null( $args['featured'] ) ) {
@@ -61,97 +64,109 @@ function get_job_listings( $args = array() ) {
61
  );
62
  }
63
 
64
- if ( ! is_null( $args['filled'] ) ) {
65
  $query_args['meta_query'][] = array(
66
  'key' => '_filled',
67
  'value' => '1',
68
  'compare' => $args['filled'] ? '=' : '!='
69
  );
70
- } elseif ( get_option( 'job_manager_hide_filled_positions' ) == 1 ) {
71
- $query_args['meta_query'][] = array(
72
- 'key' => '_filled',
73
- 'value' => '1',
74
- 'compare' => '!='
75
- );
76
  }
77
 
78
- // Location search - search geolocation data and location meta
79
- if ( $args['search_location'] ) {
80
- $location_post_ids = $wpdb->get_col( apply_filters( 'get_job_listings_location_post_ids_sql', $wpdb->prepare( "
81
- SELECT DISTINCT post_id FROM {$wpdb->postmeta}
82
- WHERE meta_key IN ( 'geolocation_city', 'geolocation_country_long', 'geolocation_country_short', 'geolocation_formatted_address', 'geolocation_state_long', 'geolocation_state_short', 'geolocation_street', 'geolocation_zipcode', '_job_location' )
83
- AND meta_value LIKE '%%%s%%'
84
- ", $args['search_location'] ) ) );
85
-
86
- $location_post_ids = array_merge( $location_post_ids, array( 0 ) );
87
- } else {
88
- $location_post_ids = array();
89
  }
90
 
91
- // Keyword search - search meta as well as post content
92
- if ( $args['search_keywords'] ) {
93
- $search_keywords = array_map( 'trim', explode( ',', $args['search_keywords'] ) );
94
- $posts_search_keywords_sql = array();
95
- $postmeta_search_keywords_sql = array();
96
-
97
- foreach ( $search_keywords as $keyword ) {
98
- $postmeta_search_keywords_sql[] = " meta_value LIKE '%" . esc_sql( $keyword ) . "%' ";
99
- $posts_search_keywords_sql[] = "
100
- post_title LIKE '%" . esc_sql( $keyword ) . "%'
101
- OR post_content LIKE '%" . esc_sql( $keyword ) . "%'
102
- ";
103
- }
104
-
105
- $keyword_post_ids = $wpdb->get_col( "
106
- SELECT DISTINCT post_id FROM {$wpdb->postmeta}
107
- WHERE " . implode( ' OR ', $postmeta_search_keywords_sql ) . "
108
- " );
109
 
110
- $keyword_post_ids = array_merge( $keyword_post_ids, $wpdb->get_col( "
111
- SELECT ID FROM {$wpdb->posts}
112
- WHERE ( " . implode( ' OR ', $posts_search_keywords_sql ) . " )
113
- AND post_type = 'job_listing'
114
- " ), array( 0 ) );
115
- } else {
116
- $keyword_post_ids = array();
117
  }
118
 
119
- // Merge post ids
120
- if ( ! empty( $location_post_ids ) && ! empty( $keyword_post_ids ) ) {
121
- $query_args['post__in'] = array_intersect( $location_post_ids, $keyword_post_ids );
122
- } elseif ( ! empty( $location_post_ids ) || ! empty( $keyword_post_ids ) ) {
123
- $query_args['post__in'] = array_merge( $location_post_ids, $keyword_post_ids );
124
  }
125
 
126
  $query_args = apply_filters( 'job_manager_get_listings', $query_args, $args );
127
 
128
- if ( empty( $query_args['meta_query'] ) )
129
  unset( $query_args['meta_query'] );
 
130
 
131
- if ( empty( $query_args['tax_query'] ) )
132
  unset( $query_args['tax_query'] );
133
-
134
- if ( $args['orderby'] == 'featured' ) {
135
- $query_args['orderby'] = 'meta_key';
136
- $query_args['meta_key'] = '_featured';
137
- add_filter( 'posts_clauses', 'order_featured_job_listing' );
138
  }
139
 
140
  // Filter args
141
  $query_args = apply_filters( 'get_job_listings_query_args', $query_args, $args );
142
 
 
 
 
143
  do_action( 'before_get_job_listings', $query_args, $args );
144
 
145
- $result = new WP_Query( $query_args );
 
 
 
 
146
 
147
  do_action( 'after_get_job_listings', $query_args, $args );
148
 
149
  remove_filter( 'posts_clauses', 'order_featured_job_listing' );
 
150
 
151
  return $result;
152
  }
153
  endif;
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  if ( ! function_exists( 'get_job_listing_post_statuses' ) ) :
156
  /**
157
  * Get post statuses used for jobs
@@ -171,23 +186,6 @@ function get_job_listing_post_statuses() {
171
  }
172
  endif;
173
 
174
- if ( ! function_exists( 'order_featured_job_listing' ) ) :
175
- /**
176
- * WP Core doens't let us change the sort direction for invidual orderby params - http://core.trac.wordpress.org/ticket/17065
177
- *
178
- * @access public
179
- * @param array $args
180
- * @return array
181
- */
182
- function order_featured_job_listing( $args ) {
183
- global $wpdb;
184
-
185
- $args['orderby'] = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_date DESC";
186
-
187
- return $args;
188
- }
189
- endif;
190
-
191
  if ( ! function_exists( 'get_featured_job_ids' ) ) :
192
  /**
193
  * Gets the ids of featured jobs.
@@ -478,22 +476,24 @@ function job_manager_user_can_edit_pending_submissions() {
478
  */
479
  function job_manager_dropdown_categories( $args = '' ) {
480
  $defaults = array(
481
- 'orderby' => 'id',
482
- 'order' => 'ASC',
483
- 'show_count' => 0,
484
- 'hide_empty' => 1,
485
- 'child_of' => 0,
486
- 'exclude' => '',
487
- 'echo' => 1,
488
- 'selected' => 0,
489
- 'hierarchical' => 0,
490
- 'name' => 'cat',
491
- 'id' => '',
492
- 'class' => 'job-manager-category-dropdown ' . ( is_rtl() ? 'chosen-rtl' : '' ),
493
- 'depth' => 0,
494
- 'taxonomy' => 'job_listing_category',
495
- 'value' => 'id',
496
- 'placeholder' => __( 'Choose a category&hellip;', 'wp-job-manager' )
 
 
497
  );
498
 
499
  $r = wp_parse_args( $args, $defaults );
@@ -504,12 +504,23 @@ function job_manager_dropdown_categories( $args = '' ) {
504
 
505
  extract( $r );
506
 
507
- $categories = get_terms( $taxonomy, $r );
 
 
 
 
 
 
 
508
  $name = esc_attr( $name );
509
  $class = esc_attr( $class );
510
  $id = $id ? esc_attr( $id ) : $name;
511
 
512
- $output = "<select name='{$name}[]' id='$id' class='$class' multiple='multiple' data-placeholder='{$placeholder}'>\n";
 
 
 
 
513
 
514
  if ( ! empty( $categories ) ) {
515
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php' );
@@ -546,4 +557,105 @@ function job_manager_get_permalink( $page ) {
546
  } else {
547
  return false;
548
  }
549
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  * @return void
8
  */
9
  function get_job_listings( $args = array() ) {
10
+ global $wpdb, $job_manager_keyword;
11
 
12
  $args = wp_parse_args( $args, array(
13
  'search_location' => '',
14
  'search_keywords' => '',
15
  'search_categories' => array(),
16
  'job_types' => array(),
17
+ 'offset' => 0,
18
+ 'posts_per_page' => 20,
19
  'orderby' => 'date',
20
  'order' => 'DESC',
21
  'featured' => null,
22
+ 'filled' => null,
23
+ 'fields' => 'all'
24
  ) );
25
 
26
  $query_args = array(
27
+ 'post_type' => 'job_listing',
28
+ 'post_status' => 'publish',
29
+ 'ignore_sticky_posts' => 1,
30
+ 'offset' => absint( $args['offset'] ),
31
+ 'posts_per_page' => intval( $args['posts_per_page'] ),
32
+ 'orderby' => $args['orderby'],
33
+ 'order' => $args['order'],
34
+ 'tax_query' => array(),
35
+ 'meta_query' => array(),
36
+ 'update_post_term_cache' => false,
37
+ 'update_post_meta_cache' => false,
38
+ 'cache_results' => false,
39
+ 'fields' => $args['fields']
40
  );
41
 
42
+ if ( $args['posts_per_page'] < 0 ) {
43
+ $query_args['no_found_rows'] = true;
 
 
 
 
44
  }
45
 
46
+ if ( ! empty( $args['search_location'] ) ) {
47
+ $location_meta_keys = array( 'geolocation_formatted_address', '_job_location' );
48
+ $location_search = array( 'relation' => 'OR' );
49
+ foreach ( $location_meta_keys as $meta_key ) {
50
+ $location_search[] = array(
51
+ 'key' => $meta_key,
52
+ 'value' => $args['search_location'],
53
+ 'compare' => 'like'
54
+ );
55
+ }
56
+ $query_args['meta_query'][] = $location_search;
57
  }
58
 
59
  if ( ! is_null( $args['featured'] ) ) {
64
  );
65
  }
66
 
67
+ if ( ! is_null( $args['filled'] ) || 1 === absint( get_option( 'job_manager_hide_filled_positions' ) ) ) {
68
  $query_args['meta_query'][] = array(
69
  'key' => '_filled',
70
  'value' => '1',
71
  'compare' => $args['filled'] ? '=' : '!='
72
  );
 
 
 
 
 
 
73
  }
74
 
75
+ if ( ! empty( $args['job_types'] ) ) {
76
+ $query_args['tax_query'][] = array(
77
+ 'taxonomy' => 'job_listing_type',
78
+ 'field' => 'slug',
79
+ 'terms' => $args['job_types']
80
+ );
 
 
 
 
 
81
  }
82
 
83
+ if ( ! empty( $args['search_categories'] ) ) {
84
+ $field = is_numeric( $args['search_categories'][0] ) ? 'term_id' : 'slug';
85
+ $query_args['tax_query'][] = array(
86
+ 'taxonomy' => 'job_listing_category',
87
+ 'field' => $field,
88
+ 'terms' => $args['search_categories'],
89
+ 'operator' => 'all' === get_option( 'job_manager_category_filter_type', 'all' ) ? 'AND' : 'IN'
90
+ );
91
+ }
 
 
 
 
 
 
 
 
 
92
 
93
+ if ( 'featured' === $args['orderby'] ) {
94
+ add_filter( 'posts_clauses', 'order_featured_job_listing' );
 
 
 
 
 
95
  }
96
 
97
+ if ( $job_manager_keyword = sanitize_text_field( $args['search_keywords'] ) ) {
98
+ $query_args['_keyword'] = $job_manager_keyword; // Does nothing but needed for unique hash
99
+ add_filter( 'posts_clauses', 'get_job_listings_keyword_search' );
 
 
100
  }
101
 
102
  $query_args = apply_filters( 'job_manager_get_listings', $query_args, $args );
103
 
104
+ if ( empty( $query_args['meta_query'] ) ) {
105
  unset( $query_args['meta_query'] );
106
+ }
107
 
108
+ if ( empty( $query_args['tax_query'] ) ) {
109
  unset( $query_args['tax_query'] );
 
 
 
 
 
110
  }
111
 
112
  // Filter args
113
  $query_args = apply_filters( 'get_job_listings_query_args', $query_args, $args );
114
 
115
+ // Generate hash
116
+ $query_args_hash = 'jm-' . md5( json_encode( $query_args ) . WP_Job_Manager_Cache_Helper::get_transient_version( 'get_job_listings' ) );
117
+
118
  do_action( 'before_get_job_listings', $query_args, $args );
119
 
120
+ if ( false === ( $result = get_transient( $query_args_hash ) ) ) {
121
+ $result = new WP_Query( $query_args );
122
+
123
+ set_transient( $query_args_hash, $result, DAY_IN_SECONDS * 30 );
124
+ }
125
 
126
  do_action( 'after_get_job_listings', $query_args, $args );
127
 
128
  remove_filter( 'posts_clauses', 'order_featured_job_listing' );
129
+ remove_filter( 'posts_clauses', 'get_job_listings_keyword_search' );
130
 
131
  return $result;
132
  }
133
  endif;
134
 
135
+ if ( ! function_exists( 'get_job_listings_keyword_search' ) ) :
136
+ /**
137
+ * Join and where query for keywords
138
+ *
139
+ * @param array $args
140
+ * @return array
141
+ */
142
+ function get_job_listings_keyword_search( $args ) {
143
+ global $wpdb, $job_manager_keyword;
144
+
145
+ // Query matching ids to avoid more joins
146
+ $post_ids = $wpdb->get_col( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_value LIKE '%" . esc_sql( $job_manager_keyword ) . "%'" );
147
+
148
+ $args['where'] .= " AND ( {$wpdb->posts}.post_title LIKE '%" . esc_sql( $job_manager_keyword ) . "%' OR {$wpdb->posts}.post_content LIKE '%" . esc_sql( $job_manager_keyword ) . "%' OR {$wpdb->posts}.ID IN (" . esc_sql( implode( ',', $post_ids ) ) . ") ) ";
149
+
150
+ return $args;
151
+ }
152
+ endif;
153
+
154
+ if ( ! function_exists( 'order_featured_job_listing' ) ) :
155
+ /**
156
+ * WP Core doens't let us change the sort direction for invidual orderby params - http://core.trac.wordpress.org/ticket/17065
157
+ *
158
+ * @param array $args
159
+ * @return array
160
+ */
161
+ function order_featured_job_listing( $args ) {
162
+ global $wpdb;
163
+
164
+ $args['orderby'] = "$wpdb->posts.menu_order ASC, $wpdb->posts.post_date DESC";
165
+
166
+ return $args;
167
+ }
168
+ endif;
169
+
170
  if ( ! function_exists( 'get_job_listing_post_statuses' ) ) :
171
  /**
172
  * Get post statuses used for jobs
186
  }
187
  endif;
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  if ( ! function_exists( 'get_featured_job_ids' ) ) :
190
  /**
191
  * Gets the ids of featured jobs.
476
  */
477
  function job_manager_dropdown_categories( $args = '' ) {
478
  $defaults = array(
479
+ 'orderby' => 'id',
480
+ 'order' => 'ASC',
481
+ 'show_count' => 0,
482
+ 'hide_empty' => 1,
483
+ 'child_of' => 0,
484
+ 'exclude' => '',
485
+ 'echo' => 1,
486
+ 'selected' => 0,
487
+ 'hierarchical' => 0,
488
+ 'name' => 'cat',
489
+ 'id' => '',
490
+ 'class' => 'job-manager-category-dropdown ' . ( is_rtl() ? 'chosen-rtl' : '' ),
491
+ 'depth' => 0,
492
+ 'taxonomy' => 'job_listing_category',
493
+ 'value' => 'id',
494
+ 'multiple' => true,
495
+ 'show_option_all' => false,
496
+ 'placeholder' => __( 'Choose a category&hellip;', 'wp-job-manager' )
497
  );
498
 
499
  $r = wp_parse_args( $args, $defaults );
504
 
505
  extract( $r );
506
 
507
+ // Store in a transient to help sites with many cats
508
+ $categories_hash = 'jmc-' . md5( json_encode( $r ) . WP_Job_Manager_Cache_Helper::get_transient_version( 'get_job_listings' ) );
509
+
510
+ if ( false === ( $categories = get_transient( $categories_hash ) ) ) {
511
+ $categories = get_terms( $taxonomy, $r );
512
+ set_transient( $categories_hash, $categories, DAY_IN_SECONDS * 30 );
513
+ }
514
+
515
  $name = esc_attr( $name );
516
  $class = esc_attr( $class );
517
  $id = $id ? esc_attr( $id ) : $name;
518
 
519
+ $output = "<select name='{$name}[]' id='$id' class='$class' " . ( $multiple ? "multiple='multiple'" : '' ) . " data-placeholder='{$placeholder}'>\n";
520
+
521
+ if ( $show_option_all ) {
522
+ $output .= '<option value="">' . $show_option_all . '</option>';
523
+ }
524
 
525
  if ( ! empty( $categories ) ) {
526
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php' );
557
  } else {
558
  return false;
559
  }
560
+ }
561
+
562
+ /**
563
+ * Filters the upload dir when $job_manager_upload is true
564
+ * @param array $pathdata
565
+ * @return array
566
+ */
567
+ function job_manager_upload_dir( $pathdata ) {
568
+ global $job_manager_upload, $job_manager_uploading_file;
569
+
570
+ if ( ! empty( $job_manager_upload ) ) {
571
+ $dir = apply_filters( 'job_manager_upload_dir', 'job-manager-uploads/' . sanitize_key( $job_manager_uploading_file ), sanitize_key( $job_manager_uploading_file ) );
572
+
573
+ if ( empty( $pathdata['subdir'] ) ) {
574
+ $pathdata['path'] = $pathdata['path'] . '/' . $dir;
575
+ $pathdata['url'] = $pathdata['url'] . '/' . $dir;
576
+ $pathdata['subdir'] = '/' . $dir;
577
+ } else {
578
+ $new_subdir = '/' . $dir . $pathdata['subdir'];
579
+ $pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
580
+ $pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
581
+ $pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
582
+ }
583
+ }
584
+
585
+ return $pathdata;
586
+ }
587
+ add_filter( 'upload_dir', 'job_manager_upload_dir' );
588
+
589
+ /**
590
+ * Prepare files for upload by standardizing them into an array. This adds support for multiple file upload fields.
591
+ * @param array $file_data
592
+ * @return array
593
+ */
594
+ function job_manager_prepare_uploaded_files( $file_data ) {
595
+ $files_to_upload = array();
596
+
597
+ if ( is_array( $file_data['name'] ) ) {
598
+ foreach( $file_data['name'] as $file_data_key => $file_data_value ) {
599
+ if ( $file_data['name'][ $file_data_key ] ) {
600
+ $files_to_upload[] = array(
601
+ 'name' => $file_data['name'][ $file_data_key ],
602
+ 'type' => $file_data['type'][ $file_data_key ],
603
+ 'tmp_name' => $file_data['tmp_name'][ $file_data_key ],
604
+ 'error' => $file_data['error'][ $file_data_key ],
605
+ 'size' => $file_data['size'][ $file_data_key ]
606
+ );
607
+ }
608
+ }
609
+ } else {
610
+ $files_to_upload[] = $file_data;
611
+ }
612
+
613
+ return $files_to_upload;
614
+ }
615
+
616
+ /**
617
+ * Upload a file using WordPress file API.
618
+ * @param array $file_data Array of $_FILE data to upload.
619
+ * @param array $args Optional arguments
620
+ * @return array|WP_Error Array of objects containing either file information or an error
621
+ */
622
+ function job_manager_upload_file( $file, $args = array() ) {
623
+ global $job_manager_upload, $job_manager_uploading_file;
624
+
625
+ include_once( ABSPATH . 'wp-admin/includes/file.php' );
626
+ include_once( ABSPATH . 'wp-admin/includes/media.php' );
627
+
628
+ $args = wp_parse_args( $args, array(
629
+ 'file_key' => '',
630
+ 'file_label' => '',
631
+ 'allowed_mime_types' => get_allowed_mime_types()
632
+ ) );
633
+
634
+ $job_manager_upload = true;
635
+ $job_manager_uploading_file = $args['file_key'];
636
+ $uploaded_file = new stdClass();
637
+
638
+ if ( ! in_array( $file['type'], $args['allowed_mime_types'] ) ) {
639
+ if ( $args['file_label'] ) {
640
+ return new WP_Error( 'upload', sprintf( __( '"%s" (filetype %s) needs to be one of the following file types: %s', 'wp-job-manager' ), $args['file_label'], $file['type'], implode( ', ', array_keys( $args['allowed_mime_types'] ) ) ) );
641
+ } else {
642
+ return new WP_Error( 'upload', sprintf( __( 'Uploaded files need to be one of the following file types: %s', 'wp-job-manager' ), implode( ', ', array_keys( $args['allowed_mime_types'] ) ) ) );
643
+ }
644
+ } else {
645
+ $upload = wp_handle_upload( $file, apply_filters( 'submit_job_wp_handle_upload_overrides', array( 'test_form' => false ) ) );
646
+ if ( ! empty( $upload['error'] ) ) {
647
+ return new WP_Error( 'upload', $upload['error'] );
648
+ } else {
649
+ $uploaded_file->url = $upload['url'];
650
+ $uploaded_file->name = basename( $upload['file'] );
651
+ $uploaded_file->type = $upload['type'];
652
+ $uploaded_file->size = $file['size'];
653
+ $uploaded_file->extension = substr( strrchr( $uploaded_file->name, '.' ), 1 );
654
+ }
655
+ }
656
+
657
+ $job_manager_upload = false;
658
+ $job_manager_uploading_file = '';
659
+
660
+ return $uploaded_file;
661
+ }
wp-job-manager-template.php CHANGED
@@ -146,7 +146,6 @@ function get_the_job_status( $post = null ) {
146
  */
147
  function is_position_filled( $post = null ) {
148
  $post = get_post( $post );
149
-
150
  return $post->_filled ? true : false;
151
  }
152
 
@@ -158,10 +157,20 @@ function is_position_filled( $post = null ) {
158
  */
159
  function is_position_featured( $post = null ) {
160
  $post = get_post( $post );
161
-
162
  return $post->_featured ? true : false;
163
  }
164
 
 
 
 
 
 
 
 
 
 
 
 
165
  /**
166
  * the_job_permalink function.
167
  *
146
  */
147
  function is_position_filled( $post = null ) {
148
  $post = get_post( $post );
 
149
  return $post->_filled ? true : false;
150
  }
151
 
157
  */
158
  function is_position_featured( $post = null ) {
159
  $post = get_post( $post );
 
160
  return $post->_featured ? true : false;
161
  }
162
 
163
+ /**
164
+ * Return whether or not applications are allowed
165
+ *
166
+ * @param object $post
167
+ * @return boolean
168
+ */
169
+ function candidates_can_apply( $post = null ) {
170
+ $post = get_post( $post );
171
+ return ! is_position_filled() && ! in_array( $post->post_status, array( 'preview', 'expired' ) );
172
+ }
173
+
174
  /**
175
  * the_job_permalink function.
176
  *
wp-job-manager.php CHANGED
@@ -3,10 +3,10 @@
3
  Plugin Name: WP Job Manager
4
  Plugin URI: https://wpjobmanager.com/
5
  Description: Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
6
- Version: 1.20.1
7
  Author: Mike Jolley
8
  Author URI: http://mikejolley.com
9
- Requires at least: 3.8
10
  Tested up to: 4.1
11
  Text Domain: wp-job-manager
12
  Domain Path: /languages
@@ -31,7 +31,7 @@ class WP_Job_Manager {
31
  */
32
  public function __construct() {
33
  // Define constants
34
- define( 'JOB_MANAGER_VERSION', '1.20.1' );
35
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
36
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
37
 
@@ -42,6 +42,7 @@ class WP_Job_Manager {
42
  include( 'includes/class-wp-job-manager-api.php' );
43
  include( 'includes/class-wp-job-manager-forms.php' );
44
  include( 'includes/class-wp-job-manager-geocode.php' );
 
45
 
46
  if ( is_admin() ) {
47
  include( 'includes/admin/class-wp-job-manager-admin.php' );
@@ -98,7 +99,7 @@ class WP_Job_Manager {
98
  */
99
  public function frontend_scripts() {
100
  $ajax_url = admin_url( 'admin-ajax.php', 'relative' );
101
- $ajax_filter_deps = array( 'jquery' );
102
 
103
  // WPML workaround until this is standardized
104
  if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
@@ -113,13 +114,36 @@ class WP_Job_Manager {
113
  $ajax_filter_deps[] = 'chosen';
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  wp_register_script( 'wp-job-manager-ajax-filters', JOB_MANAGER_PLUGIN_URL . '/assets/js/ajax-filters.min.js', $ajax_filter_deps, JOB_MANAGER_VERSION, true );
117
  wp_register_script( 'wp-job-manager-job-dashboard', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-dashboard.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
118
  wp_register_script( 'wp-job-manager-job-application', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-application.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
119
  wp_register_script( 'wp-job-manager-job-submission', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-submission.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
120
  wp_localize_script( 'wp-job-manager-ajax-filters', 'job_manager_ajax_filters', array(
121
- 'ajax_url' => $ajax_url,
122
- 'is_rtl' => is_rtl() ? 1 : 0
 
123
  ) );
124
  wp_localize_script( 'wp-job-manager-job-dashboard', 'job_manager_job_dashboard', array(
125
  'i18n_confirm_delete' => __( 'Are you sure you want to delete this listing?', 'wp-job-manager' )
3
  Plugin Name: WP Job Manager
4
  Plugin URI: https://wpjobmanager.com/
5
  Description: Manage job listings from the WordPress admin panel, and allow users to post jobs directly to your site.
6
+ Version: 1.21.0
7
  Author: Mike Jolley
8
  Author URI: http://mikejolley.com
9
+ Requires at least: 4.1
10
  Tested up to: 4.1
11
  Text Domain: wp-job-manager
12
  Domain Path: /languages
31
  */
32
  public function __construct() {
33
  // Define constants
34
+ define( 'JOB_MANAGER_VERSION', '1.21.0' );
35
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
36
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
37
 
42
  include( 'includes/class-wp-job-manager-api.php' );
43
  include( 'includes/class-wp-job-manager-forms.php' );
44
  include( 'includes/class-wp-job-manager-geocode.php' );
45
+ include( 'includes/class-wp-job-manager-cache-helper.php' );
46
 
47
  if ( is_admin() ) {
48
  include( 'includes/admin/class-wp-job-manager-admin.php' );
99
  */
100
  public function frontend_scripts() {
101
  $ajax_url = admin_url( 'admin-ajax.php', 'relative' );
102
+ $ajax_filter_deps = array( 'jquery', 'jquery-deserialize' );
103
 
104
  // WPML workaround until this is standardized
105
  if ( defined( 'ICL_LANGUAGE_CODE' ) ) {
114
  $ajax_filter_deps[] = 'chosen';
115
  }
116
 
117
+ if ( apply_filters( 'job_manager_ajax_file_upload_enabled', true ) ) {
118
+ wp_register_script( 'jquery-iframe-transport', JOB_MANAGER_PLUGIN_URL . '/assets/js/jquery-fileupload/jquery.iframe-transport.js', array( 'jquery' ), '1.8.3', true );
119
+ wp_register_script( 'jquery-fileupload', JOB_MANAGER_PLUGIN_URL . '/assets/js/jquery-fileupload/jquery.fileupload.js', array( 'jquery', 'jquery-iframe-transport', 'jquery-ui-widget' ), '5.42.3', true );
120
+ wp_register_script( 'wp-job-manager-ajax-file-upload', JOB_MANAGER_PLUGIN_URL . '/assets/js/ajax-file-upload.min.js', array( 'jquery', 'jquery-fileupload' ), JOB_MANAGER_VERSION, true );
121
+
122
+ ob_start();
123
+ get_job_manager_template( 'form-fields/uploaded-file-html.php', array( 'name' => '', 'value' => '', 'extension' => 'jpg' ) );
124
+ $js_field_html_img = ob_get_clean();
125
+
126
+ ob_start();
127
+ get_job_manager_template( 'form-fields/uploaded-file-html.php', array( 'name' => '', 'value' => '', 'extension' => 'zip' ) );
128
+ $js_field_html = ob_get_clean();
129
+
130
+ wp_localize_script( 'wp-job-manager-ajax-file-upload', 'job_manager_ajax_file_upload', array(
131
+ 'ajax_url' => $ajax_url,
132
+ 'js_field_html_img' => esc_js( str_replace( "\n", "", $js_field_html_img ) ),
133
+ 'js_field_html' => esc_js( str_replace( "\n", "", $js_field_html ) ),
134
+ 'i18n_invalid_file_type' => __( 'Invalid file type. Accepted types:', 'wp-job-manager' )
135
+ ) );
136
+ }
137
+
138
+ wp_register_script( 'jquery-deserialize', JOB_MANAGER_PLUGIN_URL . '/assets/js/jquery-deserialize/jquery.deserialize.js', array( 'jquery' ), '1.2.1', true );
139
  wp_register_script( 'wp-job-manager-ajax-filters', JOB_MANAGER_PLUGIN_URL . '/assets/js/ajax-filters.min.js', $ajax_filter_deps, JOB_MANAGER_VERSION, true );
140
  wp_register_script( 'wp-job-manager-job-dashboard', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-dashboard.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
141
  wp_register_script( 'wp-job-manager-job-application', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-application.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
142
  wp_register_script( 'wp-job-manager-job-submission', JOB_MANAGER_PLUGIN_URL . '/assets/js/job-submission.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
143
  wp_localize_script( 'wp-job-manager-ajax-filters', 'job_manager_ajax_filters', array(
144
+ 'ajax_url' => $ajax_url,
145
+ 'is_rtl' => is_rtl() ? 1 : 0,
146
+ 'i18n_load_prev_listings' => __( 'Load previous listings', 'wp-job-manager' )
147
  ) );
148
  wp_localize_script( 'wp-job-manager-job-dashboard', 'job_manager_job_dashboard', array(
149
  'i18n_confirm_delete' => __( 'Are you sure you want to delete this listing?', 'wp-job-manager' )