WP Job Manager - Version 1.31.0

Version Description

  • Change: Minimum WordPress version is now 4.7.0.
  • Enhancement: Add email notifications with initial support for new jobs, updated jobs, and expiring listings.
  • Enhancement: For GDPR, scrub WPJM data from database on uninstall if option is enabled.
  • Enhancement: Filter by Filled and Featured status in WP admin.
  • Enhancement: Simplify the display of application URLs.
  • Enhancement: When using WPML, prevent changes to page options when on a non-default language. (@vukvukovich)
  • Enhancement: Include company logo in structured data. (@RajeebTheGreat)
  • Enhancement: Use more efficient jQuery selectors in scripts. (@RajeebTheGreat)
  • Enhancement: Use proper <h2> tag in content-summary-job_listing.php template for the job title. (@abdullah1908)
  • Enhancement: Hide empty categories on [job] filter.
  • Fix: Update calls to get_terms() to use the new format.
  • Fix: Maintain the current tab when saving settings in WP Admin.
  • Fix: Enqueue the date picker CSS when used on the front-end.
  • Fix: Remove errors when widget instance was created without setting defaults.
  • REST API Pre-release: Add support for job category taxonomy endpoints.
  • Dev: Add $job_id parameter to job_manager_job_dashboard_do_action_{$action} action hook. (@jonasvogel)
  • Dev: Add support for hidden WPJM settings in WP Admin.
Download this release

Release Info

Developer jakeom
Plugin Icon 128x128 WP Job Manager
Version 1.31.0
Comparing to
See all releases

Code changes from version 1.30.2 to 1.31.0

Files changed (63) hide show
  1. assets/css/admin.css +1 -1
  2. assets/css/admin.less +35 -0
  3. assets/js/admin.js +3 -3
  4. assets/js/admin.min.js +1 -1
  5. assets/js/job-application.js +1 -1
  6. assets/js/job-application.min.js +1 -1
  7. assets/js/job-submission.js +2 -2
  8. assets/js/job-submission.min.js +1 -1
  9. changelog.txt +19 -0
  10. includes/3rd-party/wpml.php +45 -0
  11. includes/abstracts/abstract-wp-job-manager-email-template.php +131 -0
  12. includes/abstracts/abstract-wp-job-manager-email.php +223 -0
  13. includes/admin/class-wp-job-manager-admin.php +2 -6
  14. includes/admin/class-wp-job-manager-cpt.php +121 -1
  15. includes/admin/class-wp-job-manager-settings.php +306 -124
  16. includes/admin/class-wp-job-manager-writepanels.php +2 -2
  17. includes/class-wp-job-manager-data-cleaner.php +365 -0
  18. includes/class-wp-job-manager-email-notifications.php +814 -0
  19. includes/class-wp-job-manager-shortcodes.php +1 -1
  20. includes/class-wp-job-manager-usage-tracking-data.php +4 -2
  21. includes/class-wp-job-manager-usage-tracking.php +1 -1
  22. includes/class-wp-job-manager-widget.php +18 -0
  23. includes/emails/class-wp-job-manager-email-admin-expiring-job.php +52 -0
  24. includes/emails/class-wp-job-manager-email-admin-new-job.php +87 -0
  25. includes/emails/class-wp-job-manager-email-admin-updated-job.php +87 -0
  26. includes/emails/class-wp-job-manager-email-employer-expiring-job.php +155 -0
  27. includes/rest-api/class-wp-job-manager-controllers-status.php +1 -1
  28. includes/rest-api/class-wp-job-manager-models-job-categories-custom-fields.php +26 -0
  29. includes/rest-api/class-wp-job-manager-models-status.php +4 -1
  30. includes/rest-api/class-wp-job-manager-registrable-job-categories.php +42 -0
  31. includes/rest-api/class-wp-job-manager-registrable-job-listings.php +4 -0
  32. includes/rest-api/class-wp-job-manager-registrable-job-types.php +13 -184
  33. includes/rest-api/class-wp-job-manager-registrable-taxonomy-type.php +229 -0
  34. includes/rest-api/class-wp-job-manager-rest-api.php +5 -0
  35. includes/widgets/class-wp-job-manager-widget-featured-jobs.php +6 -4
  36. includes/widgets/class-wp-job-manager-widget-recent-jobs.php +4 -2
  37. languages/wp-job-manager.pot +399 -184
  38. lib/emogrifier/class-emogrifier.php +1557 -0
  39. readme.txt +21 -2
  40. templates/account-signin.php +4 -4
  41. templates/content-summary-job_listing.php +2 -2
  42. templates/emails/admin-expiring-job.php +46 -0
  43. templates/emails/admin-new-job.php +44 -0
  44. templates/emails/admin-updated-job.php +44 -0
  45. templates/emails/email-footer.php +30 -0
  46. templates/emails/email-header.php +37 -0
  47. templates/emails/email-job-details.php +41 -0
  48. templates/emails/email-styles.php +83 -0
  49. templates/emails/employer-expiring-job.php +45 -0
  50. templates/emails/plain/admin-expiring-job.php +44 -0
  51. templates/emails/plain/admin-new-job.php +41 -0
  52. templates/emails/plain/admin-updated-job.php +41 -0
  53. templates/emails/plain/email-footer.php +17 -0
  54. templates/emails/plain/email-header.php +17 -0
  55. templates/emails/plain/email-job-details.php +28 -0
  56. templates/emails/plain/employer-expiring-job.php +43 -0
  57. templates/form-fields/date-field.php +2 -1
  58. templates/job-application-url.php +2 -2
  59. templates/job-filters.php +5 -5
  60. uninstall.php +27 -32
  61. wp-job-manager-functions.php +21 -6
  62. wp-job-manager-template.php +57 -0
  63. wp-job-manager.php +35 -4
assets/css/admin.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}.job-manager-settings-wrap .updated{display:none}.job-manager-settings-wrap .job-manager-updated{display:block;margin:1em 0 0}a.wpjm-activate-licence-link,a.wpjm-activate-licence-link:active,a.wpjm-activate-licence-link:hover,a.wpjm-activate-licence-link:link,a.wpjm-activate-licence-link:visited{color:#ff4500}.wpjm-licences{margin-top:10px}.wpjm-licences .licence-row{align-items:center;border:solid 1px #e2e0e2;display:flex;background-color:#fff;flex-wrap:wrap;min-height:82px;margin-bottom:20px;position:relative}.wpjm-licences .plugin-info{font-size:18px;flex-basis:320px;padding:0 20px;margin-right:10px}.wpjm-licences .plugin-info .plugin-author{font-size:12px}.wpjm-licences .plugin-licence{flex:1;flex-basis:40%;padding-bottom:5px}.wpjm-licences .plugin-licence label{white-space:nowrap}.widefat td.column-featured_job,.widefat td.column-filled,.widefat td.column-job_status{width:46px;text-align:left;padding-left:11px}.widefat th.column-featured_job,.widefat th.column-filled,.widefat th.column-job_status{width:1em}.widefat th.column-featured_job span,.widefat th.column-filled span,.widefat th.column-job_status span{display:block;width:1em;height:1em;line-height:1em;padding:1px 0 0 0;overflow:hidden}.widefat th.column-featured_job span:before,.widefat th.column-filled span:before,.widefat th.column-job_status span:before{content:'\e803';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}.widefat th.column-filled span:before{content:'\e807'}.widefat th.column-job_status span:before{content:'\e828'}.widefat .column-job_posted strong{display:block;margin-bottom:.2em}.widefat td.column-job_status span{position:relative;font-size:1em;line-height:1.5em;width:1em;height:0;padding:2em 0 0 0;overflow:hidden;display:block}.widefat td.column-job_status span:before{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;position:absolute;top:0;left:0;line-height:1.5em;vertical-align:middle;color:#999;content:'\e829'}.widefat td.column-job_status .status-trash:before{content:'\e82b';color:#a00}.widefat td.column-job_status .status-pending:before{content:'\e82c';color:#ffba00}.widefat td.column-job_status .status-publish:before{content:'\e82f';color:#73a724}.widefat td.column-job_status .status-expired:before{content:'\e82e';color:#a00}.widefat .column-job_listing_type{text-align:left;width:6em;word-wrap:normal!important}.widefat .column-job_listing_type .job-type{color:#fff;padding:4px;font-size:11px;-webkit-border-radius:2px;border-radius:2px;display:block;background-color:#f08d3c;text-align:center}.widefat .column-job_listing_type .full-time{background-color:#90da36}.widefat .column-job_listing_type .part-time{background-color:#f08d3c}.widefat .column-job_listing_type .temporary{background-color:#d93674}.widefat .column-job_listing_type .freelance{background-color:#39c}.widefat .column-job_listing_type .internship{background-color:#6033cc}.widefat th.column-job_position{width:20%}.widefat td.column-job_position{width:20%;height:34px}.widefat td.column-job_position .job_position{position:relative;padding-right:50px!important}.widefat td.column-job_position a.job_title{font-weight:700}.widefat td.column-job_position img{width:32px;height:32px;position:absolute;right:7px;top:0;-webkit-border-radius:50%;border-radius:50%;box-shadow:0 1px 0 1px rgba(0,0,0,.1);-webkit-box-shadow:0 1px 0 1px rgba(0,0,0,.1);-moz-box-shadow:0 1px 0 1px rgba(0,0,0,.1);border:1px solid #fff}.widefat td.column-job_position .company{margin-top:.2em;display:block;padding-top:2px;color:#bbb}.widefat .column-job_location{width:10%}.widefat .column-job_actions{text-align:right;width:128px}.widefat .column-job_actions strong{display:block;margin-bottom:.2em}.widefat .column-job_actions .actions{padding-top:2px}.widefat .column-job_actions a.button{display:inline-block;margin:0 0 2px 4px;cursor:pointer;padding:0 6px!important;font-size:1em!important;line-height:2em!important;overflow:hidden}.widefat .column-job_actions a.button-icon{width:2em!important;padding:0!important}.widefat .column-job_actions a.button-icon:before{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;float:left;width:2em!important;line-height:2em}.widefat .column-job_actions .icon-view:before{content:'\e805'}.widefat .column-job_actions .icon-edit:before{content:'\e804'}.widefat .column-job_actions .icon-delete:before{content:'\e82b'}.widefat .column-job_actions .icon-approve:before{content:'\e802'}.wp_job_manager_meta_data{zoom:1}.wp_job_manager_meta_data:after,.wp_job_manager_meta_data:before{content:"";display:table}.wp_job_manager_meta_data:after{clear:both}.wp_job_manager_meta_data .form-field{width:50%;line-height:2em;float:left;box-sizing:border-box;padding:0 12px 0 0;margin:0 0 12px;clear:both}.wp_job_manager_meta_data .form-field:nth-child(even){float:right;padding:0 0 0 12px;clear:right}.wp_job_manager_meta_data .form-field:nth-last-child(-n+2){margin-bottom:0;padding-bottom:0;border-bottom:0}.wp_job_manager_meta_data .form-field label{vertical-align:middle;display:block;font-weight:700;margin:0}.wp_job_manager_meta_data .form-field .tips{cursor:help;float:right;font-weight:400;color:#999}.wp_job_manager_meta_data .form-field input{width:100%;margin:1px 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;vertical-align:middle}.wp_job_manager_meta_data .form-field input.checkbox,.wp_job_manager_meta_data .form-field input.radio{width:auto;margin:4px 2px;display:inline-block}.wp_job_manager_meta_data .form-field .description{display:block;color:#999}.wp_job_manager_meta_data .form-field.form-field-checkbox .description{display:inline}.wp_job_manager_meta_data .form-field .file_url input{width:75%}.wp_job_manager_meta_data .form-field .button{margin-left:4px}.wp_job_manager_meta_data .form-field .file_no_url{-o-animation:flash .3s linear infinite alternate;-webkit-animation:flash .3s linear infinite alternate;-moz-animation:flash .3s linear infinite alternate;animation:flash .3s linear infinite alternate}@-o-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-ms-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-moz-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-webkit-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@keyframes flash{from{background-color:unset}to{background-color:#dc3232}}#tiptip_holder{display:none;position:absolute;top:0;left:0;z-index:99999}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_left{padding-right:5px}#tiptip_content{font-size:11px;color:#fff;padding:4px 8px;background:#464646;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;box-shadow:1px 1px 3px rgba(0,0,0,.1);-webkit-box-shadow:1px 1px 3px rgba(0,0,0,.1);-moz-box-shadow:1px 1px 3px rgba(0,0,0,.1);text-align:center}#tiptip_content code{background:#999;padding:1px}#tiptip_arrow,#tiptip_arrow_inner{position:absolute;border-color:transparent;border-style:solid;border-width:6px;height:0;width:0}#tiptip_holder.tip_top #tiptip_arrow_inner{margin-top:-7px;margin-left:-6px;border-top-color:#464646}#tiptip_holder.tip_bottom #tiptip_arrow_inner{margin-top:-5px;margin-left:-6px;border-bottom-color:#464646}#tiptip_holder.tip_right #tiptip_arrow_inner{margin-top:-6px;margin-left:-5px;border-right-color:#464646}#tiptip_holder.tip_left #tiptip_arrow_inner{margin-top:-6px;margin-left:-7px;border-left-color:#464646}.wp_job_manager_add_ons_wrap .products{overflow:hidden}.wp_job_manager_add_ons_wrap .products li{float:left;margin:0 1em 1em 0!important;padding:0;vertical-align:top;width:350px}.wp_job_manager_add_ons_wrap .products li a{text-decoration:none;color:inherit;border:1px solid #ddd;display:block;min-height:220px;overflow:hidden;background:#f6f6f6;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),inset 0 -1px 0 rgba(0,0,0,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),inset 0 -1px 0 rgba(0,0,0,.1)}.wp_job_manager_add_ons_wrap .products li img{display:inline-block;width:auto;max-height:60px;max-width:90px;margin:0 5px 0 0;float:left}.wp_job_manager_add_ons_wrap .products li h2{margin:0!important;padding:20px!important;background:#fff;height:20px;font-size:16px}.wp_job_manager_add_ons_wrap .products li p{padding:20px!important;margin:0!important;border-top:1px solid #f1f1f1}.wp_job_manager_add_ons_wrap .products li .price{display:none}.rtl .widefat .column-job_actions a.button-icon:before{float:right}.rtl .wp_job_manager_meta_data p{padding:0 20% 0 0}.rtl .wp_job_manager_meta_data label{left:auto;right:0}@media only screen and (max-width:782px){.wpjm-licences .plugin-info{padding:10px}.wpjm-licences .plugin-licence{padding:10px}.widefat .job_position.column-primary{display:table-cell!important}.widefat .toggle-row:before{top:5px}.widefat .column-job_actions{text-align:left}.widefat .column-job_actions a.button-icon:before{float:left}.rtl .widefat .column-job_actions{text-align:right}.rtl .widefat .column-job_actions a.button-icon:before{float:right}.wp_job_manager_meta_data .form-field{width:100%;padding:0}.wp_job_manager_meta_data .form-field:nth-child(even){float:none;padding:0;margin-bottom:12px;clear:both}.wp_job_manager_meta_data .form-field:nth-last-child(-n+2){float:none;padding:0;margin-bottom:12px;clear:both}}
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}.job-manager-settings-wrap .updated{display:none}.job-manager-settings-wrap .job-manager-updated{display:block;margin:1em 0 0}a.wpjm-activate-licence-link,a.wpjm-activate-licence-link:active,a.wpjm-activate-licence-link:hover,a.wpjm-activate-licence-link:link,a.wpjm-activate-licence-link:visited{color:#ff4500}.wpjm-licences{margin-top:10px}.wpjm-licences .licence-row{align-items:center;border:solid 1px #e2e0e2;display:flex;background-color:#fff;flex-wrap:wrap;min-height:82px;margin-bottom:20px;position:relative}.wpjm-licences .plugin-info{font-size:18px;flex-basis:320px;padding:0 20px;margin-right:10px}.wpjm-licences .plugin-info .plugin-author{font-size:12px}.wpjm-licences .plugin-licence{flex:1;flex-basis:40%;padding-bottom:5px}.wpjm-licences .plugin-licence label{white-space:nowrap}.widefat td.column-featured_job,.widefat td.column-filled,.widefat td.column-job_status{width:46px;text-align:left;padding-left:11px}.widefat th.column-featured_job,.widefat th.column-filled,.widefat th.column-job_status{width:1em}.widefat th.column-featured_job span,.widefat th.column-filled span,.widefat th.column-job_status span{display:block;width:1em;height:1em;line-height:1em;padding:1px 0 0 0;overflow:hidden}.widefat th.column-featured_job span:before,.widefat th.column-filled span:before,.widefat th.column-job_status span:before{content:'\e803';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}.widefat th.column-filled span:before{content:'\e807'}.widefat th.column-job_status span:before{content:'\e828'}.widefat .column-job_posted strong{display:block;margin-bottom:.2em}.widefat td.column-job_status span{position:relative;font-size:1em;line-height:1.5em;width:1em;height:0;padding:2em 0 0 0;overflow:hidden;display:block}.widefat td.column-job_status span:before{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;position:absolute;top:0;left:0;line-height:1.5em;vertical-align:middle;color:#999;content:'\e829'}.widefat td.column-job_status .status-trash:before{content:'\e82b';color:#a00}.widefat td.column-job_status .status-pending:before{content:'\e82c';color:#ffba00}.widefat td.column-job_status .status-publish:before{content:'\e82f';color:#73a724}.widefat td.column-job_status .status-expired:before{content:'\e82e';color:#a00}.widefat .column-job_listing_type{text-align:left;width:6em;word-wrap:normal!important}.widefat .column-job_listing_type .job-type{color:#fff;padding:4px;font-size:11px;-webkit-border-radius:2px;border-radius:2px;display:block;background-color:#f08d3c;text-align:center}.widefat .column-job_listing_type .full-time{background-color:#90da36}.widefat .column-job_listing_type .part-time{background-color:#f08d3c}.widefat .column-job_listing_type .temporary{background-color:#d93674}.widefat .column-job_listing_type .freelance{background-color:#39c}.widefat .column-job_listing_type .internship{background-color:#6033cc}.widefat th.column-job_position{width:20%}.widefat td.column-job_position{width:20%;height:34px}.widefat td.column-job_position .job_position{position:relative;padding-right:50px!important}.widefat td.column-job_position a.job_title{font-weight:700}.widefat td.column-job_position img{width:32px;height:32px;position:absolute;right:7px;top:0;-webkit-border-radius:50%;border-radius:50%;box-shadow:0 1px 0 1px rgba(0,0,0,.1);-webkit-box-shadow:0 1px 0 1px rgba(0,0,0,.1);-moz-box-shadow:0 1px 0 1px rgba(0,0,0,.1);border:1px solid #fff}.widefat td.column-job_position .company{margin-top:.2em;display:block;padding-top:2px;color:#bbb}.widefat .column-job_location{width:10%}.widefat .column-job_actions{text-align:right;width:128px}.widefat .column-job_actions strong{display:block;margin-bottom:.2em}.widefat .column-job_actions .actions{padding-top:2px}.widefat .column-job_actions a.button{display:inline-block;margin:0 0 2px 4px;cursor:pointer;padding:0 6px!important;font-size:1em!important;line-height:2em!important;overflow:hidden}.widefat .column-job_actions a.button-icon{width:2em!important;padding:0!important}.widefat .column-job_actions a.button-icon:before{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;float:left;width:2em!important;line-height:2em}.widefat .column-job_actions .icon-view:before{content:'\e805'}.widefat .column-job_actions .icon-edit:before{content:'\e804'}.widefat .column-job_actions .icon-delete:before{content:'\e82b'}.widefat .column-job_actions .icon-approve:before{content:'\e802'}.wp_job_manager_meta_data{zoom:1}.wp_job_manager_meta_data:after,.wp_job_manager_meta_data:before{content:"";display:table}.wp_job_manager_meta_data:after{clear:both}.wp_job_manager_meta_data .form-field{width:50%;line-height:2em;float:left;box-sizing:border-box;padding:0 12px 0 0;margin:0 0 12px;clear:both}.wp_job_manager_meta_data .form-field:nth-child(even){float:right;padding:0 0 0 12px;clear:right}.wp_job_manager_meta_data .form-field:nth-last-child(-n+2){margin-bottom:0;padding-bottom:0;border-bottom:0}.wp_job_manager_meta_data .form-field label{vertical-align:middle;display:block;font-weight:700;margin:0}.wp_job_manager_meta_data .form-field .tips{cursor:help;float:right;font-weight:400;color:#999}.wp_job_manager_meta_data .form-field input{width:100%;margin:1px 0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;vertical-align:middle}.wp_job_manager_meta_data .form-field input.checkbox,.wp_job_manager_meta_data .form-field input.radio{width:auto;margin:4px 2px;display:inline-block}.wp_job_manager_meta_data .form-field .description{display:block;color:#999}.wp_job_manager_meta_data .form-field.form-field-checkbox .description{display:inline}.wp_job_manager_meta_data .form-field .file_url input{width:75%}.wp_job_manager_meta_data .form-field .button{margin-left:4px}.wp_job_manager_meta_data .form-field .file_no_url{-o-animation:flash .3s linear infinite alternate;-webkit-animation:flash .3s linear infinite alternate;-moz-animation:flash .3s linear infinite alternate;animation:flash .3s linear infinite alternate}@-o-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-ms-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-moz-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@-webkit-keyframes flash{from{background-color:unset}to{background-color:#dc3232}}@keyframes flash{from{background-color:unset}to{background-color:#dc3232}}#tiptip_holder{display:none;position:absolute;top:0;left:0;z-index:99999}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_left{padding-right:5px}#tiptip_content{font-size:11px;color:#fff;padding:4px 8px;background:#464646;border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;box-shadow:1px 1px 3px rgba(0,0,0,.1);-webkit-box-shadow:1px 1px 3px rgba(0,0,0,.1);-moz-box-shadow:1px 1px 3px rgba(0,0,0,.1);text-align:center}#tiptip_content code{background:#999;padding:1px}#tiptip_arrow,#tiptip_arrow_inner{position:absolute;border-color:transparent;border-style:solid;border-width:6px;height:0;width:0}#tiptip_holder.tip_top #tiptip_arrow_inner{margin-top:-7px;margin-left:-6px;border-top-color:#464646}#tiptip_holder.tip_bottom #tiptip_arrow_inner{margin-top:-5px;margin-left:-6px;border-bottom-color:#464646}#tiptip_holder.tip_right #tiptip_arrow_inner{margin-top:-6px;margin-left:-5px;border-right-color:#464646}#tiptip_holder.tip_left #tiptip_arrow_inner{margin-top:-6px;margin-left:-7px;border-left-color:#464646}.wp_job_manager_add_ons_wrap .products{overflow:hidden}.wp_job_manager_add_ons_wrap .products li{float:left;margin:0 1em 1em 0!important;padding:0;vertical-align:top;width:350px}.wp_job_manager_add_ons_wrap .products li a{text-decoration:none;color:inherit;border:1px solid #ddd;display:block;min-height:220px;overflow:hidden;background:#f6f6f6;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),inset 0 -1px 0 rgba(0,0,0,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),inset 0 -1px 0 rgba(0,0,0,.1)}.wp_job_manager_add_ons_wrap .products li img{display:inline-block;width:auto;max-height:60px;max-width:90px;margin:0 5px 0 0;float:left}.wp_job_manager_add_ons_wrap .products li h2{margin:0!important;padding:20px!important;background:#fff;height:20px;font-size:16px}.wp_job_manager_add_ons_wrap .products li p{padding:20px!important;margin:0!important;border-top:1px solid #f1f1f1}.wp_job_manager_add_ons_wrap .products li .price{display:none}.rtl .widefat .column-job_actions a.button-icon:before{float:right}.rtl .wp_job_manager_meta_data p{padding:0 20% 0 0}.rtl .wp_job_manager_meta_data label{left:auto;right:0}table.form-table.settings tr{border-bottom:1px solid rgba(0,0,0,.1)}table.form-table.settings tr.no-separator,table.form-table.settings tr:last-child{border-bottom:0}div.setting-enable-expand{border:1px solid rgba(0,0,0,.1);padding:15px 10px}div.setting-enable-expand .sub-settings-expandable{display:none;padding-left:25px}div.setting-enable-expand .sub-settings-expandable.expanded{display:block}tr.email-setting-row td{padding:5px}@media only screen and (max-width:782px){.wpjm-licences .plugin-info{padding:10px}.wpjm-licences .plugin-licence{padding:10px}.widefat .job_position.column-primary{display:table-cell!important}.widefat .toggle-row:before{top:5px}.widefat .column-job_actions{text-align:left}.widefat .column-job_actions a.button-icon:before{float:left}.rtl .widefat .column-job_actions{text-align:right}.rtl .widefat .column-job_actions a.button-icon:before{float:right}.wp_job_manager_meta_data .form-field{width:100%;padding:0}.wp_job_manager_meta_data .form-field:nth-child(even){float:none;padding:0;margin-bottom:12px;clear:both}.wp_job_manager_meta_data .form-field:nth-last-child(-n+2){float:none;padding:0;margin-bottom:12px;clear:both}}
assets/css/admin.less CHANGED
@@ -451,6 +451,41 @@ a.wpjm-activate-licence-link:active {
451
  }
452
  }
453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  /**
455
  * Mobile styles
456
  */
451
  }
452
  }
453
 
454
+ /**
455
+ * Settings
456
+ */
457
+
458
+ table.form-table.settings {
459
+ tr {
460
+ border-bottom: 1px solid rgba( 0, 0, 0, .1);
461
+
462
+ &:last-child,
463
+ &.no-separator {
464
+ border-bottom: 0;
465
+ }
466
+ }
467
+ }
468
+
469
+ div.setting-enable-expand {
470
+ border: 1px solid rgba( 0, 0, 0, .1);
471
+ padding: 15px 10px;
472
+
473
+ .sub-settings-expandable {
474
+ display: none;
475
+ padding-left: 25px;
476
+
477
+ &.expanded {
478
+ display: block;
479
+ }
480
+ }
481
+ }
482
+
483
+ tr.email-setting-row {
484
+ td {
485
+ padding: 5px;
486
+ }
487
+ }
488
+
489
  /**
490
  * Mobile styles
491
  */
assets/js/admin.js CHANGED
@@ -19,7 +19,7 @@ jQuery(document).ready(function($) {
19
  var file_target_input;
20
  var file_target_wrapper;
21
 
22
- $(document).on('click', '.wp_job_manager_add_another_file_button', function( event ){
23
  event.preventDefault();
24
 
25
  var field_name = $( this ).data( 'field_name' );
@@ -31,7 +31,7 @@ jQuery(document).ready(function($) {
31
  $( this ).before( '<span class="file_url"><input type="text" name="' + field_name + '[]" placeholder="' + field_placeholder + '" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="' + button_text + '">' + button + '</button><button class="button button-small wp_job_manager_view_file_button">' + view_button + '</button></span>' );
32
  } );
33
 
34
- $(document).on('click', '.wp_job_manager_view_file_button', function ( event ) {
35
  event.preventDefault();
36
 
37
  file_target_wrapper = $( this ).closest( '.file_url' );
@@ -50,7 +50,7 @@ jQuery(document).ready(function($) {
50
 
51
  });
52
 
53
- $(document).on('click', '.wp_job_manager_upload_file_button', function( event ){
54
  event.preventDefault();
55
 
56
  file_target_wrapper = $( this ).closest('.file_url');
19
  var file_target_input;
20
  var file_target_wrapper;
21
 
22
+ $( document.body ).on('click', '.wp_job_manager_add_another_file_button', function( event ){
23
  event.preventDefault();
24
 
25
  var field_name = $( this ).data( 'field_name' );
31
  $( this ).before( '<span class="file_url"><input type="text" name="' + field_name + '[]" placeholder="' + field_placeholder + '" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="' + button_text + '">' + button + '</button><button class="button button-small wp_job_manager_view_file_button">' + view_button + '</button></span>' );
32
  } );
33
 
34
+ $( document.body ).on('click', '.wp_job_manager_view_file_button', function ( event ) {
35
  event.preventDefault();
36
 
37
  file_target_wrapper = $( this ).closest( '.file_url' );
50
 
51
  });
52
 
53
+ $( document.body ).on('click', '.wp_job_manager_upload_file_button', function( event ){
54
  event.preventDefault();
55
 
56
  file_target_wrapper = $( this ).closest('.file_url');
assets/js/admin.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(t){t(".tips, .help_tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),t("p.form-field-author").on("click","a.change-author",function(){return t(this).closest("p").find(".current-author").hide(),t(this).closest("p").find(".change-author").show(),!1});var e,a,i;t(document).on("click",".wp_job_manager_add_another_file_button",function(e){e.preventDefault();var a=t(this).data("field_name"),i=t(this).data("field_placeholder"),n=t(this).data("uploader_button_text"),o=t(this).data("uploader_button"),l=t(this).data("view_button");t(this).before('<span class="file_url"><input type="text" name="'+a+'[]" placeholder="'+i+'" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="'+n+'">'+o+'</button><button class="button button-small wp_job_manager_view_file_button">'+l+"</button></span>")}),t(document).on("click",".wp_job_manager_view_file_button",function(e){e.preventDefault(),i=t(this).closest(".file_url");var n=(a=i.find("input")).val();n.indexOf("://")>-1?window.open(n,"_blank"):(a.addClass("file_no_url"),setTimeout(function(){a.removeClass("file_no_url")},1e3))}),t(document).on("click",".wp_job_manager_upload_file_button",function(n){n.preventDefault(),i=t(this).closest(".file_url"),a=i.find("input"),e?e.open():((e=wp.media.frames.file_frame=wp.media({title:t(this).data("uploader_title"),button:{text:t(this).data("uploader_button_text")},multiple:!1})).on("select",function(){var i=e.state().get("selection").first().toJSON();t(a).val(i.url)}),e.open())})}),jQuery(document).ready(function(t){var e="job_listing_type";t("#"+e+"checklist li :radio, #"+e+"checklist-pop :radio").live("click",function(){var a=t(this),i=a.is(":checked"),n=a.val();t("#"+e+"checklist li :radio, #"+e+"checklist-pop :radio").prop("checked",!1),t("#in-"+e+"-"+n+", #in-popular-"+e+"-"+n).prop("checked",i)})});
1
+ jQuery(document).ready(function(t){t(".tips, .help_tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),t("p.form-field-author").on("click","a.change-author",function(){return t(this).closest("p").find(".current-author").hide(),t(this).closest("p").find(".change-author").show(),!1});var e,a,o;t(document.body).on("click",".wp_job_manager_add_another_file_button",function(e){e.preventDefault();var a=t(this).data("field_name"),o=t(this).data("field_placeholder"),i=t(this).data("uploader_button_text"),n=t(this).data("uploader_button"),l=t(this).data("view_button");t(this).before('<span class="file_url"><input type="text" name="'+a+'[]" placeholder="'+o+'" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="'+i+'">'+n+'</button><button class="button button-small wp_job_manager_view_file_button">'+l+"</button></span>")}),t(document.body).on("click",".wp_job_manager_view_file_button",function(e){e.preventDefault(),o=t(this).closest(".file_url");var i=(a=o.find("input")).val();i.indexOf("://")>-1?window.open(i,"_blank"):(a.addClass("file_no_url"),setTimeout(function(){a.removeClass("file_no_url")},1e3))}),t(document.body).on("click",".wp_job_manager_upload_file_button",function(i){i.preventDefault(),o=t(this).closest(".file_url"),a=o.find("input"),e?e.open():((e=wp.media.frames.file_frame=wp.media({title:t(this).data("uploader_title"),button:{text:t(this).data("uploader_button_text")},multiple:!1})).on("select",function(){var o=e.state().get("selection").first().toJSON();t(a).val(o.url)}),e.open())})}),jQuery(document).ready(function(t){var e="job_listing_type";t("#"+e+"checklist li :radio, #"+e+"checklist-pop :radio").live("click",function(){var a=t(this),o=a.is(":checked"),i=a.val();t("#"+e+"checklist li :radio, #"+e+"checklist-pop :radio").prop("checked",!1),t("#in-"+e+"-"+i+", #in-popular-"+e+"-"+i).prop("checked",o)})});
assets/js/job-application.js CHANGED
@@ -4,7 +4,7 @@ jQuery(document).ready(function($) {
4
  $( '.application_details' ).hide();
5
  }
6
 
7
- $( 'body' ).on( 'click', '.job_application .application_button', function() {
8
  var $details = $(this).siblings('.application_details').first();
9
  var $button = $(this);
10
  $details.slideToggle( 400, function() {
4
  $( '.application_details' ).hide();
5
  }
6
 
7
+ $( document.body ).on( 'click', '.job_application .application_button', function() {
8
  var $details = $(this).siblings('.application_details').first();
9
  var $button = $(this);
10
  $details.slideToggle( 400, function() {
assets/js/job-application.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(i){i("body").hasClass("job-application-details-keep-open")||i(".application_details").hide(),i("body").on("click",".job_application .application_button",function(){var t=i(this).siblings(".application_details").first(),o=i(this);t.slideToggle(400,function(){if(i(this).is(":visible")){var e=Math.max(Math.min(t.outerHeight(),200),.33*t.outerHeight()),a=t.offset().top+e,n=5;i("#wpadminbar").length>0&&"fixed"===i("#wpadminbar").css("position")&&(n+=i("#wpadminbar").outerHeight()),i("header").length>0&&"fixed"===i("header").css("position")&&(n+=i("header").outerHeight());var s=i(window).scrollTop()+window.innerHeight,l=t.offset().top+t.outerHeight()-s,p=window.innerHeight-n;l>0&&t.outerHeight()<.9*p?i("html, body").animate({scrollTop:i(window).scrollTop()+l+5},400):s<a&&i("html, body").animate({scrollTop:o.offset().top-n},600)}})})});
1
+ jQuery(document).ready(function(i){i("body").hasClass("job-application-details-keep-open")||i(".application_details").hide(),i(document.body).on("click",".job_application .application_button",function(){var t=i(this).siblings(".application_details").first(),o=i(this);t.slideToggle(400,function(){if(i(this).is(":visible")){var e=Math.max(Math.min(t.outerHeight(),200),.33*t.outerHeight()),a=t.offset().top+e,n=5;i("#wpadminbar").length>0&&"fixed"===i("#wpadminbar").css("position")&&(n+=i("#wpadminbar").outerHeight()),i("header").length>0&&"fixed"===i("header").css("position")&&(n+=i("header").outerHeight());var s=i(window).scrollTop()+window.innerHeight,l=t.offset().top+t.outerHeight()-s,d=window.innerHeight-n;l>0&&t.outerHeight()<.9*d?i("html, body").animate({scrollTop:i(window).scrollTop()+l+5},400):s<a&&i("html, body").animate({scrollTop:o.offset().top-n},600)}})})});
assets/js/job-submission.js CHANGED
@@ -1,9 +1,9 @@
1
  jQuery(document).ready(function($) {
2
- $('body').on( 'click', '.job-manager-remove-uploaded-file', function() {
3
  $(this).closest( '.job-manager-uploaded-file' ).remove();
4
  return false;
5
  });
6
- $('body').on( 'submit', '.job-manager-form:not(.prevent-spinner-behavior)', function() {
7
  $(this).find( '.spinner' ).addClass( 'is-active' );
8
  $(this).find( 'input[type=submit]' ).addClass( 'disabled' ).on( 'click', function() { return false; } );
9
  });
1
  jQuery(document).ready(function($) {
2
+ $( document.body ).on( 'click', '.job-manager-remove-uploaded-file', function() {
3
  $(this).closest( '.job-manager-uploaded-file' ).remove();
4
  return false;
5
  });
6
+ $( document.body ).on( 'submit', '.job-manager-form:not(.prevent-spinner-behavior)', function() {
7
  $(this).find( '.spinner' ).addClass( 'is-active' );
8
  $(this).find( 'input[type=submit]' ).addClass( 'disabled' ).on( 'click', function() { return false; } );
9
  });
assets/js/job-submission.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(n){n("body").on("click",".job-manager-remove-uploaded-file",function(){return n(this).closest(".job-manager-uploaded-file").remove(),!1}),n("body").on("submit",".job-manager-form:not(.prevent-spinner-behavior)",function(){n(this).find(".spinner").addClass("is-active"),n(this).find("input[type=submit]").addClass("disabled").on("click",function(){return!1})})});
1
+ jQuery(document).ready(function(n){n(document.body).on("click",".job-manager-remove-uploaded-file",function(){return n(this).closest(".job-manager-uploaded-file").remove(),!1}),n(document.body).on("submit",".job-manager-form:not(.prevent-spinner-behavior)",function(){n(this).find(".spinner").addClass("is-active"),n(this).find("input[type=submit]").addClass("disabled").on("click",function(){return!1})})});
changelog.txt CHANGED
@@ -1,3 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 1.30.2 =
2
  * Enhancement: Show notice when user is using an older version of WordPress.
3
  * Enhancement: Hide unnecessary view mode in WP Admin's Job Listings page. (@RajeebTheGreat)
1
+ = 1.31.0 =
2
+ * Change: Minimum WordPress version is now 4.7.0.
3
+ * Enhancement: Add email notifications with initial support for new jobs, updated jobs, and expiring listings.
4
+ * Enhancement: For GDPR, scrub WPJM data from database on uninstall if option is enabled.
5
+ * Enhancement: Filter by Filled and Featured status in WP admin.
6
+ * Enhancement: Simplify the display of application URLs.
7
+ * Enhancement: When using WPML, prevent changes to page options when on a non-default language. (@vukvukovich)
8
+ * Enhancement: Include company logo in structured data. (@RajeebTheGreat)
9
+ * Enhancement: Use more efficient jQuery selectors in scripts. (@RajeebTheGreat)
10
+ * Enhancement: Use proper `<h2>` tag in `content-summary-job_listing.php` template for the job title. (@abdullah1908)
11
+ * Enhancement: Hide empty categories on `[job]` filter.
12
+ * Fix: Update calls to `get_terms()` to use the new format.
13
+ * Fix: Maintain the current tab when saving settings in WP Admin.
14
+ * Fix: Enqueue the date picker CSS when used on the front-end.
15
+ * Fix: Remove errors when widget instance was created without setting defaults.
16
+ * REST API Pre-release: Add support for job category taxonomy endpoints.
17
+ * Dev: Add `$job_id` parameter to `job_manager_job_dashboard_do_action_{$action}` action hook. (@jonasvogel)
18
+ * Dev: Add support for hidden WPJM settings in WP Admin.
19
+
20
  = 1.30.2 =
21
  * Enhancement: Show notice when user is using an older version of WordPress.
22
  * Enhancement: Hide unnecessary view mode in WP Admin's Job Listings page. (@RajeebTheGreat)
includes/3rd-party/wpml.php CHANGED
@@ -12,7 +12,16 @@ function wpml_wpjm_init() {
12
  add_action( 'get_job_listings_init', 'wpml_wpjm_set_language' );
13
  add_filter( 'wpjm_lang', 'wpml_wpjm_get_job_listings_lang' );
14
  add_filter( 'wpjm_page_id', 'wpml_wpjm_page_id' );
 
 
 
 
 
 
 
 
15
  }
 
16
  add_action( 'wpml_loaded', 'wpml_wpjm_init' );
17
  add_action( 'wpml_loaded', 'wpml_wpjm_set_language' );
18
 
@@ -37,6 +46,7 @@ function wpml_wpjm_set_language() {
37
  * @since 1.26.0
38
  *
39
  * @param string $lang
 
40
  * @return string
41
  */
42
  function wpml_wpjm_get_job_listings_lang( $lang ) {
@@ -47,8 +57,43 @@ function wpml_wpjm_get_job_listings_lang( $lang ) {
47
  * Returns the page ID for the current language.
48
  *
49
  * @param int $page_id
 
50
  * @return int
51
  */
52
  function wpml_wpjm_page_id( $page_id ) {
53
  return apply_filters( 'wpml_object_id', $page_id, 'page', true );
54
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  add_action( 'get_job_listings_init', 'wpml_wpjm_set_language' );
13
  add_filter( 'wpjm_lang', 'wpml_wpjm_get_job_listings_lang' );
14
  add_filter( 'wpjm_page_id', 'wpml_wpjm_page_id' );
15
+
16
+ $default_lang = apply_filters( 'wpml_default_language', null );
17
+ $current_lang = apply_filters( 'wpml_current_language', null );
18
+
19
+ // Add filter only for non default languages.
20
+ if ( $current_lang !== $default_lang ) {
21
+ add_filter( 'job_manager_settings', 'wpml_wpjm_hide_page_selection' );
22
+ }
23
  }
24
+
25
  add_action( 'wpml_loaded', 'wpml_wpjm_init' );
26
  add_action( 'wpml_loaded', 'wpml_wpjm_set_language' );
27
 
46
  * @since 1.26.0
47
  *
48
  * @param string $lang
49
+ *
50
  * @return string
51
  */
52
  function wpml_wpjm_get_job_listings_lang( $lang ) {
57
  * Returns the page ID for the current language.
58
  *
59
  * @param int $page_id
60
+ *
61
  * @return int
62
  */
63
  function wpml_wpjm_page_id( $page_id ) {
64
  return apply_filters( 'wpml_object_id', $page_id, 'page', true );
65
  }
66
+
67
+ /**
68
+ * Set WPJM page options to hidden for non default languages.
69
+ *
70
+ * @since 1.31.0
71
+ *
72
+ * @param array $settings
73
+ *
74
+ * @return array
75
+ */
76
+ function wpml_wpjm_hide_page_selection( $settings ) {
77
+ foreach ( $settings['job_pages'][1] as $key => $setting ) {
78
+ if ( 'page' !== $setting['type'] ) {
79
+ continue;
80
+ }
81
+ $setting['type'] = 'hidden';
82
+ $setting['human_value'] = __( 'Page Not Set', 'wp-job-manager' );
83
+ $current_value = get_option( $setting['name'] );
84
+ if ( $current_value ) {
85
+ $page = get_post( apply_filters( 'wpml_object_id', $current_value, 'page' ) );
86
+
87
+ if ( $page ) {
88
+ $setting['human_value'] = $page->post_title;
89
+ }
90
+ }
91
+
92
+ $default_lang = apply_filters( 'wpml_default_language', null );
93
+ $url_to_edit_page = admin_url( 'edit.php?post_type=job_listing&page=job-manager-settings&lang=' . $default_lang . '#settings-job_pages' );
94
+ $setting['desc'] = sprintf( __( '<a href="%s">Switch to primary language</a> to edit this setting.', 'wp-job-manager' ), $url_to_edit_page );
95
+ $settings['job_pages'][1][ $key ] = $setting;
96
+ }
97
+
98
+ return $settings;
99
+ }
includes/abstracts/abstract-wp-job-manager-email-template.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Abstract class for an email notification built using templates.
9
+ *
10
+ * @package wp-job-manager
11
+ *
12
+ * @since 1.31.0
13
+ */
14
+
15
+ abstract class WP_Job_Manager_Email_Template extends WP_Job_Manager_Email {
16
+ /**
17
+ * Get the template path for overriding templates.
18
+ *
19
+ * @type abstract
20
+ * @return string
21
+ */
22
+ public static function get_template_path() {
23
+ return 'job_manager';
24
+ }
25
+
26
+ /**
27
+ * Get the default template path that WP Job Manager should look for the templates.
28
+ *
29
+ * @type abstract
30
+ * @return string
31
+ */
32
+ public static function get_template_default_path() {
33
+ return '';
34
+ }
35
+
36
+ /**
37
+ * Get the rich text version of the email content.
38
+ *
39
+ * @return string
40
+ */
41
+ public function get_rich_content() {
42
+ return $this->get_template( false );
43
+ }
44
+
45
+ /**
46
+ * Get the plaintext version of the email content.
47
+ *
48
+ * @return string
49
+ */
50
+ public function get_plain_content() {
51
+ if ( $this->has_template( true ) ) {
52
+ return $this->get_template( true );
53
+ }
54
+ return parent::get_plain_content();
55
+ }
56
+
57
+ /**
58
+ * Get the contents of a template.
59
+ *
60
+ * @param bool $plain_text
61
+ * @return string
62
+ */
63
+ public function get_template( $plain_text = false ) {
64
+ $template_file = $this->locate_template( $plain_text );
65
+ if ( ! $template_file ) {
66
+ return '';
67
+ }
68
+ $args = $this->get_args();
69
+ $email = $this;
70
+
71
+ ob_start();
72
+ include $template_file;
73
+ return ob_get_clean();
74
+ }
75
+
76
+ /**
77
+ * Check to see if a template exists for this email.
78
+ *
79
+ * @param bool $plain_text
80
+ * @return bool
81
+ */
82
+ public function has_template( $plain_text = false ) {
83
+ $template_file = $this->locate_template( $plain_text );
84
+ return $template_file && file_exists( $template_file );
85
+ }
86
+
87
+ /**
88
+ * Locate template for this email.
89
+ *
90
+ * @param bool $plain_text
91
+ * @return string
92
+ */
93
+ protected function locate_template( $plain_text ) {
94
+ $class_name = get_class( $this );
95
+ $template_path = call_user_func( array( $class_name, 'get_template_path' ) );
96
+ $template_default_path = call_user_func( array( $class_name, 'get_template_default_path' ) );
97
+ return locate_job_manager_template( $this->get_template_file_name( $plain_text ), $template_path, $template_default_path );
98
+ }
99
+
100
+ /**
101
+ * Generate the file name for the email template.
102
+ *
103
+ * @param bool $plain_text
104
+ * @return string
105
+ */
106
+ protected function get_template_file_name( $plain_text = false ) {
107
+ $class_name = get_class( $this );
108
+ // PHP 5.2: Using `call_user_func()` but `$class_name::get_key()` preferred.
109
+ $email_notification_key = call_user_func( array( $class_name, 'get_key') );
110
+ $template_name = str_replace( '_', '-', $email_notification_key );
111
+ return self::generate_template_file_name( $template_name, $plain_text );
112
+ }
113
+
114
+ /**
115
+ * Generate the file name for the email template.
116
+ *
117
+ * @param string $template_name
118
+ * @param bool $plain_text
119
+ * @return string
120
+ */
121
+ public static function generate_template_file_name( $template_name, $plain_text = false ) {
122
+ $file_name_parts = array( 'emails' );
123
+ if ( $plain_text ) {
124
+ $file_name_parts[] = 'plain';
125
+ }
126
+
127
+ $file_name_parts[] = $template_name . '.php';
128
+
129
+ return implode( '/', $file_name_parts );
130
+ }
131
+ }
includes/abstracts/abstract-wp-job-manager-email.php ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Abstract class for an email notification.
9
+ *
10
+ * Do not rely on WordPress global variables or functions that rely on global variables such as `wp_get_current_user()`.
11
+ * Email might be generated when no longer in scope. Instead, pass the values on as an argument when initiating the email
12
+ * notification.
13
+ *
14
+ * Additionally, inside of plugins and themes, load email notification files based on this class inside the
15
+ * `job_manager_email_init` hook. This will prevent unnecessary loading and won't include the files if this abstract
16
+ * class isn't available.
17
+ *
18
+ * Example:
19
+ * ```
20
+ * add_action( 'job_manager_email_init', 'custom_plugin_include_emails' );
21
+ * function custom_plugin_include_emails() {
22
+ * include_once 'emails/custom-plugin-sent-resume.php`;
23
+ * }
24
+ * ```
25
+ *
26
+ * @package wp-job-manager
27
+ *
28
+ * @since 1.31.0
29
+ */
30
+
31
+ abstract class WP_Job_Manager_Email {
32
+ /**
33
+ * @var array
34
+ */
35
+ private $args = array();
36
+
37
+ /**
38
+ * @var array
39
+ */
40
+ private $settings = array();
41
+
42
+ /**
43
+ * WP_Job_Manager_Email constructor.
44
+ *
45
+ * @param array $args Arguments used in forming email notification.
46
+ * @param array $settings Settings for this notification.
47
+ */
48
+ final public function __construct( $args, $settings ) {
49
+ $this->args = $this->prepare_args( (array) $args );
50
+ $this->settings = (array) $settings;
51
+ }
52
+
53
+ /**
54
+ * Get the unique email notification key.
55
+ *
56
+ * @type abstract
57
+ *
58
+ * @return string
59
+ */
60
+ public static function get_key() {
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Get the friendly name for this email notification.
66
+ *
67
+ * @type abstract
68
+ * @return string
69
+ */
70
+ public static function get_name() {
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Get the description for this email notification.
76
+ *
77
+ * @type abstract
78
+ * @return string
79
+ */
80
+ public static function get_description() {
81
+ return '';
82
+ }
83
+
84
+ /**
85
+ * Get the context for where this email notification is used. Used to direct which admin settings to show.
86
+ *
87
+ * @type abstract
88
+ * @return string
89
+ */
90
+ public static function get_context() {
91
+ return 'job_manager';
92
+ }
93
+
94
+ /**
95
+ * Get the email subject.
96
+ *
97
+ * @return string
98
+ */
99
+ abstract public function get_subject();
100
+
101
+ /**
102
+ * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
103
+ *
104
+ * @return string|bool Email from value or false to use WordPress' default.
105
+ */
106
+ abstract public function get_from();
107
+
108
+ /**
109
+ * Get array or comma-separated list of email addresses to send message.
110
+ *
111
+ * @return string|array
112
+ */
113
+ abstract public function get_to();
114
+
115
+ /**
116
+ * Get the rich text version of the email content.
117
+ *
118
+ * @return string
119
+ */
120
+ abstract public function get_rich_content();
121
+
122
+ /**
123
+ * Expand arguments as necessary for the generation of the email.
124
+ *
125
+ * @param $args
126
+ * @return mixed
127
+ */
128
+ protected function prepare_args( $args ) {
129
+ if ( isset( $args['job_id'] ) ) {
130
+ $job = get_post( $args['job_id'] );
131
+ if ( $job instanceof WP_Post ) {
132
+ $args['job'] = $job;
133
+ }
134
+ }
135
+ if ( isset( $args['job'] ) && $args['job'] instanceof WP_Post ) {
136
+ $author = get_user_by( 'ID', $args['job']->post_author );
137
+ if ( $author instanceof WP_User ) {
138
+ $args['author'] = $author;
139
+ }
140
+ }
141
+
142
+ return $args;
143
+ }
144
+
145
+ /**
146
+ * Checks the arguments and returns whether the email notification is properly set up.
147
+ *
148
+ * @return bool
149
+ */
150
+ abstract public function is_valid();
151
+
152
+ /**
153
+ * Returns the list of file paths to attach to an email.
154
+ *
155
+ * @return array
156
+ */
157
+ public function get_attachments() {
158
+ return array();
159
+ }
160
+
161
+ /**
162
+ * Returns the value of the CC header, if needed.
163
+ *
164
+ * @return string|null
165
+ */
166
+ public function get_cc() {
167
+ return null;
168
+ }
169
+
170
+ /**
171
+ * Get the base headers for the email. No need to add CC or From headers. Content-type is added when sending rich-text.
172
+ *
173
+ * @return array
174
+ */
175
+ public function get_headers() {
176
+ return array();
177
+ }
178
+
179
+ /**
180
+ * Get the plaintext version of the email content.
181
+ *
182
+ * @return string
183
+ */
184
+ public function get_plain_content() {
185
+ return strip_tags( $this->get_rich_content() );
186
+ }
187
+
188
+ /**
189
+ * Get the settings for this email notifications.
190
+ *
191
+ * @return array
192
+ */
193
+ public static function get_setting_fields() {
194
+ return array();
195
+ }
196
+
197
+ /**
198
+ * Is this email notification enabled by default?
199
+ *
200
+ * @return bool
201
+ */
202
+ public static function is_default_enabled() {
203
+ return true;
204
+ }
205
+
206
+ /**
207
+ * Returns the args that the email notification was sent with.
208
+ *
209
+ * @return array
210
+ */
211
+ final protected function get_args() {
212
+ return $this->args;
213
+ }
214
+
215
+ /**
216
+ * Returns the settings values.
217
+ *
218
+ * @return array
219
+ */
220
+ final protected function get_settings() {
221
+ return $this->settings;
222
+ }
223
+ }
includes/admin/class-wp-job-manager-admin.php CHANGED
@@ -83,7 +83,7 @@ class WP_Job_Manager_Admin {
83
  }
84
 
85
  echo '<div class="error">';
86
- echo '<p>' . sprintf( __( 'The upcoming release of <strong>WP Job Manager 1.31.0</strong> will require a more recent version of WordPress. <a href="%s">Please update WordPress</a> before updating WP Job Manager.', 'wp-job-manager' ), esc_url( self_admin_url( 'update-core.php' ) ) ) . '</p>';
87
  echo '</div>';
88
  }
89
 
@@ -116,14 +116,10 @@ class WP_Job_Manager_Admin {
116
  * Enqueues CSS and JS assets.
117
  */
118
  public function admin_enqueue_scripts() {
119
- global $wp_scripts;
120
-
121
  $screen = get_current_screen();
122
 
123
  if ( in_array( $screen->id, apply_filters( 'job_manager_admin_screen_ids', array( 'edit-job_listing', 'plugins', 'job_listing', 'job_listing_page_job-manager-settings', 'job_listing_page_job-manager-addons' ) ) ) ) {
124
- $jquery_version = isset( $wp_scripts->registered['jquery-ui-core']->ver ) ? $wp_scripts->registered['jquery-ui-core']->ver : '1.9.2';
125
-
126
- wp_enqueue_style( 'jquery-ui-style', '//code.jquery.com/ui/' . $jquery_version . '/themes/smoothness/jquery-ui.css', array(), $jquery_version );
127
  wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css', array(), JOB_MANAGER_VERSION );
128
  wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL. '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
129
  wp_enqueue_script( 'job_manager_datepicker_js', JOB_MANAGER_PLUGIN_URL. '/assets/js/datepicker.min.js', array( 'jquery', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
83
  }
84
 
85
  echo '<div class="error">';
86
+ echo '<p>' . sprintf( __( '<strong>WP Job Manager</strong> requires a more recent version of WordPress. <a href="%s">Please update WordPresse</a> to avoid issues.', 'wp-job-manager' ), esc_url( self_admin_url( 'update-core.php' ) ) ) . '</p>';
87
  echo '</div>';
88
  }
89
 
116
  * Enqueues CSS and JS assets.
117
  */
118
  public function admin_enqueue_scripts() {
 
 
119
  $screen = get_current_screen();
120
 
121
  if ( in_array( $screen->id, apply_filters( 'job_manager_admin_screen_ids', array( 'edit-job_listing', 'plugins', 'job_listing', 'job_listing_page_job-manager-settings', 'job_listing_page_job-manager-addons' ) ) ) ) {
122
+ wp_enqueue_style( 'jquery-ui' );
 
 
123
  wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css', array(), JOB_MANAGER_VERSION );
124
  wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL. '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
125
  wp_enqueue_script( 'job_manager_datepicker_js', JOB_MANAGER_PLUGIN_URL. '/assets/js/datepicker.min.js', array( 'jquery', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
includes/admin/class-wp-job-manager-cpt.php CHANGED
@@ -44,6 +44,7 @@ class WP_Job_Manager_CPT {
44
  add_filter( 'manage_edit-job_listing_sortable_columns', array( $this, 'sortable_columns' ) );
45
  add_filter( 'request', array( $this, 'sort_columns' ) );
46
  add_action( 'parse_query', array( $this, 'search_meta' ) );
 
47
  add_filter( 'get_search_query', array( $this, 'search_meta_label' ) );
48
  add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
49
  add_action( 'bulk_actions-edit-job_listing', array( $this, 'add_bulk_actions' ) );
@@ -55,6 +56,7 @@ class WP_Job_Manager_CPT {
55
  if ( get_option( 'job_manager_enable_categories' ) ) {
56
  add_action( "restrict_manage_posts", array( $this, "jobs_by_category" ) );
57
  }
 
58
 
59
  foreach ( array( 'post', 'post-new' ) as $hook ) {
60
  add_action( "admin_footer-{$hook}.php", array( $this,'extend_submitdiv_post_status' ) );
@@ -282,13 +284,14 @@ class WP_Job_Manager_CPT {
282
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php' );
283
 
284
  $r = array();
 
285
  $r['pad_counts'] = 1;
286
  $r['hierarchical'] = 1;
287
  $r['hide_empty'] = 0;
288
  $r['show_count'] = 1;
289
  $r['selected'] = ( isset( $wp_query->query['job_listing_category'] ) ) ? $wp_query->query['job_listing_category'] : '';
290
  $r['menu_order'] = false;
291
- $terms = get_terms( 'job_listing_category', $r );
292
  $walker = new WP_Job_Manager_Category_Walker;
293
 
294
  if ( ! $terms ) {
@@ -303,6 +306,84 @@ class WP_Job_Manager_CPT {
303
  echo $output;
304
  }
305
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  /**
307
  * Filters page title placeholder text to show custom label.
308
  *
@@ -597,6 +678,45 @@ class WP_Job_Manager_CPT {
597
  $wp->query_vars['post__in'] = $post_ids;
598
  }
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  /**
601
  * Changes the label when searching meta.
602
  *
44
  add_filter( 'manage_edit-job_listing_sortable_columns', array( $this, 'sortable_columns' ) );
45
  add_filter( 'request', array( $this, 'sort_columns' ) );
46
  add_action( 'parse_query', array( $this, 'search_meta' ) );
47
+ add_action( 'parse_query', array( $this, 'filter_meta' ) );
48
  add_filter( 'get_search_query', array( $this, 'search_meta_label' ) );
49
  add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
50
  add_action( 'bulk_actions-edit-job_listing', array( $this, 'add_bulk_actions' ) );
56
  if ( get_option( 'job_manager_enable_categories' ) ) {
57
  add_action( "restrict_manage_posts", array( $this, "jobs_by_category" ) );
58
  }
59
+ add_action( "restrict_manage_posts", array( $this, "jobs_meta_filters" ) );
60
 
61
  foreach ( array( 'post', 'post-new' ) as $hook ) {
62
  add_action( "admin_footer-{$hook}.php", array( $this,'extend_submitdiv_post_status' ) );
284
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php' );
285
 
286
  $r = array();
287
+ $r['taxonomy'] = 'job_listing_category';
288
  $r['pad_counts'] = 1;
289
  $r['hierarchical'] = 1;
290
  $r['hide_empty'] = 0;
291
  $r['show_count'] = 1;
292
  $r['selected'] = ( isset( $wp_query->query['job_listing_category'] ) ) ? $wp_query->query['job_listing_category'] : '';
293
  $r['menu_order'] = false;
294
+ $terms = get_terms( $r );
295
  $walker = new WP_Job_Manager_Category_Walker;
296
 
297
  if ( ! $terms ) {
306
  echo $output;
307
  }
308
 
309
+ /**
310
+ * Output dropdowns for filters based on post meta.
311
+ *
312
+ * @since 1.31.0
313
+ */
314
+ public function jobs_meta_filters() {
315
+ global $typenow;
316
+
317
+ // Only add the filters for job_listings
318
+ if ( 'job_listing' !== $typenow ) {
319
+ return;
320
+ }
321
+
322
+ // Filter by Filled.
323
+ $this->jobs_filter_dropdown( 'job_listing_filled', array(
324
+ array(
325
+ 'value' => '',
326
+ 'text' => __( 'Select Filled', 'wp-job-manager' ),
327
+ ),
328
+ array(
329
+ 'value' => '1',
330
+ 'text' => __( 'Filled', 'wp-job-manager' ),
331
+ ),
332
+ array(
333
+ 'value' => '0',
334
+ 'text' => __( 'Not Filled', 'wp-job-manager' ),
335
+ ),
336
+ ) );
337
+
338
+ // Filter by Featured.
339
+ $this->jobs_filter_dropdown( 'job_listing_featured', array(
340
+ array(
341
+ 'value' => '',
342
+ 'text' => __( 'Select Featured', 'wp-job-manager' ),
343
+ ),
344
+ array(
345
+ 'value' => '1',
346
+ 'text' => __( 'Featured', 'wp-job-manager' ),
347
+ ),
348
+ array(
349
+ 'value' => '0',
350
+ 'text' => __( 'Not Featured', 'wp-job-manager' ),
351
+ ),
352
+ ) );
353
+ }
354
+
355
+ /**
356
+ * Shows dropdown to filter by the given URL parameter. The dropdown will
357
+ * have three options: "Select $name", "$name", and "Not $name".
358
+ *
359
+ * The $options element should be an array of arrays, each with the
360
+ * attributes needed to create an <option> HTML element. The attributes are
361
+ * as follows:
362
+ *
363
+ * $options[i]['value'] The value for the <option> HTML element.
364
+ * $options[i]['text'] The text for the <option> HTML element.
365
+ *
366
+ * @since 1.31.0
367
+ *
368
+ * @param string $param The URL parameter.
369
+ * @param array $options The options for the dropdown. See the
370
+ * description above.
371
+ */
372
+ private function jobs_filter_dropdown( $param, $options ) {
373
+ $selected = isset( $_GET[ $param ] ) ? $_GET[ $param ] : '';
374
+
375
+ $output = "<select name=\"$param\" id=\"dropdown_$param\">";
376
+
377
+ foreach ( $options as $option ) {
378
+ $output .= '<option value="' . esc_attr( $option['value'] ) . '"'
379
+ . ( $selected === $option['value'] ? ' selected' : '' )
380
+ . '>' . esc_html( $option['text'] ) . '</option>';
381
+ }
382
+ $output .= '</select>';
383
+
384
+ echo $output;
385
+ }
386
+
387
  /**
388
  * Filters page title placeholder text to show custom label.
389
  *
678
  $wp->query_vars['post__in'] = $post_ids;
679
  }
680
 
681
+ /**
682
+ * Filters by meta fields.
683
+ *
684
+ * @param WP_Query $wp
685
+ */
686
+ public function filter_meta( $wp ) {
687
+ global $pagenow;
688
+
689
+ if ( 'edit.php' !== $pagenow || 'job_listing' !== $wp->query_vars['post_type'] ) {
690
+ return;
691
+ }
692
+
693
+ $meta_query = $wp->get( 'meta_query' );
694
+ if ( ! is_array( $meta_query ) ) {
695
+ $meta_query = array();
696
+ }
697
+
698
+ // Filter on _filled meta.
699
+ if ( isset( $_GET['job_listing_filled'] ) && '' !== $_GET['job_listing_filled'] ) {
700
+ $meta_query[] = array(
701
+ 'key' => '_filled',
702
+ 'value' => $_GET['job_listing_filled'],
703
+ );
704
+ }
705
+
706
+ // Filter on _featured meta.
707
+ if ( isset( $_GET['job_listing_featured'] ) && '' !== $_GET['job_listing_featured'] ) {
708
+ $meta_query[] = array(
709
+ 'key' => '_featured',
710
+ 'value' => $_GET['job_listing_featured'],
711
+ );
712
+ }
713
+
714
+ // Set new meta query.
715
+ if ( ! empty( $meta_query ) ) {
716
+ $wp->set( 'meta_query', $meta_query );
717
+ }
718
+ }
719
+
720
  /**
721
  * Changes the label when searching meta.
722
  *
includes/admin/class-wp-job-manager-settings.php CHANGED
@@ -99,6 +99,15 @@ class WP_Job_Manager_Settings {
99
  'desc' => sprintf( __( 'Google requires an API key to retrieve location information for job listings. Acquire an API key from the <a href="%s">Google Maps API developer site</a>.', 'wp-job-manager' ), 'https://developers.google.com/maps/documentation/geocoding/get-api-key' ),
100
  'attributes' => array()
101
  ),
 
 
 
 
 
 
 
 
 
102
  ),
103
  ),
104
  'job_listings' => array(
@@ -378,17 +387,17 @@ class WP_Job_Manager_Settings {
378
  $this->init_settings();
379
  ?>
380
  <div class="wrap job-manager-settings-wrap">
381
- <form method="post" action="options.php">
382
 
383
  <?php settings_fields( $this->settings_group ); ?>
384
 
385
- <h2 class="nav-tab-wrapper">
386
- <?php
387
- foreach ( $this->settings as $key => $section ) {
388
- echo '<a href="#settings-' . sanitize_title( $key ) . '" class="nav-tab">' . esc_html( $section[0] ) . '</a>';
389
- }
390
- ?>
391
- </h2>
392
 
393
  <?php
394
  if ( ! empty( $_GET['settings-updated'] ) ) {
@@ -397,142 +406,48 @@ class WP_Job_Manager_Settings {
397
  }
398
 
399
  foreach ( $this->settings as $key => $section ) {
400
-
401
  echo '<div id="settings-' . sanitize_title( $key ) . '" class="settings_panel">';
402
-
403
- echo '<table class="form-table">';
 
 
404
 
405
  foreach ( $section[1] as $option ) {
406
-
407
- $placeholder = ( ! empty( $option['placeholder'] ) ) ? 'placeholder="' . $option['placeholder'] . '"' : '';
408
- $class = ! empty( $option['class'] ) ? $option['class'] : '';
409
- $value = get_option( $option['name'] );
410
- $option['type'] = ! empty( $option['type'] ) ? $option['type'] : '';
411
- $attributes = array();
412
-
413
- if ( ! empty( $option['attributes'] ) && is_array( $option['attributes'] ) )
414
- foreach ( $option['attributes'] as $attribute_name => $attribute_value )
415
- $attributes[] = esc_attr( $attribute_name ) . '="' . esc_attr( $attribute_value ) . '"';
416
-
417
- echo '<tr valign="top" class="' . $class . '"><th scope="row"><label for="setting-' . $option['name'] . '">' . $option['label'] . '</a></th><td>';
418
-
419
- switch ( $option['type'] ) {
420
-
421
- case "checkbox" :
422
-
423
- ?><label><input id="setting-<?php echo $option['name']; ?>" name="<?php echo $option['name']; ?>" type="checkbox" value="1" <?php echo implode( ' ', $attributes ); ?> <?php checked( '1', $value ); ?> /> <?php echo $option['cb_label']; ?></label><?php
424
-
425
- if ( $option['desc'] )
426
- echo ' <p class="description">' . $option['desc'] . '</p>';
427
-
428
- break;
429
- case "textarea" :
430
-
431
- ?><textarea id="setting-<?php echo $option['name']; ?>" class="large-text" cols="50" rows="3" name="<?php echo $option['name']; ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?>><?php echo esc_textarea( $value ); ?></textarea><?php
432
-
433
- if ( $option['desc'] )
434
- echo ' <p class="description">' . $option['desc'] . '</p>';
435
-
436
- break;
437
- case "select" :
438
-
439
- ?><select id="setting-<?php echo $option['name']; ?>" class="regular-text" name="<?php echo $option['name']; ?>" <?php echo implode( ' ', $attributes ); ?>><?php
440
- foreach( $option['options'] as $key => $name )
441
- echo '<option value="' . esc_attr( $key ) . '" ' . selected( $value, $key, false ) . '>' . esc_html( $name ) . '</option>';
442
- ?></select><?php
443
-
444
- if ( $option['desc'] ) {
445
- echo ' <p class="description">' . $option['desc'] . '</p>';
446
- }
447
-
448
- break;
449
- case "radio":
450
- ?><fieldset>
451
- <legend class="screen-reader-text">
452
- <span><?php echo esc_html( $option['label'] ); ?></span>
453
- </legend><?php
454
-
455
- if ( $option['desc'] ) {
456
- echo '<p class="description">' . $option['desc'] . '</p>';
457
- }
458
-
459
- foreach( $option['options'] as $key => $name )
460
- echo '<label><input name="' . esc_attr( $option['name'] ) . '" type="radio" value="' . esc_attr( $key ) . '" ' . checked( $value, $key, false ) . ' />' . esc_html( $name ) . '</label><br>';
461
-
462
- ?></fieldset><?php
463
-
464
- break;
465
- case "page" :
466
-
467
- $args = array(
468
- 'name' => $option['name'],
469
- 'id' => $option['name'],
470
- 'sort_column' => 'menu_order',
471
- 'sort_order' => 'ASC',
472
- 'show_option_none' => __( '--no page--', 'wp-job-manager' ),
473
- 'echo' => false,
474
- 'selected' => absint( $value )
475
- );
476
-
477
- echo str_replace(' id=', " data-placeholder='" . __( 'Select a page&hellip;', 'wp-job-manager' ) . "' id=", wp_dropdown_pages( $args ) );
478
-
479
- if ( $option['desc'] ) {
480
- echo ' <p class="description">' . $option['desc'] . '</p>';
481
- }
482
-
483
- break;
484
- case "password" :
485
-
486
- ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="password" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
487
-
488
- if ( $option['desc'] ) {
489
- echo ' <p class="description">' . $option['desc'] . '</p>';
490
- }
491
-
492
- break;
493
- case "number" :
494
- ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="number" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
495
-
496
- if ( $option['desc'] ) {
497
- echo ' <p class="description">' . $option['desc'] . '</p>';
498
- }
499
- break;
500
- case "" :
501
- case "input" :
502
- case "text" :
503
- ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="text" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
504
-
505
- if ( $option['desc'] ) {
506
- echo ' <p class="description">' . $option['desc'] . '</p>';
507
- }
508
- break;
509
- default :
510
- do_action( 'wp_job_manager_admin_field_' . $option['type'], $option, $attributes, $value, $placeholder );
511
- break;
512
-
513
- }
514
-
515
- echo '</td></tr>';
516
  }
517
 
518
- echo '</table></div>';
 
 
 
 
519
 
520
  }
521
  ?>
522
  <p class="submit">
523
  <input type="submit" class="button-primary" value="<?php _e( 'Save Changes', 'wp-job-manager' ); ?>" />
524
  </p>
525
- </form>
526
  </div>
527
  <script type="text/javascript">
528
  jQuery('.nav-tab-wrapper a').click(function() {
 
 
 
529
  jQuery('.settings_panel').hide();
530
  jQuery('.nav-tab-active').removeClass('nav-tab-active');
531
  jQuery( jQuery(this).attr('href') ).show();
532
  jQuery(this).addClass('nav-tab-active');
 
 
533
  return false;
534
  });
535
  var goto_hash = window.location.hash;
 
 
 
536
  if ( goto_hash ) {
537
  var the_tab = jQuery( 'a[href="' + goto_hash + '"]' );
538
  if ( the_tab.length > 0 ) {
@@ -567,7 +482,7 @@ class WP_Job_Manager_Settings {
567
 
568
  $generate_username_from_email.change(function() {
569
  if ( jQuery( this ).is(':checked') ) {
570
- $use_standard_password_setup_email.data('original-state', $use_standard_password_setup_email.is(':checked')).prop('checked', true).prop('disabled', true);
571
  } else {
572
  $use_standard_password_setup_email.prop('disabled', false);
573
  if ( undefined !== $use_standard_password_setup_email.data('original-state') ) {
@@ -575,7 +490,274 @@ class WP_Job_Manager_Settings {
575
  }
576
  }
577
  }).change();
 
 
 
 
 
 
 
 
 
 
578
  </script>
579
  <?php
580
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  }
99
  'desc' => sprintf( __( 'Google requires an API key to retrieve location information for job listings. Acquire an API key from the <a href="%s">Google Maps API developer site</a>.', 'wp-job-manager' ), 'https://developers.google.com/maps/documentation/geocoding/get-api-key' ),
100
  'attributes' => array()
101
  ),
102
+ array(
103
+ 'name' => 'job_manager_delete_data_on_uninstall',
104
+ 'std' => '0',
105
+ 'label' => __( 'Delete Data On Uninstall', 'wp-job-manager' ),
106
+ 'cb_label' => __( 'Delete WP Job Manager data when the plugin is deleted. Once removed, this data cannot be restored.', 'wp-job-manager' ),
107
+ 'desc' => '',
108
+ 'type' => 'checkbox',
109
+ 'attributes' => array()
110
+ ),
111
  ),
112
  ),
113
  'job_listings' => array(
387
  $this->init_settings();
388
  ?>
389
  <div class="wrap job-manager-settings-wrap">
390
+ <form class="job-manager-options" method="post" action="options.php">
391
 
392
  <?php settings_fields( $this->settings_group ); ?>
393
 
394
+ <h2 class="nav-tab-wrapper">
395
+ <?php
396
+ foreach ( $this->settings as $key => $section ) {
397
+ echo '<a href="#settings-' . sanitize_title( $key ) . '" class="nav-tab">' . esc_html( $section[0] ) . '</a>';
398
+ }
399
+ ?>
400
+ </h2>
401
 
402
  <?php
403
  if ( ! empty( $_GET['settings-updated'] ) ) {
406
  }
407
 
408
  foreach ( $this->settings as $key => $section ) {
409
+ $section_args = isset( $section[2] ) ? (array) $section[2] : array();
410
  echo '<div id="settings-' . sanitize_title( $key ) . '" class="settings_panel">';
411
+ if ( ! empty( $section_args['before'] ) ) {
412
+ echo '<p class="before-settings">' . $section_args['before'] . '</p>';
413
+ }
414
+ echo '<table class="form-table settings parent-settings">';
415
 
416
  foreach ( $section[1] as $option ) {
417
+ $value = get_option( $option['name'] );
418
+ $this->output_field( $option, $value );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  }
420
 
421
+ echo '</table>';
422
+ if ( ! empty( $section_args['after'] ) ) {
423
+ echo '<p class="after-settings">' . $section_args['after'] . '</p>';
424
+ }
425
+ echo '</div>';
426
 
427
  }
428
  ?>
429
  <p class="submit">
430
  <input type="submit" class="button-primary" value="<?php _e( 'Save Changes', 'wp-job-manager' ); ?>" />
431
  </p>
432
+ </form>
433
  </div>
434
  <script type="text/javascript">
435
  jQuery('.nav-tab-wrapper a').click(function() {
436
+ if ( '#' !== jQuery(this).attr( 'href' ).substr( 0, 1 ) ) {
437
+ return false;
438
+ }
439
  jQuery('.settings_panel').hide();
440
  jQuery('.nav-tab-active').removeClass('nav-tab-active');
441
  jQuery( jQuery(this).attr('href') ).show();
442
  jQuery(this).addClass('nav-tab-active');
443
+ window.location.hash = jQuery(this).attr('href');
444
+ jQuery( 'form.job-manager-options' ).attr( 'action', 'options.php' + jQuery(this).attr( 'href' ) );
445
  return false;
446
  });
447
  var goto_hash = window.location.hash;
448
+ if ( '#' === goto_hash.substr( 0, 1 ) ) {
449
+ jQuery( 'form.job-manager-options' ).attr( 'action', 'options.php' + jQuery(this).attr( 'href' ) );
450
+ }
451
  if ( goto_hash ) {
452
  var the_tab = jQuery( 'a[href="' + goto_hash + '"]' );
453
  if ( the_tab.length > 0 ) {
482
 
483
  $generate_username_from_email.change(function() {
484
  if ( jQuery( this ).is(':checked') ) {
485
+ $use_standard_password_setup_email.data('original-state', $use_standard_password_setup_email.is(':checked')).prop('checked', true).prop('disabled', true);
486
  } else {
487
  $use_standard_password_setup_email.prop('disabled', false);
488
  if ( undefined !== $use_standard_password_setup_email.data('original-state') ) {
490
  }
491
  }
492
  }).change();
493
+
494
+ jQuery( '.sub-settings-expander' ).on( 'change', function() {
495
+ var $expandable = jQuery(this).parent().siblings( '.sub-settings-expandable' );
496
+ var checked = jQuery(this).is( ':checked' );
497
+ if ( checked ) {
498
+ $expandable.addClass( 'expanded' );
499
+ } else {
500
+ $expandable.removeClass( 'expanded' );
501
+ }
502
+ } ).trigger( 'change' );
503
  </script>
504
  <?php
505
  }
506
+
507
+ /**
508
+ * Checkbox input field.
509
+ *
510
+ * @param array $option
511
+ * @param array $attributes
512
+ * @param mixed $value
513
+ * @param string $placeholder (Ignored).
514
+ */
515
+ protected function input_checkbox( $option, $attributes, $value, $placeholder ) {
516
+ ?><label>
517
+ <input type="hidden" name="<?php echo $option['name']; ?>" value="0" />
518
+ <input id="setting-<?php echo $option['name']; ?>" name="<?php echo $option['name']; ?>" type="checkbox" value="1" <?php echo implode( ' ', $attributes ); ?> <?php checked( '1', $value ); ?> /> <?php echo $option['cb_label']; ?></label><?php
519
+
520
+ if ( ! empty( $option['desc'] ) ) {
521
+ echo ' <p class="description">' . $option['desc'] . '</p>';
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Text area input field.
527
+ *
528
+ * @param array $option
529
+ * @param array $attributes
530
+ * @param mixed $value
531
+ * @param string $placeholder
532
+ */
533
+ protected function input_textarea ( $option, $attributes, $value, $placeholder ) {
534
+ ?><textarea id="setting-<?php echo $option['name']; ?>" class="large-text" cols="50" rows="3" name="<?php echo $option['name']; ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?>><?php echo esc_textarea( $value ); ?></textarea><?php
535
+
536
+ if ( ! empty( $option['desc'] ) ) {
537
+ echo ' <p class="description">' . $option['desc'] . '</p>';
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Select input field.
543
+ *
544
+ * @param array $option
545
+ * @param array $attributes
546
+ * @param mixed $value
547
+ * @param string $placeholder (Ignored).
548
+ */
549
+ protected function input_select( $option, $attributes, $value, $placeholder ) {
550
+ ?><select id="setting-<?php echo $option['name']; ?>" class="regular-text" name="<?php echo $option['name']; ?>" <?php echo implode( ' ', $attributes ); ?>><?php
551
+ foreach( $option['options'] as $key => $name )
552
+ echo '<option value="' . esc_attr( $key ) . '" ' . selected( $value, $key, false ) . '>' . esc_html( $name ) . '</option>';
553
+ ?></select><?php
554
+
555
+ if ( ! empty( $option['desc'] ) ) {
556
+ echo ' <p class="description">' . $option['desc'] . '</p>';
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Radio input field.
562
+ *
563
+ * @param array $option
564
+ * @param array $attributes
565
+ * @param mixed $value
566
+ * @param string $placeholder (Ignored).
567
+ */
568
+ protected function input_radio( $option, $attributes, $value, $placeholder ) {
569
+ ?><fieldset>
570
+ <legend class="screen-reader-text">
571
+ <span><?php echo esc_html( $option['label'] ); ?></span>
572
+ </legend><?php
573
+ if ( ! empty( $option['desc'] ) ) {
574
+ echo ' <p class="description">' . $option['desc'] . '</p>';
575
+ }
576
+
577
+ foreach( $option['options'] as $key => $name ) {
578
+ echo '<label><input name="' . esc_attr( $option['name'] ) . '" type="radio" value="' . esc_attr( $key ) . '" ' . checked( $value, $key, false ) . ' />' . esc_html( $name ) . '</label><br>';
579
+ }
580
+ ?></fieldset><?php
581
+ }
582
+
583
+ /**
584
+ * Page input field.
585
+ *
586
+ * @param array $option
587
+ * @param array $attributes
588
+ * @param mixed $value
589
+ * @param string $placeholder (Ignored).
590
+ */
591
+ protected function input_page( $option, $attributes, $value, $placeholder ) {
592
+ $args = array(
593
+ 'name' => $option['name'],
594
+ 'id' => $option['name'],
595
+ 'sort_column' => 'menu_order',
596
+ 'sort_order' => 'ASC',
597
+ 'show_option_none' => __( '--no page--', 'wp-job-manager' ),
598
+ 'echo' => false,
599
+ 'selected' => absint( $value )
600
+ );
601
+
602
+ echo str_replace(' id=', " data-placeholder='" . __( 'Select a page&hellip;', 'wp-job-manager' ) . "' id=", wp_dropdown_pages( $args ) );
603
+
604
+ if ( ! empty( $option['desc'] ) ) {
605
+ echo ' <p class="description">' . $option['desc'] . '</p>';
606
+ }
607
+ }
608
+
609
+ /**
610
+ * Hidden input field.
611
+ *
612
+ * @param array $option
613
+ * @param array $attributes
614
+ * @param mixed $value
615
+ * @param string $placeholder (Ignored).
616
+ */
617
+ protected function input_hidden( $option, $attributes, $value, $placeholder ) {
618
+ $human_value = $value;
619
+ if( $option['human_value'] ) {
620
+ $human_value = $option['human_value'];
621
+ }
622
+ ?><input id="setting-<?php echo $option['name']; ?>" type="hidden" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> /><strong><?php echo esc_html( $human_value ) ?></strong><?php
623
+
624
+ if ( ! empty( $option['desc'] ) ) {
625
+ echo ' <p class="description">' . $option['desc'] . '</p>';
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Password input field.
631
+ *
632
+ * @param array $option
633
+ * @param array $attributes
634
+ * @param mixed $value
635
+ * @param string $placeholder
636
+ */
637
+ protected function input_password( $option, $attributes, $value, $placeholder ) {
638
+ ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="password" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
639
+
640
+ if ( ! empty( $option['desc'] ) ) {
641
+ echo ' <p class="description">' . $option['desc'] . '</p>';
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Number input field.
647
+ *
648
+ * @param array $option
649
+ * @param array $attributes
650
+ * @param mixed $value
651
+ * @param string $placeholder
652
+ */
653
+ protected function input_number( $option, $attributes, $value, $placeholder ) {
654
+ echo isset( $option['before'] ) ? $option['before'] : '';
655
+ ?><input id="setting-<?php echo $option['name']; ?>" class="small-text" type="number" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
656
+ echo isset( $option['after'] ) ? $option['after'] : '';
657
+ if ( ! empty( $option['desc'] ) ) {
658
+ echo ' <p class="description">' . $option['desc'] . '</p>';
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Text input field.
664
+ *
665
+ * @param array $option
666
+ * @param array $attributes
667
+ * @param mixed $value
668
+ * @param string $placeholder
669
+ */
670
+ protected function input_text( $option, $attributes, $value, $placeholder ) {
671
+ ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="text" name="<?php echo $option['name']; ?>" value="<?php echo esc_attr( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
672
+
673
+ if ( ! empty( $option['desc'] ) ) {
674
+ echo ' <p class="description">' . $option['desc'] . '</p>';
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Outputs the field row.
680
+ *
681
+ * @param array $option
682
+ * @param mixed $value
683
+ */
684
+ protected function output_field( $option, $value ) {
685
+ $placeholder = ( ! empty( $option['placeholder'] ) ) ? 'placeholder="' . $option['placeholder'] . '"' : '';
686
+ $class = ! empty( $option['class'] ) ? $option['class'] : '';
687
+ $option['type'] = ! empty( $option['type'] ) ? $option['type'] : 'text';
688
+ $attributes = array();
689
+ if ( ! empty( $option['attributes'] ) && is_array( $option['attributes'] ) ) {
690
+ foreach ( $option['attributes'] as $attribute_name => $attribute_value ) {
691
+ $attributes[] = esc_attr( $attribute_name ) . '="' . esc_attr( $attribute_value ) . '"';
692
+ }
693
+ }
694
+
695
+ echo '<tr valign="top" class="' . $class . '">';
696
+
697
+ if ( ! empty( $option['label'] ) ) {
698
+ echo '<th scope="row"><label for="setting-' . $option[ 'name' ] . '">' . $option[ 'label' ] . '</a></th><td>';
699
+ } else {
700
+ echo '<td colspan="2">';
701
+ }
702
+
703
+ $method_name = 'input_' . $option['type'];
704
+ if ( method_exists( $this, $method_name ) ) {
705
+ $this->$method_name( $option, $attributes, $value, $placeholder );
706
+ } else {
707
+ do_action( 'wp_job_manager_admin_field_' . $option['type'], $option, $attributes, $value, $placeholder );
708
+ }
709
+ echo '</td></tr>';
710
+ }
711
+
712
+ /**
713
+ * Multiple settings stored in one setting array that are shown when the `enable` setting is checked.
714
+ *
715
+ * @param array $option
716
+ * @param array $attributes
717
+ * @param array $values
718
+ * @param string $placeholder
719
+ */
720
+ protected function input_multi_enable_expand( $option, $attributes, $values, $placeholder ) {
721
+ echo '<div class="setting-enable-expand">';
722
+ $enable_option = $option['enable_field'];
723
+ $enable_option['name'] = $option['name'] . '[' . $enable_option['name'] . ']';
724
+ $enable_option['type'] = 'checkbox';
725
+ $enable_option['attributes'] = array( 'class="sub-settings-expander"' );
726
+ $this->input_checkbox( $enable_option, $enable_option['attributes'], $values[ $option['enable_field']['name'] ], null );
727
+
728
+ echo '<div class="sub-settings-expandable">';
729
+ $this->input_multi( $option, $attributes, $values, $placeholder );
730
+ echo '</div>';
731
+ echo '</div>';
732
+ }
733
+
734
+ /**
735
+ * Multiple settings stored in one setting array.
736
+ *
737
+ * @param array $option
738
+ * @param array $attributes
739
+ * @param array $values
740
+ * @param string $placeholder
741
+ */
742
+ protected function input_multi( $option, $attributes, $values, $placeholder ) {
743
+ echo '<table class="form-table settings child-settings">';
744
+ foreach ( $option['settings'] as $sub_option ) {
745
+ $value = isset( $values[ $sub_option['name'] ] ) ? $values[ $sub_option['name'] ] : $sub_option['std'];
746
+ $sub_option['name'] = $option['name'] . '[' . $sub_option['name'] . ']';
747
+ $this->output_field( $sub_option, $value );
748
+ }
749
+ echo '</table>';
750
+ }
751
+
752
+ /**
753
+ * Proxy for text input field.
754
+ *
755
+ * @param array $option
756
+ * @param array $attributes
757
+ * @param mixed $value
758
+ * @param string $placeholder
759
+ */
760
+ protected function input_input( $option, $attributes, $value, $placeholder ) {
761
+ $this->input_text( $option, $attributes, $value, $placeholder );
762
+ }
763
  }
includes/admin/class-wp-job-manager-writepanels.php CHANGED
@@ -181,12 +181,12 @@ class WP_Job_Manager_Writepanels {
181
  $name = 'tax_input[' . $taxonomy . ']';
182
 
183
  // Get all the terms for this taxonomy
184
- $terms = get_terms( $taxonomy, array( 'hide_empty' => 0 ) );
185
  $postterms = get_the_terms( $post->ID, $taxonomy );
186
  $current = ( $postterms ? array_pop( $postterms ) : false );
187
  $current = ( $current ? $current->term_id : 0 );
188
  // Get current and popular terms
189
- $popular = get_terms( $taxonomy, array( 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );
190
  $postterms = get_the_terms( $post->ID,$taxonomy );
191
  $current = ($postterms ? array_pop($postterms) : false);
192
  $current = ($current ? $current->term_id : 0);
181
  $name = 'tax_input[' . $taxonomy . ']';
182
 
183
  // Get all the terms for this taxonomy
184
+ $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => 0 ) );
185
  $postterms = get_the_terms( $post->ID, $taxonomy );
186
  $current = ( $postterms ? array_pop( $postterms ) : false );
187
  $current = ( $current ? $current->term_id : 0 );
188
  // Get current and popular terms
189
+ $popular = get_terms( array( 'taxonomy' => $taxonomy, 'orderby' => 'count', 'order' => 'DESC', 'number' => 10, 'hierarchical' => false ) );
190
  $postterms = get_the_terms( $post->ID,$taxonomy );
191
  $current = ($postterms ? array_pop($postterms) : false);
192
  $current = ($current ? $current->term_id : 0);
includes/class-wp-job-manager-data-cleaner.php ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Defines a class with methods for cleaning up plugin data. To be used when
4
+ * the plugin is deleted.
5
+ *
6
+ * @package Core
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ // Exit if accessed directly.
11
+ exit;
12
+ }
13
+
14
+ /**
15
+ * Methods for cleaning up all plugin data.
16
+ *
17
+ * @author Automattic
18
+ * @since 1.31.0
19
+ */
20
+ class WP_Job_Manager_Data_Cleaner {
21
+
22
+ /**
23
+ * Custom post types to be deleted.
24
+ *
25
+ * @var $custom_post_types
26
+ */
27
+ private static $custom_post_types = array(
28
+ 'job_listing',
29
+ );
30
+
31
+ /**
32
+ * Taxonomies to be deleted.
33
+ *
34
+ * @var $taxonomies
35
+ */
36
+ private static $taxonomies = array(
37
+ 'job_listing_category',
38
+ 'job_listing_type',
39
+ );
40
+
41
+ /** Cron jobs to be unscheduled.
42
+ *
43
+ * @var $cron_jobs
44
+ */
45
+ private static $cron_jobs = array(
46
+ 'job_manager_check_for_expired_jobs',
47
+ 'job_manager_delete_old_previews',
48
+ 'job_manager_clear_expired_transients',
49
+ 'job_manager_email_daily_notices',
50
+ 'job_manager_usage_tracking_send_usage_data',
51
+ );
52
+
53
+ /**
54
+ * Options to be deleted.
55
+ *
56
+ * @var $options
57
+ */
58
+ private static $options = array(
59
+ 'wp_job_manager_version',
60
+ 'job_manager_installed_terms',
61
+ 'wpjm_permalinks',
62
+ 'job_manager_helper',
63
+ 'job_manager_date_format',
64
+ 'job_manager_google_maps_api_key',
65
+ 'job_manager_usage_tracking_enabled',
66
+ 'job_manager_usage_tracking_opt_in_hide',
67
+ 'job_manager_per_page',
68
+ 'job_manager_hide_filled_positions',
69
+ 'job_manager_hide_expired',
70
+ 'job_manager_hide_expired_content',
71
+ 'job_manager_enable_categories',
72
+ 'job_manager_enable_default_category_multiselect',
73
+ 'job_manager_category_filter_type',
74
+ 'job_manager_enable_types',
75
+ 'job_manager_multi_job_type',
76
+ 'job_manager_user_requires_account',
77
+ 'job_manager_enable_registration',
78
+ 'job_manager_generate_username_from_email',
79
+ 'job_manager_use_standard_password_setup_email',
80
+ 'job_manager_registration_role',
81
+ 'job_manager_submission_requires_approval',
82
+ 'job_manager_user_can_edit_pending_submissions',
83
+ 'job_manager_user_edit_published_submissions',
84
+ 'job_manager_submission_duration',
85
+ 'job_manager_allowed_application_method',
86
+ 'job_manager_recaptcha_label',
87
+ 'job_manager_recaptcha_site_key',
88
+ 'job_manager_recaptcha_secret_key',
89
+ 'job_manager_enable_recaptcha_job_submission',
90
+ 'job_manager_submit_job_form_page_id',
91
+ 'job_manager_job_dashboard_page_id',
92
+ 'job_manager_jobs_page_id',
93
+ 'job_manager_submit_page_slug',
94
+ 'job_manager_job_dashboard_page_slug',
95
+ 'job_manager_delete_data_on_uninstall',
96
+ 'job_manager_email_admin_updated_job',
97
+ 'job_manager_email_admin_new_job',
98
+ 'job_manager_email_admin_expiring_job',
99
+ 'job_manager_email_employer_expiring_job',
100
+ );
101
+
102
+ /**
103
+ * Site options to be deleted.
104
+ *
105
+ * @var $site_options
106
+ */
107
+ private static $site_options = array(
108
+ 'job_manager_helper',
109
+ );
110
+
111
+ /**
112
+ * Transient names (as MySQL regexes) to be deleted. The prefixes
113
+ * "_transient_" and "_transient_timeout_" will be prepended.
114
+ *
115
+ * @var $transients
116
+ */
117
+ private static $transients = array(
118
+ '_job_manager_activation_redirect',
119
+ 'get_job_listings-transient-version',
120
+ 'jm_.*',
121
+ );
122
+
123
+ /**
124
+ * Role to be removed.
125
+ *
126
+ * @var $role
127
+ */
128
+ private static $role = 'employer';
129
+
130
+ /**
131
+ * Capabilities to be deleted.
132
+ *
133
+ * @var $caps
134
+ */
135
+ private static $caps = array(
136
+ 'manage_job_listings',
137
+ 'edit_job_listing',
138
+ 'read_job_listing',
139
+ 'delete_job_listing',
140
+ 'edit_job_listings',
141
+ 'edit_others_job_listings',
142
+ 'publish_job_listings',
143
+ 'read_private_job_listings',
144
+ 'delete_job_listings',
145
+ 'delete_private_job_listings',
146
+ 'delete_published_job_listings',
147
+ 'delete_others_job_listings',
148
+ 'edit_private_job_listings',
149
+ 'edit_published_job_listings',
150
+ 'manage_job_listing_terms',
151
+ 'edit_job_listing_terms',
152
+ 'delete_job_listing_terms',
153
+ 'assign_job_listing_terms',
154
+ );
155
+
156
+ /**
157
+ * User meta key names to be deleted.
158
+ *
159
+ * @var array $user_meta_keys
160
+ */
161
+ private static $user_meta_keys = array(
162
+ '_company_logo',
163
+ '_company_name',
164
+ '_company_website',
165
+ '_company_tagline',
166
+ '_company_twitter',
167
+ '_company_video',
168
+ );
169
+
170
+ /**
171
+ * Cleanup all data.
172
+ *
173
+ * @access public
174
+ */
175
+ public static function cleanup_all() {
176
+ self::cleanup_custom_post_types();
177
+ self::cleanup_taxonomies();
178
+ self::cleanup_pages();
179
+ self::cleanup_cron_jobs();
180
+ self::cleanup_roles_and_caps();
181
+ self::cleanup_transients();
182
+ self::cleanup_user_meta();
183
+ self::cleanup_options();
184
+ self::cleanup_site_options();
185
+ }
186
+
187
+ /**
188
+ * Cleanup data for custom post types.
189
+ *
190
+ * @access private
191
+ */
192
+ private static function cleanup_custom_post_types() {
193
+ foreach ( self::$custom_post_types as $post_type ) {
194
+ $items = get_posts( array(
195
+ 'post_type' => $post_type,
196
+ 'post_status' => 'any',
197
+ 'numberposts' => -1,
198
+ 'fields' => 'ids',
199
+ ) );
200
+
201
+ foreach ( $items as $item ) {
202
+ wp_trash_post( $item );
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Cleanup data for taxonomies.
209
+ *
210
+ * @access private
211
+ */
212
+ private static function cleanup_taxonomies() {
213
+ global $wpdb;
214
+
215
+ foreach ( self::$taxonomies as $taxonomy ) {
216
+ $terms = $wpdb->get_results(
217
+ $wpdb->prepare(
218
+ "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s",
219
+ $taxonomy
220
+ )
221
+ );
222
+
223
+ // Delete all data for each term.
224
+ foreach ( $terms as $term ) {
225
+ $wpdb->delete( $wpdb->term_relationships, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
226
+ $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
227
+ $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
228
+ $wpdb->delete( $wpdb->termmeta, array( 'term_id' => $term->term_id ) );
229
+ }
230
+
231
+ if ( function_exists( 'clean_taxonomy_cache' ) ) {
232
+ clean_taxonomy_cache( $taxonomy );
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Cleanup data for pages.
239
+ *
240
+ * @access private
241
+ */
242
+ private static function cleanup_pages() {
243
+ // Trash the Submit Job page.
244
+ $submit_job_form_page_id = get_option( 'job_manager_submit_job_form_page_id' );
245
+ if ( $submit_job_form_page_id ) {
246
+ wp_trash_post( $submit_job_form_page_id );
247
+ }
248
+
249
+ // Trash the Job Dashboard page.
250
+ $job_dashboard_page_id = get_option( 'job_manager_job_dashboard_page_id' );
251
+ if ( $job_dashboard_page_id ) {
252
+ wp_trash_post( $job_dashboard_page_id );
253
+ }
254
+
255
+ // Trash the Jobs page.
256
+ $jobs_page_id = get_option( 'job_manager_jobs_page_id' );
257
+ if ( $jobs_page_id ) {
258
+ wp_trash_post( $jobs_page_id );
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Cleanup data for options.
264
+ *
265
+ * @access private
266
+ */
267
+ private static function cleanup_options() {
268
+ foreach ( self::$options as $option ) {
269
+ delete_option( $option );
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Cleanup data for site options.
275
+ *
276
+ * @access private
277
+ */
278
+ private static function cleanup_site_options() {
279
+ foreach ( self::$site_options as $option ) {
280
+ delete_site_option( $option );
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Cleanup transients from the database.
286
+ *
287
+ * @access private
288
+ */
289
+ private static function cleanup_transients() {
290
+ global $wpdb;
291
+
292
+ foreach ( array( '_transient_', '_transient_timeout_' ) as $prefix ) {
293
+ foreach ( self::$transients as $transient ) {
294
+ $wpdb->query(
295
+ $wpdb->prepare(
296
+ "DELETE FROM $wpdb->options WHERE option_name RLIKE %s",
297
+ $prefix . $transient
298
+ )
299
+ );
300
+ }
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Cleanup data for roles and caps.
306
+ *
307
+ * @access private
308
+ */
309
+ private static function cleanup_roles_and_caps() {
310
+ global $wp_roles;
311
+
312
+ // Remove caps from roles.
313
+ $role_names = array_keys( $wp_roles->roles );
314
+ foreach ( $role_names as $role_name ) {
315
+ $role = get_role( $role_name );
316
+ self::remove_all_job_manager_caps( $role );
317
+ }
318
+
319
+ // Remove caps and role from users.
320
+ $users = get_users( array() );
321
+ foreach ( $users as $user ) {
322
+ self::remove_all_job_manager_caps( $user );
323
+ $user->remove_role( self::$role );
324
+ }
325
+
326
+ // Remove role.
327
+ remove_role( self::$role );
328
+ }
329
+
330
+ /**
331
+ * Helper method to remove WPJM caps from a user or role object.
332
+ *
333
+ * @param (WP_User|WP_Role) $object the user or role object.
334
+ */
335
+ private static function remove_all_job_manager_caps( $object ) {
336
+ foreach ( self::$caps as $cap ) {
337
+ $object->remove_cap( $cap );
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Cleanup user meta from the database.
343
+ *
344
+ * @access private
345
+ */
346
+ private static function cleanup_user_meta() {
347
+ global $wpdb;
348
+
349
+ foreach ( self::$user_meta_keys as $meta_key ) {
350
+ $wpdb->delete( $wpdb->usermeta, array( 'meta_key' => $meta_key ) );
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Cleanup cron jobs. Note that this should be done on deactivation, but
356
+ * doing it here as well for safety.
357
+ *
358
+ * @access private
359
+ */
360
+ private static function cleanup_cron_jobs() {
361
+ foreach ( self::$cron_jobs as $job ) {
362
+ wp_clear_scheduled_hook( $job );
363
+ }
364
+ }
365
+ }
includes/class-wp-job-manager-email-notifications.php ADDED
@@ -0,0 +1,814 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Base class for WP Job Manager's email notification system.
9
+ *
10
+ * @package wp-job-manager
11
+ * @since 1.31.0
12
+ */
13
+ final class WP_Job_Manager_Email_Notifications {
14
+ const EMAIL_SETTING_PREFIX = 'job_manager_email_';
15
+ const EMAIL_SETTING_ENABLED = 'enabled';
16
+ const EMAIL_SETTING_PLAIN_TEXT = 'plain_text';
17
+
18
+ /**
19
+ * @var array
20
+ */
21
+ private static $deferred_notifications = array();
22
+
23
+ /**
24
+ * Sets up initial hooks.
25
+ *
26
+ * @static
27
+ */
28
+ public static function init() {
29
+ add_action( 'job_manager_send_notification', array( __CLASS__, '_schedule_notification' ), 10, 2 );
30
+ add_action( 'job_manager_email_init', array( __CLASS__, '_lazy_init' ) );
31
+ add_action( 'job_manager_email_job_details', array( __CLASS__, 'output_job_details' ), 10, 4 );
32
+ add_action( 'job_manager_email_header', array( __CLASS__, 'output_header' ), 10, 3 );
33
+ add_action( 'job_manager_email_footer', array( __CLASS__, 'output_footer' ), 10, 3 );
34
+ add_action( 'job_manager_email_daily_notices', array( __CLASS__, 'send_employer_expiring_notice' ) );
35
+ add_action( 'job_manager_email_daily_notices', array( __CLASS__, 'send_admin_expiring_notice' ) );
36
+ add_filter( 'job_manager_settings', array( __CLASS__, 'add_job_manager_email_settings' ), 1 );
37
+ add_action( 'job_manager_job_submitted', array( __CLASS__, 'send_new_job_notification' ) );
38
+ add_action( 'job_manager_user_edit_job_listing', array( __CLASS__, 'send_updated_job_notification' ) );
39
+ }
40
+
41
+ /**
42
+ * Gets list of email notifications handled by WP Job Manager core.
43
+ *
44
+ * @return array
45
+ */
46
+ public static function core_email_notifications() {
47
+ return array(
48
+ 'WP_Job_Manager_Email_Admin_New_Job',
49
+ 'WP_Job_Manager_Email_Admin_Updated_Job',
50
+ 'WP_Job_Manager_Email_Admin_Expiring_Job',
51
+ 'WP_Job_Manager_Email_Employer_Expiring_Job',
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Sets up an email notification to be sent at the end of the script's execution.
57
+ *
58
+ * @param string $notification
59
+ * @param array $args
60
+ */
61
+ public static function _schedule_notification( $notification, $args = array() ) {
62
+ self::maybe_init();
63
+
64
+ self::$deferred_notifications[] = array( $notification, $args );
65
+ }
66
+
67
+ /**
68
+ * Sends all notifications collected during execution.
69
+ *
70
+ * Do not call manually.
71
+ *
72
+ * @access private
73
+ */
74
+ public static function _send_deferred_notifications() {
75
+ $email_notifications = self::get_email_notifications( true );
76
+ foreach ( self::$deferred_notifications as $email ) {
77
+ if (
78
+ ! is_string( $email[0] )
79
+ || ! isset( $email_notifications[ $email[0] ] )
80
+ ) {
81
+ continue;
82
+ }
83
+
84
+ $email_class = $email_notifications[ $email[0] ];
85
+ $email_notification_key = $email[0];
86
+ $email_args = is_array( $email[1] ) ? $email[1] : array();
87
+
88
+ self::send_email( $email[0], new $email_class( $email_args, self::get_email_settings( $email_notification_key ) ) );
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Initialize if necessary.
94
+ */
95
+ public static function maybe_init() {
96
+ if ( 0 === did_action( 'job_manager_email_init' ) ) {
97
+ /**
98
+ * Lazily load remaining files needed for email notifications. Do this here instead of in
99
+ * `shutdown` for proper logging in case of syntax errors.
100
+ *
101
+ * @since 1.31.0
102
+ */
103
+ do_action( 'job_manager_email_init' );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Include email files.
109
+ *
110
+ * Do not call manually.
111
+ *
112
+ * @access private
113
+ */
114
+ public static function _lazy_init() {
115
+ add_action( 'shutdown', array( __CLASS__, '_send_deferred_notifications' ) );
116
+
117
+ include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-new-job.php';
118
+ include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-updated-job.php';
119
+ include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-employer-expiring-job.php';
120
+ include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-expiring-job.php';
121
+
122
+ if ( ! class_exists( 'Emogrifier' ) && class_exists( 'DOMDocument' ) ) {
123
+ include_once JOB_MANAGER_PLUGIN_DIR . '/lib/emogrifier/class-emogrifier.php';
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Clear the deferred notifications email array.
129
+ *
130
+ * Do not call manually. Only for help with tests.
131
+ *
132
+ * @access private
133
+ */
134
+ public static function _clear_deferred_notifications() {
135
+ if ( ! defined( 'PHPUNIT_WPJM_TESTSUITE' ) || ! PHPUNIT_WPJM_TESTSUITE ) {
136
+ die( "This is just for use while testing" );
137
+ }
138
+ self::$deferred_notifications = array();
139
+ }
140
+
141
+ /**
142
+ * Gets a list of all email notifications that WP Job Manager handles.
143
+ *
144
+ * @param bool $enabled_notifications_only
145
+ * @return array
146
+ */
147
+ public static function get_email_notifications( $enabled_notifications_only = false ) {
148
+ self::maybe_init();
149
+
150
+ /**
151
+ * Retrieves all email notifications to be sent.
152
+ *
153
+ * @since 1.31.0
154
+ *
155
+ * @param array $email_notifications All the email notifications to be registered.
156
+ */
157
+ $email_notification_classes = array_unique( apply_filters( 'job_manager_email_notifications', self::core_email_notifications() ) );
158
+ $email_notifications = array();
159
+
160
+ /**
161
+ * @var WP_Job_Manager_Email $email_class
162
+ */
163
+ foreach ( $email_notification_classes as $email_class ) {
164
+ // Check to make sure email notification is valid.
165
+ if ( ! self::is_email_notification_valid( $email_class ) ) {
166
+ continue;
167
+ }
168
+
169
+ // PHP 5.2: Using `call_user_func()` but `$email_class::get_key()` preferred.
170
+ $email_notification_key = call_user_func( array( $email_class, 'get_key') );
171
+ if (
172
+ isset( $email_notifications[ $email_notification_key ] )
173
+ || ( $enabled_notifications_only && ! self::is_email_notification_enabled( $email_notification_key ) )
174
+ ) {
175
+ continue;
176
+ }
177
+
178
+ $email_notifications[ $email_notification_key ] = $email_class;
179
+ }
180
+
181
+ return $email_notifications;
182
+ }
183
+
184
+ /**
185
+ * Show details about the job listing.
186
+ *
187
+ * @param WP_Post $job The job listing to show details for.
188
+ * @param WP_Job_Manager_Email $email Email object for the notification.
189
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
190
+ * @param bool $plain_text True if the email is being sent as plain text.
191
+ */
192
+ public static function output_job_details( $job, $email, $sent_to_admin, $plain_text = false ) {
193
+ $template_segment = self::locate_template_file( 'email-job-details', $plain_text );
194
+ if ( ! file_exists( $template_segment ) ) {
195
+ return;
196
+ }
197
+
198
+ $fields = self::get_job_detail_fields( $job, $sent_to_admin, $plain_text );
199
+
200
+ include $template_segment;
201
+ }
202
+
203
+ /**
204
+ * Get the job fields to show in email templates.
205
+ *
206
+ * @param WP_Post $job
207
+ * @param bool $sent_to_admin
208
+ * @param bool $plain_text
209
+ * @return array
210
+ */
211
+ private static function get_job_detail_fields( WP_Post $job, $sent_to_admin, $plain_text = false ) {
212
+ $fields = array();
213
+
214
+ $fields['job_title'] = array(
215
+ 'label' => __( 'Job title', 'wp-job-manager' ),
216
+ 'value' => $job->post_title,
217
+ );
218
+
219
+ if ( $sent_to_admin || 'publish' === $job->post_status ) {
220
+ $fields['job_title']['url'] = get_permalink( $job );
221
+ }
222
+
223
+ $job_location = get_the_job_location( $job );
224
+ if ( ! empty( $job_location ) ) {
225
+ $fields['job_location'] = array(
226
+ 'label' => __( 'Location', 'wp-job-manager' ),
227
+ 'value' => $job_location,
228
+ );
229
+ }
230
+
231
+ if ( get_option( 'job_manager_enable_types' ) && wp_count_terms( 'job_listing_type' ) > 0 ) {
232
+ $job_types = wpjm_get_the_job_types( $job );
233
+ if ( ! empty( $job_types ) ) {
234
+ $fields[ 'job_type' ] = array(
235
+ 'label' => __( 'Job type', 'wp-job-manager' ),
236
+ 'value' => implode( ', ', wp_list_pluck( $job_types, 'name' ) ),
237
+ );
238
+ }
239
+ }
240
+
241
+ if ( get_option( 'job_manager_enable_categories' ) && wp_count_terms( 'job_listing_category' ) > 0 ) {
242
+ $job_categories = wpjm_get_the_job_categories( $job );
243
+ if ( ! empty( $job_categories ) ) {
244
+ $fields[ 'job_category' ] = array(
245
+ 'label' => __( 'Job category', 'wp-job-manager' ),
246
+ 'value' => implode( ', ', wp_list_pluck( $job_categories, 'name' ) ),
247
+ );
248
+ }
249
+ }
250
+
251
+ $company_name = get_the_company_name( $job );
252
+ if ( ! empty( $company_name ) ) {
253
+ $fields['company_name'] = array(
254
+ 'label' => __( 'Company name', 'wp-job-manager' ),
255
+ 'value' => $company_name,
256
+ );
257
+ }
258
+
259
+ $company_website = get_the_company_website( $job );
260
+ if ( ! empty( $company_website ) ) {
261
+ $fields['company_website'] = array(
262
+ 'label' => __( 'Company website', 'wp-job-manager' ),
263
+ 'value' => $plain_text ? $company_website : sprintf( '<a href="%1$s">%1$s</a>', esc_url( $company_website, array( 'http', 'https' ) ) ),
264
+ );
265
+ }
266
+
267
+ $job_expires = get_post_meta( $job->ID, '_job_expires', true );
268
+ if ( ! empty( $job_expires ) ) {
269
+ $job_expires_str = date_i18n( get_option( 'date_format' ), strtotime( $job_expires ) );
270
+ $fields['job_expires'] = array(
271
+ 'label' => __( 'Listing expires', 'wp-job-manager' ),
272
+ 'value' => $job_expires_str,
273
+ );
274
+ }
275
+
276
+ if ( $sent_to_admin ) {
277
+ $author = get_user_by( 'ID', $job->post_author );
278
+ if ( $author instanceof WP_User ) {
279
+ $fields['author'] = array(
280
+ 'label' => __( 'Posted by', 'wp-job-manager' ),
281
+ 'value' => $author->user_nicename,
282
+ 'url' => 'mailto:' . $author->user_email,
283
+ );
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Modify the fields shown in email notifications in the details summary a job listing.
289
+ *
290
+ * @since 1.31.0
291
+ *
292
+ * @param array $fields {
293
+ * Array of fields. Each field is keyed with a unique identifier.
294
+ * {
295
+ * @type string $label Label to show next to field.
296
+ * @type string $value Value for field.
297
+ * @type string $url URL to provide with the value (optional).
298
+ * }
299
+ * }
300
+ * @param WP_Post $job Job listing.
301
+ * @param bool $sent_to_admin True if being sent in an admin notification.
302
+ * @param bool $plain_text True if being sent as plain text.
303
+ */
304
+ return apply_filters( 'job_manager_emails_job_detail_fields', $fields, $job, $sent_to_admin, $plain_text );
305
+ }
306
+
307
+ /**
308
+ * Output email header.
309
+ *
310
+ * @param string $email_notification_key Email notification key for email being sent.
311
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
312
+ * @param bool $plain_text True if the email is being sent as plain text.
313
+ */
314
+ public static function output_header( $email_notification_key, $sent_to_admin, $plain_text = false ) {
315
+ $template_segment = self::email_template_path_alternative( $email_notification_key, 'email-header', $plain_text );
316
+ if ( false === $template_segment ) {
317
+ $template_segment = self::locate_template_file( 'email-header', $plain_text );
318
+ }
319
+ if ( ! $template_segment || ! file_exists( $template_segment ) ) {
320
+ return;
321
+ }
322
+ include $template_segment;
323
+ }
324
+
325
+ /**
326
+ * Output email footer.
327
+ *
328
+ * @param string $email_notification_key Email notification key for email being sent.
329
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
330
+ * @param bool $plain_text True if the email is being sent as plain text.
331
+ */
332
+ public static function output_footer( $email_notification_key, $sent_to_admin, $plain_text = false ) {
333
+ $template_segment = self::email_template_path_alternative( $email_notification_key, 'email-footer', $plain_text );
334
+ if ( false === $template_segment ) {
335
+ $template_segment = self::locate_template_file( 'email-footer', $plain_text );
336
+ }
337
+ if ( ! $template_segment || ! file_exists( $template_segment ) ) {
338
+ return;
339
+ }
340
+ include $template_segment;
341
+ }
342
+
343
+ /**
344
+ * Checks for an alternative email template segment in the template path specified by the current email.
345
+ * Useful to provide alternative email headers and footers for a specific WPJM extension plugin.
346
+ *
347
+ * @param string $email_notification_key Email notification key for email being sent.
348
+ * @param string $template_name Name of the template to check
349
+ * @param bool $plain_text True if the email is being sent as plain text.
350
+ * @return bool|string Returns path to template path alternative or false if none exists.
351
+ */
352
+ private static function email_template_path_alternative( $email_notification_key, $template_name, $plain_text ) {
353
+ $email_class = self::get_email_class( $email_notification_key );
354
+ if ( ! $email_class || ! is_subclass_of( $email_class, 'WP_Job_Manager_Email_Template' ) ) {
355
+ return false;
356
+ }
357
+
358
+ $template_default_path = call_user_func( array( $email_class, 'get_template_default_path' ) );
359
+ if ( '' === $template_default_path ) {
360
+ return false;
361
+ }
362
+
363
+ $template_path = call_user_func( array( $email_class, 'get_template_path' ) );
364
+ $template = self::locate_template_file( $template_name, $plain_text, $template_path, $template_default_path );
365
+ if ( '' === $template ) {
366
+ return false;
367
+ }
368
+
369
+ return $template;
370
+ }
371
+
372
+ /**
373
+ * Locate template file.
374
+ *
375
+ * @param string $template_name
376
+ * @param bool $plain_text
377
+ * @param string $template_path
378
+ * @param string $default_path
379
+ * @return string
380
+ */
381
+ public static function locate_template_file( $template_name, $plain_text = false, $template_path = 'job_manager', $default_path = '' ) {
382
+ return locate_job_manager_template( WP_Job_Manager_Email_Template::generate_template_file_name( $template_name, $plain_text ), $template_path, $default_path );
383
+ }
384
+
385
+ /**
386
+ * Add email notification settings for the job manager context.
387
+ *
388
+ * @param array $settings
389
+ * @return array
390
+ */
391
+ public static function add_job_manager_email_settings( $settings ) {
392
+ return self::add_email_settings( $settings, WP_Job_Manager_Email::get_context() );
393
+ }
394
+
395
+ /**
396
+ * Add email notification settings for a context.
397
+ *
398
+ * @param array $settings
399
+ * @param string $context
400
+ * @return array
401
+ */
402
+ public static function add_email_settings( $settings, $context ) {
403
+ $email_notifications = self::get_email_notifications( false );
404
+ $email_settings = array();
405
+
406
+ foreach ( $email_notifications as $email_notification_key => $email_class ) {
407
+ $email_notification_context = call_user_func( array( $email_class, 'get_context' ) );
408
+ if ( $context !== $email_notification_context ) { continue; }
409
+
410
+ $email_settings[] = array(
411
+ 'type' => 'multi_enable_expand',
412
+ 'class' => 'email-setting-row no-separator',
413
+ 'name' => self::EMAIL_SETTING_PREFIX . call_user_func( array( $email_class, 'get_key' ) ),
414
+ 'enable_field' => array(
415
+ 'name' => self::EMAIL_SETTING_ENABLED,
416
+ 'cb_label' => call_user_func( array( $email_class, 'get_name' ) ),
417
+ 'desc' => call_user_func( array( $email_class, 'get_description' ) ),
418
+ ),
419
+ 'label' => false,
420
+ 'std' => self::get_email_setting_defaults( $email_notification_key ),
421
+ 'settings' => self::get_email_setting_fields( $email_notification_key ),
422
+ );
423
+ }
424
+
425
+ if ( ! empty( $email_settings ) ) {
426
+ $settings['email_notifications'] = array(
427
+ __( 'Email Notifications', 'wp-job-manager' ),
428
+ $email_settings,
429
+ array(
430
+ 'before' => __( 'Select the email notifications to enable.', 'wp-job-manager' ),
431
+ ),
432
+ );
433
+ }
434
+
435
+ return $settings;
436
+ }
437
+
438
+ /**
439
+ * Checks if a particular notification is enabled or not.
440
+ *
441
+ * @param string $email_notification_key
442
+ * @return bool
443
+ */
444
+ public static function is_email_notification_enabled( $email_notification_key ) {
445
+ $settings = self::get_email_settings( $email_notification_key );
446
+
447
+ $is_email_notification_enabled = ! empty( $settings[ self::EMAIL_SETTING_ENABLED ] );
448
+
449
+ /**
450
+ * Filter whether an notification email is enabled.
451
+ *
452
+ * @since 1.31.0
453
+ *
454
+ * @param bool $is_email_notification_enabled
455
+ * @param string $email_notification_key
456
+ */
457
+ return apply_filters( 'job_manager_email_is_email_notification_enabled', $is_email_notification_enabled, $email_notification_key );
458
+ }
459
+
460
+ /**
461
+ * Checks if we should send emails using plain text.
462
+ *
463
+ * @param string $email_notification_key
464
+ * @return bool
465
+ */
466
+ public static function send_as_plain_text( $email_notification_key ) {
467
+ $settings = self::get_email_settings( $email_notification_key );
468
+
469
+ $send_as_plain_text = ! empty( $settings[ self::EMAIL_SETTING_PLAIN_TEXT ] );
470
+
471
+ /**
472
+ * Filter whether to send emails as plain text.
473
+ *
474
+ * @since 1.31.0
475
+ *
476
+ * @param bool $send_as_plain_text
477
+ * @param string $email_notification_key
478
+ */
479
+ return apply_filters( 'job_manager_email_send_as_plain_text', $send_as_plain_text, $email_notification_key );
480
+ }
481
+
482
+ /**
483
+ * Sending notices to employers for expiring job listings.
484
+ */
485
+ public static function send_employer_expiring_notice() {
486
+ self::maybe_init();
487
+
488
+ $email_key = WP_Job_Manager_Email_Employer_Expiring_Job::get_key();
489
+ if ( ! self::is_email_notification_enabled( $email_key ) ) {
490
+ return;
491
+ }
492
+ $settings = self::get_email_settings( $email_key );
493
+ $days_notice = WP_Job_Manager_Email_Employer_Expiring_Job::get_notice_period( $settings );
494
+ self::send_expiring_notice( $email_key, $days_notice );
495
+ }
496
+
497
+ /**
498
+ * Sending notices to the site administrator for expiring job listings.
499
+ */
500
+ public static function send_admin_expiring_notice() {
501
+ self::maybe_init();
502
+
503
+ $email_key = WP_Job_Manager_Email_Admin_Expiring_Job::get_key();
504
+ if ( ! self::is_email_notification_enabled( $email_key ) ) {
505
+ return;
506
+ }
507
+ $settings = self::get_email_settings( $email_key );
508
+ $days_notice = WP_Job_Manager_Email_Admin_Expiring_Job::get_notice_period( $settings );
509
+ self::send_expiring_notice( $email_key, $days_notice );
510
+ }
511
+
512
+ /**
513
+ * Fire the action to send a new job notification to the admin.
514
+ *
515
+ * @param int $job_id
516
+ */
517
+ public static function send_new_job_notification( $job_id ) {
518
+ do_action( 'job_manager_send_notification', 'admin_new_job', array( 'job_id' => $job_id ) );
519
+ }
520
+
521
+ /**
522
+ * Fire the action to send a updated job notification to the admin.
523
+ *
524
+ * @param int $job_id
525
+ */
526
+ public static function send_updated_job_notification( $job_id ) {
527
+ do_action( 'job_manager_send_notification', 'admin_updated_job', array( 'job_id' => $job_id ) );
528
+ }
529
+
530
+ /**
531
+ * Send notice based on job expiration date.
532
+ *
533
+ * @param string $email_notification_key
534
+ * @param int $days_notice
535
+ */
536
+ private static function send_expiring_notice( $email_notification_key, $days_notice ) {
537
+ global $wpdb;
538
+
539
+ $notice_before_ts = current_time( 'timestamp' ) + ( DAY_IN_SECONDS * $days_notice );
540
+ $job_ids_sql = $wpdb->prepare( "
541
+ SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
542
+ LEFT JOIN {$wpdb->posts} as posts ON postmeta.post_id = posts.ID
543
+ WHERE postmeta.meta_key = '_job_expires'
544
+ AND postmeta.meta_value = %s
545
+ AND posts.post_status = 'publish'
546
+ AND posts.post_type = 'job_listing'
547
+ ", date( 'Y-m-d', $notice_before_ts ) );
548
+ $job_ids = $wpdb->get_col( $job_ids_sql );
549
+
550
+ if ( $job_ids ) {
551
+ foreach ( $job_ids as $job_id ) {
552
+ do_action( 'job_manager_send_notification', $email_notification_key, array( 'job_id' => $job_id ) );
553
+ }
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Get the setting fields for an email.
559
+ *
560
+ * @param string $email_notification_key
561
+ * @return array
562
+ */
563
+ private static function get_email_setting_fields( $email_notification_key ) {
564
+ $email_class = self::get_email_class( $email_notification_key );
565
+ $core_settings = array(
566
+ array(
567
+ 'name' => 'plain_text',
568
+ 'std' => '0',
569
+ 'label' => __( 'Format', 'wp-job-manager' ),
570
+ 'type' => 'radio',
571
+ 'options' => array(
572
+ '1' => __( 'Send plain text email', 'wp-job-manager' ),
573
+ '0' => __( 'Send rich text email', 'wp-job-manager' ),
574
+ ),
575
+ ),
576
+ );
577
+ $email_settings = call_user_func( array( $email_class, 'get_setting_fields' ) );
578
+ return array_merge( $core_settings, $email_settings );
579
+ }
580
+
581
+ /**
582
+ * Get the settings for the email.
583
+ *
584
+ * @param string $email_notification_key
585
+ * @return array
586
+ */
587
+ private static function get_email_settings( $email_notification_key ) {
588
+ $option_name = self::EMAIL_SETTING_PREFIX . $email_notification_key;
589
+ $option_value = get_option( $option_name );
590
+ if ( empty( $option_value ) || ! is_array( $option_value ) ) {
591
+ $option_value = array();
592
+ }
593
+ $default_settings = self::get_email_setting_defaults( $email_notification_key );
594
+
595
+ return array_merge( $default_settings, $option_value );
596
+ }
597
+
598
+ /**
599
+ * Gets the default values for the email notification.
600
+ *
601
+ * @param string $email_notification_key
602
+ * @return array
603
+ */
604
+ private static function get_email_setting_defaults( $email_notification_key ) {
605
+ $settings = self::get_email_setting_fields( $email_notification_key );
606
+ $email_class = self::get_email_class( $email_notification_key );
607
+
608
+ $defaults = array();
609
+ $defaults[ self::EMAIL_SETTING_ENABLED ] = call_user_func( array( $email_class, 'is_default_enabled' ) ) ? '1' : '0';
610
+
611
+ foreach ( $settings as $setting ) {
612
+ $defaults[ $setting['name'] ] = null;
613
+ if ( isset( $setting['std'] ) ) {
614
+ $defaults[ $setting['name'] ] = $setting['std'];
615
+ }
616
+ }
617
+
618
+ return $defaults;
619
+ }
620
+
621
+ /**
622
+ * Get the email class from the unique key.
623
+ *
624
+ * @param string $email_notification_key
625
+ * @return bool|string
626
+ */
627
+ private static function get_email_class( $email_notification_key ) {
628
+ $email_notifications = self::get_email_notifications( false );
629
+
630
+ return isset( $email_notifications[ $email_notification_key ] ) ? $email_notifications[ $email_notification_key ] : false;
631
+ }
632
+
633
+ /**
634
+ * Returns the total number of deferred notifications to be sent. Used in unit tests.
635
+ *
636
+ * @access private
637
+ *
638
+ * @return int
639
+ */
640
+ public static function _get_deferred_notification_count() {
641
+ return count( self::$deferred_notifications );
642
+ }
643
+
644
+ /**
645
+ * Confirms an email notification is valid.
646
+ *
647
+ * @access private
648
+ *
649
+ * @param string $email_class
650
+ * @return bool
651
+ */
652
+ private static function is_email_notification_valid( $email_class ) {
653
+ // PHP 5.2: Using `call_user_func()` but `$email_class::get_key()` preferred.
654
+ return is_string( $email_class )
655
+ && class_exists( $email_class )
656
+ && is_subclass_of( $email_class, 'WP_Job_Manager_Email' )
657
+ && false !== call_user_func( array( $email_class, 'get_key') )
658
+ && false !== call_user_func( array( $email_class, 'get_name') );
659
+ }
660
+
661
+ /**
662
+ * Sends an email notification.
663
+ *
664
+ * @access private
665
+ *
666
+ * @param string $email_notification_key
667
+ * @param WP_Job_Manager_Email $email
668
+ * @return bool
669
+ */
670
+ private static function send_email( $email_notification_key, WP_Job_Manager_Email $email ) {
671
+ if ( ! $email->is_valid() ) {
672
+ return false;
673
+ }
674
+
675
+ $fields = array( 'to', 'from', 'subject', 'rich_content', 'plain_content', 'attachments', 'cc', 'headers' );
676
+ $args = array();
677
+ foreach ( $fields as $field ) {
678
+ $method = 'get_' . $field;
679
+
680
+ /**
681
+ * Filter email values for job manager notifications.
682
+ *
683
+ * @since 1.31.0
684
+ *
685
+ * @param mixed $email_field_value Value to be filtered.
686
+ * @param WP_Job_Manager_Email $email Email notification object.
687
+ */
688
+ $args[ $field ] = apply_filters( "job_manager_email_{$email_notification_key}_{$field}", $email->$method(), $email );
689
+ }
690
+
691
+ $headers = is_array( $args['headers'] ) ? $args['headers'] : array();
692
+
693
+ if ( ! empty( $args['from'] ) ) {
694
+ $headers[] = 'From: ' . $args['from'];
695
+ }
696
+
697
+ if ( ! self::send_as_plain_text( $email_notification_key ) ) {
698
+ $headers[] = 'Content-Type: text/html';
699
+ }
700
+
701
+ $content = self::get_email_content( $email_notification_key, $args );
702
+
703
+ /**
704
+ * Allows for short-circuiting the actual sending of email notifications.
705
+ *
706
+ * @since 1.31.0
707
+ *
708
+ * @param bool $do_send_notification True if we should send the notification.
709
+ * @param WP_Job_Manager_Email $email Email notification object.
710
+ * @param array $args Email arguments for generating email.
711
+ * @param string $content Email content.
712
+ * @param array $headers Email headers.
713
+ * @param
714
+ */
715
+ if ( ! apply_filters( 'job_manager_email_do_send_notification', true, $email, $args, $content, $headers ) ) {
716
+ return false;
717
+ }
718
+ return wp_mail( $args['to'], $args['subject'], $content, $headers, $args['attachments'] );
719
+ }
720
+
721
+ /**
722
+ * Generates the content for an email.
723
+ *
724
+ * @access private
725
+ *
726
+ * @param string $email_notification_key
727
+ * @param array $args
728
+ * @return string
729
+ */
730
+ private static function get_email_content( $email_notification_key, $args ) {
731
+ $plain_text = self::send_as_plain_text( $email_notification_key );
732
+
733
+ ob_start();
734
+
735
+ /**
736
+ * Output the header for all job manager emails.
737
+ *
738
+ * @since 1.31.0
739
+ *
740
+ * @param string $email_notification_key Unique email notification key.
741
+ * @param array $args Arguments passed for generating email.
742
+ * @param bool $plain_text True if sending plain text email.
743
+ */
744
+ do_action( 'job_manager_email_header', $email_notification_key, $args, $plain_text );
745
+
746
+ if ( $plain_text ) {
747
+ echo html_entity_decode( wptexturize( $args['plain_content'] ) );
748
+ } else {
749
+ echo wpautop( wptexturize( $args['rich_content'] ) );
750
+ }
751
+
752
+ /**
753
+ * Output the footer for all job manager emails.
754
+ *
755
+ * @since 1.31.0
756
+ *
757
+ * @param string $email_notification_key Unique email notification key.
758
+ * @param array $args Arguments passed for generating email.
759
+ * @param bool $plain_text True if sending plain text email.
760
+ */
761
+ do_action( 'job_manager_email_footer', $email_notification_key, $args, $plain_text );
762
+
763
+ $content = ob_get_clean();
764
+ if ( ! $plain_text ) {
765
+ $content = self::inject_styles( $content );
766
+ }
767
+
768
+ /**
769
+ * Filter the content of the email.
770
+ *
771
+ * @since 1.31.0
772
+ *
773
+ * @param string $content Email content.
774
+ * @param string $email_notification_key Unique email notification key.
775
+ * @param array $args Arguments passed for generating email.
776
+ * @param bool $plain_text True if sending plain text email.
777
+ */
778
+ return apply_filters( 'job_manager_email_content', $content, $email_notification_key, $args, $plain_text );
779
+ }
780
+
781
+ /**
782
+ * Inject inline styles into email content.
783
+ *
784
+ * @param string $content
785
+ * @return string
786
+ */
787
+ private static function inject_styles( $content ) {
788
+ if ( class_exists( 'Emogrifier' ) ) {
789
+ try {
790
+ $emogrifier = new Emogrifier( $content, self::get_styles() );
791
+ $content = $emogrifier->emogrify();
792
+ } catch ( Exception $e ) {
793
+ trigger_error( 'Unable to inject styles into email notification: ' . $e->getMessage() );
794
+ }
795
+ }
796
+ return $content;
797
+ }
798
+
799
+ /**
800
+ * Gets the CSS styles to be used in email notifications.
801
+ *
802
+ * @return bool|string
803
+ */
804
+ private static function get_styles() {
805
+ $email_styles_template = self::locate_template_file( 'email-styles' );
806
+ if ( ! file_exists( $email_styles_template ) ) {
807
+ return false;
808
+ }
809
+ ob_start();
810
+ include $email_styles_template;
811
+ return ob_get_clean();
812
+ }
813
+
814
+ }
includes/class-wp-job-manager-shortcodes.php CHANGED
@@ -152,7 +152,7 @@ class WP_Job_Manager_Shortcodes {
152
 
153
  break;
154
  default :
155
- do_action( 'job_manager_job_dashboard_do_action_' . $action );
156
  break;
157
  }
158
 
152
 
153
  break;
154
  default :
155
+ do_action( 'job_manager_job_dashboard_do_action_' . $action, $job_id );
156
  break;
157
  }
158
 
includes/class-wp-job-manager-usage-tracking-data.php CHANGED
@@ -102,7 +102,8 @@ class WP_Job_Manager_Usage_Tracking_Data {
102
 
103
  $count = 0;
104
  $terms = get_terms(
105
- 'job_listing_category', array(
 
106
  'hide_empty' => false,
107
  )
108
  );
@@ -128,7 +129,8 @@ class WP_Job_Manager_Usage_Tracking_Data {
128
  private static function get_job_type_has_description_count() {
129
  $count = 0;
130
  $terms = get_terms(
131
- 'job_listing_type', array(
 
132
  'hide_empty' => false,
133
  )
134
  );
102
 
103
  $count = 0;
104
  $terms = get_terms(
105
+ array(
106
+ 'taxonomy' => 'job_listing_category',
107
  'hide_empty' => false,
108
  )
109
  );
129
  private static function get_job_type_has_description_count() {
130
  $count = 0;
131
  $terms = get_terms(
132
+ array(
133
+ 'taxonomy' => 'job_listing_type',
134
  'hide_empty' => false,
135
  )
136
  );
includes/class-wp-job-manager-usage-tracking.php CHANGED
@@ -123,7 +123,7 @@ class WP_Job_Manager_Usage_Tracking extends WP_Job_Manager_Usage_Tracking_Base {
123
  'std' => '0',
124
  'type' => 'checkbox',
125
  'desc' => '',
126
- 'label' => __( 'Enable usage tracking', 'wp-job-manager' ),
127
  'cb_label' => $this->opt_in_checkbox_text(),
128
  );
129
 
123
  'std' => '0',
124
  'type' => 'checkbox',
125
  'desc' => '',
126
+ 'label' => __( 'Enable Usage Tracking', 'wp-job-manager' ),
127
  'cb_label' => $this->opt_in_checkbox_text(),
128
  );
129
 
includes/class-wp-job-manager-widget.php CHANGED
@@ -182,6 +182,24 @@ class WP_Job_Manager_Widget extends WP_Widget {
182
  }
183
  }
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  /**
186
  * Echoes the widget content.
187
  *
182
  }
183
  }
184
 
185
+ /**
186
+ * Gets the instance with the default values for all settings.
187
+ *
188
+ * @return array
189
+ */
190
+ protected function get_default_instance() {
191
+ $defaults = array();
192
+ if ( ! empty( $this->settings ) ) {
193
+ foreach ( $this->settings as $key => $setting ) {
194
+ $defaults[ $key ] = null;
195
+ if ( isset( $setting['std'] ) ) {
196
+ $defaults[ $key ] = $setting['std'];
197
+ }
198
+ }
199
+ }
200
+ return $defaults;
201
+ }
202
+
203
  /**
204
  * Echoes the widget content.
205
  *
includes/emails/class-wp-job-manager-email-admin-expiring-job.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Email notification to the site administrator when a job is expiring.
9
+ *
10
+ * @package wp-job-manager
11
+ * @since 1.31.0
12
+ * @extends WP_Job_Manager_Email
13
+ */
14
+ class WP_Job_Manager_Email_Admin_Expiring_Job extends WP_Job_Manager_Email_Employer_Expiring_Job {
15
+ /**
16
+ * Get the unique email notification key.
17
+ *
18
+ * @return string
19
+ */
20
+ public static function get_key() {
21
+ return 'admin_expiring_job';
22
+ }
23
+
24
+ /**
25
+ * Get the friendly name for this email notification.
26
+ *
27
+ * @return string
28
+ */
29
+ public static function get_name() {
30
+ return __( 'Admin Notice of Expiring Job Listings', 'wp-job-manager' );
31
+ }
32
+
33
+ /**
34
+ * Get the description for this email notification.
35
+ *
36
+ * @type abstract
37
+ * @return string
38
+ */
39
+ public static function get_description() {
40
+ return __( 'Send notices to the site administrator before a job listing expires.', 'wp-job-manager' );
41
+ }
42
+
43
+ /**
44
+ * Get array or comma-separated list of email addresses to send message.
45
+ *
46
+ * @return string|array
47
+ */
48
+ public function get_to() {
49
+ return get_option( 'admin_email', false );
50
+ }
51
+
52
+ }
includes/emails/class-wp-job-manager-email-admin-new-job.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Email notification to administrator when a new job is submitted.
9
+ *
10
+ * @package wp-job-manager
11
+ * @since 1.31.0
12
+ * @extends WP_Job_Manager_Email
13
+ */
14
+ class WP_Job_Manager_Email_Admin_New_Job extends WP_Job_Manager_Email_Template {
15
+ /**
16
+ * Get the unique email notification key.
17
+ *
18
+ * @return string
19
+ */
20
+ public static function get_key() {
21
+ return 'admin_new_job';
22
+ }
23
+
24
+ /**
25
+ * Get the friendly name for this email notification.
26
+ *
27
+ * @return string
28
+ */
29
+ public static function get_name() {
30
+ return __( 'Admin Notice of New Listing', 'wp-job-manager' );
31
+ }
32
+
33
+ /**
34
+ * Get the description for this email notification.
35
+ *
36
+ * @type abstract
37
+ * @return string
38
+ */
39
+ public static function get_description() {
40
+ return __( 'Send a notice to the site administrator when a new job is submitted on the frontend.', 'wp-job-manager' );
41
+ }
42
+
43
+ /**
44
+ * Get the email subject.
45
+ *
46
+ * @return string
47
+ */
48
+ public function get_subject() {
49
+ $args = $this->get_args();
50
+
51
+ /**
52
+ * @var WP_Post $job
53
+ */
54
+ $job = $args['job'];
55
+ return sprintf( __( 'New Job Listing Submitted: %s', 'wp-job-manager' ), $job->post_title );
56
+ }
57
+
58
+ /**
59
+ * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
60
+ *
61
+ * @return string|bool Email from value or false to use WordPress' default.
62
+ */
63
+ public function get_from() {
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Get array or comma-separated list of email addresses to send message.
69
+ *
70
+ * @return string|array
71
+ */
72
+ public function get_to() {
73
+ return get_option( 'admin_email', false );
74
+ }
75
+
76
+ /**
77
+ * Checks the arguments and returns whether the email notification is properly set up.
78
+ *
79
+ * @return bool
80
+ */
81
+ public function is_valid() {
82
+ $args = $this->get_args();
83
+ return isset( $args['job'] )
84
+ && $args['job'] instanceof WP_Post
85
+ && $this->get_to();
86
+ }
87
+ }
includes/emails/class-wp-job-manager-email-admin-updated-job.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Email notification to administrator when a job is updated.
9
+ *
10
+ * @package wp-job-manager
11
+ * @since 1.31.0
12
+ * @extends WP_Job_Manager_Email
13
+ */
14
+ class WP_Job_Manager_Email_Admin_Updated_Job extends WP_Job_Manager_Email_Template {
15
+ /**
16
+ * Get the unique email notification key.
17
+ *
18
+ * @return string
19
+ */
20
+ public static function get_key() {
21
+ return 'admin_updated_job';
22
+ }
23
+
24
+ /**
25
+ * Get the friendly name for this email notification.
26
+ *
27
+ * @return string
28
+ */
29
+ public static function get_name() {
30
+ return __( 'Admin Notice of Updated Listing', 'wp-job-manager' );
31
+ }
32
+
33
+ /**
34
+ * Get the description for this email notification.
35
+ *
36
+ * @type abstract
37
+ * @return string
38
+ */
39
+ public static function get_description() {
40
+ return __( 'Send a notice to the site administrator when a job is updated on the frontend.', 'wp-job-manager' );
41
+ }
42
+
43
+ /**
44
+ * Get the email subject.
45
+ *
46
+ * @return string
47
+ */
48
+ public function get_subject() {
49
+ $args = $this->get_args();
50
+
51
+ /**
52
+ * @var WP_Post $job
53
+ */
54
+ $job = $args['job'];
55
+ return sprintf( __( 'Job Listing Updated: %s', 'wp-job-manager' ), $job->post_title );
56
+ }
57
+
58
+ /**
59
+ * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
60
+ *
61
+ * @return string|bool Email from value or false to use WordPress' default.
62
+ */
63
+ public function get_from() {
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Get array or comma-separated list of email addresses to send message.
69
+ *
70
+ * @return string|array
71
+ */
72
+ public function get_to() {
73
+ return get_option( 'admin_email', false );
74
+ }
75
+
76
+ /**
77
+ * Checks the arguments and returns whether the email notification is properly set up.
78
+ *
79
+ * @return bool
80
+ */
81
+ public function is_valid() {
82
+ $args = $this->get_args();
83
+ return isset( $args['job'] )
84
+ && $args['job'] instanceof WP_Post
85
+ && $this->get_to();
86
+ }
87
+ }
includes/emails/class-wp-job-manager-email-employer-expiring-job.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ /**
8
+ * Email notification to employers when a job is expiring.
9
+ *
10
+ * @package wp-job-manager
11
+ * @since 1.31.0
12
+ * @extends WP_Job_Manager_Email
13
+ */
14
+ class WP_Job_Manager_Email_Employer_Expiring_Job extends WP_Job_Manager_Email_Template {
15
+ const SETTING_NOTICE_PERIOD_NAME = 'notice_period_days';
16
+ const SETTING_NOTICE_PERIOD_DEFAULT = '1';
17
+
18
+ /**
19
+ * Get the unique email notification key.
20
+ *
21
+ * @return string
22
+ */
23
+ public static function get_key() {
24
+ return 'employer_expiring_job';
25
+ }
26
+
27
+ /**
28
+ * Get the friendly name for this email notification.
29
+ *
30
+ * @return string
31
+ */
32
+ public static function get_name() {
33
+ return __( 'Employer Notice of Expiring Job Listings', 'wp-job-manager' );
34
+ }
35
+
36
+ /**
37
+ * Get the description for this email notification.
38
+ *
39
+ * @type abstract
40
+ * @return string
41
+ */
42
+ public static function get_description() {
43
+ return __( 'Send notices to employers before a job listing expires.', 'wp-job-manager' );
44
+ }
45
+
46
+ /**
47
+ * Get the notice period in days from the notification settings.
48
+ *
49
+ * @param array $settings
50
+ * @return int
51
+ */
52
+ public static function get_notice_period( $settings ) {
53
+ if ( isset( $settings[ self::SETTING_NOTICE_PERIOD_NAME ] ) ) {
54
+ return absint( $settings[ self::SETTING_NOTICE_PERIOD_NAME ] );
55
+ }
56
+ return absint( self::SETTING_NOTICE_PERIOD_DEFAULT );
57
+ }
58
+
59
+ /**
60
+ * Get the email subject.
61
+ *
62
+ * @return string
63
+ */
64
+ public function get_subject() {
65
+ $args = $this->get_args();
66
+
67
+ /**
68
+ * @var WP_Post $job
69
+ */
70
+ $job = $args['job'];
71
+ return sprintf( __( 'Job Listing Expiring: %s', 'wp-job-manager' ), $job->post_title );
72
+ }
73
+
74
+ /**
75
+ * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
76
+ *
77
+ * @return string|bool Email from value or false to use WordPress' default.
78
+ */
79
+ public function get_from() {
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Get array or comma-separated list of email addresses to send message.
85
+ *
86
+ * @return string|array
87
+ */
88
+ public function get_to() {
89
+ $args = $this->get_args();
90
+ return $args['author']->user_email;
91
+ }
92
+
93
+ /**
94
+ * Expand arguments as necessary for the generation of the email.
95
+ *
96
+ * @param $args
97
+ * @return mixed
98
+ */
99
+ protected function prepare_args( $args ) {
100
+ $args = parent::prepare_args( $args );
101
+
102
+ if ( isset( $args['job'] ) ) {
103
+ $args['expiring_today'] = false;
104
+ $today = date( 'Y-m-d', current_time( 'timestamp' ) );
105
+ $expiring_date = date( 'Y-m-d', strtotime( $args['job']->_job_expires ) );
106
+ if ( ! empty( $args['job']->_job_expires ) && $today === $expiring_date ) {
107
+ $args['expiring_today'] = true;
108
+ }
109
+ }
110
+
111
+ return $args;
112
+ }
113
+
114
+ /**
115
+ * Get the settings for this email notifications.
116
+ *
117
+ * @return array
118
+ */
119
+ public static function get_setting_fields() {
120
+ $fields = parent::get_setting_fields();
121
+ $fields[] = array(
122
+ 'name' => self::SETTING_NOTICE_PERIOD_NAME,
123
+ 'std' => self::SETTING_NOTICE_PERIOD_DEFAULT,
124
+ 'label' => __( 'Notice Period', 'wp-job-manager' ),
125
+ 'type' => 'number',
126
+ 'after' => ' ' . __( 'days', 'wp-job-manager' ),
127
+ 'attributes' => array( 'min' => 0 ),
128
+ );
129
+ return $fields;
130
+ }
131
+
132
+ /**
133
+ * Is this email notification enabled by default?
134
+ *
135
+ * @return bool
136
+ */
137
+ public static function is_default_enabled() {
138
+ return false;
139
+ }
140
+
141
+ /**
142
+ * Checks the arguments and returns whether the email notification is properly set up.
143
+ *
144
+ * @return bool
145
+ */
146
+ public function is_valid() {
147
+ $args = $this->get_args();
148
+ return isset( $args['job'] )
149
+ && $args['job'] instanceof WP_Post
150
+ && isset( $args['author'] )
151
+ && $args['author'] instanceof WP_User
152
+ && ! empty( $args['author']->user_email );
153
+ }
154
+
155
+ }
includes/rest-api/class-wp-job-manager-controllers-status.php CHANGED
@@ -95,7 +95,7 @@ class WP_Job_Manager_Controllers_Status extends WP_Job_Manager_REST_Controller_M
95
  public function update( $request ) {
96
  $key = $request->get_param( 'key' );
97
  $value = $request->get_param( 'value' );
98
- if ( empty( $value ) ) {
99
  if ( ! function_exists( 'json_decode' ) ) {
100
  include_once ABSPATH . WPINC . 'compat.php';
101
  }
95
  public function update( $request ) {
96
  $key = $request->get_param( 'key' );
97
  $value = $request->get_param( 'value' );
98
+ if ( ! isset( $value ) ) {
99
  if ( ! function_exists( 'json_decode' ) ) {
100
  include_once ABSPATH . WPINC . 'compat.php';
101
  }
includes/rest-api/class-wp-job-manager-models-job-categories-custom-fields.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Declaration of Job Categories Custom Fields Model
4
+ *
5
+ * @package WPJM/REST
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
+
12
+ /**
13
+ * Class WP_Job_Manager_Models_Job_Categories_Custom_Fields
14
+ */
15
+ class WP_Job_Manager_Models_Job_Categories_Custom_Fields extends WP_Job_Manager_REST_Model
16
+ implements WP_Job_Manager_REST_Interfaces_Model {
17
+
18
+ /**
19
+ * Declare Fields.
20
+ *
21
+ * @return array
22
+ */
23
+ public function declare_fields() {
24
+ return array();
25
+ }
26
+ }
includes/rest-api/class-wp-job-manager-models-status.php CHANGED
@@ -38,8 +38,11 @@ class WP_Job_Manager_Models_Status extends WP_Job_Manager_REST_Model
38
  * @return bool
39
  */
40
  public function permissions_check( $request, $action ) {
 
 
 
41
  if ( in_array( $action, array( 'index', 'show' ), true ) ) {
42
- return true;
43
  }
44
  return current_user_can( 'manage_options' );
45
  }
38
  * @return bool
39
  */
40
  public function permissions_check( $request, $action ) {
41
+ if ( ! is_user_logged_in() ) {
42
+ return false;
43
+ }
44
  if ( in_array( $action, array( 'index', 'show' ), true ) ) {
45
+ return current_user_can( 'manage_job_listings' );
46
  }
47
  return current_user_can( 'manage_options' );
48
  }
includes/rest-api/class-wp-job-manager-registrable-job-categories.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Exposes Job Categories Taxonomy REST Api
4
+ *
5
+ * @package WPJM/REST
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
+
12
+ /**
13
+ * Class WP_Job_Manager_Registrable_Job_Categories
14
+ */
15
+ class WP_Job_Manager_Registrable_Job_Categories extends WP_Job_Manager_Registrable_Taxonomy_Type {
16
+ /**
17
+ * Gets the taxonomy type to register.
18
+ *
19
+ * @return string Taxonomy type to expose.
20
+ */
21
+ public function get_taxonomy_type() {
22
+ return 'job_listing_category';
23
+ }
24
+
25
+ /**
26
+ * Gets the REST API base slug.
27
+ *
28
+ * @return string Slug for REST API base.
29
+ */
30
+ public function get_rest_base() {
31
+ return 'job-categories';
32
+ }
33
+
34
+ /**
35
+ * Gets the REST API model class name.
36
+ *
37
+ * @return string Class name for the taxonomy type's model.
38
+ */
39
+ public function get_model_class_name() {
40
+ return 'WP_Job_Manager_Models_Job_Categories_Custom_Fields';
41
+ }
42
+ }
includes/rest-api/class-wp-job-manager-registrable-job-listings.php CHANGED
@@ -7,6 +7,10 @@
7
  * @package WPJM/REST
8
  */
9
 
 
 
 
 
10
  /**
11
  * Class MT_Controller_Extension
12
  */
7
  * @package WPJM/REST
8
  */
9
 
10
+ if ( ! defined( 'ABSPATH' ) ) {
11
+ exit;
12
+ }
13
+
14
  /**
15
  * Class MT_Controller_Extension
16
  */
includes/rest-api/class-wp-job-manager-registrable-job-types.php CHANGED
@@ -12,202 +12,31 @@ if ( ! defined( 'ABSPATH' ) ) {
12
  /**
13
  * Class WP_Job_Manager_Registrable_Job_Types
14
  */
15
- class WP_Job_Manager_Registrable_Job_Types implements WP_Job_Manager_REST_Interfaces_Registrable {
16
-
17
- /**
18
- * The Model Prototype
19
- *
20
- * @var WP_Job_Manager_REST_Model
21
- */
22
- private $model_prototype;
23
-
24
- /**
25
- * Rest Field Name
26
- *
27
- * @var string
28
- */
29
- private $rest_field_name;
30
-
31
  /**
32
- * Taxonomy Type
33
  *
34
- * @var string
35
  */
36
- private $taxonomy_type;
37
-
38
- /**
39
- * Register Job Types
40
- *
41
- * @param WP_Job_Manager_REST_Environment $environment The Environment to use.
42
- * @throws WP_Job_Manager_REST_Exception Throws.
43
- *
44
- * @return bool|WP_Error true if valid otherwise error.
45
- */
46
- public function register( $environment ) {
47
- global $wp_taxonomies;
48
- $this->taxonomy_type = 'job_listing_type';
49
- $this->rest_field_name = 'fields';
50
-
51
- if ( ! isset( $wp_taxonomies[ $this->taxonomy_type ] ) ) {
52
- return false;
53
- }
54
-
55
- if ( $wp_taxonomies[ $this->taxonomy_type ]->show_in_rest ) {
56
- return true;
57
- }
58
-
59
- $wp_taxonomies[ $this->taxonomy_type ]->show_in_rest = true;
60
- $wp_taxonomies[ $this->taxonomy_type ]->rest_base = 'job-types';
61
-
62
- $this->model_prototype = $environment->model( 'WP_Job_Manager_Models_Job_Types_Custom_Fields' );
63
-
64
- if ( ! $this->model_prototype ) {
65
- return new WP_Error( 'model-not-found' );
66
- }
67
- register_rest_field( $this->taxonomy_type, $this->rest_field_name, array(
68
- 'get_callback' => array( $this, 'get_employment_type' ),
69
- 'update_callback' => array( $this, 'update_employment_type' ),
70
- 'schema' => $this->get_item_schema(),
71
- ) );
72
-
73
- return true;
74
  }
75
 
76
  /**
77
- * Get Item Schema
78
  *
79
- * @return array
80
  */
81
- public function get_item_schema() {
82
- $fields = $this->model_prototype->get_fields();
83
- $properties = array();
84
- $required = array();
85
- foreach ( $fields as $field_declaration ) {
86
- /**
87
- * Our declaration
88
- *
89
- * @var WP_Job_Manager_REST_Field_Declaration $field_declaration
90
- */
91
- $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property();
92
- if ( $field_declaration->is_required() ) {
93
- $required[] = $field_declaration->get_data_transfer_name();
94
- }
95
- }
96
- $schema = array(
97
- '$schema' => 'http://json-schema.org/schema#',
98
- 'title' => $this->model_prototype->get_name(),
99
- 'type' => 'object',
100
- 'properties' => (array) apply_filters( 'mixtape_rest_api_schema_properties', $properties, $this->model_prototype ),
101
- );
102
-
103
- if ( ! empty( $required ) ) {
104
- $schema['required'] = $required;
105
- }
106
-
107
- return $schema;
108
  }
109
 
110
  /**
111
- * Our Get Fields.
112
- *
113
- * @param array $object Object.
114
- * @param string $field_name Field Name.
115
- * @param WP_REST_Request $request Request.
116
- * @param string $object_type Object Type.
117
  *
118
- * @return mixed|string
119
- * @throws WP_Job_Manager_REST_Exception If type not there.
120
  */
121
- public function get_employment_type( $object, $field_name, $request, $object_type ) {
122
- if ( $this->taxonomy_type !== $object_type ) {
123
- return null;
124
- }
125
-
126
- if ( $this->rest_field_name !== $field_name ) {
127
- return null;
128
- }
129
-
130
- $object_id = absint( $object['id'] );
131
- $model = $this->get_model( $object_id );
132
- return $model->to_dto();
133
- }
134
-
135
- /**
136
- * Get a model if exists
137
- *
138
- * @param int $object_id Object ID.
139
- * @return WP_Job_Manager_REST_Interfaces_Model
140
- * @throws WP_Job_Manager_REST_Exception On Error.
141
- */
142
- private function get_model( $object_id ) {
143
- $data = array();
144
- foreach ( $this->model_prototype->get_fields( WP_Job_Manager_REST_Field_Declaration::META ) as $field_declaration ) {
145
- $field_name = $field_declaration->get_name();
146
- if ( metadata_exists( 'term', $object_id, $field_name ) ) {
147
- $meta = get_term_meta( $object_id, $field_name, true );
148
- $data[ $field_name ] = $meta;
149
- }
150
- }
151
-
152
- return $this->model_prototype->create( $data, array(
153
- 'deserialize' => true,
154
- ) );
155
- }
156
-
157
- /**
158
- * Our Reader.
159
- *
160
- * @param mixed $data Data.
161
- * @param object $object Object.
162
- * @param string $field_name Field Name.
163
- * @param WP_REST_Request $request Request.
164
- * @param string $object_type Object Type.
165
- *
166
- * @return mixed|string
167
- * @throws WP_Job_Manager_REST_Exception If type not there.
168
- */
169
- public function update_employment_type( $data, $object, $field_name, $request, $object_type ) {
170
- if ( $this->taxonomy_type !== $object_type ) {
171
- return null;
172
- }
173
-
174
- if ( $this->rest_field_name !== $field_name ) {
175
- return null;
176
- }
177
-
178
- if ( ! is_a( $object, 'WP_Term' ) ) {
179
- return null;
180
- }
181
-
182
- $term_id = absint( $object->term_id );
183
- if ( ! $term_id ) {
184
- // No way to update this. Bail.
185
- return new WP_Error( 'job-types-error-invalid-id', 'job-types-error-invalid-id', array(
186
- 'status' => 400,
187
- ) );
188
- }
189
- $existing_model = $this->get_model( $term_id );
190
-
191
- $updated = $existing_model->update_from_array( $data );
192
- if ( is_wp_error( $updated ) ) {
193
- return $updated;
194
- }
195
-
196
- $maybe_validation_error = $updated->sanitize()->validate();
197
- if ( is_wp_error( $maybe_validation_error ) ) {
198
- return $maybe_validation_error;
199
- }
200
-
201
- $serialized_data = $updated->serialize( WP_Job_Manager_REST_Field_Declaration::META );
202
-
203
- foreach ( $serialized_data as $field_name => $val ) {
204
- if ( metadata_exists( 'term', $term_id, $field_name ) ) {
205
- update_term_meta( $term_id, $field_name, $val );
206
- } else {
207
- add_term_meta( $term_id, $field_name, $val );
208
- }
209
- }
210
-
211
- return true;
212
  }
213
  }
12
  /**
13
  * Class WP_Job_Manager_Registrable_Job_Types
14
  */
15
+ class WP_Job_Manager_Registrable_Job_Types extends WP_Job_Manager_Registrable_Taxonomy_Type {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  /**
17
+ * Gets the taxonomy type to register.
18
  *
19
+ * @return string Taxonomy type to expose.
20
  */
21
+ public function get_taxonomy_type() {
22
+ return 'job_listing_type';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
 
25
  /**
26
+ * Gets the REST API base slug.
27
  *
28
+ * @return string Slug for REST API base.
29
  */
30
+ public function get_rest_base() {
31
+ return 'job-types';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  /**
35
+ * Gets the REST API model class name.
 
 
 
 
 
36
  *
37
+ * @return string Class name for the taxonomy type's model.
 
38
  */
39
+ public function get_model_class_name() {
40
+ return 'WP_Job_Manager_Models_Job_Types_Custom_Fields';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
  }
includes/rest-api/class-wp-job-manager-registrable-taxonomy-type.php ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Exposes Taxonomy REST Api
4
+ *
5
+ * @package WPJM/REST
6
+ */
7
+
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
+
12
+ /**
13
+ * Class WP_Job_Manager_Registrable_Taxonomy_Type
14
+ */
15
+ abstract class WP_Job_Manager_Registrable_Taxonomy_Type implements WP_Job_Manager_REST_Interfaces_Registrable {
16
+
17
+ /**
18
+ * The Model Prototype
19
+ *
20
+ * @var WP_Job_Manager_REST_Model
21
+ */
22
+ private $model_prototype;
23
+
24
+ /**
25
+ * REST Field Name
26
+ *
27
+ * @var string
28
+ */
29
+ private $rest_field_name;
30
+
31
+ /**
32
+ * Gets the taxonomy type to register.
33
+ *
34
+ * @return string Taxonomy type to expose.
35
+ */
36
+ abstract public function get_taxonomy_type();
37
+
38
+ /**
39
+ * Gets the REST API base slug.
40
+ *
41
+ * @return string Slug for REST API base.
42
+ */
43
+ abstract public function get_rest_base();
44
+
45
+ /**
46
+ * Gets the REST API model class name.
47
+ *
48
+ * @return string Class name for the taxonomy type's model.
49
+ */
50
+ abstract public function get_model_class_name();
51
+
52
+ /**
53
+ * Register Job Categories
54
+ *
55
+ * @param WP_Job_Manager_REST_Environment $environment The Environment to use.
56
+ * @throws WP_Job_Manager_REST_Exception Throws.
57
+ *
58
+ * @return bool|WP_Error true if valid otherwise error.
59
+ */
60
+ public function register( $environment ) {
61
+ global $wp_taxonomies;
62
+
63
+ $taxonomy_type = $this->get_taxonomy_type();
64
+ $this->rest_field_name = 'fields';
65
+
66
+ if ( ! isset( $wp_taxonomies[ $taxonomy_type ] ) ) {
67
+ return false;
68
+ }
69
+
70
+ if ( $wp_taxonomies[ $taxonomy_type ]->show_in_rest ) {
71
+ return true;
72
+ }
73
+
74
+ $wp_taxonomies[ $taxonomy_type ]->show_in_rest = true;
75
+ $wp_taxonomies[ $taxonomy_type ]->rest_base = $this->get_rest_base();
76
+
77
+ $this->model_prototype = $environment->model( $this->get_model_class_name() );
78
+
79
+ if ( ! $this->model_prototype ) {
80
+ return new WP_Error( 'model-not-found' );
81
+ }
82
+ register_rest_field( $taxonomy_type, $this->rest_field_name, array(
83
+ 'get_callback' => array( $this, 'get_taxonomy_term' ),
84
+ 'update_callback' => array( $this, 'update_taxonomy_term' ),
85
+ 'schema' => $this->get_item_schema(),
86
+ ) );
87
+
88
+ return true;
89
+ }
90
+
91
+ /**
92
+ * Get Item Schema
93
+ *
94
+ * @return array
95
+ */
96
+ public function get_item_schema() {
97
+ $fields = $this->model_prototype->get_fields();
98
+ $properties = array();
99
+ $required = array();
100
+ foreach ( $fields as $field_declaration ) {
101
+ /**
102
+ * Our declaration
103
+ *
104
+ * @var WP_Job_Manager_REST_Field_Declaration $field_declaration
105
+ */
106
+ $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property();
107
+ if ( $field_declaration->is_required() ) {
108
+ $required[] = $field_declaration->get_data_transfer_name();
109
+ }
110
+ }
111
+ $schema = array(
112
+ '$schema' => 'http://json-schema.org/schema#',
113
+ 'title' => $this->model_prototype->get_name(),
114
+ 'type' => 'object',
115
+ 'properties' => (array) apply_filters( 'mixtape_rest_api_schema_properties', $properties, $this->model_prototype ),
116
+ );
117
+
118
+ if ( ! empty( $required ) ) {
119
+ $schema['required'] = $required;
120
+ }
121
+
122
+ return $schema;
123
+ }
124
+
125
+ /**
126
+ * Our Get Fields.
127
+ *
128
+ * @param array $object Object.
129
+ * @param string $field_name Field Name.
130
+ * @param WP_REST_Request $request Request.
131
+ * @param string $object_type Object Type.
132
+ *
133
+ * @return mixed|string
134
+ * @throws WP_Job_Manager_REST_Exception If type not there.
135
+ */
136
+ public function get_taxonomy_term( $object, $field_name, $request, $object_type ) {
137
+ if ( $this->get_taxonomy_type() !== $object_type ) {
138
+ return null;
139
+ }
140
+
141
+ if ( $this->rest_field_name !== $field_name ) {
142
+ return null;
143
+ }
144
+
145
+ $object_id = absint( $object['id'] );
146
+ $model = $this->get_model( $object_id );
147
+ return $model->to_dto();
148
+ }
149
+
150
+ /**
151
+ * Get a model if exists
152
+ *
153
+ * @param int $object_id Object ID.
154
+ * @return WP_Job_Manager_REST_Interfaces_Model
155
+ * @throws WP_Job_Manager_REST_Exception On Error.
156
+ */
157
+ private function get_model( $object_id ) {
158
+ $data = array();
159
+ foreach ( $this->model_prototype->get_fields( WP_Job_Manager_REST_Field_Declaration::META ) as $field_declaration ) {
160
+ $field_name = $field_declaration->get_name();
161
+ if ( metadata_exists( 'term', $object_id, $field_name ) ) {
162
+ $meta = get_term_meta( $object_id, $field_name, true );
163
+ $data[ $field_name ] = $meta;
164
+ }
165
+ }
166
+
167
+ return $this->model_prototype->create( $data, array(
168
+ 'deserialize' => true,
169
+ ) );
170
+ }
171
+
172
+ /**
173
+ * Our Reader.
174
+ *
175
+ * @param mixed $data Data.
176
+ * @param object $object Object.
177
+ * @param string $field_name Field Name.
178
+ * @param WP_REST_Request $request Request.
179
+ * @param string $object_type Object Type.
180
+ *
181
+ * @return mixed|string
182
+ * @throws WP_Job_Manager_REST_Exception If type not there.
183
+ */
184
+ public function update_taxonomy_term( $data, $object, $field_name, $request, $object_type ) {
185
+ if ( $this->get_taxonomy_type() !== $object_type ) {
186
+ return null;
187
+ }
188
+
189
+ if ( $this->rest_field_name !== $field_name ) {
190
+ return null;
191
+ }
192
+
193
+ if ( ! is_a( $object, 'WP_Term' ) ) {
194
+ return null;
195
+ }
196
+
197
+ $rest_base = $this->get_rest_base();
198
+ $term_id = absint( $object->term_id );
199
+ if ( ! $term_id ) {
200
+ // No way to update this. Bail.
201
+ return new WP_Error( $rest_base . '-error-invalid-id', $rest_base . '-error-invalid-id', array(
202
+ 'status' => 400,
203
+ ) );
204
+ }
205
+ $existing_model = $this->get_model( $term_id );
206
+
207
+ $updated = $existing_model->update_from_array( $data );
208
+ if ( is_wp_error( $updated ) ) {
209
+ return $updated;
210
+ }
211
+
212
+ $maybe_validation_error = $updated->sanitize()->validate();
213
+ if ( is_wp_error( $maybe_validation_error ) ) {
214
+ return $maybe_validation_error;
215
+ }
216
+
217
+ $serialized_data = $updated->serialize( WP_Job_Manager_REST_Field_Declaration::META );
218
+
219
+ foreach ( $serialized_data as $field_name => $val ) {
220
+ if ( metadata_exists( 'term', $term_id, $field_name ) ) {
221
+ update_term_meta( $term_id, $field_name, $val );
222
+ } else {
223
+ add_term_meta( $term_id, $field_name, $val );
224
+ }
225
+ }
226
+
227
+ return true;
228
+ }
229
+ }
includes/rest-api/class-wp-job-manager-rest-api.php CHANGED
@@ -92,8 +92,11 @@ class WP_Job_Manager_REST_API {
92
  include_once 'class-wp-job-manager-controllers-status.php';
93
  include_once 'class-wp-job-manager-models-job-listings-custom-fields.php';
94
  include_once 'class-wp-job-manager-models-job-types-custom-fields.php';
 
95
  include_once 'class-wp-job-manager-registrable-job-listings.php';
 
96
  include_once 'class-wp-job-manager-registrable-job-types.php';
 
97
 
98
  // Models.
99
  $env->define_model( 'WP_Job_Manager_Models_Settings' )
@@ -103,6 +106,7 @@ class WP_Job_Manager_REST_API {
103
  $env->define_model( 'WP_Job_Manager_Filters_Status' );
104
  $env->define_model( 'WP_Job_Manager_Models_Job_Listings_Custom_Fields' );
105
  $env->define_model( 'WP_Job_Manager_Models_Job_Types_Custom_Fields' );
 
106
 
107
  // Endpoints.
108
  $env->rest_api( 'wpjm/v1' )
@@ -113,6 +117,7 @@ class WP_Job_Manager_REST_API {
113
  'WP_Job_Manager_Models_Job_Listings_Custom_Fields',
114
  'fields' ) );
115
  $env->add_registrable( new WP_Job_Manager_Registrable_Job_Types() );
 
116
  }
117
  }
118
 
92
  include_once 'class-wp-job-manager-controllers-status.php';
93
  include_once 'class-wp-job-manager-models-job-listings-custom-fields.php';
94
  include_once 'class-wp-job-manager-models-job-types-custom-fields.php';
95
+ include_once 'class-wp-job-manager-models-job-categories-custom-fields.php';
96
  include_once 'class-wp-job-manager-registrable-job-listings.php';
97
+ include_once 'class-wp-job-manager-registrable-taxonomy-type.php';
98
  include_once 'class-wp-job-manager-registrable-job-types.php';
99
+ include_once 'class-wp-job-manager-registrable-job-categories.php';
100
 
101
  // Models.
102
  $env->define_model( 'WP_Job_Manager_Models_Settings' )
106
  $env->define_model( 'WP_Job_Manager_Filters_Status' );
107
  $env->define_model( 'WP_Job_Manager_Models_Job_Listings_Custom_Fields' );
108
  $env->define_model( 'WP_Job_Manager_Models_Job_Types_Custom_Fields' );
109
+ $env->define_model( 'WP_Job_Manager_Models_Job_Categories_Custom_Fields' );
110
 
111
  // Endpoints.
112
  $env->rest_api( 'wpjm/v1' )
117
  'WP_Job_Manager_Models_Job_Listings_Custom_Fields',
118
  'fields' ) );
119
  $env->add_registrable( new WP_Job_Manager_Registrable_Job_Types() );
120
+ $env->add_registrable( new WP_Job_Manager_Registrable_Job_Categories() );
121
  }
122
  }
123
 
includes/widgets/class-wp-job-manager-widget-featured-jobs.php CHANGED
@@ -73,13 +73,15 @@ class WP_Job_Manager_Widget_Featured_Jobs extends WP_Job_Manager_Widget {
73
  return;
74
  }
75
 
 
 
76
  ob_start();
77
 
78
  extract( $args );
79
- $titleInstance = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : '';
80
- $number = isset( $instance['number'] ) ? absint( $instance['number'] ) : '';
81
- $orderby = isset( $instance['orderby'] ) ? esc_attr( $instance['orderby'] ) : 'date';
82
- $order = isset( $instance['order'] ) ? esc_attr( $instance['order'] ) : 'DESC';
83
  $title = apply_filters( 'widget_title', $titleInstance, $instance, $this->id_base );
84
  $jobs = get_job_listings( array(
85
  'posts_per_page' => $number,
73
  return;
74
  }
75
 
76
+ $instance = array_merge( $this->get_default_instance(), $instance );
77
+
78
  ob_start();
79
 
80
  extract( $args );
81
+ $titleInstance = esc_attr( $instance['title'] );
82
+ $number = absint( $instance['number'] );
83
+ $orderby = esc_attr( $instance['orderby'] );
84
+ $order = esc_attr( $instance['order'] );
85
  $title = apply_filters( 'widget_title', $titleInstance, $instance, $this->id_base );
86
  $jobs = get_job_listings( array(
87
  'posts_per_page' => $number,
includes/widgets/class-wp-job-manager-widget-recent-jobs.php CHANGED
@@ -63,6 +63,8 @@ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
63
  return;
64
  }
65
 
 
 
66
  ob_start();
67
 
68
  extract( $args );
@@ -70,8 +72,8 @@ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
70
  $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
71
  $number = absint( $instance['number'] );
72
  $jobs = get_job_listings( array(
73
- 'search_location' => isset( $instance['location'] ) ? $instance['location'] : '',
74
- 'search_keywords' => isset( $instance['keyword'] ) ? $instance['keyword'] : '',
75
  'posts_per_page' => $number,
76
  'orderby' => 'date',
77
  'order' => 'DESC',
63
  return;
64
  }
65
 
66
+ $instance = array_merge( $this->get_default_instance(), $instance );
67
+
68
  ob_start();
69
 
70
  extract( $args );
72
  $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
73
  $number = absint( $instance['number'] );
74
  $jobs = get_job_listings( array(
75
+ 'search_location' => $instance['location'],
76
+ 'search_keywords' => $instance['keyword'],
77
  'posts_per_page' => $number,
78
  'orderby' => 'date',
79
  'order' => 'DESC',
languages/wp-job-manager.pot CHANGED
@@ -2,9 +2,9 @@
2
  # This file is distributed under the GPL2+.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Job Manager 1.30.2\n"
6
  "Report-Msgid-Bugs-To: https://github.com/Automattic/WP-Job-Manager/issues\n"
7
- "POT-Creation-Date: 2018-03-28 10:44:19+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
@@ -13,13 +13,21 @@ msgstr ""
13
  "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
14
  "X-Generator: grunt-wp-i18n 0.5.4\n"
15
 
 
 
 
 
 
 
 
 
16
  #: includes/abstracts/abstract-wp-job-manager-form.php:312
17
  #: includes/abstracts/abstract-wp-job-manager-form.php:325
18
  msgid "\"%s\" check failed. Please try again."
19
  msgstr ""
20
 
21
  #: includes/admin/class-wp-job-manager-addons.php:111
22
- #: includes/admin/class-wp-job-manager-admin.php:150
23
  #: includes/admin/views/html-admin-page-addons.php:2
24
  msgid "WP Job Manager Add-ons"
25
  msgstr ""
@@ -31,178 +39,202 @@ msgstr ""
31
 
32
  #: includes/admin/class-wp-job-manager-admin.php:86
33
  msgid ""
34
- "The upcoming release of <strong>WP Job Manager 1.31.0</strong> will require "
35
- "a more recent version of WordPress. <a href=\"%s\">Please update "
36
- "WordPress</a> before updating WP Job Manager."
37
  msgstr ""
38
 
39
  #: includes/admin/class-wp-job-manager-admin.php:97
40
  msgid "<a href=\"%s\" style=\"color: red\">WordPress Update Required</a>"
41
  msgstr ""
42
 
43
- #: includes/admin/class-wp-job-manager-admin.php:147
44
  msgid "Settings"
45
  msgstr ""
46
 
47
- #: includes/admin/class-wp-job-manager-admin.php:150
48
  msgid "Add-ons"
49
  msgstr ""
50
 
51
- #: includes/admin/class-wp-job-manager-cpt.php:72
52
  msgid "Approve %s"
53
  msgstr ""
54
 
55
- #: includes/admin/class-wp-job-manager-cpt.php:73
56
  msgid "%s approved"
57
  msgstr ""
58
 
59
- #: includes/admin/class-wp-job-manager-cpt.php:77
60
  msgid "Expire %s"
61
  msgstr ""
62
 
63
- #: includes/admin/class-wp-job-manager-cpt.php:78
64
  msgid "%s expired"
65
  msgstr ""
66
 
67
- #: includes/admin/class-wp-job-manager-cpt.php:82
68
  msgid "Mark %s Filled"
69
  msgstr ""
70
 
71
- #: includes/admin/class-wp-job-manager-cpt.php:83
72
  msgid "%s marked as filled"
73
  msgstr ""
74
 
75
- #: includes/admin/class-wp-job-manager-cpt.php:87
76
  msgid "Mark %s Not Filled"
77
  msgstr ""
78
 
79
- #: includes/admin/class-wp-job-manager-cpt.php:88
80
  msgid "%s marked as not filled"
81
  msgstr ""
82
 
83
- #: includes/admin/class-wp-job-manager-cpt.php:299
84
  msgid "Select category"
85
  msgstr ""
86
 
87
- #: includes/admin/class-wp-job-manager-cpt.php:315
88
- #: includes/admin/class-wp-job-manager-cpt.php:359
89
- msgid "Position"
90
  msgstr ""
91
 
92
  #: includes/admin/class-wp-job-manager-cpt.php:330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  msgid "%s updated. <a href=\"%s\">View</a>"
94
  msgstr ""
95
 
96
- #: includes/admin/class-wp-job-manager-cpt.php:331
97
  msgid "Custom field updated."
98
  msgstr ""
99
 
100
- #: includes/admin/class-wp-job-manager-cpt.php:332
101
  msgid "Custom field deleted."
102
  msgstr ""
103
 
104
- #: includes/admin/class-wp-job-manager-cpt.php:333
105
  msgid "%s updated."
106
  msgstr ""
107
 
108
- #: includes/admin/class-wp-job-manager-cpt.php:334
109
  msgid "%s restored to revision from %s"
110
  msgstr ""
111
 
112
- #: includes/admin/class-wp-job-manager-cpt.php:335
113
  msgid "%s published. <a href=\"%s\">View</a>"
114
  msgstr ""
115
 
116
- #: includes/admin/class-wp-job-manager-cpt.php:336
117
  msgid "%s saved."
118
  msgstr ""
119
 
120
- #: includes/admin/class-wp-job-manager-cpt.php:337
121
  msgid "%s submitted. <a target=\"_blank\" href=\"%s\">Preview</a>"
122
  msgstr ""
123
 
124
- #: includes/admin/class-wp-job-manager-cpt.php:338
125
  msgid ""
126
  "%s scheduled for: <strong>%1$s</strong>. <a target=\"_blank\" "
127
  "href=\"%2$s\">Preview</a>"
128
  msgstr ""
129
 
130
- #: includes/admin/class-wp-job-manager-cpt.php:340
131
  msgid "%s draft updated. <a target=\"_blank\" href=\"%s\">Preview</a>"
132
  msgstr ""
133
 
134
- #: includes/admin/class-wp-job-manager-cpt.php:360
135
  msgid "Type"
136
  msgstr ""
137
 
138
- #: includes/admin/class-wp-job-manager-cpt.php:361
139
  #: includes/admin/class-wp-job-manager-writepanels.php:55
 
140
  #: includes/forms/class-wp-job-manager-form-submit-job.php:178
141
  #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:38
142
  #: templates/job-filters.php:35 templates/job-filters.php:36
143
  msgid "Location"
144
  msgstr ""
145
 
146
- #: includes/admin/class-wp-job-manager-cpt.php:362
147
  msgid "Status"
148
  msgstr ""
149
 
150
- #: includes/admin/class-wp-job-manager-cpt.php:363
151
  msgid "Posted"
152
  msgstr ""
153
 
154
- #: includes/admin/class-wp-job-manager-cpt.php:364
155
  msgid "Expires"
156
  msgstr ""
157
 
158
- #: includes/admin/class-wp-job-manager-cpt.php:365
159
- #: includes/admin/class-wp-job-manager-settings.php:145
160
  msgid "Categories"
161
  msgstr ""
162
 
163
- #: includes/admin/class-wp-job-manager-cpt.php:366
164
  msgid "Featured?"
165
  msgstr ""
166
 
167
- #: includes/admin/class-wp-job-manager-cpt.php:367
168
  #: includes/class-wp-job-manager-shortcodes.php:218
169
  msgid "Filled?"
170
  msgstr ""
171
 
172
- #: includes/admin/class-wp-job-manager-cpt.php:368
173
  msgid "Actions"
174
  msgstr ""
175
 
176
- #: includes/admin/class-wp-job-manager-cpt.php:432
177
  msgid "ID: %d"
178
  msgstr ""
179
 
180
- #: includes/admin/class-wp-job-manager-cpt.php:462
181
  msgid "by a guest"
182
  msgstr ""
183
 
184
- #: includes/admin/class-wp-job-manager-cpt.php:462
185
  msgid "by %s"
186
  msgstr ""
187
 
188
- #: includes/admin/class-wp-job-manager-cpt.php:480
189
  msgid "Approve"
190
  msgstr ""
191
 
192
- #: includes/admin/class-wp-job-manager-cpt.php:488
193
  #: includes/admin/class-wp-job-manager-writepanels.php:257
194
  #: includes/admin/class-wp-job-manager-writepanels.php:260
195
  #: includes/admin/class-wp-job-manager-writepanels.php:263
196
  msgid "View"
197
  msgstr ""
198
 
199
- #: includes/admin/class-wp-job-manager-cpt.php:495
200
  #: includes/class-wp-job-manager-post-types.php:221
201
  #: templates/job-dashboard.php:52 templates/job-dashboard.php:70
202
  msgid "Edit"
203
  msgstr ""
204
 
205
- #: includes/admin/class-wp-job-manager-cpt.php:502
206
  #: templates/job-dashboard.php:75
207
  msgid "Delete"
208
  msgstr ""
@@ -253,387 +285,397 @@ msgid ""
253
  msgstr ""
254
 
255
  #: includes/admin/class-wp-job-manager-settings.php:105
 
 
 
 
 
 
 
 
 
 
256
  #: includes/class-wp-job-manager-post-types.php:217
257
  #: includes/class-wp-job-manager-post-types.php:293
258
  msgid "Job Listings"
259
  msgstr ""
260
 
261
- #: includes/admin/class-wp-job-manager-settings.php:111
262
  msgid "Listings Per Page"
263
  msgstr ""
264
 
265
- #: includes/admin/class-wp-job-manager-settings.php:112
266
  msgid "Number of job listings to display per page."
267
  msgstr ""
268
 
269
- #: includes/admin/class-wp-job-manager-settings.php:118
270
  msgid "Filled Positions"
271
  msgstr ""
272
 
273
- #: includes/admin/class-wp-job-manager-settings.php:119
274
  msgid "Hide filled positions"
275
  msgstr ""
276
 
277
- #: includes/admin/class-wp-job-manager-settings.php:120
278
  msgid "Filled positions will not display in your archives."
279
  msgstr ""
280
 
281
- #: includes/admin/class-wp-job-manager-settings.php:127
282
  msgid "Hide Expired Listings"
283
  msgstr ""
284
 
285
- #: includes/admin/class-wp-job-manager-settings.php:128
286
  msgid "Hide expired listings in job archives/search"
287
  msgstr ""
288
 
289
- #: includes/admin/class-wp-job-manager-settings.php:129
290
  msgid "Expired job listings will not be searchable."
291
  msgstr ""
292
 
293
- #: includes/admin/class-wp-job-manager-settings.php:136
294
  msgid "Hide Expired Listings Content"
295
  msgstr ""
296
 
297
- #: includes/admin/class-wp-job-manager-settings.php:137
298
  msgid "Hide content in expired single job listings"
299
  msgstr ""
300
 
301
- #: includes/admin/class-wp-job-manager-settings.php:138
302
  msgid ""
303
  "Your site will display the titles of expired listings, but not the content "
304
  "of the listings. Otherwise, expired listings display their full content "
305
  "minus the application area."
306
  msgstr ""
307
 
308
- #: includes/admin/class-wp-job-manager-settings.php:146
309
  msgid "Enable listing categories"
310
  msgstr ""
311
 
312
- #: includes/admin/class-wp-job-manager-settings.php:147
313
  msgid ""
314
  "This lets users select from a list of categories when submitting a job. "
315
  "Note: an admin has to create categories before site users can select them."
316
  msgstr ""
317
 
318
- #: includes/admin/class-wp-job-manager-settings.php:154
319
  msgid "Multi-select Categories"
320
  msgstr ""
321
 
322
- #: includes/admin/class-wp-job-manager-settings.php:155
323
  msgid "Default to category multiselect"
324
  msgstr ""
325
 
326
- #: includes/admin/class-wp-job-manager-settings.php:156
327
  msgid ""
328
  "The category selection box will default to allowing multiple selections on "
329
  "the [jobs] shortcode. Without this, users will only be able to select a "
330
  "single category when submitting jobs."
331
  msgstr ""
332
 
333
- #: includes/admin/class-wp-job-manager-settings.php:163
334
  msgid "Category Filter Type"
335
  msgstr ""
336
 
337
- #: includes/admin/class-wp-job-manager-settings.php:164
338
  msgid ""
339
  "Determines the logic used to display jobs when selecting multiple "
340
  "categories."
341
  msgstr ""
342
 
343
- #: includes/admin/class-wp-job-manager-settings.php:167
344
  msgid "Jobs will be shown if within ANY selected category"
345
  msgstr ""
346
 
347
- #: includes/admin/class-wp-job-manager-settings.php:168
348
  msgid "Jobs will be shown if within ALL selected categories"
349
  msgstr ""
350
 
351
- #: includes/admin/class-wp-job-manager-settings.php:174
352
  msgid "Types"
353
  msgstr ""
354
 
355
- #: includes/admin/class-wp-job-manager-settings.php:175
356
  msgid "Enable listing types"
357
  msgstr ""
358
 
359
- #: includes/admin/class-wp-job-manager-settings.php:176
360
  msgid ""
361
  "This lets users select from a list of types when submitting a job. Note: an "
362
  "admin has to create types before site users can select them."
363
  msgstr ""
364
 
365
- #: includes/admin/class-wp-job-manager-settings.php:183
366
  msgid "Multi-select Listing Types"
367
  msgstr ""
368
 
369
- #: includes/admin/class-wp-job-manager-settings.php:184
370
  msgid "Allow multiple types for listings"
371
  msgstr ""
372
 
373
- #: includes/admin/class-wp-job-manager-settings.php:185
374
  msgid ""
375
  "This allows users to select more than one type when submitting a job. The "
376
  "metabox on the post editor and the selection box on the front-end job "
377
  "submission form will both reflect this."
378
  msgstr ""
379
 
380
- #: includes/admin/class-wp-job-manager-settings.php:192
381
  msgid "Job Submission"
382
  msgstr ""
383
 
384
- #: includes/admin/class-wp-job-manager-settings.php:197
385
  msgid "Account Required"
386
  msgstr ""
387
 
388
- #: includes/admin/class-wp-job-manager-settings.php:198
389
  msgid "Require an account to submit listings"
390
  msgstr ""
391
 
392
- #: includes/admin/class-wp-job-manager-settings.php:199
393
  msgid "Limits job listing submissions to registered, logged-in users."
394
  msgstr ""
395
 
396
- #: includes/admin/class-wp-job-manager-settings.php:206
397
  msgid "Account Creation"
398
  msgstr ""
399
 
400
- #: includes/admin/class-wp-job-manager-settings.php:207
401
  msgid "Enable account creation during submission"
402
  msgstr ""
403
 
404
- #: includes/admin/class-wp-job-manager-settings.php:208
405
  msgid ""
406
  "Includes account creation on the listing submission form, to allow "
407
  "non-registered users to create an account and submit a job listing "
408
  "simultaneously."
409
  msgstr ""
410
 
411
- #: includes/admin/class-wp-job-manager-settings.php:215
412
  msgid "Account Username"
413
  msgstr ""
414
 
415
- #: includes/admin/class-wp-job-manager-settings.php:216
416
  msgid "Generate usernames from email addresses"
417
  msgstr ""
418
 
419
- #: includes/admin/class-wp-job-manager-settings.php:217
420
  msgid ""
421
  "Automatically generates usernames for new accounts from the registrant's "
422
  "email address. If this is not enabled, a \"username\" field will display "
423
  "instead."
424
  msgstr ""
425
 
426
- #: includes/admin/class-wp-job-manager-settings.php:224
427
  msgid "Account Password"
428
  msgstr ""
429
 
430
- #: includes/admin/class-wp-job-manager-settings.php:225
431
  msgid "Email new users a link to set a password"
432
  msgstr ""
433
 
434
- #: includes/admin/class-wp-job-manager-settings.php:226
435
  msgid ""
436
  "Sends an email to the user with their username and a link to set their "
437
  "password. If this is not enabled, a \"password\" field will display "
438
  "instead, and their email address won't be verified."
439
  msgstr ""
440
 
441
- #: includes/admin/class-wp-job-manager-settings.php:233
442
  msgid "Account Role"
443
  msgstr ""
444
 
445
- #: includes/admin/class-wp-job-manager-settings.php:234
446
  msgid ""
447
  "Any new accounts created during submission will have this role. If you "
448
  "haven't enabled account creation during submission in the options above, "
449
  "your own method of assigning roles will apply."
450
  msgstr ""
451
 
452
- #: includes/admin/class-wp-job-manager-settings.php:241
453
  msgid "Moderate New Listings"
454
  msgstr ""
455
 
456
- #: includes/admin/class-wp-job-manager-settings.php:242
457
  msgid "Require admin approval of all new listing submissions"
458
  msgstr ""
459
 
460
- #: includes/admin/class-wp-job-manager-settings.php:243
461
  msgid ""
462
  "Sets all new submissions to \"pending.\" They will not appear on your site "
463
  "until an admin approves them."
464
  msgstr ""
465
 
466
- #: includes/admin/class-wp-job-manager-settings.php:250
467
  msgid "Allow Pending Edits"
468
  msgstr ""
469
 
470
- #: includes/admin/class-wp-job-manager-settings.php:251
471
  msgid "Allow editing of pending listings"
472
  msgstr ""
473
 
474
- #: includes/admin/class-wp-job-manager-settings.php:252
475
  msgid ""
476
  "Users can continue to edit pending listings until they are approved by an "
477
  "admin."
478
  msgstr ""
479
 
480
- #: includes/admin/class-wp-job-manager-settings.php:259
481
  msgid "Allow Published Edits"
482
  msgstr ""
483
 
484
- #: includes/admin/class-wp-job-manager-settings.php:260
485
  msgid "Allow editing of published listings"
486
  msgstr ""
487
 
488
- #: includes/admin/class-wp-job-manager-settings.php:261
489
  msgid ""
490
  "Choose whether published job listings can be edited and if edits require "
491
  "admin approval. When moderation is required, the original job listings will "
492
  "be unpublished while edits await admin approval."
493
  msgstr ""
494
 
495
- #: includes/admin/class-wp-job-manager-settings.php:264
496
  msgid "Users cannot edit"
497
  msgstr ""
498
 
499
- #: includes/admin/class-wp-job-manager-settings.php:265
500
  msgid "Users can edit without admin approval"
501
  msgstr ""
502
 
503
- #: includes/admin/class-wp-job-manager-settings.php:266
504
  msgid "Users can edit, but edits require admin approval"
505
  msgstr ""
506
 
507
- #: includes/admin/class-wp-job-manager-settings.php:273
508
  msgid "Listing Duration"
509
  msgstr ""
510
 
511
- #: includes/admin/class-wp-job-manager-settings.php:274
512
  msgid ""
513
  "Listings will display for the set number of days, then expire. Leave this "
514
  "field blank if you don't want listings to have an expiration date."
515
  msgstr ""
516
 
517
- #: includes/admin/class-wp-job-manager-settings.php:280
518
  msgid "Application Method"
519
  msgstr ""
520
 
521
- #: includes/admin/class-wp-job-manager-settings.php:281
522
  msgid ""
523
  "Choose the application method job listers will need to provide. Specify URL "
524
  "or email address only, or allow listers to choose which they prefer."
525
  msgstr ""
526
 
527
- #: includes/admin/class-wp-job-manager-settings.php:284
528
  msgid "Email address or website URL"
529
  msgstr ""
530
 
531
- #: includes/admin/class-wp-job-manager-settings.php:285
532
  msgid "Email addresses only"
533
  msgstr ""
534
 
535
- #: includes/admin/class-wp-job-manager-settings.php:286
536
  msgid "Website URLs only"
537
  msgstr ""
538
 
539
- #: includes/admin/class-wp-job-manager-settings.php:292
540
  msgid "reCAPTCHA"
541
  msgstr ""
542
 
543
- #: includes/admin/class-wp-job-manager-settings.php:296
544
  msgid "Are you human?"
545
  msgstr ""
546
 
547
- #: includes/admin/class-wp-job-manager-settings.php:298
548
  msgid "Field Label"
549
  msgstr ""
550
 
551
- #: includes/admin/class-wp-job-manager-settings.php:299
552
  msgid "The label used for the reCAPTCHA field on forms."
553
  msgstr ""
554
 
555
- #: includes/admin/class-wp-job-manager-settings.php:306
556
  msgid "Site Key"
557
  msgstr ""
558
 
559
- #: includes/admin/class-wp-job-manager-settings.php:307
560
  msgid ""
561
  "You can retrieve your site key from <a href=\"%s\">Google's reCAPTCHA admin "
562
  "dashboard</a>."
563
  msgstr ""
564
 
565
- #: includes/admin/class-wp-job-manager-settings.php:314
566
  msgid "Secret Key"
567
  msgstr ""
568
 
569
- #: includes/admin/class-wp-job-manager-settings.php:315
570
  msgid ""
571
  "You can retrieve your secret key from <a href=\"%s\">Google's reCAPTCHA "
572
  "admin dashboard</a>."
573
  msgstr ""
574
 
575
- #: includes/admin/class-wp-job-manager-settings.php:321
576
  msgid "Job Submission Form"
577
  msgstr ""
578
 
579
- #: includes/admin/class-wp-job-manager-settings.php:322
580
  msgid "Display a reCAPTCHA field on job submission form."
581
  msgstr ""
582
 
583
- #: includes/admin/class-wp-job-manager-settings.php:323
584
  msgid ""
585
  "This will help prevent bots from submitting job listings. You must have "
586
  "entered a valid site key and secret key above."
587
  msgstr ""
588
 
589
- #: includes/admin/class-wp-job-manager-settings.php:330
590
  msgid "Pages"
591
  msgstr ""
592
 
593
- #: includes/admin/class-wp-job-manager-settings.php:335
594
  msgid "Submit Job Form Page"
595
  msgstr ""
596
 
597
- #: includes/admin/class-wp-job-manager-settings.php:336
598
  msgid ""
599
  "Select the page where you've used the [submit_job_form] shortcode. This "
600
  "lets the plugin know the location of the form."
601
  msgstr ""
602
 
603
- #: includes/admin/class-wp-job-manager-settings.php:342
604
  msgid "Job Dashboard Page"
605
  msgstr ""
606
 
607
- #: includes/admin/class-wp-job-manager-settings.php:343
608
  msgid ""
609
  "Select the page where you've used the [job_dashboard] shortcode. This lets "
610
  "the plugin know the location of the dashboard."
611
  msgstr ""
612
 
613
- #: includes/admin/class-wp-job-manager-settings.php:349
614
  msgid "Job Listings Page"
615
  msgstr ""
616
 
617
- #: includes/admin/class-wp-job-manager-settings.php:350
618
  msgid ""
619
  "Select the page where you've used the [jobs] shortcode. This lets the "
620
  "plugin know the location of the job listings page."
621
  msgstr ""
622
 
623
- #: includes/admin/class-wp-job-manager-settings.php:396
624
  msgid "Settings successfully saved"
625
  msgstr ""
626
 
627
- #: includes/admin/class-wp-job-manager-settings.php:472
628
- msgid "--no page--"
629
  msgstr ""
630
 
631
- #: includes/admin/class-wp-job-manager-settings.php:477
632
- msgid "Select a page&hellip;"
633
  msgstr ""
634
 
635
- #: includes/admin/class-wp-job-manager-settings.php:523
636
- msgid "Save Changes"
637
  msgstr ""
638
 
639
  #: includes/admin/class-wp-job-manager-setup.php:51
@@ -908,6 +950,7 @@ msgid "Listing Expiry Date"
908
  msgstr ""
909
 
910
  #: includes/admin/class-wp-job-manager-writepanels.php:118
 
911
  #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:75
912
  msgid "Posted by"
913
  msgstr ""
@@ -970,6 +1013,55 @@ msgstr[1] ""
970
  msgid "You must be logged in to upload files using this method."
971
  msgstr ""
972
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  #: includes/class-wp-job-manager-geocode.php:221
974
  msgid "No results found"
975
  msgstr ""
@@ -988,11 +1080,6 @@ msgstr ""
988
  msgid "Employer"
989
  msgstr ""
990
 
991
- #: includes/class-wp-job-manager-post-types.php:90
992
- #: includes/forms/class-wp-job-manager-form-submit-job.php:195
993
- msgid "Job category"
994
- msgstr ""
995
-
996
  #: includes/class-wp-job-manager-post-types.php:91
997
  msgid "Job categories"
998
  msgstr ""
@@ -1041,11 +1128,6 @@ msgstr ""
1041
  msgid "New %s Name"
1042
  msgstr ""
1043
 
1044
- #: includes/class-wp-job-manager-post-types.php:139
1045
- #: includes/forms/class-wp-job-manager-form-submit-job.php:186
1046
- msgid "Job type"
1047
- msgstr ""
1048
-
1049
  #: includes/class-wp-job-manager-post-types.php:140
1050
  msgid "Job types"
1051
  msgstr ""
@@ -1183,7 +1265,64 @@ msgid ""
1183
  msgstr ""
1184
 
1185
  #: includes/class-wp-job-manager-usage-tracking.php:126
1186
- msgid "Enable usage tracking"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1187
  msgstr ""
1188
 
1189
  #: includes/forms/class-wp-job-manager-form-edit-job.php:93
@@ -1231,7 +1370,7 @@ msgid "Application email"
1231
  msgstr ""
1232
 
1233
  #: includes/forms/class-wp-job-manager-form-submit-job.php:148
1234
- #: wp-job-manager-template.php:646
1235
  msgid "you@yourdomain.com"
1236
  msgstr ""
1237
 
@@ -1268,10 +1407,6 @@ msgstr ""
1268
  msgid "Description"
1269
  msgstr ""
1270
 
1271
- #: includes/forms/class-wp-job-manager-form-submit-job.php:220
1272
- msgid "Company name"
1273
- msgstr ""
1274
-
1275
  #: includes/forms/class-wp-job-manager-form-submit-job.php:223
1276
  msgid "Enter the name of the company"
1277
  msgstr ""
@@ -1318,7 +1453,7 @@ msgid "%s is invalid"
1318
  msgstr ""
1319
 
1320
  #: includes/forms/class-wp-job-manager-form-submit-job.php:332
1321
- #: wp-job-manager-functions.php:1242
1322
  msgid "\"%s\" (filetype %s) needs to be one of the following file types: %s"
1323
  msgstr ""
1324
 
@@ -1497,10 +1632,6 @@ msgstr ""
1497
  msgid "Every Two Weeks"
1498
  msgstr ""
1499
 
1500
- #: lib/usage-tracking/class-usage-tracking-base.php:472
1501
- msgid "Enable Usage Tracking"
1502
- msgstr ""
1503
-
1504
  #: lib/usage-tracking/class-usage-tracking-base.php:475
1505
  msgid "Disable Usage Tracking"
1506
  msgstr ""
@@ -1552,8 +1683,8 @@ msgstr ""
1552
 
1553
  #: templates/account-signin.php:45
1554
  msgid ""
1555
- "If you don&rsquo;t have an account you can %screate one below by entering "
1556
- "your email address/username."
1557
  msgstr ""
1558
 
1559
  #: templates/account-signin.php:45
@@ -1593,17 +1724,101 @@ msgstr ""
1593
  msgid "This listing has expired."
1594
  msgstr ""
1595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1596
  #: templates/form-fields/file-field.php:45
1597
  msgid "Maximum file size: %s."
1598
  msgstr ""
1599
 
1600
  #: templates/form-fields/multiselect-field.php:20
1601
- #: wp-job-manager-functions.php:1029
1602
  msgid "No results match"
1603
  msgstr ""
1604
 
1605
  #: templates/form-fields/multiselect-field.php:20
1606
- #: wp-job-manager-functions.php:1030
1607
  msgid "Select Some Options"
1608
  msgstr ""
1609
 
@@ -1619,7 +1834,7 @@ msgid ""
1619
  msgstr ""
1620
 
1621
  #: templates/job-application-url.php:18
1622
- msgid "To apply for this job please visit the following URL:"
1623
  msgstr ""
1624
 
1625
  #: templates/job-application.php:24
@@ -1704,67 +1919,67 @@ msgstr ""
1704
  msgid "%s submitted successfully. Your listing will be visible once approved."
1705
  msgstr ""
1706
 
1707
- #: wp-job-manager-functions.php:421
1708
  msgid "Reset"
1709
  msgstr ""
1710
 
1711
- #: wp-job-manager-functions.php:425
1712
  msgid "RSS"
1713
  msgstr ""
1714
 
1715
- #: wp-job-manager-functions.php:525
1716
  msgid "Invalid email address."
1717
  msgstr ""
1718
 
1719
- #: wp-job-manager-functions.php:533
1720
  msgid "Your email address isn&#8217;t correct."
1721
  msgstr ""
1722
 
1723
- #: wp-job-manager-functions.php:537
1724
  msgid "This email is already registered, please choose another one."
1725
  msgstr ""
1726
 
1727
- #: wp-job-manager-functions.php:835
1728
  msgid "Full Time"
1729
  msgstr ""
1730
 
1731
- #: wp-job-manager-functions.php:836
1732
  msgid "Part Time"
1733
  msgstr ""
1734
 
1735
- #: wp-job-manager-functions.php:837
1736
  msgid "Contractor"
1737
  msgstr ""
1738
 
1739
- #: wp-job-manager-functions.php:838
1740
  msgid "Temporary"
1741
  msgstr ""
1742
 
1743
- #: wp-job-manager-functions.php:839
1744
  msgid "Intern"
1745
  msgstr ""
1746
 
1747
- #: wp-job-manager-functions.php:840
1748
  msgid "Volunteer"
1749
  msgstr ""
1750
 
1751
- #: wp-job-manager-functions.php:841
1752
  msgid "Per Diem"
1753
  msgstr ""
1754
 
1755
- #: wp-job-manager-functions.php:842
1756
  msgid "Other"
1757
  msgstr ""
1758
 
1759
- #: wp-job-manager-functions.php:909
1760
  msgid "Passwords must be at least 8 characters long."
1761
  msgstr ""
1762
 
1763
- #: wp-job-manager-functions.php:1028
1764
  msgid "Choose a category&hellip;"
1765
  msgstr ""
1766
 
1767
- #: wp-job-manager-functions.php:1244
1768
  msgid "Uploaded files need to be one of the following file types: %s"
1769
  msgstr ""
1770
 
@@ -1776,43 +1991,43 @@ msgstr ""
1776
  msgid "Application via \"%s\" listing on %s"
1777
  msgstr ""
1778
 
1779
- #: wp-job-manager-template.php:620
1780
  msgid "Username"
1781
  msgstr ""
1782
 
1783
- #: wp-job-manager-template.php:628
1784
  msgid "Password"
1785
  msgstr ""
1786
 
1787
- #: wp-job-manager-template.php:638
1788
  msgid "Verify Password"
1789
  msgstr ""
1790
 
1791
- #: wp-job-manager-template.php:645
1792
  msgid "Your email"
1793
  msgstr ""
1794
 
1795
- #: wp-job-manager-template.php:672
1796
  msgid "Posted on "
1797
  msgstr ""
1798
 
1799
- #: wp-job-manager-template.php:674 wp-job-manager-template.php:694
1800
  msgid "Posted %s ago"
1801
  msgstr ""
1802
 
1803
- #: wp-job-manager-template.php:717
1804
  msgid "Anywhere"
1805
  msgstr ""
1806
 
1807
- #: wp-job-manager.php:270
1808
  msgid "Load previous listings"
1809
  msgstr ""
1810
 
1811
- #: wp-job-manager.php:333
1812
  msgid "Invalid file type. Accepted types:"
1813
  msgstr ""
1814
 
1815
- #: wp-job-manager.php:344
1816
  msgid "Are you sure you want to delete this listing?"
1817
  msgstr ""
1818
 
@@ -1834,7 +2049,7 @@ msgstr ""
1834
  msgid "Automattic"
1835
  msgstr ""
1836
 
1837
- #: includes/admin/class-wp-job-manager-admin.php:135
1838
  #: includes/forms/class-wp-job-manager-form-submit-job.php:401
1839
  #. translators: jQuery date format, see
1840
  #. http:api.jqueryui.com/datepicker/#utility-formatDate
2
  # This file is distributed under the GPL2+.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Job Manager 1.31.0\n"
6
  "Report-Msgid-Bugs-To: https://github.com/Automattic/WP-Job-Manager/issues\n"
7
+ "POT-Creation-Date: 2018-04-26 10:23:25+00:00\n"
8
  "MIME-Version: 1.0\n"
9
  "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
13
  "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
14
  "X-Generator: grunt-wp-i18n 0.5.4\n"
15
 
16
+ #: includes/3rd-party/wpml.php:82
17
+ msgid "Page Not Set"
18
+ msgstr ""
19
+
20
+ #: includes/3rd-party/wpml.php:94
21
+ msgid "<a href=\"%s\">Switch to primary language</a> to edit this setting."
22
+ msgstr ""
23
+
24
  #: includes/abstracts/abstract-wp-job-manager-form.php:312
25
  #: includes/abstracts/abstract-wp-job-manager-form.php:325
26
  msgid "\"%s\" check failed. Please try again."
27
  msgstr ""
28
 
29
  #: includes/admin/class-wp-job-manager-addons.php:111
30
+ #: includes/admin/class-wp-job-manager-admin.php:146
31
  #: includes/admin/views/html-admin-page-addons.php:2
32
  msgid "WP Job Manager Add-ons"
33
  msgstr ""
39
 
40
  #: includes/admin/class-wp-job-manager-admin.php:86
41
  msgid ""
42
+ "<strong>WP Job Manager</strong> requires a more recent version of "
43
+ "WordPress. <a href=\"%s\">Please update WordPresse</a> to avoid issues."
 
44
  msgstr ""
45
 
46
  #: includes/admin/class-wp-job-manager-admin.php:97
47
  msgid "<a href=\"%s\" style=\"color: red\">WordPress Update Required</a>"
48
  msgstr ""
49
 
50
+ #: includes/admin/class-wp-job-manager-admin.php:143
51
  msgid "Settings"
52
  msgstr ""
53
 
54
+ #: includes/admin/class-wp-job-manager-admin.php:146
55
  msgid "Add-ons"
56
  msgstr ""
57
 
58
+ #: includes/admin/class-wp-job-manager-cpt.php:74
59
  msgid "Approve %s"
60
  msgstr ""
61
 
62
+ #: includes/admin/class-wp-job-manager-cpt.php:75
63
  msgid "%s approved"
64
  msgstr ""
65
 
66
+ #: includes/admin/class-wp-job-manager-cpt.php:79
67
  msgid "Expire %s"
68
  msgstr ""
69
 
70
+ #: includes/admin/class-wp-job-manager-cpt.php:80
71
  msgid "%s expired"
72
  msgstr ""
73
 
74
+ #: includes/admin/class-wp-job-manager-cpt.php:84
75
  msgid "Mark %s Filled"
76
  msgstr ""
77
 
78
+ #: includes/admin/class-wp-job-manager-cpt.php:85
79
  msgid "%s marked as filled"
80
  msgstr ""
81
 
82
+ #: includes/admin/class-wp-job-manager-cpt.php:89
83
  msgid "Mark %s Not Filled"
84
  msgstr ""
85
 
86
+ #: includes/admin/class-wp-job-manager-cpt.php:90
87
  msgid "%s marked as not filled"
88
  msgstr ""
89
 
90
+ #: includes/admin/class-wp-job-manager-cpt.php:302
91
  msgid "Select category"
92
  msgstr ""
93
 
94
+ #: includes/admin/class-wp-job-manager-cpt.php:326
95
+ msgid "Select Filled"
 
96
  msgstr ""
97
 
98
  #: includes/admin/class-wp-job-manager-cpt.php:330
99
+ msgid "Filled"
100
+ msgstr ""
101
+
102
+ #: includes/admin/class-wp-job-manager-cpt.php:334
103
+ msgid "Not Filled"
104
+ msgstr ""
105
+
106
+ #: includes/admin/class-wp-job-manager-cpt.php:342
107
+ msgid "Select Featured"
108
+ msgstr ""
109
+
110
+ #: includes/admin/class-wp-job-manager-cpt.php:346
111
+ msgid "Featured"
112
+ msgstr ""
113
+
114
+ #: includes/admin/class-wp-job-manager-cpt.php:350
115
+ msgid "Not Featured"
116
+ msgstr ""
117
+
118
+ #: includes/admin/class-wp-job-manager-cpt.php:396
119
+ #: includes/admin/class-wp-job-manager-cpt.php:440
120
+ msgid "Position"
121
+ msgstr ""
122
+
123
+ #: includes/admin/class-wp-job-manager-cpt.php:411
124
  msgid "%s updated. <a href=\"%s\">View</a>"
125
  msgstr ""
126
 
127
+ #: includes/admin/class-wp-job-manager-cpt.php:412
128
  msgid "Custom field updated."
129
  msgstr ""
130
 
131
+ #: includes/admin/class-wp-job-manager-cpt.php:413
132
  msgid "Custom field deleted."
133
  msgstr ""
134
 
135
+ #: includes/admin/class-wp-job-manager-cpt.php:414
136
  msgid "%s updated."
137
  msgstr ""
138
 
139
+ #: includes/admin/class-wp-job-manager-cpt.php:415
140
  msgid "%s restored to revision from %s"
141
  msgstr ""
142
 
143
+ #: includes/admin/class-wp-job-manager-cpt.php:416
144
  msgid "%s published. <a href=\"%s\">View</a>"
145
  msgstr ""
146
 
147
+ #: includes/admin/class-wp-job-manager-cpt.php:417
148
  msgid "%s saved."
149
  msgstr ""
150
 
151
+ #: includes/admin/class-wp-job-manager-cpt.php:418
152
  msgid "%s submitted. <a target=\"_blank\" href=\"%s\">Preview</a>"
153
  msgstr ""
154
 
155
+ #: includes/admin/class-wp-job-manager-cpt.php:419
156
  msgid ""
157
  "%s scheduled for: <strong>%1$s</strong>. <a target=\"_blank\" "
158
  "href=\"%2$s\">Preview</a>"
159
  msgstr ""
160
 
161
+ #: includes/admin/class-wp-job-manager-cpt.php:421
162
  msgid "%s draft updated. <a target=\"_blank\" href=\"%s\">Preview</a>"
163
  msgstr ""
164
 
165
+ #: includes/admin/class-wp-job-manager-cpt.php:441
166
  msgid "Type"
167
  msgstr ""
168
 
169
+ #: includes/admin/class-wp-job-manager-cpt.php:442
170
  #: includes/admin/class-wp-job-manager-writepanels.php:55
171
+ #: includes/class-wp-job-manager-email-notifications.php:226
172
  #: includes/forms/class-wp-job-manager-form-submit-job.php:178
173
  #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:38
174
  #: templates/job-filters.php:35 templates/job-filters.php:36
175
  msgid "Location"
176
  msgstr ""
177
 
178
+ #: includes/admin/class-wp-job-manager-cpt.php:443
179
  msgid "Status"
180
  msgstr ""
181
 
182
+ #: includes/admin/class-wp-job-manager-cpt.php:444
183
  msgid "Posted"
184
  msgstr ""
185
 
186
+ #: includes/admin/class-wp-job-manager-cpt.php:445
187
  msgid "Expires"
188
  msgstr ""
189
 
190
+ #: includes/admin/class-wp-job-manager-cpt.php:446
191
+ #: includes/admin/class-wp-job-manager-settings.php:154
192
  msgid "Categories"
193
  msgstr ""
194
 
195
+ #: includes/admin/class-wp-job-manager-cpt.php:447
196
  msgid "Featured?"
197
  msgstr ""
198
 
199
+ #: includes/admin/class-wp-job-manager-cpt.php:448
200
  #: includes/class-wp-job-manager-shortcodes.php:218
201
  msgid "Filled?"
202
  msgstr ""
203
 
204
+ #: includes/admin/class-wp-job-manager-cpt.php:449
205
  msgid "Actions"
206
  msgstr ""
207
 
208
+ #: includes/admin/class-wp-job-manager-cpt.php:513
209
  msgid "ID: %d"
210
  msgstr ""
211
 
212
+ #: includes/admin/class-wp-job-manager-cpt.php:543
213
  msgid "by a guest"
214
  msgstr ""
215
 
216
+ #: includes/admin/class-wp-job-manager-cpt.php:543
217
  msgid "by %s"
218
  msgstr ""
219
 
220
+ #: includes/admin/class-wp-job-manager-cpt.php:561
221
  msgid "Approve"
222
  msgstr ""
223
 
224
+ #: includes/admin/class-wp-job-manager-cpt.php:569
225
  #: includes/admin/class-wp-job-manager-writepanels.php:257
226
  #: includes/admin/class-wp-job-manager-writepanels.php:260
227
  #: includes/admin/class-wp-job-manager-writepanels.php:263
228
  msgid "View"
229
  msgstr ""
230
 
231
+ #: includes/admin/class-wp-job-manager-cpt.php:576
232
  #: includes/class-wp-job-manager-post-types.php:221
233
  #: templates/job-dashboard.php:52 templates/job-dashboard.php:70
234
  msgid "Edit"
235
  msgstr ""
236
 
237
+ #: includes/admin/class-wp-job-manager-cpt.php:583
238
  #: templates/job-dashboard.php:75
239
  msgid "Delete"
240
  msgstr ""
285
  msgstr ""
286
 
287
  #: includes/admin/class-wp-job-manager-settings.php:105
288
+ msgid "Delete Data On Uninstall"
289
+ msgstr ""
290
+
291
+ #: includes/admin/class-wp-job-manager-settings.php:106
292
+ msgid ""
293
+ "Delete WP Job Manager data when the plugin is deleted. Once removed, this "
294
+ "data cannot be restored."
295
+ msgstr ""
296
+
297
+ #: includes/admin/class-wp-job-manager-settings.php:114
298
  #: includes/class-wp-job-manager-post-types.php:217
299
  #: includes/class-wp-job-manager-post-types.php:293
300
  msgid "Job Listings"
301
  msgstr ""
302
 
303
+ #: includes/admin/class-wp-job-manager-settings.php:120
304
  msgid "Listings Per Page"
305
  msgstr ""
306
 
307
+ #: includes/admin/class-wp-job-manager-settings.php:121
308
  msgid "Number of job listings to display per page."
309
  msgstr ""
310
 
311
+ #: includes/admin/class-wp-job-manager-settings.php:127
312
  msgid "Filled Positions"
313
  msgstr ""
314
 
315
+ #: includes/admin/class-wp-job-manager-settings.php:128
316
  msgid "Hide filled positions"
317
  msgstr ""
318
 
319
+ #: includes/admin/class-wp-job-manager-settings.php:129
320
  msgid "Filled positions will not display in your archives."
321
  msgstr ""
322
 
323
+ #: includes/admin/class-wp-job-manager-settings.php:136
324
  msgid "Hide Expired Listings"
325
  msgstr ""
326
 
327
+ #: includes/admin/class-wp-job-manager-settings.php:137
328
  msgid "Hide expired listings in job archives/search"
329
  msgstr ""
330
 
331
+ #: includes/admin/class-wp-job-manager-settings.php:138
332
  msgid "Expired job listings will not be searchable."
333
  msgstr ""
334
 
335
+ #: includes/admin/class-wp-job-manager-settings.php:145
336
  msgid "Hide Expired Listings Content"
337
  msgstr ""
338
 
339
+ #: includes/admin/class-wp-job-manager-settings.php:146
340
  msgid "Hide content in expired single job listings"
341
  msgstr ""
342
 
343
+ #: includes/admin/class-wp-job-manager-settings.php:147
344
  msgid ""
345
  "Your site will display the titles of expired listings, but not the content "
346
  "of the listings. Otherwise, expired listings display their full content "
347
  "minus the application area."
348
  msgstr ""
349
 
350
+ #: includes/admin/class-wp-job-manager-settings.php:155
351
  msgid "Enable listing categories"
352
  msgstr ""
353
 
354
+ #: includes/admin/class-wp-job-manager-settings.php:156
355
  msgid ""
356
  "This lets users select from a list of categories when submitting a job. "
357
  "Note: an admin has to create categories before site users can select them."
358
  msgstr ""
359
 
360
+ #: includes/admin/class-wp-job-manager-settings.php:163
361
  msgid "Multi-select Categories"
362
  msgstr ""
363
 
364
+ #: includes/admin/class-wp-job-manager-settings.php:164
365
  msgid "Default to category multiselect"
366
  msgstr ""
367
 
368
+ #: includes/admin/class-wp-job-manager-settings.php:165
369
  msgid ""
370
  "The category selection box will default to allowing multiple selections on "
371
  "the [jobs] shortcode. Without this, users will only be able to select a "
372
  "single category when submitting jobs."
373
  msgstr ""
374
 
375
+ #: includes/admin/class-wp-job-manager-settings.php:172
376
  msgid "Category Filter Type"
377
  msgstr ""
378
 
379
+ #: includes/admin/class-wp-job-manager-settings.php:173
380
  msgid ""
381
  "Determines the logic used to display jobs when selecting multiple "
382
  "categories."
383
  msgstr ""
384
 
385
+ #: includes/admin/class-wp-job-manager-settings.php:176
386
  msgid "Jobs will be shown if within ANY selected category"
387
  msgstr ""
388
 
389
+ #: includes/admin/class-wp-job-manager-settings.php:177
390
  msgid "Jobs will be shown if within ALL selected categories"
391
  msgstr ""
392
 
393
+ #: includes/admin/class-wp-job-manager-settings.php:183
394
  msgid "Types"
395
  msgstr ""
396
 
397
+ #: includes/admin/class-wp-job-manager-settings.php:184
398
  msgid "Enable listing types"
399
  msgstr ""
400
 
401
+ #: includes/admin/class-wp-job-manager-settings.php:185
402
  msgid ""
403
  "This lets users select from a list of types when submitting a job. Note: an "
404
  "admin has to create types before site users can select them."
405
  msgstr ""
406
 
407
+ #: includes/admin/class-wp-job-manager-settings.php:192
408
  msgid "Multi-select Listing Types"
409
  msgstr ""
410
 
411
+ #: includes/admin/class-wp-job-manager-settings.php:193
412
  msgid "Allow multiple types for listings"
413
  msgstr ""
414
 
415
+ #: includes/admin/class-wp-job-manager-settings.php:194
416
  msgid ""
417
  "This allows users to select more than one type when submitting a job. The "
418
  "metabox on the post editor and the selection box on the front-end job "
419
  "submission form will both reflect this."
420
  msgstr ""
421
 
422
+ #: includes/admin/class-wp-job-manager-settings.php:201
423
  msgid "Job Submission"
424
  msgstr ""
425
 
426
+ #: includes/admin/class-wp-job-manager-settings.php:206
427
  msgid "Account Required"
428
  msgstr ""
429
 
430
+ #: includes/admin/class-wp-job-manager-settings.php:207
431
  msgid "Require an account to submit listings"
432
  msgstr ""
433
 
434
+ #: includes/admin/class-wp-job-manager-settings.php:208
435
  msgid "Limits job listing submissions to registered, logged-in users."
436
  msgstr ""
437
 
438
+ #: includes/admin/class-wp-job-manager-settings.php:215
439
  msgid "Account Creation"
440
  msgstr ""
441
 
442
+ #: includes/admin/class-wp-job-manager-settings.php:216
443
  msgid "Enable account creation during submission"
444
  msgstr ""
445
 
446
+ #: includes/admin/class-wp-job-manager-settings.php:217
447
  msgid ""
448
  "Includes account creation on the listing submission form, to allow "
449
  "non-registered users to create an account and submit a job listing "
450
  "simultaneously."
451
  msgstr ""
452
 
453
+ #: includes/admin/class-wp-job-manager-settings.php:224
454
  msgid "Account Username"
455
  msgstr ""
456
 
457
+ #: includes/admin/class-wp-job-manager-settings.php:225
458
  msgid "Generate usernames from email addresses"
459
  msgstr ""
460
 
461
+ #: includes/admin/class-wp-job-manager-settings.php:226
462
  msgid ""
463
  "Automatically generates usernames for new accounts from the registrant's "
464
  "email address. If this is not enabled, a \"username\" field will display "
465
  "instead."
466
  msgstr ""
467
 
468
+ #: includes/admin/class-wp-job-manager-settings.php:233
469
  msgid "Account Password"
470
  msgstr ""
471
 
472
+ #: includes/admin/class-wp-job-manager-settings.php:234
473
  msgid "Email new users a link to set a password"
474
  msgstr ""
475
 
476
+ #: includes/admin/class-wp-job-manager-settings.php:235
477
  msgid ""
478
  "Sends an email to the user with their username and a link to set their "
479
  "password. If this is not enabled, a \"password\" field will display "
480
  "instead, and their email address won't be verified."
481
  msgstr ""
482
 
483
+ #: includes/admin/class-wp-job-manager-settings.php:242
484
  msgid "Account Role"
485
  msgstr ""
486
 
487
+ #: includes/admin/class-wp-job-manager-settings.php:243
488
  msgid ""
489
  "Any new accounts created during submission will have this role. If you "
490
  "haven't enabled account creation during submission in the options above, "
491
  "your own method of assigning roles will apply."
492
  msgstr ""
493
 
494
+ #: includes/admin/class-wp-job-manager-settings.php:250
495
  msgid "Moderate New Listings"
496
  msgstr ""
497
 
498
+ #: includes/admin/class-wp-job-manager-settings.php:251
499
  msgid "Require admin approval of all new listing submissions"
500
  msgstr ""
501
 
502
+ #: includes/admin/class-wp-job-manager-settings.php:252
503
  msgid ""
504
  "Sets all new submissions to \"pending.\" They will not appear on your site "
505
  "until an admin approves them."
506
  msgstr ""
507
 
508
+ #: includes/admin/class-wp-job-manager-settings.php:259
509
  msgid "Allow Pending Edits"
510
  msgstr ""
511
 
512
+ #: includes/admin/class-wp-job-manager-settings.php:260
513
  msgid "Allow editing of pending listings"
514
  msgstr ""
515
 
516
+ #: includes/admin/class-wp-job-manager-settings.php:261
517
  msgid ""
518
  "Users can continue to edit pending listings until they are approved by an "
519
  "admin."
520
  msgstr ""
521
 
522
+ #: includes/admin/class-wp-job-manager-settings.php:268
523
  msgid "Allow Published Edits"
524
  msgstr ""
525
 
526
+ #: includes/admin/class-wp-job-manager-settings.php:269
527
  msgid "Allow editing of published listings"
528
  msgstr ""
529
 
530
+ #: includes/admin/class-wp-job-manager-settings.php:270
531
  msgid ""
532
  "Choose whether published job listings can be edited and if edits require "
533
  "admin approval. When moderation is required, the original job listings will "
534
  "be unpublished while edits await admin approval."
535
  msgstr ""
536
 
537
+ #: includes/admin/class-wp-job-manager-settings.php:273
538
  msgid "Users cannot edit"
539
  msgstr ""
540
 
541
+ #: includes/admin/class-wp-job-manager-settings.php:274
542
  msgid "Users can edit without admin approval"
543
  msgstr ""
544
 
545
+ #: includes/admin/class-wp-job-manager-settings.php:275
546
  msgid "Users can edit, but edits require admin approval"
547
  msgstr ""
548
 
549
+ #: includes/admin/class-wp-job-manager-settings.php:282
550
  msgid "Listing Duration"
551
  msgstr ""
552
 
553
+ #: includes/admin/class-wp-job-manager-settings.php:283
554
  msgid ""
555
  "Listings will display for the set number of days, then expire. Leave this "
556
  "field blank if you don't want listings to have an expiration date."
557
  msgstr ""
558
 
559
+ #: includes/admin/class-wp-job-manager-settings.php:289
560
  msgid "Application Method"
561
  msgstr ""
562
 
563
+ #: includes/admin/class-wp-job-manager-settings.php:290
564
  msgid ""
565
  "Choose the application method job listers will need to provide. Specify URL "
566
  "or email address only, or allow listers to choose which they prefer."
567
  msgstr ""
568
 
569
+ #: includes/admin/class-wp-job-manager-settings.php:293
570
  msgid "Email address or website URL"
571
  msgstr ""
572
 
573
+ #: includes/admin/class-wp-job-manager-settings.php:294
574
  msgid "Email addresses only"
575
  msgstr ""
576
 
577
+ #: includes/admin/class-wp-job-manager-settings.php:295
578
  msgid "Website URLs only"
579
  msgstr ""
580
 
581
+ #: includes/admin/class-wp-job-manager-settings.php:301
582
  msgid "reCAPTCHA"
583
  msgstr ""
584
 
585
+ #: includes/admin/class-wp-job-manager-settings.php:305
586
  msgid "Are you human?"
587
  msgstr ""
588
 
589
+ #: includes/admin/class-wp-job-manager-settings.php:307
590
  msgid "Field Label"
591
  msgstr ""
592
 
593
+ #: includes/admin/class-wp-job-manager-settings.php:308
594
  msgid "The label used for the reCAPTCHA field on forms."
595
  msgstr ""
596
 
597
+ #: includes/admin/class-wp-job-manager-settings.php:315
598
  msgid "Site Key"
599
  msgstr ""
600
 
601
+ #: includes/admin/class-wp-job-manager-settings.php:316
602
  msgid ""
603
  "You can retrieve your site key from <a href=\"%s\">Google's reCAPTCHA admin "
604
  "dashboard</a>."
605
  msgstr ""
606
 
607
+ #: includes/admin/class-wp-job-manager-settings.php:323
608
  msgid "Secret Key"
609
  msgstr ""
610
 
611
+ #: includes/admin/class-wp-job-manager-settings.php:324
612
  msgid ""
613
  "You can retrieve your secret key from <a href=\"%s\">Google's reCAPTCHA "
614
  "admin dashboard</a>."
615
  msgstr ""
616
 
617
+ #: includes/admin/class-wp-job-manager-settings.php:330
618
  msgid "Job Submission Form"
619
  msgstr ""
620
 
621
+ #: includes/admin/class-wp-job-manager-settings.php:331
622
  msgid "Display a reCAPTCHA field on job submission form."
623
  msgstr ""
624
 
625
+ #: includes/admin/class-wp-job-manager-settings.php:332
626
  msgid ""
627
  "This will help prevent bots from submitting job listings. You must have "
628
  "entered a valid site key and secret key above."
629
  msgstr ""
630
 
631
+ #: includes/admin/class-wp-job-manager-settings.php:339
632
  msgid "Pages"
633
  msgstr ""
634
 
635
+ #: includes/admin/class-wp-job-manager-settings.php:344
636
  msgid "Submit Job Form Page"
637
  msgstr ""
638
 
639
+ #: includes/admin/class-wp-job-manager-settings.php:345
640
  msgid ""
641
  "Select the page where you've used the [submit_job_form] shortcode. This "
642
  "lets the plugin know the location of the form."
643
  msgstr ""
644
 
645
+ #: includes/admin/class-wp-job-manager-settings.php:351
646
  msgid "Job Dashboard Page"
647
  msgstr ""
648
 
649
+ #: includes/admin/class-wp-job-manager-settings.php:352
650
  msgid ""
651
  "Select the page where you've used the [job_dashboard] shortcode. This lets "
652
  "the plugin know the location of the dashboard."
653
  msgstr ""
654
 
655
+ #: includes/admin/class-wp-job-manager-settings.php:358
656
  msgid "Job Listings Page"
657
  msgstr ""
658
 
659
+ #: includes/admin/class-wp-job-manager-settings.php:359
660
  msgid ""
661
  "Select the page where you've used the [jobs] shortcode. This lets the "
662
  "plugin know the location of the job listings page."
663
  msgstr ""
664
 
665
+ #: includes/admin/class-wp-job-manager-settings.php:405
666
  msgid "Settings successfully saved"
667
  msgstr ""
668
 
669
+ #: includes/admin/class-wp-job-manager-settings.php:430
670
+ msgid "Save Changes"
671
  msgstr ""
672
 
673
+ #: includes/admin/class-wp-job-manager-settings.php:597
674
+ msgid "--no page--"
675
  msgstr ""
676
 
677
+ #: includes/admin/class-wp-job-manager-settings.php:602
678
+ msgid "Select a page&hellip;"
679
  msgstr ""
680
 
681
  #: includes/admin/class-wp-job-manager-setup.php:51
950
  msgstr ""
951
 
952
  #: includes/admin/class-wp-job-manager-writepanels.php:118
953
+ #: includes/class-wp-job-manager-email-notifications.php:280
954
  #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:75
955
  msgid "Posted by"
956
  msgstr ""
1013
  msgid "You must be logged in to upload files using this method."
1014
  msgstr ""
1015
 
1016
+ #: includes/class-wp-job-manager-email-notifications.php:215
1017
+ msgid "Job title"
1018
+ msgstr ""
1019
+
1020
+ #: includes/class-wp-job-manager-email-notifications.php:235
1021
+ #: includes/class-wp-job-manager-post-types.php:139
1022
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:186
1023
+ msgid "Job type"
1024
+ msgstr ""
1025
+
1026
+ #: includes/class-wp-job-manager-email-notifications.php:245
1027
+ #: includes/class-wp-job-manager-post-types.php:90
1028
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:195
1029
+ msgid "Job category"
1030
+ msgstr ""
1031
+
1032
+ #: includes/class-wp-job-manager-email-notifications.php:254
1033
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:220
1034
+ msgid "Company name"
1035
+ msgstr ""
1036
+
1037
+ #: includes/class-wp-job-manager-email-notifications.php:262
1038
+ msgid "Company website"
1039
+ msgstr ""
1040
+
1041
+ #: includes/class-wp-job-manager-email-notifications.php:271
1042
+ msgid "Listing expires"
1043
+ msgstr ""
1044
+
1045
+ #: includes/class-wp-job-manager-email-notifications.php:427
1046
+ msgid "Email Notifications"
1047
+ msgstr ""
1048
+
1049
+ #: includes/class-wp-job-manager-email-notifications.php:430
1050
+ msgid "Select the email notifications to enable."
1051
+ msgstr ""
1052
+
1053
+ #: includes/class-wp-job-manager-email-notifications.php:569
1054
+ msgid "Format"
1055
+ msgstr ""
1056
+
1057
+ #: includes/class-wp-job-manager-email-notifications.php:572
1058
+ msgid "Send plain text email"
1059
+ msgstr ""
1060
+
1061
+ #: includes/class-wp-job-manager-email-notifications.php:573
1062
+ msgid "Send rich text email"
1063
+ msgstr ""
1064
+
1065
  #: includes/class-wp-job-manager-geocode.php:221
1066
  msgid "No results found"
1067
  msgstr ""
1080
  msgid "Employer"
1081
  msgstr ""
1082
 
 
 
 
 
 
1083
  #: includes/class-wp-job-manager-post-types.php:91
1084
  msgid "Job categories"
1085
  msgstr ""
1128
  msgid "New %s Name"
1129
  msgstr ""
1130
 
 
 
 
 
 
1131
  #: includes/class-wp-job-manager-post-types.php:140
1132
  msgid "Job types"
1133
  msgstr ""
1265
  msgstr ""
1266
 
1267
  #: includes/class-wp-job-manager-usage-tracking.php:126
1268
+ #: lib/usage-tracking/class-usage-tracking-base.php:472
1269
+ msgid "Enable Usage Tracking"
1270
+ msgstr ""
1271
+
1272
+ #: includes/emails/class-wp-job-manager-email-admin-expiring-job.php:30
1273
+ msgid "Admin Notice of Expiring Job Listings"
1274
+ msgstr ""
1275
+
1276
+ #: includes/emails/class-wp-job-manager-email-admin-expiring-job.php:40
1277
+ msgid "Send notices to the site administrator before a job listing expires."
1278
+ msgstr ""
1279
+
1280
+ #: includes/emails/class-wp-job-manager-email-admin-new-job.php:30
1281
+ msgid "Admin Notice of New Listing"
1282
+ msgstr ""
1283
+
1284
+ #: includes/emails/class-wp-job-manager-email-admin-new-job.php:40
1285
+ msgid ""
1286
+ "Send a notice to the site administrator when a new job is submitted on the "
1287
+ "frontend."
1288
+ msgstr ""
1289
+
1290
+ #: includes/emails/class-wp-job-manager-email-admin-new-job.php:55
1291
+ msgid "New Job Listing Submitted: %s"
1292
+ msgstr ""
1293
+
1294
+ #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:30
1295
+ msgid "Admin Notice of Updated Listing"
1296
+ msgstr ""
1297
+
1298
+ #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:40
1299
+ msgid ""
1300
+ "Send a notice to the site administrator when a job is updated on the "
1301
+ "frontend."
1302
+ msgstr ""
1303
+
1304
+ #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:55
1305
+ msgid "Job Listing Updated: %s"
1306
+ msgstr ""
1307
+
1308
+ #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:33
1309
+ msgid "Employer Notice of Expiring Job Listings"
1310
+ msgstr ""
1311
+
1312
+ #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:43
1313
+ msgid "Send notices to employers before a job listing expires."
1314
+ msgstr ""
1315
+
1316
+ #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:71
1317
+ msgid "Job Listing Expiring: %s"
1318
+ msgstr ""
1319
+
1320
+ #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:124
1321
+ msgid "Notice Period"
1322
+ msgstr ""
1323
+
1324
+ #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:126
1325
+ msgid "days"
1326
  msgstr ""
1327
 
1328
  #: includes/forms/class-wp-job-manager-form-edit-job.php:93
1370
  msgstr ""
1371
 
1372
  #: includes/forms/class-wp-job-manager-form-submit-job.php:148
1373
+ #: wp-job-manager-template.php:703
1374
  msgid "you@yourdomain.com"
1375
  msgstr ""
1376
 
1407
  msgid "Description"
1408
  msgstr ""
1409
 
 
 
 
 
1410
  #: includes/forms/class-wp-job-manager-form-submit-job.php:223
1411
  msgid "Enter the name of the company"
1412
  msgstr ""
1453
  msgstr ""
1454
 
1455
  #: includes/forms/class-wp-job-manager-form-submit-job.php:332
1456
+ #: wp-job-manager-functions.php:1257
1457
  msgid "\"%s\" (filetype %s) needs to be one of the following file types: %s"
1458
  msgstr ""
1459
 
1632
  msgid "Every Two Weeks"
1633
  msgstr ""
1634
 
 
 
 
 
1635
  #: lib/usage-tracking/class-usage-tracking-base.php:475
1636
  msgid "Disable Usage Tracking"
1637
  msgstr ""
1683
 
1684
  #: templates/account-signin.php:45
1685
  msgid ""
1686
+ "If you don't have an account you can %screate one below by entering your "
1687
+ "email address/username."
1688
  msgstr ""
1689
 
1690
  #: templates/account-signin.php:45
1724
  msgid "This listing has expired."
1725
  msgstr ""
1726
 
1727
+ #: templates/emails/admin-expiring-job.php:30
1728
+ #: templates/emails/employer-expiring-job.php:30
1729
+ msgid "The following job listing is expiring today from <a href=\"%s\">%s</a>."
1730
+ msgstr ""
1731
+
1732
+ #: templates/emails/admin-expiring-job.php:32
1733
+ #: templates/emails/employer-expiring-job.php:32
1734
+ msgid "The following job listing is expiring soon from <a href=\"%s\">%s</a>."
1735
+ msgstr ""
1736
+
1737
+ #: templates/emails/admin-expiring-job.php:35
1738
+ msgid "Visit <a href=\"%s\">WordPress admin</a> to manage the listing."
1739
+ msgstr ""
1740
+
1741
+ #: templates/emails/admin-new-job.php:24
1742
+ msgid "A new job listing has been submitted to <a href=\"%s\">%s</a>."
1743
+ msgstr ""
1744
+
1745
+ #: templates/emails/admin-new-job.php:27
1746
+ #: templates/emails/plain/admin-new-job.php:26
1747
+ msgid "It has been published and is now available to the public."
1748
+ msgstr ""
1749
+
1750
+ #: templates/emails/admin-new-job.php:30
1751
+ msgid ""
1752
+ "It is awaiting approval by an administrator in <a href=\"%s\">WordPress "
1753
+ "admin</a>."
1754
+ msgstr ""
1755
+
1756
+ #: templates/emails/admin-updated-job.php:24
1757
+ msgid "A job listing has been updated on <a href=\"%s\">%s</a>."
1758
+ msgstr ""
1759
+
1760
+ #: templates/emails/admin-updated-job.php:27
1761
+ #: templates/emails/plain/admin-updated-job.php:26
1762
+ msgid "The changes have been published and are now available to the public."
1763
+ msgstr ""
1764
+
1765
+ #: templates/emails/admin-updated-job.php:30
1766
+ msgid ""
1767
+ "The job listing is not publicly available until the changes are approved by "
1768
+ "an administrator in the site's <a href=\"%s\">WordPress admin</a>."
1769
+ msgstr ""
1770
+
1771
+ #: templates/emails/employer-expiring-job.php:34
1772
+ msgid "Visit the <a href=\"%s\">job listing dashboard</a> to manage the listing."
1773
+ msgstr ""
1774
+
1775
+ #: templates/emails/plain/admin-expiring-job.php:29
1776
+ #: templates/emails/plain/employer-expiring-job.php:29
1777
+ msgid "The following job listing is expiring today from %s (%s)."
1778
+ msgstr ""
1779
+
1780
+ #: templates/emails/plain/admin-expiring-job.php:31
1781
+ #: templates/emails/plain/employer-expiring-job.php:31
1782
+ msgid "The following job listing is expiring soon from %s (%s)."
1783
+ msgstr ""
1784
+
1785
+ #: templates/emails/plain/admin-expiring-job.php:34
1786
+ msgid "Visit WordPress admin (%s) to manage the listing."
1787
+ msgstr ""
1788
+
1789
+ #: templates/emails/plain/admin-new-job.php:23
1790
+ msgid "A new job listing has been submitted to %s (%s)."
1791
+ msgstr ""
1792
+
1793
+ #: templates/emails/plain/admin-new-job.php:29
1794
+ msgid "It is awaiting approval by an administrator in WordPress admin (%s)."
1795
+ msgstr ""
1796
+
1797
+ #: templates/emails/plain/admin-updated-job.php:23
1798
+ msgid "A job listing has been updated on %s (%s)."
1799
+ msgstr ""
1800
+
1801
+ #: templates/emails/plain/admin-updated-job.php:29
1802
+ msgid ""
1803
+ "The job listing is not publicly available until the changes are approved by "
1804
+ "an administrator in the site's WordPress admin (%s)."
1805
+ msgstr ""
1806
+
1807
+ #: templates/emails/plain/employer-expiring-job.php:33
1808
+ msgid "Visit the job listing dashboard (%s) to manage the listing."
1809
+ msgstr ""
1810
+
1811
  #: templates/form-fields/file-field.php:45
1812
  msgid "Maximum file size: %s."
1813
  msgstr ""
1814
 
1815
  #: templates/form-fields/multiselect-field.php:20
1816
+ #: wp-job-manager-functions.php:1043
1817
  msgid "No results match"
1818
  msgstr ""
1819
 
1820
  #: templates/form-fields/multiselect-field.php:20
1821
+ #: wp-job-manager-functions.php:1044
1822
  msgid "Select Some Options"
1823
  msgstr ""
1824
 
1834
  msgstr ""
1835
 
1836
  #: templates/job-application-url.php:18
1837
+ msgid "To apply for this job please visit"
1838
  msgstr ""
1839
 
1840
  #: templates/job-application.php:24
1919
  msgid "%s submitted successfully. Your listing will be visible once approved."
1920
  msgstr ""
1921
 
1922
+ #: wp-job-manager-functions.php:435
1923
  msgid "Reset"
1924
  msgstr ""
1925
 
1926
+ #: wp-job-manager-functions.php:439
1927
  msgid "RSS"
1928
  msgstr ""
1929
 
1930
+ #: wp-job-manager-functions.php:539
1931
  msgid "Invalid email address."
1932
  msgstr ""
1933
 
1934
+ #: wp-job-manager-functions.php:547
1935
  msgid "Your email address isn&#8217;t correct."
1936
  msgstr ""
1937
 
1938
+ #: wp-job-manager-functions.php:551
1939
  msgid "This email is already registered, please choose another one."
1940
  msgstr ""
1941
 
1942
+ #: wp-job-manager-functions.php:849
1943
  msgid "Full Time"
1944
  msgstr ""
1945
 
1946
+ #: wp-job-manager-functions.php:850
1947
  msgid "Part Time"
1948
  msgstr ""
1949
 
1950
+ #: wp-job-manager-functions.php:851
1951
  msgid "Contractor"
1952
  msgstr ""
1953
 
1954
+ #: wp-job-manager-functions.php:852
1955
  msgid "Temporary"
1956
  msgstr ""
1957
 
1958
+ #: wp-job-manager-functions.php:853
1959
  msgid "Intern"
1960
  msgstr ""
1961
 
1962
+ #: wp-job-manager-functions.php:854
1963
  msgid "Volunteer"
1964
  msgstr ""
1965
 
1966
+ #: wp-job-manager-functions.php:855
1967
  msgid "Per Diem"
1968
  msgstr ""
1969
 
1970
+ #: wp-job-manager-functions.php:856
1971
  msgid "Other"
1972
  msgstr ""
1973
 
1974
+ #: wp-job-manager-functions.php:923
1975
  msgid "Passwords must be at least 8 characters long."
1976
  msgstr ""
1977
 
1978
+ #: wp-job-manager-functions.php:1042
1979
  msgid "Choose a category&hellip;"
1980
  msgstr ""
1981
 
1982
+ #: wp-job-manager-functions.php:1259
1983
  msgid "Uploaded files need to be one of the following file types: %s"
1984
  msgstr ""
1985
 
1991
  msgid "Application via \"%s\" listing on %s"
1992
  msgstr ""
1993
 
1994
+ #: wp-job-manager-template.php:677
1995
  msgid "Username"
1996
  msgstr ""
1997
 
1998
+ #: wp-job-manager-template.php:685
1999
  msgid "Password"
2000
  msgstr ""
2001
 
2002
+ #: wp-job-manager-template.php:695
2003
  msgid "Verify Password"
2004
  msgstr ""
2005
 
2006
+ #: wp-job-manager-template.php:702
2007
  msgid "Your email"
2008
  msgstr ""
2009
 
2010
+ #: wp-job-manager-template.php:729
2011
  msgid "Posted on "
2012
  msgstr ""
2013
 
2014
+ #: wp-job-manager-template.php:731 wp-job-manager-template.php:751
2015
  msgid "Posted %s ago"
2016
  msgstr ""
2017
 
2018
+ #: wp-job-manager-template.php:774
2019
  msgid "Anywhere"
2020
  msgstr ""
2021
 
2022
+ #: wp-job-manager.php:301
2023
  msgid "Load previous listings"
2024
  msgstr ""
2025
 
2026
+ #: wp-job-manager.php:364
2027
  msgid "Invalid file type. Accepted types:"
2028
  msgstr ""
2029
 
2030
+ #: wp-job-manager.php:375
2031
  msgid "Are you sure you want to delete this listing?"
2032
  msgstr ""
2033
 
2049
  msgid "Automattic"
2050
  msgstr ""
2051
 
2052
+ #: includes/admin/class-wp-job-manager-admin.php:131
2053
  #: includes/forms/class-wp-job-manager-form-submit-job.php:401
2054
  #. translators: jQuery date format, see
2055
  #. http:api.jqueryui.com/datepicker/#utility-formatDate
lib/emogrifier/class-emogrifier.php ADDED
@@ -0,0 +1,1557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This class provides functions for converting CSS styles into inline style attributes in your HTML code.
4
+ *
5
+ * For more information, please see the README.md file.
6
+ *
7
+ * @version 1.2.0
8
+ *
9
+ * @author Cameron Brooks
10
+ * @author Jaime Prado
11
+ * @author Oliver Klee <typo3-coding@oliverklee.de>
12
+ * @author Roman Ožana <ozana@omdesign.cz>
13
+ * @author Sander Kruger <s.kruger@invessel.com>
14
+ *
15
+ * @see https://raw.githubusercontent.com/MyIntervals/emogrifier/V1.2.0/Classes/Emogrifier.php
16
+ */
17
+ // @codingStandardsIgnoreFile
18
+ class Emogrifier
19
+ {
20
+ /**
21
+ * @var int
22
+ */
23
+ const CACHE_KEY_CSS = 0;
24
+
25
+ /**
26
+ * @var int
27
+ */
28
+ const CACHE_KEY_SELECTOR = 1;
29
+
30
+ /**
31
+ * @var int
32
+ */
33
+ const CACHE_KEY_XPATH = 2;
34
+
35
+ /**
36
+ * @var int
37
+ */
38
+ const CACHE_KEY_CSS_DECLARATIONS_BLOCK = 3;
39
+
40
+ /**
41
+ * @var int
42
+ */
43
+ const CACHE_KEY_COMBINED_STYLES = 4;
44
+
45
+ /**
46
+ * for calculating nth-of-type and nth-child selectors
47
+ *
48
+ * @var int
49
+ */
50
+ const INDEX = 0;
51
+
52
+ /**
53
+ * for calculating nth-of-type and nth-child selectors
54
+ *
55
+ * @var int
56
+ */
57
+ const MULTIPLIER = 1;
58
+
59
+ /**
60
+ * @var string
61
+ */
62
+ const ID_ATTRIBUTE_MATCHER = '/(\\w+)?\\#([\\w\\-]+)/';
63
+
64
+ /**
65
+ * @var string
66
+ */
67
+ const CLASS_ATTRIBUTE_MATCHER = '/(\\w+|[\\*\\]])?((\\.[\\w\\-]+)+)/';
68
+
69
+ /**
70
+ * @var string
71
+ */
72
+ const CONTENT_TYPE_META_TAG = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
73
+
74
+ /**
75
+ * @var string
76
+ */
77
+ const DEFAULT_DOCUMENT_TYPE = '<!DOCTYPE html>';
78
+
79
+ /**
80
+ * @var string
81
+ */
82
+ private $html = '';
83
+
84
+ /**
85
+ * @var string
86
+ */
87
+ private $css = '';
88
+
89
+ /**
90
+ * @var bool[]
91
+ */
92
+ private $excludedSelectors = array();
93
+
94
+ /**
95
+ * @var string[]
96
+ */
97
+ private $unprocessableHtmlTags = array( 'wbr' );
98
+
99
+ /**
100
+ * @var bool[]
101
+ */
102
+ private $allowedMediaTypes = array( 'all' => true, 'screen' => true, 'print' => true );
103
+
104
+ /**
105
+ * @var mixed[]
106
+ */
107
+ private $caches = array(
108
+ self::CACHE_KEY_CSS => array(),
109
+ self::CACHE_KEY_SELECTOR => array(),
110
+ self::CACHE_KEY_XPATH => array(),
111
+ self::CACHE_KEY_CSS_DECLARATIONS_BLOCK => array(),
112
+ self::CACHE_KEY_COMBINED_STYLES => array(),
113
+ );
114
+
115
+ /**
116
+ * the visited nodes with the XPath paths as array keys
117
+ *
118
+ * @var \DOMElement[]
119
+ */
120
+ private $visitedNodes = array();
121
+
122
+ /**
123
+ * the styles to apply to the nodes with the XPath paths as array keys for the outer array
124
+ * and the attribute names/values as key/value pairs for the inner array
125
+ *
126
+ * @var string[][]
127
+ */
128
+ private $styleAttributesForNodes = array();
129
+
130
+ /**
131
+ * Determines whether the "style" attributes of tags in the the HTML passed to this class should be preserved.
132
+ * If set to false, the value of the style attributes will be discarded.
133
+ *
134
+ * @var bool
135
+ */
136
+ private $isInlineStyleAttributesParsingEnabled = true;
137
+
138
+ /**
139
+ * Determines whether the <style> blocks in the HTML passed to this class should be parsed.
140
+ *
141
+ * If set to true, the <style> blocks will be removed from the HTML and their contents will be applied to the HTML
142
+ * via inline styles.
143
+ *
144
+ * If set to false, the <style> blocks will be left as they are in the HTML.
145
+ *
146
+ * @var bool
147
+ */
148
+ private $isStyleBlocksParsingEnabled = true;
149
+
150
+ /**
151
+ * Determines whether elements with the `display: none` property are
152
+ * removed from the DOM.
153
+ *
154
+ * @var bool
155
+ */
156
+ private $shouldKeepInvisibleNodes = true;
157
+
158
+ /**
159
+ * @var string[]
160
+ */
161
+ private $xPathRules = array(
162
+ // child
163
+ '/\\s*>\\s*/' => '/',
164
+ // adjacent sibling
165
+ '/\\s+\\+\\s+/' => '/following-sibling::*[1]/self::',
166
+ // descendant
167
+ '/\\s+(?=.*[^\\]]{1}$)/' => '//',
168
+ // :first-child
169
+ '/([^\\/]+):first-child/i' => '*[1]/self::\\1',
170
+ // :last-child
171
+ '/([^\\/]+):last-child/i' => '*[last()]/self::\\1',
172
+ // attribute only
173
+ '/^\\[(\\w+|\\w+\\=[\'"]?\\w+[\'"]?)\\]/' => '*[@\\1]',
174
+ // attribute
175
+ '/(\\w)\\[(\\w+)\\]/' => '\\1[@\\2]',
176
+ // exact attribute
177
+ '/(\\w)\\[(\\w+)\\=[\'"]?([\\w\\s]+)[\'"]?\\]/' => '\\1[@\\2="\\3"]',
178
+ // element attribute~=
179
+ '/([\\w\\*]+)\\[(\\w+)[\\s]*\\~\\=[\\s]*[\'"]?([\\w-_\\/]+)[\'"]?\\]/'
180
+ => '\\1[contains(concat(" ", @\\2, " "), concat(" ", "\\3", " "))]',
181
+ // element attribute^=
182
+ '/([\\w\\*]+)\\[(\\w+)[\\s]*\\^\\=[\\s]*[\'"]?([\\w-_\\/]+)[\'"]?\\]/' => '\\1[starts-with(@\\2, "\\3")]',
183
+ // element attribute*=
184
+ '/([\\w\\*]+)\\[(\\w+)[\\s]*\\*\\=[\\s]*[\'"]?([\\w-_\\s\\/:;]+)[\'"]?\\]/' => '\\1[contains(@\\2, "\\3")]',
185
+ // element attribute$=
186
+ '/([\\w\\*]+)\\[(\\w+)[\\s]*\\$\\=[\\s]*[\'"]?([\\w-_\\s\\/]+)[\'"]?\\]/'
187
+ => '\\1[substring(@\\2, string-length(@\\2) - string-length("\\3") + 1) = "\\3"]',
188
+ // element attribute|=
189
+ '/([\\w\\*]+)\\[(\\w+)[\\s]*\\|\\=[\\s]*[\'"]?([\\w-_\\s\\/]+)[\'"]?\\]/'
190
+ => '\\1[@\\2="\\3" or starts-with(@\\2, concat("\\3", "-"))]',
191
+ );
192
+
193
+ /**
194
+ * Determines whether CSS styles that have an equivalent HTML attribute
195
+ * should be mapped and attached to those elements.
196
+ *
197
+ * @var bool
198
+ */
199
+ private $shouldMapCssToHtml = false;
200
+
201
+ /**
202
+ * This multi-level array contains simple mappings of CSS properties to
203
+ * HTML attributes. If a mapping only applies to certain HTML nodes or
204
+ * only for certain values, the mapping is an object with a whitelist
205
+ * of nodes and values.
206
+ *
207
+ * @var mixed[][]
208
+ */
209
+ private $cssToHtmlMap = array(
210
+ 'background-color' => array(
211
+ 'attribute' => 'bgcolor',
212
+ ),
213
+ 'text-align' => array(
214
+ 'attribute' => 'align',
215
+ 'nodes' => array('p', 'div', 'td'),
216
+ 'values' => array('left', 'right', 'center', 'justify'),
217
+ ),
218
+ 'float' => array(
219
+ 'attribute' => 'align',
220
+ 'nodes' => array('table', 'img'),
221
+ 'values' => array('left', 'right'),
222
+ ),
223
+ 'border-spacing' => array(
224
+ 'attribute' => 'cellspacing',
225
+ 'nodes' => array('table'),
226
+ ),
227
+ );
228
+
229
+ public static $_media = '';
230
+
231
+ /**
232
+ * The constructor.
233
+ *
234
+ * @param string $html the HTML to emogrify, must be UTF-8-encoded
235
+ * @param string $css the CSS to merge, must be UTF-8-encoded
236
+ */
237
+ public function __construct($html = '', $css = '')
238
+ {
239
+ $this->setHtml($html);
240
+ $this->setCss($css);
241
+ }
242
+
243
+ /**
244
+ * The destructor.
245
+ */
246
+ public function __destruct()
247
+ {
248
+ $this->purgeVisitedNodes();
249
+ }
250
+
251
+ /**
252
+ * Sets the HTML to emogrify.
253
+ *
254
+ * @param string $html the HTML to emogrify, must be UTF-8-encoded
255
+ *
256
+ * @return void
257
+ */
258
+ public function setHtml($html)
259
+ {
260
+ $this->html = $html;
261
+ }
262
+
263
+ /**
264
+ * Sets the CSS to merge with the HTML.
265
+ *
266
+ * @param string $css the CSS to merge, must be UTF-8-encoded
267
+ *
268
+ * @return void
269
+ */
270
+ public function setCss($css)
271
+ {
272
+ $this->css = $css;
273
+ }
274
+
275
+ /**
276
+ * Applies $this->css to $this->html and returns the HTML with the CSS
277
+ * applied.
278
+ *
279
+ * This method places the CSS inline.
280
+ *
281
+ * @return string
282
+ *
283
+ * @throws \BadMethodCallException
284
+ */
285
+ public function emogrify()
286
+ {
287
+ if ($this->html === '') {
288
+ throw new BadMethodCallException('Please set some HTML first before calling emogrify.', 1390393096);
289
+ }
290
+
291
+ self::$_media = ''; // reset.
292
+
293
+ $xmlDocument = $this->createXmlDocument();
294
+ $this->process($xmlDocument);
295
+
296
+ return $xmlDocument->saveHTML();
297
+ }
298
+
299
+ /**
300
+ * Applies $this->css to $this->html and returns only the HTML content
301
+ * within the <body> tag.
302
+ *
303
+ * This method places the CSS inline.
304
+ *
305
+ * @return string
306
+ *
307
+ * @throws \BadMethodCallException
308
+ */
309
+ public function emogrifyBodyContent()
310
+ {
311
+ if ($this->html === '') {
312
+ throw new BadMethodCallException('Please set some HTML first before calling emogrify.', 1390393096);
313
+ }
314
+
315
+ $xmlDocument = $this->createXmlDocument();
316
+ $this->process($xmlDocument);
317
+
318
+ $innerDocument = new DOMDocument();
319
+ foreach ($xmlDocument->documentElement->getElementsByTagName('body')->item(0)->childNodes as $childNode) {
320
+ $innerDocument->appendChild($innerDocument->importNode($childNode, true));
321
+ }
322
+
323
+ return html_entity_decode($innerDocument->saveHTML());
324
+ }
325
+
326
+ /**
327
+ * Applies $this->css to $xmlDocument.
328
+ *
329
+ * This method places the CSS inline.
330
+ *
331
+ * @param \DOMDocument $xmlDocument
332
+ *
333
+ * @return void
334
+ *
335
+ * @throws \InvalidArgumentException
336
+ */
337
+ protected function process(DOMDocument $xmlDocument)
338
+ {
339
+ $xPath = new DOMXPath($xmlDocument);
340
+ $this->clearAllCaches();
341
+
342
+ // Before be begin processing the CSS file, parse the document and normalize all existing CSS attributes.
343
+ // This changes 'DISPLAY: none' to 'display: none'.
344
+ // We wouldn't have to do this if DOMXPath supported XPath 2.0.
345
+ // Also store a reference of nodes with existing inline styles so we don't overwrite them.
346
+ $this->purgeVisitedNodes();
347
+
348
+ set_error_handler(array($this, 'handleXpathError'), E_WARNING);
349
+
350
+ $nodesWithStyleAttributes = $xPath->query('//*[@style]');
351
+ if ($nodesWithStyleAttributes !== false) {
352
+ /** @var \DOMElement $node */
353
+ foreach ($nodesWithStyleAttributes as $node) {
354
+ if ($this->isInlineStyleAttributesParsingEnabled) {
355
+ $this->normalizeStyleAttributes($node);
356
+ } else {
357
+ $node->removeAttribute('style');
358
+ }
359
+ }
360
+ }
361
+
362
+ // grab any existing style blocks from the html and append them to the existing CSS
363
+ // (these blocks should be appended so as to have precedence over conflicting styles in the existing CSS)
364
+ $allCss = $this->css;
365
+
366
+ if ($this->isStyleBlocksParsingEnabled) {
367
+ $allCss .= $this->getCssFromAllStyleNodes($xPath);
368
+ }
369
+
370
+ $cssParts = $this->splitCssAndMediaQuery($allCss);
371
+ $excludedNodes = $this->getNodesToExclude($xPath);
372
+ $cssRules = $this->parseCssRules($cssParts['css']);
373
+ foreach ($cssRules as $cssRule) {
374
+ // query the body for the xpath selector
375
+ $nodesMatchingCssSelectors = $xPath->query($this->translateCssToXpath($cssRule['selector']));
376
+ // ignore invalid selectors
377
+ if ($nodesMatchingCssSelectors === false) {
378
+ continue;
379
+ }
380
+
381
+ /** @var \DOMElement $node */
382
+ foreach ($nodesMatchingCssSelectors as $node) {
383
+ if (in_array($node, $excludedNodes, true)) {
384
+ continue;
385
+ }
386
+
387
+ // if it has a style attribute, get it, process it, and append (overwrite) new stuff
388
+ if ($node->hasAttribute('style')) {
389
+ // break it up into an associative array
390
+ $oldStyleDeclarations = $this->parseCssDeclarationsBlock($node->getAttribute('style'));
391
+ } else {
392
+ $oldStyleDeclarations = array();
393
+ }
394
+ $newStyleDeclarations = $this->parseCssDeclarationsBlock($cssRule['declarationsBlock']);
395
+ if ($this->shouldMapCssToHtml) {
396
+ $this->mapCssToHtmlAttributes($newStyleDeclarations, $node);
397
+ }
398
+ $node->setAttribute(
399
+ 'style',
400
+ $this->generateStyleStringFromDeclarationsArrays($oldStyleDeclarations, $newStyleDeclarations)
401
+ );
402
+ }
403
+ }
404
+
405
+ restore_error_handler();
406
+
407
+ if ($this->isInlineStyleAttributesParsingEnabled) {
408
+ $this->fillStyleAttributesWithMergedStyles();
409
+ }
410
+
411
+ if ($this->shouldKeepInvisibleNodes) {
412
+ $this->removeInvisibleNodes($xPath);
413
+ }
414
+
415
+ $this->copyCssWithMediaToStyleNode($xmlDocument, $xPath, $cssParts['media']);
416
+ }
417
+
418
+ /**
419
+ * Applies $styles to $node.
420
+ *
421
+ * This method maps CSS styles to HTML attributes and adds those to the
422
+ * node.
423
+ *
424
+ * @param string[] $styles the new CSS styles taken from the global styles to be applied to this node
425
+ * @param \DOMNode $node node to apply styles to
426
+ *
427
+ * @return void
428
+ */
429
+ private function mapCssToHtmlAttributes(array $styles, DOMNode $node)
430
+ {
431
+ foreach ($styles as $property => $value) {
432
+ // Strip !important indicator
433
+ $value = trim(str_replace('!important', '', $value));
434
+ $this->mapCssToHtmlAttribute($property, $value, $node);
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Tries to apply the CSS style to $node as an attribute.
440
+ *
441
+ * This method maps a CSS rule to HTML attributes and adds those to the node.
442
+ *
443
+ * @param string $property the name of the CSS property to map
444
+ * @param string $value the value of the style rule to map
445
+ * @param \DOMNode $node node to apply styles to
446
+ *
447
+ * @return void
448
+ */
449
+ private function mapCssToHtmlAttribute($property, $value, DOMNode $node)
450
+ {
451
+ if (!$this->mapSimpleCssProperty($property, $value, $node)) {
452
+ $this->mapComplexCssProperty($property, $value, $node);
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Looks up the CSS property in the mapping table and maps it if it matches the conditions.
458
+ *
459
+ * @param string $property the name of the CSS property to map
460
+ * @param string $value the value of the style rule to map
461
+ * @param \DOMNode $node node to apply styles to
462
+ *
463
+ * @return bool true if the property cab be mapped using the simple mapping table
464
+ */
465
+ private function mapSimpleCssProperty($property, $value, DOMNode $node)
466
+ {
467
+ if (!isset($this->cssToHtmlMap[$property])) {
468
+ return false;
469
+ }
470
+
471
+ $mapping = $this->cssToHtmlMap[$property];
472
+ $nodesMatch = !isset($mapping['nodes']) || in_array($node->nodeName, $mapping['nodes'], true);
473
+ $valuesMatch = !isset($mapping['values']) || in_array($value, $mapping['values'], true);
474
+ if (!$nodesMatch || !$valuesMatch) {
475
+ return false;
476
+ }
477
+
478
+ $node->setAttribute($mapping['attribute'], $value);
479
+
480
+ return true;
481
+ }
482
+
483
+ /**
484
+ * Maps CSS properties that need special transformation to an HTML attribute.
485
+ *
486
+ * @param string $property the name of the CSS property to map
487
+ * @param string $value the value of the style rule to map
488
+ * @param \DOMNode $node node to apply styles to
489
+ *
490
+ * @return void
491
+ */
492
+ private function mapComplexCssProperty($property, $value, DOMNode $node)
493
+ {
494
+ $nodeName = $node->nodeName;
495
+ $isTable = $nodeName === 'table';
496
+ $isImage = $nodeName === 'img';
497
+ $isTableOrImage = $isTable || $isImage;
498
+
499
+ switch ($property) {
500
+ case 'background':
501
+ // Parse out the color, if any
502
+ $styles = explode(' ', $value);
503
+ $first = $styles[0];
504
+ if (!is_numeric(substr($first, 0, 1)) && substr($first, 0, 3) !== 'url') {
505
+ // This is not a position or image, assume it's a color
506
+ $node->setAttribute('bgcolor', $first);
507
+ }
508
+ break;
509
+ case 'width':
510
+ // intentional fall-through
511
+ case 'height':
512
+ // Only parse values in px and %, but not values like "auto".
513
+ if (preg_match('/^\d+(px|%)$/', $value)) {
514
+ // Remove 'px'. This regex only conserves numbers and %
515
+ $number = preg_replace('/[^0-9.%]/', '', $value);
516
+ $node->setAttribute($property, $number);
517
+ }
518
+ break;
519
+ case 'margin':
520
+ if ($isTableOrImage) {
521
+ $margins = $this->parseCssShorthandValue($value);
522
+ if ($margins['left'] === 'auto' && $margins['right'] === 'auto') {
523
+ $node->setAttribute('align', 'center');
524
+ }
525
+ }
526
+ break;
527
+ case 'border':
528
+ if ($isTableOrImage) {
529
+ if ($value === 'none' || $value === '0') {
530
+ $node->setAttribute('border', '0');
531
+ }
532
+ }
533
+ break;
534
+ default:
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Parses a shorthand CSS value and splits it into individual values
540
+ *
541
+ * @param string $value a string of CSS value with 1, 2, 3 or 4 sizes
542
+ * For example: padding: 0 auto;
543
+ * '0 auto' is split into top: 0, left: auto, bottom: 0,
544
+ * right: auto.
545
+ *
546
+ * @return string[] an array of values for top, right, bottom and left (using these as associative array keys)
547
+ */
548
+ private function parseCssShorthandValue($value)
549
+ {
550
+ $values = preg_split('/\\s+/', $value);
551
+
552
+ $css = array();
553
+ $css['top'] = $values[0];
554
+ $css['right'] = (count($values) > 1) ? $values[1] : $css['top'];
555
+ $css['bottom'] = (count($values) > 2) ? $values[2] : $css['top'];
556
+ $css['left'] = (count($values) > 3) ? $values[3] : $css['right'];
557
+
558
+ return $css;
559
+ }
560
+
561
+ /**
562
+ * Extracts and parses the individual rules from a CSS string.
563
+ *
564
+ * @param string $css a string of raw CSS code
565
+ *
566
+ * @return string[][] an array of string sub-arrays with the keys
567
+ * "selector" (the CSS selector(s), e.g., "*" or "h1"),
568
+ * "declarationsBLock" (the semicolon-separated CSS declarations for that selector(s),
569
+ * e.g., "color: red; height: 4px;"),
570
+ * and "line" (the line number e.g. 42)
571
+ */
572
+ private function parseCssRules($css)
573
+ {
574
+ $cssKey = md5($css);
575
+ if (!isset($this->caches[self::CACHE_KEY_CSS][$cssKey])) {
576
+ // process the CSS file for selectors and definitions
577
+ preg_match_all('/(?:^|[\\s^{}]*)([^{]+){([^}]*)}/mis', $css, $matches, PREG_SET_ORDER);
578
+
579
+ $cssRules = array();
580
+ /** @var string[] $cssRule */
581
+ foreach ($matches as $key => $cssRule) {
582
+ $cssDeclaration = trim($cssRule[2]);
583
+ if ($cssDeclaration === '') {
584
+ continue;
585
+ }
586
+
587
+ $selectors = explode(',', $cssRule[1]);
588
+ foreach ($selectors as $selector) {
589
+ // don't process pseudo-elements and behavioral (dynamic) pseudo-classes;
590
+ // only allow structural pseudo-classes
591
+ $hasPseudoElement = strpos($selector, '::') !== false;
592
+ $hasAnyPseudoClass = (bool) preg_match('/:[a-zA-Z]/', $selector);
593
+ $hasSupportedPseudoClass = (bool) preg_match('/:\\S+\\-(child|type\\()/i', $selector);
594
+ if ($hasPseudoElement || ($hasAnyPseudoClass && !$hasSupportedPseudoClass)) {
595
+ continue;
596
+ }
597
+
598
+ $cssRules[] = array(
599
+ 'selector' => trim($selector),
600
+ 'declarationsBlock' => $cssDeclaration,
601
+ // keep track of where it appears in the file, since order is important
602
+ 'line' => $key,
603
+ );
604
+ }
605
+ }
606
+
607
+ usort($cssRules, array($this, 'sortBySelectorPrecedence'));
608
+
609
+ $this->caches[self::CACHE_KEY_CSS][$cssKey] = $cssRules;
610
+ }
611
+
612
+ return $this->caches[self::CACHE_KEY_CSS][$cssKey];
613
+ }
614
+
615
+ /**
616
+ * Disables the parsing of inline styles.
617
+ *
618
+ * @return void
619
+ */
620
+ public function disableInlineStyleAttributesParsing()
621
+ {
622
+ $this->isInlineStyleAttributesParsingEnabled = false;
623
+ }
624
+
625
+ /**
626
+ * Disables the parsing of <style> blocks.
627
+ *
628
+ * @return void
629
+ */
630
+ public function disableStyleBlocksParsing()
631
+ {
632
+ $this->isStyleBlocksParsingEnabled = false;
633
+ }
634
+
635
+ /**
636
+ * Disables the removal of elements with `display: none` properties.
637
+ *
638
+ * @return void
639
+ */
640
+ public function disableInvisibleNodeRemoval()
641
+ {
642
+ $this->shouldKeepInvisibleNodes = false;
643
+ }
644
+
645
+ /**
646
+ * Enables the attachment/override of HTML attributes for which a
647
+ * corresponding CSS property has been set.
648
+ *
649
+ * @return void
650
+ */
651
+ public function enableCssToHtmlMapping()
652
+ {
653
+ $this->shouldMapCssToHtml = true;
654
+ }
655
+
656
+ /**
657
+ * Clears all caches.
658
+ *
659
+ * @return void
660
+ */
661
+ private function clearAllCaches()
662
+ {
663
+ $this->clearCache(self::CACHE_KEY_CSS);
664
+ $this->clearCache(self::CACHE_KEY_SELECTOR);
665
+ $this->clearCache(self::CACHE_KEY_XPATH);
666
+ $this->clearCache(self::CACHE_KEY_CSS_DECLARATIONS_BLOCK);
667
+ $this->clearCache(self::CACHE_KEY_COMBINED_STYLES);
668
+ }
669
+
670
+ /**
671
+ * Clears a single cache by key.
672
+ *
673
+ * @param int $key the cache key, must be CACHE_KEY_CSS, CACHE_KEY_SELECTOR, CACHE_KEY_XPATH
674
+ * or CACHE_KEY_CSS_DECLARATION_BLOCK
675
+ *
676
+ * @return void
677
+ *
678
+ * @throws \InvalidArgumentException
679
+ */
680
+ private function clearCache($key)
681
+ {
682
+ $allowedCacheKeys = array(
683
+ self::CACHE_KEY_CSS,
684
+ self::CACHE_KEY_SELECTOR,
685
+ self::CACHE_KEY_XPATH,
686
+ self::CACHE_KEY_CSS_DECLARATIONS_BLOCK,
687
+ self::CACHE_KEY_COMBINED_STYLES,
688
+ );
689
+ if (!in_array($key, $allowedCacheKeys, true)) {
690
+ throw new InvalidArgumentException('Invalid cache key: ' . $key, 1391822035);
691
+ }
692
+
693
+ $this->caches[$key] = array();
694
+ }
695
+
696
+ /**
697
+ * Purges the visited nodes.
698
+ *
699
+ * @return void
700
+ */
701
+ private function purgeVisitedNodes()
702
+ {
703
+ $this->visitedNodes = array();
704
+ $this->styleAttributesForNodes = array();
705
+ }
706
+
707
+ /**
708
+ * Marks a tag for removal.
709
+ *
710
+ * There are some HTML tags that DOMDocument cannot process, and it will throw an error if it encounters them.
711
+ * In particular, DOMDocument will complain if you try to use HTML5 tags in an XHTML document.
712
+ *
713
+ * Note: The tags will not be removed if they have any content.
714
+ *
715
+ * @param string $tagName the tag name, e.g., "p"
716
+ *
717
+ * @return void
718
+ */
719
+ public function addUnprocessableHtmlTag($tagName)
720
+ {
721
+ $this->unprocessableHtmlTags[] = $tagName;
722
+ }
723
+
724
+ /**
725
+ * Drops a tag from the removal list.
726
+ *
727
+ * @param string $tagName the tag name, e.g., "p"
728
+ *
729
+ * @return void
730
+ */
731
+ public function removeUnprocessableHtmlTag($tagName)
732
+ {
733
+ $key = array_search($tagName, $this->unprocessableHtmlTags, true);
734
+ if ($key !== false) {
735
+ unset($this->unprocessableHtmlTags[$key]);
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Marks a media query type to keep.
741
+ *
742
+ * @param string $mediaName the media type name, e.g., "braille"
743
+ *
744
+ * @return void
745
+ */
746
+ public function addAllowedMediaType($mediaName)
747
+ {
748
+ $this->allowedMediaTypes[$mediaName] = true;
749
+ }
750
+
751
+ /**
752
+ * Drops a media query type from the allowed list.
753
+ *
754
+ * @param string $mediaName the tag name, e.g., "braille"
755
+ *
756
+ * @return void
757
+ */
758
+ public function removeAllowedMediaType($mediaName)
759
+ {
760
+ if (isset($this->allowedMediaTypes[$mediaName])) {
761
+ unset($this->allowedMediaTypes[$mediaName]);
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Adds a selector to exclude nodes from emogrification.
767
+ *
768
+ * Any nodes that match the selector will not have their style altered.
769
+ *
770
+ * @param string $selector the selector to exclude, e.g., ".editor"
771
+ *
772
+ * @return void
773
+ */
774
+ public function addExcludedSelector($selector)
775
+ {
776
+ $this->excludedSelectors[$selector] = true;
777
+ }
778
+
779
+ /**
780
+ * No longer excludes the nodes matching this selector from emogrification.
781
+ *
782
+ * @param string $selector the selector to no longer exclude, e.g., ".editor"
783
+ *
784
+ * @return void
785
+ */
786
+ public function removeExcludedSelector($selector)
787
+ {
788
+ if (isset($this->excludedSelectors[$selector])) {
789
+ unset($this->excludedSelectors[$selector]);
790
+ }
791
+ }
792
+
793
+ /**
794
+ * This removes styles from your email that contain display:none.
795
+ * We need to look for display:none, but we need to do a case-insensitive search. Since DOMDocument only
796
+ * supports XPath 1.0, lower-case() isn't available to us. We've thus far only set attributes to lowercase,
797
+ * not attribute values. Consequently, we need to translate() the letters that would be in 'NONE' ("NOE")
798
+ * to lowercase.
799
+ *
800
+ * @param \DOMXPath $xPath
801
+ *
802
+ * @return void
803
+ */
804
+ private function removeInvisibleNodes(DOMXPath $xPath)
805
+ {
806
+ $nodesWithStyleDisplayNone = $xPath->query(
807
+ '//*[contains(translate(translate(@style," ",""),"NOE","noe"),"display:none")]'
808
+ );
809
+ if ($nodesWithStyleDisplayNone->length === 0) {
810
+ return;
811
+ }
812
+
813
+ // The checks on parentNode and is_callable below ensure that if we've deleted the parent node,
814
+ // we don't try to call removeChild on a nonexistent child node
815
+ /** @var \DOMNode $node */
816
+ foreach ($nodesWithStyleDisplayNone as $node) {
817
+ if ($node->parentNode && is_callable(array($node->parentNode, 'removeChild'))) {
818
+ $node->parentNode->removeChild($node);
819
+ }
820
+ }
821
+ }
822
+
823
+ private function normalizeStyleAttributes_callback( $m ) {
824
+ return strtolower( $m[0] );
825
+ }
826
+
827
+ /**
828
+ * Normalizes the value of the "style" attribute and saves it.
829
+ *
830
+ * @param \DOMElement $node
831
+ *
832
+ * @return void
833
+ */
834
+ private function normalizeStyleAttributes(DOMElement $node)
835
+ {
836
+ $normalizedOriginalStyle = preg_replace_callback(
837
+ '/[A-z\\-]+(?=\\:)/S',
838
+ array( $this, 'normalizeStyleAttributes_callback' ),
839
+ $node->getAttribute('style')
840
+ );
841
+
842
+ // in order to not overwrite existing style attributes in the HTML, we
843
+ // have to save the original HTML styles
844
+ $nodePath = $node->getNodePath();
845
+ if (!isset($this->styleAttributesForNodes[$nodePath])) {
846
+ $this->styleAttributesForNodes[$nodePath] = $this->parseCssDeclarationsBlock($normalizedOriginalStyle);
847
+ $this->visitedNodes[$nodePath] = $node;
848
+ }
849
+
850
+ $node->setAttribute('style', $normalizedOriginalStyle);
851
+ }
852
+
853
+ /**
854
+ * Merges styles from styles attributes and style nodes and applies them to the attribute nodes
855
+ *
856
+ * @return void
857
+ */
858
+ private function fillStyleAttributesWithMergedStyles()
859
+ {
860
+ foreach ($this->styleAttributesForNodes as $nodePath => $styleAttributesForNode) {
861
+ $node = $this->visitedNodes[$nodePath];
862
+ $currentStyleAttributes = $this->parseCssDeclarationsBlock($node->getAttribute('style'));
863
+ $node->setAttribute(
864
+ 'style',
865
+ $this->generateStyleStringFromDeclarationsArrays(
866
+ $currentStyleAttributes,
867
+ $styleAttributesForNode
868
+ )
869
+ );
870
+ }
871
+ }
872
+
873
+ /**
874
+ * This method merges old or existing name/value array with new name/value array
875
+ * and then generates a string of the combined style suitable for placing inline.
876
+ * This becomes the single point for CSS string generation allowing for consistent
877
+ * CSS output no matter where the CSS originally came from.
878
+ *
879
+ * @param string[] $oldStyles
880
+ * @param string[] $newStyles
881
+ *
882
+ * @return string
883
+ */
884
+ private function generateStyleStringFromDeclarationsArrays(array $oldStyles, array $newStyles)
885
+ {
886
+ $combinedStyles = array_merge($oldStyles, $newStyles);
887
+ $cacheKey = serialize($combinedStyles);
888
+ if (isset($this->caches[self::CACHE_KEY_COMBINED_STYLES][$cacheKey])) {
889
+ return $this->caches[self::CACHE_KEY_COMBINED_STYLES][$cacheKey];
890
+ }
891
+
892
+ foreach ($oldStyles as $attributeName => $attributeValue) {
893
+ if (!isset($newStyles[$attributeName])) {
894
+ continue;
895
+ }
896
+
897
+ $newAttributeValue = $newStyles[$attributeName];
898
+ if ($this->attributeValueIsImportant($attributeValue)
899
+ && !$this->attributeValueIsImportant($newAttributeValue)
900
+ ) {
901
+ $combinedStyles[$attributeName] = $attributeValue;
902
+ }
903
+ }
904
+
905
+ $style = '';
906
+ foreach ($combinedStyles as $attributeName => $attributeValue) {
907
+ $style .= strtolower(trim($attributeName)) . ': ' . trim($attributeValue) . '; ';
908
+ }
909
+ $trimmedStyle = rtrim($style);
910
+
911
+ $this->caches[self::CACHE_KEY_COMBINED_STYLES][$cacheKey] = $trimmedStyle;
912
+
913
+ return $trimmedStyle;
914
+ }
915
+
916
+ /**
917
+ * Checks whether $attributeValue is marked as !important.
918
+ *
919
+ * @param string $attributeValue
920
+ *
921
+ * @return bool
922
+ */
923
+ private function attributeValueIsImportant($attributeValue)
924
+ {
925
+ return strtolower(substr(trim($attributeValue), -10)) === '!important';
926
+ }
927
+
928
+ /**
929
+ * Applies $css to $xmlDocument, limited to the media queries that actually apply to the document.
930
+ *
931
+ * @param \DOMDocument $xmlDocument the document to match against
932
+ * @param \DOMXPath $xPath
933
+ * @param string $css a string of CSS
934
+ *
935
+ * @return void
936
+ */
937
+ private function copyCssWithMediaToStyleNode(DOMDocument $xmlDocument, DOMXPath $xPath, $css)
938
+ {
939
+ if ($css === '') {
940
+ return;
941
+ }
942
+
943
+ $mediaQueriesRelevantForDocument = array();
944
+
945
+ foreach ($this->extractMediaQueriesFromCss($css) as $mediaQuery) {
946
+ foreach ($this->parseCssRules($mediaQuery['css']) as $selector) {
947
+ if ($this->existsMatchForCssSelector($xPath, $selector['selector'])) {
948
+ $mediaQueriesRelevantForDocument[] = $mediaQuery['query'];
949
+ break;
950
+ }
951
+ }
952
+ }
953
+
954
+ $this->addStyleElementToDocument($xmlDocument, implode($mediaQueriesRelevantForDocument));
955
+ }
956
+
957
+ /**
958
+ * Extracts the media queries from $css while skipping empty media queries.
959
+ *
960
+ * @param string $css
961
+ *
962
+ * @return string[][] numeric array with string sub-arrays with the keys "css" and "query"
963
+ */
964
+ private function extractMediaQueriesFromCss($css)
965
+ {
966
+ preg_match_all('/@media\\b[^{]*({((?:[^{}]+|(?1))*)})/', $css, $rawMediaQueries, PREG_SET_ORDER);
967
+ $parsedQueries = array();
968
+
969
+ foreach ($rawMediaQueries as $mediaQuery) {
970
+ if ($mediaQuery[2] !== '') {
971
+ $parsedQueries[] = array(
972
+ 'css' => $mediaQuery[2],
973
+ 'query' => $mediaQuery[0],
974
+ );
975
+ }
976
+ }
977
+
978
+ return $parsedQueries;
979
+ }
980
+
981
+ /**
982
+ * Checks whether there is at least one matching element for $cssSelector.
983
+ *
984
+ * @param \DOMXPath $xPath
985
+ * @param string $cssSelector
986
+ *
987
+ * @return bool
988
+ */
989
+ private function existsMatchForCssSelector(DOMXPath $xPath, $cssSelector)
990
+ {
991
+ $nodesMatchingSelector = $xPath->query($this->translateCssToXpath($cssSelector));
992
+
993
+ return $nodesMatchingSelector !== false && $nodesMatchingSelector->length !== 0;
994
+ }
995
+
996
+ /**
997
+ * Returns CSS content.
998
+ *
999
+ * @param \DOMXPath $xPath
1000
+ *
1001
+ * @return string
1002
+ */
1003
+ private function getCssFromAllStyleNodes(DOMXPath $xPath)
1004
+ {
1005
+ $styleNodes = $xPath->query('//style');
1006
+
1007
+ if ($styleNodes === false) {
1008
+ return '';
1009
+ }
1010
+
1011
+ $css = '';
1012
+ /** @var \DOMNode $styleNode */
1013
+ foreach ($styleNodes as $styleNode) {
1014
+ $css .= "\n\n" . $styleNode->nodeValue;
1015
+ $styleNode->parentNode->removeChild($styleNode);
1016
+ }
1017
+
1018
+ return $css;
1019
+ }
1020
+
1021
+ /**
1022
+ * Adds a style element with $css to $document.
1023
+ *
1024
+ * This method is protected to allow overriding.
1025
+ *
1026
+ * @see https://github.com/jjriv/emogrifier/issues/103
1027
+ *
1028
+ * @param \DOMDocument $document
1029
+ * @param string $css
1030
+ *
1031
+ * @return void
1032
+ */
1033
+ protected function addStyleElementToDocument(DOMDocument $document, $css)
1034
+ {
1035
+ $styleElement = $document->createElement('style', $css);
1036
+ $styleAttribute = $document->createAttribute('type');
1037
+ $styleAttribute->value = 'text/css';
1038
+ $styleElement->appendChild($styleAttribute);
1039
+
1040
+ $head = $this->getOrCreateHeadElement($document);
1041
+ $head->appendChild($styleElement);
1042
+ }
1043
+
1044
+ /**
1045
+ * Returns the existing or creates a new head element in $document.
1046
+ *
1047
+ * @param \DOMDocument $document
1048
+ *
1049
+ * @return \DOMNode the head element
1050
+ */
1051
+ private function getOrCreateHeadElement(DOMDocument $document)
1052
+ {
1053
+ $head = $document->getElementsByTagName('head')->item(0);
1054
+
1055
+ if ($head === null) {
1056
+ $head = $document->createElement('head');
1057
+ $html = $document->getElementsByTagName('html')->item(0);
1058
+ $html->insertBefore($head, $document->getElementsByTagName('body')->item(0));
1059
+ }
1060
+
1061
+ return $head;
1062
+ }
1063
+
1064
+ /**
1065
+ * Splits input CSS code to an array where:
1066
+ *
1067
+ * - key "css" will be contains clean CSS code
1068
+ * - key "media" will be contains all valuable media queries
1069
+ *
1070
+ * Example:
1071
+ *
1072
+ * The CSS code
1073
+ *
1074
+ * "@import "file.css"; h1 { color:red; } @media { h1 {}} @media tv { h1 {}}"
1075
+ *
1076
+ * will be parsed into the following array:
1077
+ *
1078
+ * "css" => "h1 { color:red; }"
1079
+ * "media" => "@media { h1 {}}"
1080
+ *
1081
+ * @param string $css
1082
+ *
1083
+ * @return string[]
1084
+ */
1085
+ private function splitCssAndMediaQuery($css)
1086
+ {
1087
+ $cssWithoutComments = preg_replace('/\\/\\*.*\\*\\//sU', '', $css);
1088
+
1089
+ $mediaTypesExpression = '';
1090
+ if (!empty($this->allowedMediaTypes)) {
1091
+ $mediaTypesExpression = '|' . implode('|', array_keys($this->allowedMediaTypes));
1092
+ }
1093
+
1094
+ $cssForAllowedMediaTypes = preg_replace_callback(
1095
+ '#@media\\s+(?:only\\s)?(?:[\\s{\\(]' . $mediaTypesExpression . ')\\s?[^{]+{.*}\\s*}\\s*#misU',
1096
+ array( $this, '_media_concat' ),
1097
+ $cssWithoutComments
1098
+ );
1099
+
1100
+ // filter the CSS
1101
+ $search = array(
1102
+ 'import directives' => '/^\\s*@import\\s[^;]+;/misU',
1103
+ 'remaining media enclosures' => '/^\\s*@media\\s[^{]+{(.*)}\\s*}\\s/misU',
1104
+ );
1105
+
1106
+ $cleanedCss = preg_replace($search, '', $cssForAllowedMediaTypes);
1107
+
1108
+ return array('css' => $cleanedCss, 'media' => self::$_media);
1109
+ }
1110
+
1111
+ private function _media_concat( $matches ) {
1112
+ self::$_media .= $matches[0];
1113
+ }
1114
+
1115
+ /**
1116
+ * Creates a DOMDocument instance with the current HTML.
1117
+ *
1118
+ * @return \DOMDocument
1119
+ */
1120
+ private function createXmlDocument()
1121
+ {
1122
+ $xmlDocument = new DOMDocument;
1123
+ $xmlDocument->encoding = 'UTF-8';
1124
+ $xmlDocument->strictErrorChecking = false;
1125
+ $xmlDocument->formatOutput = true;
1126
+ $libXmlState = libxml_use_internal_errors(true);
1127
+ $xmlDocument->loadHTML($this->getUnifiedHtml());
1128
+ libxml_clear_errors();
1129
+ libxml_use_internal_errors($libXmlState);
1130
+ $xmlDocument->normalizeDocument();
1131
+
1132
+ return $xmlDocument;
1133
+ }
1134
+
1135
+ /**
1136
+ * Returns the HTML with the unprocessable HTML tags removed and
1137
+ * with added document type and Content-Type meta tag if needed.
1138
+ *
1139
+ * @return string the unified HTML
1140
+ *
1141
+ * @throws \BadMethodCallException
1142
+ */
1143
+ private function getUnifiedHtml()
1144
+ {
1145
+ $htmlWithoutUnprocessableTags = $this->removeUnprocessableTags($this->html);
1146
+ $htmlWithDocumentType = $this->ensureDocumentType($htmlWithoutUnprocessableTags);
1147
+
1148
+ return $this->addContentTypeMetaTag($htmlWithDocumentType);
1149
+ }
1150
+
1151
+ /**
1152
+ * Removes the unprocessable tags from $html (if this feature is enabled).
1153
+ *
1154
+ * @param string $html
1155
+ *
1156
+ * @return string the reworked HTML with the unprocessable tags removed
1157
+ */
1158
+ private function removeUnprocessableTags($html)
1159
+ {
1160
+ if (empty($this->unprocessableHtmlTags)) {
1161
+ return $html;
1162
+ }
1163
+
1164
+ $unprocessableHtmlTags = implode('|', $this->unprocessableHtmlTags);
1165
+
1166
+ return preg_replace(
1167
+ '/<\\/?(' . $unprocessableHtmlTags . ')[^>]*>/i',
1168
+ '',
1169
+ $html
1170
+ );
1171
+ }
1172
+
1173
+ /**
1174
+ * Makes sure that the passed HTML has a document type.
1175
+ *
1176
+ * @param string $html
1177
+ *
1178
+ * @return string HTML with document type
1179
+ */
1180
+ private function ensureDocumentType($html)
1181
+ {
1182
+ $hasDocumentType = stripos($html, '<!DOCTYPE') !== false;
1183
+ if ($hasDocumentType) {
1184
+ return $html;
1185
+ }
1186
+
1187
+ return self::DEFAULT_DOCUMENT_TYPE . $html;
1188
+ }
1189
+
1190
+ /**
1191
+ * Adds a Content-Type meta tag for the charset.
1192
+ *
1193
+ * @param string $html
1194
+ *
1195
+ * @return string the HTML with the meta tag added
1196
+ */
1197
+ private function addContentTypeMetaTag($html)
1198
+ {
1199
+ $hasContentTypeMetaTag = stristr($html, 'Content-Type') !== false;
1200
+ if ($hasContentTypeMetaTag) {
1201
+ return $html;
1202
+ }
1203
+
1204
+ // We are trying to insert the meta tag to the right spot in the DOM.
1205
+ // If we just prepended it to the HTML, we would lose attributes set to the HTML tag.
1206
+ $hasHeadTag = stripos($html, '<head') !== false;
1207
+ $hasHtmlTag = stripos($html, '<html') !== false;
1208
+
1209
+ if ($hasHeadTag) {
1210
+ $reworkedHtml = preg_replace('/<head(.*?)>/i', '<head$1>' . self::CONTENT_TYPE_META_TAG, $html);
1211
+ } elseif ($hasHtmlTag) {
1212
+ $reworkedHtml = preg_replace(
1213
+ '/<html(.*?)>/i',
1214
+ '<html$1><head>' . self::CONTENT_TYPE_META_TAG . '</head>',
1215
+ $html
1216
+ );
1217
+ } else {
1218
+ $reworkedHtml = self::CONTENT_TYPE_META_TAG . $html;
1219
+ }
1220
+
1221
+ return $reworkedHtml;
1222
+ }
1223
+
1224
+ /**
1225
+ * @param string[] $a
1226
+ * @param string[] $b
1227
+ *
1228
+ * @return int
1229
+ */
1230
+ private function sortBySelectorPrecedence(array $a, array $b)
1231
+ {
1232
+ $precedenceA = $this->getCssSelectorPrecedence($a['selector']);
1233
+ $precedenceB = $this->getCssSelectorPrecedence($b['selector']);
1234
+
1235
+ // We want these sorted in ascending order so selectors with lesser precedence get processed first and
1236
+ // selectors with greater precedence get sorted last.
1237
+ $precedenceForEquals = ($a['line'] < $b['line'] ? -1 : 1);
1238
+ $precedenceForNotEquals = ($precedenceA < $precedenceB ? -1 : 1);
1239
+ return ($precedenceA === $precedenceB) ? $precedenceForEquals : $precedenceForNotEquals;
1240
+ }
1241
+
1242
+ /**
1243
+ * @param string $selector
1244
+ *
1245
+ * @return int
1246
+ */
1247
+ private function getCssSelectorPrecedence($selector)
1248
+ {
1249
+ $selectorKey = md5($selector);
1250
+ if (!isset($this->caches[self::CACHE_KEY_SELECTOR][$selectorKey])) {
1251
+ $precedence = 0;
1252
+ $value = 100;
1253
+ // ids: worth 100, classes: worth 10, elements: worth 1
1254
+ $search = array('\\#','\\.','');
1255
+
1256
+ foreach ($search as $s) {
1257
+ if (trim($selector) === '') {
1258
+ break;
1259
+ }
1260
+ $number = 0;
1261
+ $selector = preg_replace('/' . $s . '\\w+/', '', $selector, -1, $number);
1262
+ $precedence += ($value * $number);
1263
+ $value /= 10;
1264
+ }
1265
+ $this->caches[self::CACHE_KEY_SELECTOR][$selectorKey] = $precedence;
1266
+ }
1267
+
1268
+ return $this->caches[self::CACHE_KEY_SELECTOR][$selectorKey];
1269
+ }
1270
+
1271
+ private function translateCssToXpath_callback( $matches ) {
1272
+ return strtolower($matches[0]);
1273
+ }
1274
+
1275
+ /**
1276
+ * Maps a CSS selector to an XPath query string.
1277
+ *
1278
+ * @see http://plasmasturm.org/log/444/
1279
+ *
1280
+ * @param string $cssSelector a CSS selector
1281
+ *
1282
+ * @return string the corresponding XPath selector
1283
+ */
1284
+ private function translateCssToXpath($cssSelector)
1285
+ {
1286
+ $paddedSelector = ' ' . $cssSelector . ' ';
1287
+ $lowercasePaddedSelector = preg_replace_callback(
1288
+ '/\\s+\\w+\\s+/',
1289
+ array( $this, 'translateCssToXpath_callback' ),
1290
+ $paddedSelector
1291
+ );
1292
+
1293
+ $trimmedLowercaseSelector = trim($lowercasePaddedSelector);
1294
+ $xPathKey = md5($trimmedLowercaseSelector);
1295
+ if (!isset($this->caches[self::CACHE_KEY_XPATH][$xPathKey])) {
1296
+ $roughXpath = '//' . preg_replace(
1297
+ array_keys($this->xPathRules),
1298
+ $this->xPathRules,
1299
+ $trimmedLowercaseSelector
1300
+ );
1301
+ $xPathWithIdAttributeMatchers = preg_replace_callback(
1302
+ self::ID_ATTRIBUTE_MATCHER,
1303
+ array($this, 'matchIdAttributes'),
1304
+ $roughXpath
1305
+ );
1306
+ $xPathWithIdAttributeAndClassMatchers = preg_replace_callback(
1307
+ self::CLASS_ATTRIBUTE_MATCHER,
1308
+ array($this, 'matchClassAttributes'),
1309
+ $xPathWithIdAttributeMatchers
1310
+ );
1311
+
1312
+ // Advanced selectors are going to require a bit more advanced emogrification.
1313
+ // When we required PHP 5.3, we could do this with closures.
1314
+ $xPathWithIdAttributeAndClassMatchers = preg_replace_callback(
1315
+ '/([^\\/]+):nth-child\\(\\s*(odd|even|[+\\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
1316
+ array($this, 'translateNthChild'),
1317
+ $xPathWithIdAttributeAndClassMatchers
1318
+ );
1319
+ $finalXpath = preg_replace_callback(
1320
+ '/([^\\/]+):nth-of-type\\(\s*(odd|even|[+\\-]?\\d|[+\\-]?\\d?n(\\s*[+\\-]\\s*\\d)?)\\s*\\)/i',
1321
+ array($this, 'translateNthOfType'),
1322
+ $xPathWithIdAttributeAndClassMatchers
1323
+ );
1324
+
1325
+ $this->caches[self::CACHE_KEY_SELECTOR][$xPathKey] = $finalXpath;
1326
+ }
1327
+ return $this->caches[self::CACHE_KEY_SELECTOR][$xPathKey];
1328
+ }
1329
+
1330
+ /**
1331
+ * @param string[] $match
1332
+ *
1333
+ * @return string
1334
+ */
1335
+ private function matchIdAttributes(array $match)
1336
+ {
1337
+ return ($match[1] !== '' ? $match[1] : '*') . '[@id="' . $match[2] . '"]';
1338
+ }
1339
+
1340
+ /**
1341
+ * @param string[] $match
1342
+ *
1343
+ * @return string
1344
+ */
1345
+ private function matchClassAttributes(array $match)
1346
+ {
1347
+ return ($match[1] !== '' ? $match[1] : '*') . '[contains(concat(" ",@class," "),concat(" ","' .
1348
+ implode(
1349
+ '"," "))][contains(concat(" ",@class," "),concat(" ","',
1350
+ explode('.', substr($match[2], 1))
1351
+ ) . '"," "))]';
1352
+ }
1353
+
1354
+ /**
1355
+ * @param string[] $match
1356
+ *
1357
+ * @return string
1358
+ */
1359
+ private function translateNthChild(array $match)
1360
+ {
1361
+ $parseResult = $this->parseNth($match);
1362
+
1363
+ if (isset($parseResult[self::MULTIPLIER])) {
1364
+ if ($parseResult[self::MULTIPLIER] < 0) {
1365
+ $parseResult[self::MULTIPLIER] = abs($parseResult[self::MULTIPLIER]);
1366
+ $xPathExpression = sprintf(
1367
+ '*[(last() - position()) mod %u = %u]/self::%s',
1368
+ $parseResult[self::MULTIPLIER],
1369
+ $parseResult[self::INDEX],
1370
+ $match[1]
1371
+ );
1372
+ } else {
1373
+ $xPathExpression = sprintf(
1374
+ '*[position() mod %u = %u]/self::%s',
1375
+ $parseResult[self::MULTIPLIER],
1376
+ $parseResult[self::INDEX],
1377
+ $match[1]
1378
+ );
1379
+ }
1380
+ } else {
1381
+ $xPathExpression = sprintf('*[%u]/self::%s', $parseResult[self::INDEX], $match[1]);
1382
+ }
1383
+
1384
+ return $xPathExpression;
1385
+ }
1386
+
1387
+ /**
1388
+ * @param string[] $match
1389
+ *
1390
+ * @return string
1391
+ */
1392
+ private function translateNthOfType(array $match)
1393
+ {
1394
+ $parseResult = $this->parseNth($match);
1395
+
1396
+ if (isset($parseResult[self::MULTIPLIER])) {
1397
+ if ($parseResult[self::MULTIPLIER] < 0) {
1398
+ $parseResult[self::MULTIPLIER] = abs($parseResult[self::MULTIPLIER]);
1399
+ $xPathExpression = sprintf(
1400
+ '%s[(last() - position()) mod %u = %u]',
1401
+ $match[1],
1402
+ $parseResult[self::MULTIPLIER],
1403
+ $parseResult[self::INDEX]
1404
+ );
1405
+ } else {
1406
+ $xPathExpression = sprintf(
1407
+ '%s[position() mod %u = %u]',
1408
+ $match[1],
1409
+ $parseResult[self::MULTIPLIER],
1410
+ $parseResult[self::INDEX]
1411
+ );
1412
+ }
1413
+ } else {
1414
+ $xPathExpression = sprintf('%s[%u]', $match[1], $parseResult[self::INDEX]);
1415
+ }
1416
+
1417
+ return $xPathExpression;
1418
+ }
1419
+
1420
+ /**
1421
+ * @param string[] $match
1422
+ *
1423
+ * @return int[]
1424
+ */
1425
+ private function parseNth(array $match)
1426
+ {
1427
+ if (in_array(strtolower($match[2]), array('even', 'odd'), true)) {
1428
+ // we have "even" or "odd"
1429
+ $index = strtolower($match[2]) === 'even' ? 0 : 1;
1430
+ return array(self::MULTIPLIER => 2, self::INDEX => $index);
1431
+ }
1432
+ if (stripos($match[2], 'n') === false) {
1433
+ // if there is a multiplier
1434
+ $index = (int) str_replace(' ', '', $match[2]);
1435
+ return array(self::INDEX => $index);
1436
+ }
1437
+
1438
+ if (isset($match[3])) {
1439
+ $multipleTerm = str_replace($match[3], '', $match[2]);
1440
+ $index = (int) str_replace(' ', '', $match[3]);
1441
+ } else {
1442
+ $multipleTerm = $match[2];
1443
+ $index = 0;
1444
+ }
1445
+
1446
+ $multiplier = str_ireplace('n', '', $multipleTerm);
1447
+
1448
+ if ($multiplier === '') {
1449
+ $multiplier = 1;
1450
+ } elseif ($multiplier === '0') {
1451
+ return array(self::INDEX => $index);
1452
+ } else {
1453
+ $multiplier = (int) $multiplier;
1454
+ }
1455
+
1456
+ while ($index < 0) {
1457
+ $index += abs($multiplier);
1458
+ }
1459
+
1460
+ return array(self::MULTIPLIER => $multiplier, self::INDEX => $index);
1461
+ }
1462
+
1463
+ /**
1464
+ * Parses a CSS declaration block into property name/value pairs.
1465
+ *
1466
+ * Example:
1467
+ *
1468
+ * The declaration block
1469
+ *
1470
+ * "color: #000; font-weight: bold;"
1471
+ *
1472
+ * will be parsed into the following array:
1473
+ *
1474
+ * "color" => "#000"
1475
+ * "font-weight" => "bold"
1476
+ *
1477
+ * @param string $cssDeclarationsBlock the CSS declarations block without the curly braces, may be empty
1478
+ *
1479
+ * @return string[]
1480
+ * the CSS declarations with the property names as array keys and the property values as array values
1481
+ */
1482
+ private function parseCssDeclarationsBlock($cssDeclarationsBlock)
1483
+ {
1484
+ if (isset($this->caches[self::CACHE_KEY_CSS_DECLARATIONS_BLOCK][$cssDeclarationsBlock])) {
1485
+ return $this->caches[self::CACHE_KEY_CSS_DECLARATIONS_BLOCK][$cssDeclarationsBlock];
1486
+ }
1487
+
1488
+ $properties = array();
1489
+ $declarations = preg_split('/;(?!base64|charset)/', $cssDeclarationsBlock);
1490
+
1491
+ foreach ($declarations as $declaration) {
1492
+ $matches = array();
1493
+ if (!preg_match('/^([A-Za-z\\-]+)\\s*:\\s*(.+)$/', trim($declaration), $matches)) {
1494
+ continue;
1495
+ }
1496
+
1497
+ $propertyName = strtolower($matches[1]);
1498
+ $propertyValue = $matches[2];
1499
+ $properties[$propertyName] = $propertyValue;
1500
+ }
1501
+ $this->caches[self::CACHE_KEY_CSS_DECLARATIONS_BLOCK][$cssDeclarationsBlock] = $properties;
1502
+
1503
+ return $properties;
1504
+ }
1505
+
1506
+ /**
1507
+ * Find the nodes that are not to be emogrified.
1508
+ *
1509
+ * @param \DOMXPath $xPath
1510
+ *
1511
+ * @return \DOMElement[]
1512
+ */
1513
+ private function getNodesToExclude(DOMXPath $xPath)
1514
+ {
1515
+ $excludedNodes = array();
1516
+ foreach (array_keys($this->excludedSelectors) as $selectorToExclude) {
1517
+ foreach ($xPath->query($this->translateCssToXpath($selectorToExclude)) as $node) {
1518
+ $excludedNodes[] = $node;
1519
+ }
1520
+ }
1521
+
1522
+ return $excludedNodes;
1523
+ }
1524
+
1525
+ /**
1526
+ * Handles invalid xPath expression warnings, generated by process() method,
1527
+ * during querying \DOMDocument and trigger \InvalidArgumentException
1528
+ * with invalid selector.
1529
+ *
1530
+ * @param int $type
1531
+ * @param string $message
1532
+ * @param string $file
1533
+ * @param int $line
1534
+ * @param array $context
1535
+ *
1536
+ * @return bool always false
1537
+ *
1538
+ * @throws \InvalidArgumentException
1539
+ */
1540
+ public function handleXpathError($type, $message, $file, $line, array $context)
1541
+ {
1542
+ if ($type === E_WARNING && isset($context['cssRule']['selector'])) {
1543
+ throw new InvalidArgumentException(
1544
+ sprintf(
1545
+ '%s in selector >> %s << in %s on line %s',
1546
+ $message,
1547
+ $context['cssRule']['selector'],
1548
+ $file,
1549
+ $line
1550
+ )
1551
+ );
1552
+ }
1553
+
1554
+ // the normal error handling continues when handler return false
1555
+ return false;
1556
+ }
1557
+ }
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === WP Job Manager ===
2
  Contributors: mikejolley, automattic, adamkheckler, alexsanford1, annezazu, cena, chaselivingston, csonnek, davor.altman, donnapep, donncha, drawmyface, erania-pinnera, jacobshere, jakeom, jeherve, jenhooks, jgs, jonryan, kraftbj, lamdayap, lschuyler, macmanx, nancythanki, orangesareorange, rachelsquirrel, ryancowles, richardmtl, scarstocea
3
  Tags: job manager, job listing, job board, job management, job lists, job list, job, jobs, company, hiring, employment, employer, employees, candidate, freelance, internship, job listings, positions, board, application, hiring, listing, manager, recruiting, recruitment, talent
4
- Requires at least: 4.5.0
5
  Tested up to: 4.9
6
- Stable tag: 1.30.2
7
  License: GPLv3
8
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
9
 
@@ -141,6 +141,25 @@ You can view (and contribute) translations via the [translate.wordpress.org](htt
141
 
142
  == Changelog ==
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  = 1.30.2 =
145
  * Enhancement: Show notice when user is using an older version of WordPress.
146
  * Enhancement: Hide unnecessary view mode in WP Admin's Job Listings page. (@RajeebTheGreat)
1
  === WP Job Manager ===
2
  Contributors: mikejolley, automattic, adamkheckler, alexsanford1, annezazu, cena, chaselivingston, csonnek, davor.altman, donnapep, donncha, drawmyface, erania-pinnera, jacobshere, jakeom, jeherve, jenhooks, jgs, jonryan, kraftbj, lamdayap, lschuyler, macmanx, nancythanki, orangesareorange, rachelsquirrel, ryancowles, richardmtl, scarstocea
3
  Tags: job manager, job listing, job board, job management, job lists, job list, job, jobs, company, hiring, employment, employer, employees, candidate, freelance, internship, job listings, positions, board, application, hiring, listing, manager, recruiting, recruitment, talent
4
+ Requires at least: 4.7.0
5
  Tested up to: 4.9
6
+ Stable tag: 1.31.0
7
  License: GPLv3
8
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
9
 
141
 
142
  == Changelog ==
143
 
144
+ = 1.31.0 =
145
+ * Change: Minimum WordPress version is now 4.7.0.
146
+ * Enhancement: Add email notifications with initial support for new jobs, updated jobs, and expiring listings.
147
+ * Enhancement: For GDPR, scrub WPJM data from database on uninstall if option is enabled.
148
+ * Enhancement: Filter by Filled and Featured status in WP admin.
149
+ * Enhancement: Simplify the display of application URLs.
150
+ * Enhancement: When using WPML, prevent changes to page options when on a non-default language. (@vukvukovich)
151
+ * Enhancement: Include company logo in structured data. (@RajeebTheGreat)
152
+ * Enhancement: Use more efficient jQuery selectors in scripts. (@RajeebTheGreat)
153
+ * Enhancement: Use proper `<h2>` tag in `content-summary-job_listing.php` template for the job title. (@abdullah1908)
154
+ * Enhancement: Hide empty categories on `[job]` filter.
155
+ * Fix: Update calls to `get_terms()` to use the new format.
156
+ * Fix: Maintain the current tab when saving settings in WP Admin.
157
+ * Fix: Enqueue the date picker CSS when used on the front-end.
158
+ * Fix: Remove errors when widget instance was created without setting defaults.
159
+ * REST API Pre-release: Add support for job category taxonomy endpoints.
160
+ * Dev: Add `$job_id` parameter to `job_manager_job_dashboard_do_action_{$action}` action hook. (@jonasvogel)
161
+ * Dev: Add support for hidden WPJM settings in WP Admin.
162
+
163
  = 1.30.2 =
164
  * Enhancement: Show notice when user is using an older version of WordPress.
165
  * Enhancement: Hide unnecessary view mode in WP Admin's Job Listings page. (@RajeebTheGreat)
templates/account-signin.php CHANGED
@@ -8,7 +8,7 @@
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
- * @version 1.29.1
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
@@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) {
17
  ?>
18
  <?php if ( is_user_logged_in() ) : ?>
19
 
20
- <fieldset>
21
  <label><?php _e( 'Your account', 'wp-job-manager' ); ?></label>
22
  <div class="field account-sign-in">
23
  <?php
@@ -35,14 +35,14 @@ if ( ! defined( 'ABSPATH' ) ) {
35
  $registration_fields = wpjm_get_registration_fields();
36
  $use_standard_password_email = wpjm_use_standard_password_setup_email();
37
  ?>
38
- <fieldset>
39
  <label><?php _e( 'Have an account?', 'wp-job-manager' ); ?></label>
40
  <div class="field account-sign-in">
41
  <a class="button" href="<?php echo apply_filters( 'submit_job_form_login_url', wp_login_url( get_permalink() ) ); ?>"><?php _e( 'Sign in', 'wp-job-manager' ); ?></a>
42
 
43
  <?php if ( $registration_enabled ) : ?>
44
 
45
- <?php printf( __( 'If you don&rsquo;t have an account you can %screate one below by entering your email address/username.', 'wp-job-manager' ), $account_required ? '' : __( 'optionally', 'wp-job-manager' ) . ' ' ); ?>
46
  <?php if ( $use_standard_password_email ) : ?>
47
  <?php printf( __( 'Your account details will be confirmed via email.', 'wp-job-manager' ) ); ?>
48
  <?php endif; ?>
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
+ * @version 1.31.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
17
  ?>
18
  <?php if ( is_user_logged_in() ) : ?>
19
 
20
+ <fieldset class="fieldset-logged_in">
21
  <label><?php _e( 'Your account', 'wp-job-manager' ); ?></label>
22
  <div class="field account-sign-in">
23
  <?php
35
  $registration_fields = wpjm_get_registration_fields();
36
  $use_standard_password_email = wpjm_use_standard_password_setup_email();
37
  ?>
38
+ <fieldset class="fieldset-login_required">
39
  <label><?php _e( 'Have an account?', 'wp-job-manager' ); ?></label>
40
  <div class="field account-sign-in">
41
  <a class="button" href="<?php echo apply_filters( 'submit_job_form_login_url', wp_login_url( get_permalink() ) ); ?>"><?php _e( 'Sign in', 'wp-job-manager' ); ?></a>
42
 
43
  <?php if ( $registration_enabled ) : ?>
44
 
45
+ <?php printf( __( 'If you don\'t have an account you can %screate one below by entering your email address/username.', 'wp-job-manager' ), $account_required ? '' : __( 'optionally', 'wp-job-manager' ) . ' ' ); ?>
46
  <?php if ( $use_standard_password_email ) : ?>
47
  <?php printf( __( 'Your account details will be confirmed via email.', 'wp-job-manager' ) ); ?>
48
  <?php endif; ?>
templates/content-summary-job_listing.php CHANGED
@@ -8,7 +8,7 @@
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
- * @version 1.27.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
@@ -34,7 +34,7 @@ global $job_manager;
34
 
35
  <div class="job_summary_content">
36
 
37
- <h1><?php wpjm_the_job_title(); ?></h1>
38
 
39
  <p class="meta"><?php the_job_location( false ); ?> &mdash; <?php the_job_publish_date(); ?></p>
40
 
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
+ * @version 1.31.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
34
 
35
  <div class="job_summary_content">
36
 
37
+ <h2 class="job_summary_title"><?php wpjm_the_job_title(); ?></h2>
38
 
39
  <p class="meta"><?php the_job_location( false ); ?> &mdash; <?php the_job_publish_date(); ?></p>
40
 
templates/emails/admin-expiring-job.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying the administrator of an expiring job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/employer-expiring-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ /**
24
+ * @var bool
25
+ */
26
+ $expiring_today = $args['expiring_today'];
27
+
28
+ echo '<p>';
29
+ if ( $expiring_today ) {
30
+ printf( __( 'The following job listing is expiring today from <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
31
+ } else {
32
+ printf( __( 'The following job listing is expiring soon from <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
33
+ }
34
+ $edit_post_link = admin_url( sprintf( 'post.php?post=%d&amp;action=edit', $job->ID ) );
35
+ printf( ' ' . __( 'Visit <a href="%s">WordPress admin</a> to manage the listing.', 'wp-job-manager' ), esc_url( $edit_post_link ) );
36
+ echo '</p>';
37
+
38
+ /**
39
+ * Show details about the job listing.
40
+ *
41
+ * @param WP_Post $job The job listing to show details for.
42
+ * @param WP_Job_Manager_Email $email Email object for the notification.
43
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
44
+ * @param bool $plain_text True if the email is being sent as plain text.
45
+ */
46
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/admin-new-job.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying admin of a new job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/admin-new-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+ ?>
23
+ <p><?php
24
+ printf( __( 'A new job listing has been submitted to <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
25
+ switch ( $job->post_status ) {
26
+ case 'publish':
27
+ printf( ' ' . __( 'It has been published and is now available to the public.', 'wp-job-manager' ) );
28
+ break;
29
+ case 'pending':
30
+ printf( ' ' . __( 'It is awaiting approval by an administrator in <a href="%s">WordPress admin</a>.', 'wp-job-manager' ), esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) );
31
+ break;
32
+ }
33
+ ?></p>
34
+ <?php
35
+
36
+ /**
37
+ * Show details about the job listing.
38
+ *
39
+ * @param WP_Post $job The job listing to show details for.
40
+ * @param WP_Job_Manager_Email $email Email object for the notification.
41
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
42
+ * @param bool $plain_text True if the email is being sent as plain text.
43
+ */
44
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/admin-updated-job.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying admin of an updated job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/admin-updated-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+ ?>
23
+ <p><?php
24
+ printf( __( 'A job listing has been updated on <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
25
+ switch ( $job->post_status ) {
26
+ case 'publish':
27
+ printf( ' ' . __( 'The changes have been published and are now available to the public.', 'wp-job-manager' ) );
28
+ break;
29
+ case 'pending':
30
+ printf( ' ' . __( 'The job listing is not publicly available until the changes are approved by an administrator in the site\'s <a href="%s">WordPress admin</a>.', 'wp-job-manager' ), esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) );
31
+ break;
32
+ }
33
+ ?></p>
34
+ <?php
35
+
36
+ /**
37
+ * Show details about the job listing.
38
+ *
39
+ * @param WP_Post $job The job listing to show details for.
40
+ * @param WP_Job_Manager_Email $email Email object for the notification.
41
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
42
+ * @param bool $plain_text True if the email is being sent as plain text.
43
+ */
44
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/email-footer.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Footer for email notifications.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/email-footer.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+ ?>
18
+ </div>
19
+ </td>
20
+ </tr>
21
+ </table>
22
+ </td>
23
+ </tr>
24
+ </table>
25
+ </td>
26
+ </tr>
27
+ </table>
28
+ </div>
29
+ </body>
30
+ </html>
templates/emails/email-header.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Header for email notifications.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/email-header.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+ ?>
18
+ <!DOCTYPE html>
19
+ <html <?php language_attributes(); ?>>
20
+ <head>
21
+ <meta http-equiv="Content-Type" content="text/html; charset=<?php bloginfo( 'charset' ); ?>" />
22
+ <title><?php echo get_bloginfo( 'name', 'display' ); ?></title>
23
+ </head>
24
+ <body <?php echo is_rtl() ? 'rightmargin' : 'leftmargin'; ?>="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">
25
+ <div id="wrapper" dir="<?php echo is_rtl() ? 'rtl' : 'ltr'?>">
26
+ <table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
27
+ <tr>
28
+ <td align="center" valign="top">
29
+ <!-- Body -->
30
+ <table border="0" cellpadding="0" cellspacing="0" width="600" id="template_body">
31
+ <tr>
32
+ <td valign="top" id="body_content">
33
+ <!-- Content -->
34
+ <table border="0" cellpadding="20" cellspacing="0" width="100%">
35
+ <tr>
36
+ <td valign="top">
37
+ <div id="body_content_inner">
templates/emails/email-job-details.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content for showing job details.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/email-job-details.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ $text_align = is_rtl() ? 'right' : 'left';
19
+
20
+ if ( ! empty( $fields ) ) : ?>
21
+ <div class="job-manager-email-job-details-container email-container">
22
+ <table border="0" cellpadding="10" cellspacing="0" width="100%" class="job-manager-email-job-details details">
23
+ <?php foreach ( $fields as $field ) : ?>
24
+ <tr>
25
+ <td class="detail-label" style="text-align:<?php echo $text_align; ?>;">
26
+ <?php echo wp_kses_post( $field['label'] ); ?>
27
+ </td>
28
+ <td class="detail-value" style="text-align:<?php echo $text_align; ?>;">
29
+ <?php
30
+ if ( ! empty( $field['url'] ) ) {
31
+ echo sprintf( '<a href="%s">%s</a>', esc_url( $field['url'] ), wp_kses_post( $field['value'] ) );
32
+ } else {
33
+ echo wp_kses_post( $field['value'] );
34
+ }
35
+ ?>
36
+ </td>
37
+ </tr>
38
+ <?php endforeach; ?>
39
+ </table>
40
+ </div>
41
+ <?php endif; ?>
templates/emails/email-styles.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email stylesheet.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/email-styles.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ $style_vars = array();
19
+ $style_vars['color_bg'] = '#fff';
20
+ $style_vars['color_fg'] = '#000';
21
+ $style_vars['color_light'] = '#eee';
22
+ $style_vars['color_link'] = '#036fa9';
23
+ $style_vars['font_family'] = '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif';
24
+
25
+ /**
26
+ * Change the style vars used in email generation stylesheet.
27
+ *
28
+ * @since 1.31.0
29
+ *
30
+ * @param array $style_vars Variables used in style generation.
31
+ */
32
+ $style_vars = apply_filters( 'job_manager_email_style_vars', $style_vars );
33
+
34
+ /**
35
+ * Inject styles before the core styles.
36
+ *
37
+ * @since 1.31.0
38
+ *
39
+ * @param array $style_vars Variables used in style generation.
40
+ */
41
+ do_action( 'job_manager_email_style_before', $style_vars );
42
+ ?>
43
+
44
+ #wrapper {
45
+ background-color: <?php echo esc_attr( $style_vars['color_bg'] ); ?>;
46
+ color: <?php echo esc_attr( $style_vars['color_fg'] ); ?>;
47
+ margin: 0;
48
+ padding: 70px 0 70px 0;
49
+ -webkit-text-size-adjust: none !important;
50
+ width: 100%;
51
+ font-family: <?php echo esc_attr( $style_vars['font_family'] ); ?>;
52
+ }
53
+
54
+ a {
55
+ color: <?php echo esc_attr( $style_vars['color_link'] ); ?>;
56
+ font-weight: normal;
57
+ text-decoration: underline;
58
+ }
59
+
60
+ .email-container {
61
+ margin-bottom: 10px;
62
+ }
63
+
64
+ td.detail-label,
65
+ td.detail-value {
66
+ vertical-align: middle;
67
+ border: 1px solid <?php echo esc_attr( $style_vars['color_light'] ); ?>;
68
+ }
69
+
70
+ td.detail-label {
71
+ word-wrap: break-word;
72
+ width: 40%;
73
+ }
74
+
75
+ <?php
76
+ /**
77
+ * Inject styles after the core styles.
78
+ *
79
+ * @since 1.31.0
80
+ *
81
+ * @param array $style_vars Variables used in style generation.
82
+ */
83
+ do_action( 'job_manager_email_style_after', $style_vars );
templates/emails/employer-expiring-job.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying employers of an expiring job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/employer-expiring-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ /**
24
+ * @var bool
25
+ */
26
+ $expiring_today = $args['expiring_today'];
27
+
28
+ echo '<p>';
29
+ if ( $expiring_today ) {
30
+ printf( __( 'The following job listing is expiring today from <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
31
+ } else {
32
+ printf( __( 'The following job listing is expiring soon from <a href="%s">%s</a>.', 'wp-job-manager' ), home_url(), get_bloginfo( 'name' ) );
33
+ }
34
+ printf( ' ' . __( 'Visit the <a href="%s">job listing dashboard</a> to manage the listing.', 'wp-job-manager' ), esc_url( job_manager_get_permalink( 'job_dashboard' ) ) );
35
+ echo '</p>';
36
+
37
+ /**
38
+ * Show details about the job listing.
39
+ *
40
+ * @param WP_Post $job The job listing to show details for.
41
+ * @param WP_Job_Manager_Email $email Email object for the notification.
42
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
43
+ * @param bool $plain_text True if the email is being sent as plain text.
44
+ */
45
+ do_action( 'job_manager_email_job_details', $job, $email, false, $plain_text );
templates/emails/plain/admin-expiring-job.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying the administrator of an expiring job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/employer-expiring-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ /**
24
+ * @var bool
25
+ */
26
+ $expiring_today = $args['expiring_today'];
27
+
28
+ if ( $expiring_today ) {
29
+ printf( __( 'The following job listing is expiring today from %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
30
+ } else {
31
+ printf( __( 'The following job listing is expiring soon from %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
32
+ }
33
+ $edit_post_link = admin_url( sprintf( 'post.php?post=%d&amp;action=edit', $job->ID ) );
34
+ printf( ' ' . __( 'Visit WordPress admin (%s) to manage the listing.', 'wp-job-manager' ), esc_url( $edit_post_link ) );
35
+
36
+ /**
37
+ * Show details about the job listing.
38
+ *
39
+ * @param WP_Post $job The job listing to show details for.
40
+ * @param WP_Job_Manager_Email $email Email object for the notification.
41
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
42
+ * @param bool $plain_text True if the email is being sent as plain text.
43
+ */
44
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/plain/admin-new-job.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying admin of a new job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/admin-new-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ printf( __( 'A new job listing has been submitted to %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
24
+ switch ( $job->post_status ) {
25
+ case 'publish':
26
+ printf( ' ' . __( 'It has been published and is now available to the public.', 'wp-job-manager' ) );
27
+ break;
28
+ case 'pending':
29
+ printf( ' ' . __( 'It is awaiting approval by an administrator in WordPress admin (%s).', 'wp-job-manager' ), esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) );
30
+ break;
31
+ }
32
+
33
+ /**
34
+ * Show details about the job listing.
35
+ *
36
+ * @param WP_Post $job The job listing to show details for.
37
+ * @param WP_Job_Manager_Email $email Email object for the notification.
38
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
39
+ * @param bool $plain_text True if the email is being sent as plain text.
40
+ */
41
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/plain/admin-updated-job.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying admin of an updated job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/admin-updated-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ printf( __( 'A job listing has been updated on %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
24
+ switch ( $job->post_status ) {
25
+ case 'publish':
26
+ printf( ' ' . __( 'The changes have been published and are now available to the public.', 'wp-job-manager' ) );
27
+ break;
28
+ case 'pending':
29
+ printf( ' ' . __( 'The job listing is not publicly available until the changes are approved by an administrator in the site\'s WordPress admin (%s).', 'wp-job-manager' ), esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) );
30
+ break;
31
+ }
32
+
33
+ /**
34
+ * Show details about the job listing.
35
+ *
36
+ * @param WP_Post $job The job listing to show details for.
37
+ * @param WP_Job_Manager_Email $email Email object for the notification.
38
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
39
+ * @param bool $plain_text True if the email is being sent as plain text.
40
+ */
41
+ do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text );
templates/emails/plain/email-footer.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Footer for email notifications.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/email-footer.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+ ?>
templates/emails/plain/email-header.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Header for email notifications.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/email-header.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+ ?>
templates/emails/plain/email-job-details.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content for showing job details.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/email-job-details.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ echo "\n\n";
19
+
20
+ if ( ! empty( $fields ) ) {
21
+ foreach ( $fields as $field ) {
22
+ echo strip_tags( $field[ 'label' ] ) .': '. strip_tags( $field[ 'value' ] );
23
+ if ( ! empty( $field['url'] ) ) {
24
+ echo ' (' . esc_url( $field['url'] ) . ')';
25
+ }
26
+ echo "\n";
27
+ }
28
+ }
templates/emails/plain/employer-expiring-job.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Email content when notifying employers of an expiring job listing.
4
+ *
5
+ * This template can be overridden by copying it to yourtheme/job_manager/emails/plain/employer-expiring-job.php.
6
+ *
7
+ * @see https://wpjobmanager.com/document/template-overrides/
8
+ * @author Automattic
9
+ * @package WP Job Manager
10
+ * @category Template
11
+ * @version 1.31.0
12
+ */
13
+
14
+ if ( ! defined( 'ABSPATH' ) ) {
15
+ exit; // Exit if accessed directly
16
+ }
17
+
18
+ /**
19
+ * @var WP_Post $job
20
+ */
21
+ $job = $args['job'];
22
+
23
+ /**
24
+ * @var bool
25
+ */
26
+ $expiring_today = $args['expiring_today'];
27
+
28
+ if ( $expiring_today ) {
29
+ printf( __( 'The following job listing is expiring today from %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
30
+ } else {
31
+ printf( __( 'The following job listing is expiring soon from %s (%s).', 'wp-job-manager' ), get_bloginfo( 'name' ), home_url() );
32
+ }
33
+ printf( ' ' . __( 'Visit the job listing dashboard (%s) to manage the listing.', 'wp-job-manager' ), esc_url( job_manager_get_permalink( 'job_dashboard' ) ) );
34
+
35
+ /**
36
+ * Show details about the job listing.
37
+ *
38
+ * @param WP_Post $job The job listing to show details for.
39
+ * @param WP_Job_Manager_Email $email Email object for the notification.
40
+ * @param bool $sent_to_admin True if this is being sent to an administrator.
41
+ * @param bool $plain_text True if the email is being sent as plain text.
42
+ */
43
+ do_action( 'job_manager_email_job_details', $job, $email, false, $plain_text );
templates/form-fields/date-field.php CHANGED
@@ -8,7 +8,7 @@
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
- * @version 1.30.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
@@ -16,6 +16,7 @@ if ( ! defined( 'ABSPATH' ) ) {
16
  }
17
 
18
  wp_enqueue_script( 'wp-job-manager-datepicker' );
 
19
 
20
  ?>
21
  <input type="text" class="input-date job-manager-datepicker" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"<?php if ( isset( $field['autocomplete'] ) && false === $field['autocomplete'] ) { echo ' autocomplete="off"'; } ?> id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo empty( $field['placeholder'] ) ? '' : esc_attr( $field['placeholder'] ); ?>" value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?> />
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
+ * @version 1.30.2
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
16
  }
17
 
18
  wp_enqueue_script( 'wp-job-manager-datepicker' );
19
+ wp_enqueue_style( 'jquery-ui' );
20
 
21
  ?>
22
  <input type="text" class="input-date job-manager-datepicker" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>"<?php if ( isset( $field['autocomplete'] ) && false === $field['autocomplete'] ) { echo ' autocomplete="off"'; } ?> id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo empty( $field['placeholder'] ) ? '' : esc_attr( $field['placeholder'] ); ?>" value="<?php echo isset( $field['value'] ) ? esc_attr( $field['value'] ) : ''; ?>" <?php if ( ! empty( $field['required'] ) ) echo 'required'; ?> />
templates/job-application-url.php CHANGED
@@ -8,11 +8,11 @@
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
- * @version 1.9.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
15
  exit; // Exit if accessed directly
16
  }
17
  ?>
18
- <p><?php _e( 'To apply for this job please visit the following URL:', 'wp-job-manager' ); ?> <a href="<?php echo esc_url( $apply->url ); ?>" target="_blank" rel="nofollow"><?php echo esc_html( $apply->url ); ?> &rarr;</a></p>
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
+ * @version 1.31.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
15
  exit; // Exit if accessed directly
16
  }
17
  ?>
18
+ <p><?php esc_html_e( 'To apply for this job please visit', 'wp-job-manager' ); ?> <a href="<?php echo esc_url( $apply->url ); ?>" target="_blank" rel="nofollow"><?php echo esc_html( wp_parse_url( $apply->url, PHP_URL_HOST ) ); ?></a>.</p>
templates/job-filters.php CHANGED
@@ -8,7 +8,7 @@
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
- * @version 1.21.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
@@ -40,13 +40,13 @@ do_action( 'job_manager_job_filters_before', $atts );
40
  <?php foreach ( $categories as $category ) : ?>
41
  <input type="hidden" name="search_categories[]" value="<?php echo sanitize_title( $category ); ?>" />
42
  <?php endforeach; ?>
43
- <?php elseif ( $show_categories && ! is_tax( 'job_listing_category' ) && get_terms( 'job_listing_category' ) ) : ?>
44
  <div class="search_categories">
45
  <label for="search_categories"><?php _e( 'Category', 'wp-job-manager' ); ?></label>
46
  <?php if ( $show_category_multiselect ) : ?>
47
- <?php job_manager_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category, 'hide_empty' => false ) ); ?>
48
  <?php else : ?>
49
- <?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 ) ); ?>
50
  <?php endif; ?>
51
  </div>
52
  <?php endif; ?>
@@ -59,4 +59,4 @@ do_action( 'job_manager_job_filters_before', $atts );
59
 
60
  <?php do_action( 'job_manager_job_filters_after', $atts ); ?>
61
 
62
- <noscript><?php _e( 'Your browser does not support JavaScript, or it is disabled. JavaScript must be enabled in order to view listings.', 'wp-job-manager' ); ?></noscript>
8
  * @author Automattic
9
  * @package WP Job Manager
10
  * @category Template
11
+ * @version 1.31.0
12
  */
13
 
14
  if ( ! defined( 'ABSPATH' ) ) {
40
  <?php foreach ( $categories as $category ) : ?>
41
  <input type="hidden" name="search_categories[]" value="<?php echo sanitize_title( $category ); ?>" />
42
  <?php endforeach; ?>
43
+ <?php elseif ( $show_categories && ! is_tax( 'job_listing_category' ) && get_terms( array( 'taxonomy' => 'job_listing_category' ) ) ) : ?>
44
  <div class="search_categories">
45
  <label for="search_categories"><?php _e( 'Category', 'wp-job-manager' ); ?></label>
46
  <?php if ( $show_category_multiselect ) : ?>
47
+ <?php job_manager_dropdown_categories( array( 'taxonomy' => 'job_listing_category', 'hierarchical' => 1, 'name' => 'search_categories', 'orderby' => 'name', 'selected' => $selected_category, 'hide_empty' => true ) ); ?>
48
  <?php else : ?>
49
+ <?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, 'hide_empty' => true ) ); ?>
50
  <?php endif; ?>
51
  </div>
52
  <?php endif; ?>
59
 
60
  <?php do_action( 'job_manager_job_filters_after', $atts ); ?>
61
 
62
+ <noscript><?php _e( 'Your browser does not support JavaScript, or it is disabled. JavaScript must be enabled in order to view listings.', 'wp-job-manager' ); ?></noscript>
uninstall.php CHANGED
@@ -3,38 +3,33 @@ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
3
  exit();
4
  }
5
 
6
- wp_clear_scheduled_hook( 'job_manager_delete_old_previews' );
7
- wp_clear_scheduled_hook( 'job_manager_check_for_expired_jobs' );
8
-
9
- wp_trash_post( get_option( 'job_manager_submit_job_form_page_id' ) );
10
- wp_trash_post( get_option( 'job_manager_job_dashboard_page_id' ) );
11
- wp_trash_post( get_option( 'job_manager_jobs_page_id' ) );
12
-
13
- $options = array(
14
- 'wp_job_manager_version',
15
- 'job_manager_per_page',
16
- 'job_manager_hide_filled_positions',
17
- 'job_manager_enable_categories',
18
- 'job_manager_enable_default_category_multiselect',
19
- 'job_manager_category_filter_type',
20
- 'job_manager_user_requires_account',
21
- 'job_manager_enable_registration',
22
- 'job_manager_registration_role',
23
- 'job_manager_submission_requires_approval',
24
- 'job_manager_user_can_edit_pending_submissions',
25
- 'job_manager_submission_duration',
26
- 'job_manager_allowed_application_method',
27
- 'job_manager_submit_job_form_page_id',
28
- 'job_manager_job_dashboard_page_id',
29
- 'job_manager_jobs_page_id',
30
- 'job_manager_installed_terms',
31
- 'job_manager_submit_page_slug',
32
- 'job_manager_job_dashboard_page_slug',
33
- 'job_manager_google_maps_api_key',
34
- );
35
-
36
- foreach ( $options as $option ) {
37
- delete_option( $option );
38
  }
39
 
40
  include dirname( __FILE__ ) . '/includes/class-wp-job-manager-usage-tracking.php';
3
  exit();
4
  }
5
 
6
+ // Cleanup all data.
7
+ require 'includes/class-wp-job-manager-data-cleaner.php';
8
+
9
+ if ( ! is_multisite() ) {
10
+
11
+ // Only do deletion if the setting is true.
12
+ $do_deletion = get_option( 'job_manager_delete_data_on_uninstall' );
13
+ if ( $do_deletion ) {
14
+ WP_Job_Manager_Data_Cleaner::cleanup_all();
15
+ }
16
+ } else {
17
+ global $wpdb;
18
+
19
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
20
+ $original_blog_id = get_current_blog_id();
21
+
22
+ foreach ( $blog_ids as $blog_id ) {
23
+ switch_to_blog( $blog_id );
24
+
25
+ // Only do deletion if the setting is true.
26
+ $do_deletion = get_option( 'job_manager_delete_data_on_uninstall' );
27
+ if ( $do_deletion ) {
28
+ WP_Job_Manager_Data_Cleaner::cleanup_all();
29
+ }
30
+ }
31
+
32
+ switch_to_blog( $original_blog_id );
 
 
 
 
 
33
  }
34
 
35
  include dirname( __FILE__ ) . '/includes/class-wp-job-manager-usage-tracking.php';
wp-job-manager-functions.php CHANGED
@@ -382,11 +382,25 @@ function get_job_listing_categories() {
382
  return array();
383
  }
384
 
385
- return get_terms( "job_listing_category", array(
386
  'orderby' => 'name',
387
- 'order' => 'ASC',
388
- 'hide_empty' => false,
389
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  }
391
  endif;
392
 
@@ -1046,14 +1060,15 @@ function job_manager_dropdown_categories( $args = '' ) {
1046
  $categories = get_transient( $categories_hash );
1047
 
1048
  if ( empty( $categories ) ) {
1049
- $categories = get_terms( $taxonomy, array(
 
1050
  'orderby' => $r['orderby'],
1051
  'order' => $r['order'],
1052
  'hide_empty' => $r['hide_empty'],
1053
  'parent' => $r['parent'],
1054
  'child_of' => $r['child_of'],
1055
  'exclude' => $r['exclude'],
1056
- 'hierarchical' => $r['hierarchical']
1057
  ) );
1058
  set_transient( $categories_hash, $categories, DAY_IN_SECONDS * 7 );
1059
  }
382
  return array();
383
  }
384
 
385
+ $args = array(
386
  'orderby' => 'name',
387
+ 'order' => 'ASC',
388
+ 'hide_empty' => false,
389
+ );
390
+
391
+ /**
392
+ * Change the category query arguments.
393
+ *
394
+ * @since 1.31.0
395
+ *
396
+ * @param array $args
397
+ */
398
+ $args = apply_filters( 'get_job_listing_category_args', $args );
399
+
400
+ // Prevent users from filtering the taxonomy
401
+ $args['taxonomy'] = 'job_listing_category';
402
+
403
+ return get_terms( $args );
404
  }
405
  endif;
406
 
1060
  $categories = get_transient( $categories_hash );
1061
 
1062
  if ( empty( $categories ) ) {
1063
+ $categories = get_terms( array(
1064
+ 'taxonomy' => $r['taxonomy'],
1065
  'orderby' => $r['orderby'],
1066
  'order' => $r['order'],
1067
  'hide_empty' => $r['hide_empty'],
1068
  'parent' => $r['parent'],
1069
  'child_of' => $r['child_of'],
1070
  'exclude' => $r['exclude'],
1071
+ 'hierarchical' => $r['hierarchical'],
1072
  ) );
1073
  set_transient( $categories_hash, $categories, DAY_IN_SECONDS * 7 );
1074
  }
wp-job-manager-template.php CHANGED
@@ -380,6 +380,9 @@ function wpjm_get_job_listing_structured_data( $post = null ) {
380
  $data['hiringOrganization']['sameAs'] = $company_website;
381
  $data['hiringOrganization']['url'] = $company_website;
382
  }
 
 
 
383
 
384
  $data['identifier'] = array();
385
  $data['identifier']['@type'] = 'PropertyValue';
@@ -600,6 +603,60 @@ function wpjm_get_the_job_types( $post = null ) {
600
  return apply_filters( 'wpjm_the_job_types', $types, $post );
601
  }
602
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  /**
604
  * Returns the registration fields used when an account is required.
605
  *
380
  $data['hiringOrganization']['sameAs'] = $company_website;
381
  $data['hiringOrganization']['url'] = $company_website;
382
  }
383
+ if ( $company_logo = get_the_company_logo( $post, 'full' ) ) {
384
+ $data['hiringOrganization']['logo'] = $company_logo;
385
+ }
386
 
387
  $data['identifier'] = array();
388
  $data['identifier']['@type'] = 'PropertyValue';
603
  return apply_filters( 'wpjm_the_job_types', $types, $post );
604
  }
605
 
606
+ /**
607
+ * Displays job categories for the listing.
608
+ *
609
+ * @since 1.31.0
610
+ *
611
+ * @param int|WP_Post $post Current post object.
612
+ * @param string $separator String to join the term names with.
613
+ */
614
+ function wpjm_the_job_categories( $post = null, $separator = ', ' ) {
615
+ if ( ! get_option( 'job_manager_enable_categories' ) ) {
616
+ return;
617
+ }
618
+
619
+ $job_categories = wpjm_get_the_job_categories( $post );
620
+
621
+ if ( $job_categories ) {
622
+ $names = wp_list_pluck( $job_categories, 'name' );
623
+
624
+ echo esc_html( implode( $separator, $names ) );
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Gets the job type for the listing.
630
+ *
631
+ * @since 1.31.0
632
+ *
633
+ * @param int|WP_Post $post (default: null).
634
+ * @return bool|array
635
+ */
636
+ function wpjm_get_the_job_categories( $post = null ) {
637
+ $post = get_post( $post );
638
+
639
+ if ( ! $post || 'job_listing' !== $post->post_type ) {
640
+ return false;
641
+ }
642
+
643
+ $categories = get_the_terms( $post->ID, 'job_listing_category' );
644
+
645
+ if ( empty( $categories ) || is_wp_error( $categories ) ) {
646
+ $categories = array();
647
+ }
648
+
649
+ /**
650
+ * Filter the returned job categories for a post.
651
+ *
652
+ * @since 1.31.0
653
+ *
654
+ * @param array $types
655
+ * @param WP_Post $post
656
+ */
657
+ return apply_filters( 'wpjm_the_job_categories', $categories, $post );
658
+ }
659
+
660
  /**
661
  * Returns the registration fields used when an account is required.
662
  *
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.30.2
7
  * Author: Automattic
8
  * Author URI: https://wpjobmanager.com/
9
- * Requires at least: 4.5.0
10
  * Tested up to: 4.9
11
  * Text Domain: wp-job-manager
12
  * Domain Path: /languages/
@@ -58,8 +58,8 @@ class WP_Job_Manager {
58
  */
59
  public function __construct() {
60
  // Define constants
61
- define( 'JOB_MANAGER_VERSION', '1.30.2' );
62
- define( 'JOB_MANAGER_MINIMUM_WP_VERSION', '4.7' );
63
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
64
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
65
  define( 'JOB_MANAGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
@@ -74,6 +74,9 @@ class WP_Job_Manager {
74
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-geocode.php' );
75
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-cache-helper.php' );
76
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/helper/class-wp-job-manager-helper.php' );
 
 
 
77
 
78
  add_action( 'rest_api_init', array( $this, 'rest_api' ) );
79
 
@@ -103,13 +106,18 @@ class WP_Job_Manager {
103
  add_action( 'after_setup_theme', array( $this, 'load_plugin_textdomain' ) );
104
  add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 );
105
  add_action( 'widgets_init', array( $this, 'widgets_init' ) );
 
106
  add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
107
  add_action( 'admin_init', array( $this, 'updater' ) );
108
  add_action( 'wp_logout', array( $this, 'cleanup_job_posting_cookies' ) );
 
109
 
110
  add_action( 'init', array( $this, 'usage_tracking_init' ) );
111
  register_deactivation_hook( __FILE__, array( $this, 'usage_tracking_cleanup' ) );
112
 
 
 
 
113
  // Defaults for WPJM core actions
114
  add_action( 'wpjm_notify_new_user', 'wp_job_manager_notify_new_user', 10, 2 );
115
  }
@@ -209,6 +217,19 @@ class WP_Job_Manager {
209
  if ( ! wp_next_scheduled( 'job_manager_clear_expired_transients' ) ) {
210
  wp_schedule_event( time(), 'twicedaily', 'job_manager_clear_expired_transients' );
211
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  }
213
 
214
  /**
@@ -223,6 +244,16 @@ class WP_Job_Manager {
223
  }
224
  }
225
 
 
 
 
 
 
 
 
 
 
 
226
  /**
227
  * Registers and enqueues scripts and CSS.
228
  */
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.31.0
7
  * Author: Automattic
8
  * Author URI: https://wpjobmanager.com/
9
+ * Requires at least: 4.7.0
10
  * Tested up to: 4.9
11
  * Text Domain: wp-job-manager
12
  * Domain Path: /languages/
58
  */
59
  public function __construct() {
60
  // Define constants
61
+ define( 'JOB_MANAGER_VERSION', '1.31.0' );
62
+ define( 'JOB_MANAGER_MINIMUM_WP_VERSION', '4.7.0' );
63
  define( 'JOB_MANAGER_PLUGIN_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
64
  define( 'JOB_MANAGER_PLUGIN_URL', untrailingslashit( plugins_url( basename( plugin_dir_path( __FILE__ ) ), basename( __FILE__ ) ) ) );
65
  define( 'JOB_MANAGER_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
74
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-geocode.php' );
75
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-cache-helper.php' );
76
  include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/helper/class-wp-job-manager-helper.php' );
77
+ include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/abstracts/abstract-wp-job-manager-email.php' );
78
+ include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/abstracts/abstract-wp-job-manager-email-template.php' );
79
+ include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-email-notifications.php' );
80
 
81
  add_action( 'rest_api_init', array( $this, 'rest_api' ) );
82
 
106
  add_action( 'after_setup_theme', array( $this, 'load_plugin_textdomain' ) );
107
  add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 );
108
  add_action( 'widgets_init', array( $this, 'widgets_init' ) );
109
+ add_action( 'wp_loaded', array( $this, 'register_shared_assets' ) );
110
  add_action( 'wp_enqueue_scripts', array( $this, 'frontend_scripts' ) );
111
  add_action( 'admin_init', array( $this, 'updater' ) );
112
  add_action( 'wp_logout', array( $this, 'cleanup_job_posting_cookies' ) );
113
+ add_action( 'init', array( 'WP_Job_Manager_Email_Notifications', 'init' ) );
114
 
115
  add_action( 'init', array( $this, 'usage_tracking_init' ) );
116
  register_deactivation_hook( __FILE__, array( $this, 'usage_tracking_cleanup' ) );
117
 
118
+ // Other cleanup
119
+ register_deactivation_hook( __FILE__, array( $this, 'unschedule_cron_jobs' ) );
120
+
121
  // Defaults for WPJM core actions
122
  add_action( 'wpjm_notify_new_user', 'wp_job_manager_notify_new_user', 10, 2 );
123
  }
217
  if ( ! wp_next_scheduled( 'job_manager_clear_expired_transients' ) ) {
218
  wp_schedule_event( time(), 'twicedaily', 'job_manager_clear_expired_transients' );
219
  }
220
+ if ( ! wp_next_scheduled( 'job_manager_email_daily_notices' ) ) {
221
+ wp_schedule_event( time(), 'daily', 'job_manager_email_daily_notices' );
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Unschedule cron jobs. This is run on plugin deactivation.
227
+ */
228
+ public static function unschedule_cron_jobs() {
229
+ wp_clear_scheduled_hook( 'job_manager_check_for_expired_jobs' );
230
+ wp_clear_scheduled_hook( 'job_manager_delete_old_previews' );
231
+ wp_clear_scheduled_hook( 'job_manager_clear_expired_transients' );
232
+ wp_clear_scheduled_hook( 'job_manager_email_daily_notices' );
233
  }
234
 
235
  /**
244
  }
245
  }
246
 
247
+ /**
248
+ * Registers assets used in both the frontend and WP admin.
249
+ */
250
+ public function register_shared_assets() {
251
+ global $wp_scripts;
252
+
253
+ $jquery_version = isset( $wp_scripts->registered['jquery-ui-core']->ver ) ? $wp_scripts->registered['jquery-ui-core']->ver : '1.9.2';
254
+ wp_register_style( 'jquery-ui', '//code.jquery.com/ui/' . $jquery_version . '/themes/smoothness/jquery-ui.css', array(), $jquery_version );
255
+ }
256
+
257
  /**
258
  * Registers and enqueues scripts and CSS.
259
  */